-
一、项目介绍项目是基于STM32设计的数码相册,能够通过LCD显示屏解码显示主流的图片,支持bmp、jpg、gif等格式。用户可以通过按键或者触摸屏来切换图片,同时还可以旋转显示,并能够自适应居中显示,小尺寸图片居中显示,大尺寸图片自动缩小显示(超出屏幕范围)。图片从SD卡中获取。二、设计思路2.1 硬件设计本项目所需的主要硬件:STM32F103ZET6LCD屏幕SD卡模块按键和触摸屏2.2 软件设计(1)解码图片在STM32芯片中,解码图片需要将读取到的数据存入图形缓冲区中,以便进行图画显示。常用的解码算法有JPEG解码和BMP解码。(2)图片显示为了更好的实现图片旋转和缩放功能,在显示图片时需对其进行矩阵运算。通过左右翻转和上下翻转,可实现图片的旋转功能。通过计算图片与显示屏幕之间的比例关系并进行缩放,实现自适应居中和图片的缩放功能。(3)SD卡SD卡模块可通过SPI接口与STM32芯片进行通信,读取SD卡中的图片数据,实现对图片的加载和显示。(4)按键和触摸屏在使用过程中,用户可以通过按键和触摸屏对图片进行切换、旋转和缩放等操作。通过设置中断处理函数,响应用户的操作并及时更新显示屏幕上的图片。2.3 图片播放流程图2.4 显示效果三、代码设计3.1 主函数 #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include <string.h> #include <stdio.h> #include "sd.h" //SD卡 #include "ff.h" //文件系统 #include "bmp.h" //文件系统 #include "iic.h" #include "at24c02.h" #include "xpt2046.h" #include "lcd.h" FATFS fs; // 用户定义的文件系统结构体 int main() { DIR dir_dp; FILINFO file_info; u32 sd_size; //存放SD卡返回的容量 BeepInit(); //蜂鸣器初始化 LedInit(); //LED灯初始化 UsartInit(USART1,72,115200); KeyInit(); //按键初始化 IICInit(); LcdInit(); TOUCH_Init(); //TOUCH_ADJUST(); //触摸屏校准 printf("串口工作正常!\r\n"); if(SDCardDeviceInit()) { printf("SD卡初始化失败!\r\n"); } sd_size=GetSDCardSectorCount(); //检测SD卡大小,返回值右移11位得到以M为单位的容量 printf("SD卡Sizeof:%d\r\n",sd_size>>11); f_mount(&fs,"0:",1); // 注册文件系统工作区,驱动器号 0,初始化后其他函数可使用里面的参数 LcdClear(0xFFFF); //f_mkdir("0:/目录创建测试!"); //测试OK //f_unlink("0:/123"); //删除目录,注意只能删除空目录 //f_unlink("0:/1.bmp");//删除文件 //printf("%d\r\n",Show_BMP("1.bmp")); if(f_opendir(&dir_dp,"0:/bmp")!=FR_OK)printf("目录打开失败!\r\n"); //循环读取目录 while(f_readdir(&dir_dp,&file_info)==FR_OK) { if(file_info.fname[0]==0)break; //判断目录跳出条件,表示目录已经读取完毕 if(strstr(file_info.fname,".bmp")) //过滤目录 { printf("文件名称: %s,文件大小: %ld 字节\r\n",file_info.fname,file_info.fsize); }else printf("文件名称: %s,文件大小: %ld 字节\r\n",file_info.fname,file_info.fsize); } if(f_closedir(&dir_dp)!=FR_OK)printf("目录关闭失败!\r\n"); while(1) { LED1=!LED1; DelayMs(100); } } 3.2 BMP图片解码 #include "bmp.h" unsigned short RGB888ToRGB565(unsigned int n888Color) { unsigned short n565Color = 0; // 获取RGB单色,并截取高位 unsigned char cRed = (n888Color & RGB888_RED) >> 19; unsigned char cGreen = (n888Color & RGB888_GREEN) >> 10; unsigned char cBlue = (n888Color & RGB888_BLUE) >> 3; // 连接 n565Color = (cRed << 11) + (cGreen << 5) + (cBlue << 0); return n565Color; } unsigned int RGB565ToRGB888(unsigned short n565Color) { unsigned int n888Color = 0; // 获取RGB单色,并填充低位 unsigned char cRed = (n565Color & RGB565_RED) >> 8; unsigned char cGreen = (n565Color & RGB565_GREEN) >> 3; unsigned char cBlue = (n565Color & RGB565_BLUE) << 3; // 连接 n888Color = (cRed << 16) + (cGreen << 8) + (cBlue << 0); return n888Color; } /* 函数功能:实现截图功能 参 数: char filename:文件名称 返 回 值:0表示成功,1表示失败 */ u8 C_BMP(const char *filename,u32 Width,u32 Height) { FIL file; // 用户定义的文件系统结构体 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存图片文件头的信息 BITMAPINFOHEADER BmpInfo; //图片参数信息 char *p; u32 cnt,c_32; int x,y; u16 c_16; //存放16位的颜色 /*1. 创建一张BMP图片*/ res = f_open(&file,filename, FA_OPEN_ALWAYS | FA_WRITE); if(res!=0)return 1; /*2. 创建BMP的图片头参数*/ memset(&BmpHead,0,sizeof(BITMAPFILEHEADER)); //将指定空间赋值为指定的值 p=(char*)&BmpHead.bfType; //填充BMP图片的类型 *p='B'; *(p+1)='M'; //BmpHead.bfType=0x4d42;//'B''M' //0x4d42 BmpHead.bfSize=Width*Height*3+54; //图片的总大小 BmpHead.bfOffBits=54; //图片数据的偏移量 res =f_write(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 创建BMP图片的参数*/ memset(&BmpInfo,0,sizeof(BITMAPINFOHEADER)); BmpInfo.biSize=sizeof(BITMAPINFOHEADER); //当前结构体大小 BmpInfo.biWidth=Width; BmpInfo.biHeight=Height; BmpInfo.biPlanes=1; BmpInfo.biBitCount=24; res =f_write(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt); if(res!=0)return 1; /*4. 读取LCD屏的颜色数据,用于创建BMP图片*/ for(y=Height-1;y>=0;y--) { for(x=0;x<Width;x++) { c_16=LcdReadPoint(x,y); //读取LCD屏上一个点的颜色 c_32=RGB565ToRGB888(c_16); //颜色的转换 res =f_write(&file,&c_32,3,&cnt); if(res!=0)return 1; } } /*5. 关闭文件*/ f_close(&file); } /* 函数功能:BMP图片显示功能 参 数: char filename:文件名称 返 回 值:0表示成功,1表示失败 */ u8 Show_BMP(const char *filename) { FIL file; // 用户定义的文件系统结构体 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存图片文件头的信息 BITMAPINFOHEADER BmpInfo; //图片参数信息 char *p; u32 cnt,c_24; int x,y; u16 c_16; //存放16位的颜色 /*1. 打开一张BMP图片*/ res = f_open(&file,filename,FA_READ); if(res!=0)return 1; /*2. 读取BMP的图片头参数*/ res =f_read(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 读取BMP图片的参数*/ res =f_read(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt); if(res!=0)return 1; /*4.显示BMP图片*/ f_lseek(&file,BmpHead.bfOffBits); //移动到RGB数据的存放位置 //后期的优化:读取一行的数据,再显示一行。 for(y=0;y<BmpInfo.biHeight;y++) { for(x=0;x<BmpInfo.biWidth;x++) { res =f_read(&file,&c_24,3,&cnt); if(res!=0)return 1; c_16=RGB888ToRGB565(c_24); //转换颜色 LcdDrawPoint(x,y,c_16); } } /*5. 关闭文件*/ f_close(&file); } 3.3 jpeg图片解码 #include "piclib.h" #include "nt35310_lcd.h" _pic_info picinfo; //图片信息 _pic_phy pic_phy; //图片显示物理接口 /* 函数功能: 划横线函数,需要自己实现 */ void Picture_DrawLine(u16 x0,u16 y0,u16 len,u16 color) { NT35310_Fill(x0,y0,x0+len-1,y0,color); } /* 函数功能: 矩形填充颜色 函数参数: x,y:起始坐标 width,height:宽度和高度。 color:颜色数组 */ void Picture_FillColor(u16 x,u16 y,u16 width,u16 height,u16 *color) { NT35310_DrawRectangle(x,y,x+width-1,y+height-1,*color); } /* 函数功能: 画图初始化,在画图之前,必须先调用此函数 函数参数: 指定画点/读点 */ void Picture_DisplayInit(void) { pic_phy.draw_point=NT35310_DrawPoint; //画点函数实现 pic_phy.fill=NT35310_Fill; //填充函数实现,仅GIF需要 pic_phy.draw_hline=Picture_DrawLine; //画线函数实现,仅GIF需要 pic_phy.fillcolor=Picture_FillColor; //颜色填充函数实现,仅TJPGD需要 picinfo.lcdwidth=Lcd_Width; //得到LCD的宽度像素 picinfo.lcdheight=Lcd_Height; //得到LCD的高度像素 picinfo.ImgWidth=0; //初始化宽度为0 picinfo.ImgHeight=0;//初始化高度为0 picinfo.Div_Fac=0; //初始化缩放系数为0 picinfo.S_Height=0; //初始化设定的高度为0 picinfo.S_Width=0; //初始化设定的宽度为0 picinfo.S_XOFF=0; //初始化x轴的偏移量为0 picinfo.S_YOFF=0; //初始化y轴的偏移量为0 picinfo.staticx=0; //初始化当前显示到的x坐标为0 picinfo.staticy=0; //初始化当前显示到的y坐标为0 } /* 函数功能: 初始化智能画点 说明: 内部调用 */ void Picture_PointInit(void) { float temp,temp1; temp=(float)picinfo.S_Width/picinfo.ImgWidth; temp1=(float)picinfo.S_Height/picinfo.ImgHeight; if(temp<temp1)temp1=temp;//取较小的那个 if(temp1>1)temp1=1; //使图片处于所给区域的中间 picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2; picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2; temp1*=8192;//扩大8192倍 picinfo.Div_Fac=temp1; picinfo.staticx=0xffff; picinfo.staticy=0xffff;//放到一个不可能的值上面 } /* 函数功能: 判断这个像素是否可以显示 函数参数: (x,y) :像素原始坐标 chg :功能变量. 返回值:0,不需要显示.1,需要显示 */ u8 Picture_is_Pixel(u16 x,u16 y,u8 chg) { if(x!=picinfo.staticx||y!=picinfo.staticy) { if(chg==1) { picinfo.staticx=x; picinfo.staticy=y; } return 1; }else return 0; } extern u8 jpg_decode(const u8 *filename); /* 函数功能: 绘制图片 函数参数: FileName:要显示的图片文件 BMP/JPG/JPEG/GIF x,y,width,height:坐标及显示区域尺寸 fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能. 函数说明: 图片在开始和结束的坐标点范围内显示 */ u8 Picture_DisplayJPG(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast) { u8 res;//返回值 //显示的图片高度、宽度 picinfo.S_Height=height; picinfo.S_Width=width; //显示的开始坐标点 picinfo.S_YOFF=y; picinfo.S_XOFF=x; //解码JPG/JPEG res=jpg_decode(filename); //解码JPG/JPEG return res; } 3.4 gif图片解码 #include "piclib.h" #include <stm32f10x.h> #include "gif.h" #include "ff.h" #include "delay.h" #include <string.h> const u16 _aMaskTbl[16] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, }; const u8 _aInterlaceOffset[]={8,8,4,2}; const u8 _aInterlaceYPos []={0,4,2,1}; u8 gifdecoding=0;//标记GIF正在解码. //检测GIF头 //返回值:0,是GIF89a/87a;非零,非GIF89a/87a u8 gif_check_head(FIL *file) { u8 gifversion[6]; u32 readed; u8 res; res=f_read(file,gifversion,6,(UINT*)&readed); if(res)return 1; if((gifversion[0]!='G')||(gifversion[1]!='I')||(gifversion[2]!='F')|| (gifversion[3]!='8')||((gifversion[4]!='7')&&(gifversion[4]!='9'))|| (gifversion[5]!='a'))return 2; else return 0; } //将RGB888转为RGB565 //ctb:RGB888颜色数组首地址. //返回值:RGB565颜色. u16 gif_getrgb565(u8 *ctb) { u16 r,g,b; r=(ctb[0]>>3)&0X1F; g=(ctb[1]>>2)&0X3F; b=(ctb[2]>>3)&0X1F; return b+(g<<5)+(r<<11); } //读取颜色表 //file:文件; //gif:gif信息; //num:tbl大小. //返回值:0,OK;其他,失败; u8 gif_readcolortbl(FIL *file,gif89a * gif,u16 num) { u8 rgb[3]; u16 t; u8 res; u32 readed; for(t=0;t<num;t++) { res=f_read(file,rgb,3,(UINT*)&readed); if(res)return 1;//读错误 gif->colortbl[t]=gif_getrgb565(rgb); } return 0; } //得到逻辑屏幕描述,图像尺寸等 //file:文件; //gif:gif信息; //返回值:0,OK;其他,失败; u8 gif_getinfo(FIL *file,gif89a * gif) { u32 readed; u8 res; res=f_read(file,(u8*)&gif->gifLSD,7,(UINT*)&readed); if(res)return 1; if(gif->gifLSD.flag&0x80)//存在全局颜色表 { gif->numcolors=2<<(gif->gifLSD.flag&0x07);//得到颜色表大小 if(gif_readcolortbl(file,gif,gif->numcolors))return 1;//读错误 } return 0; } //保存全局颜色表 //gif:gif信息; void gif_savegctbl(gif89a* gif) { u16 i=0; for(i=0;i<256;i++)gif->bkpcolortbl[i]=gif->colortbl[i];//保存全局颜色. } //恢复全局颜色表 //gif:gif信息; void gif_recovergctbl(gif89a* gif) { u16 i=0; for(i=0;i<256;i++)gif->colortbl[i]=gif->bkpcolortbl[i];//恢复全局颜色. } //初始化LZW相关参数 //gif:gif信息; //codesize:lzw码长度 void gif_initlzw(gif89a* gif,u8 codesize) { memset((u8 *)gif->lzw, 0, sizeof(LZW_INFO)); gif->lzw->SetCodeSize = codesize; gif->lzw->CodeSize = codesize + 1; gif->lzw->ClearCode = (1 << codesize); gif->lzw->EndCode = (1 << codesize) + 1; gif->lzw->MaxCode = (1 << codesize) + 2; gif->lzw->MaxCodeSize = (1 << codesize) << 1; gif->lzw->ReturnClear = 1; gif->lzw->LastByte = 2; gif->lzw->sp = gif->lzw->aDecompBuffer; } //读取一个数据块 //gfile:gif文件; //buf:数据缓存区 //maxnum:最大读写数据限制 u16 gif_getdatablock(FIL *gfile,u8 *buf,u16 maxnum) { u8 cnt; u32 readed; u32 fpos; f_read(gfile,&cnt,1,(UINT*)&readed);//得到LZW长度 if(cnt) { if (buf)//需要读取 { if(cnt>maxnum) { fpos=f_tell(gfile); f_lseek(gfile,fpos+cnt);//跳过 return cnt;//直接不读 } f_read(gfile,buf,cnt,(UINT*)&readed);//得到LZW长度 }else //直接跳过 { fpos=f_tell(gfile); f_lseek(gfile,fpos+cnt);//跳过 } } return cnt; } //ReadExtension //Purpose: //Reads an extension block. One extension block can consist of several data blocks. //If an unknown extension block occures, the routine failes. //返回值:0,成功; // 其他,失败 u8 gif_readextension(FIL *gfile,gif89a* gif, int *pTransIndex,u8 *pDisposal) { u8 temp; u32 readed; u8 buf[4]; f_read(gfile,&temp,1,(UINT*)&readed);//得到长度 switch(temp) { case GIF_PLAINTEXT: case GIF_APPLICATION: case GIF_COMMENT: while(gif_getdatablock(gfile,0,256)>0); //获取数据块 return 0; case GIF_GRAPHICCTL://图形控制扩展块 if(gif_getdatablock(gfile,buf,4)!=4)return 1; //图形控制扩展块的长度必须为4 gif->delay=(buf[2]<<8)|buf[1]; //得到延时 *pDisposal=(buf[0]>>2)&0x7; //得到处理方法 if((buf[0]&0x1)!=0)*pTransIndex=buf[3]; //透明色表 f_read(gfile,&temp,1,(UINT*)&readed); //得到LZW长度 if(temp!=0)return 1; //读取数据块结束符错误. return 0; } return 1;//错误的数据 } //从LZW缓存中得到下一个LZW码,每个码包含12位 //返回值:<0,错误. // 其他,正常. int gif_getnextcode(FIL *gfile,gif89a* gif) { int i,j,End; long Result; if(gif->lzw->ReturnClear) { //The first code should be a clearcode. gif->lzw->ReturnClear=0; return gif->lzw->ClearCode; } End=gif->lzw->CurBit+gif->lzw->CodeSize; if(End>=gif->lzw->LastBit) { int Count; if(gif->lzw->GetDone)return-1;//Error gif->lzw->aBuffer[0]=gif->lzw->aBuffer[gif->lzw->LastByte-2]; gif->lzw->aBuffer[1]=gif->lzw->aBuffer[gif->lzw->LastByte-1]; if((Count=gif_getdatablock(gfile,&gif->lzw->aBuffer[2],300))==0)gif->lzw->GetDone=1; if(Count<0)return -1;//Error gif->lzw->LastByte=2+Count; gif->lzw->CurBit=(gif->lzw->CurBit-gif->lzw->LastBit)+16; gif->lzw->LastBit=(2+Count)*8; End=gif->lzw->CurBit+gif->lzw->CodeSize; } j=End>>3; i=gif->lzw->CurBit>>3; if(i==j)Result=(long)gif->lzw->aBuffer[i]; else if(i+1==j)Result=(long)gif->lzw->aBuffer[i]|((long)gif->lzw->aBuffer[i+1]<<8); else Result=(long)gif->lzw->aBuffer[i]|((long)gif->lzw->aBuffer[i+1]<<8)|((long)gif->lzw->aBuffer[i+2]<<16); Result=(Result>>(gif->lzw->CurBit&0x7))&_aMaskTbl[gif->lzw->CodeSize]; gif->lzw->CurBit+=gif->lzw->CodeSize; return(int)Result; } //得到LZW的下一个码 //返回值:<0,错误(-1,不成功;-2,读到结束符了) // >=0,OK.(LZW的第一个码) int gif_getnextbyte(FIL *gfile,gif89a* gif) { int i,Code,Incode; while((Code=gif_getnextcode(gfile,gif))>=0) { if(Code==gif->lzw->ClearCode) { //Corrupt GIFs can make this happen if(gif->lzw->ClearCode>=(1<<MAX_NUM_LWZ_BITS))return -1;//Error //Clear the tables memset((u8*)gif->lzw->aCode,0,sizeof(gif->lzw->aCode)); for(i=0;i<gif->lzw->ClearCode;++i)gif->lzw->aPrefix[i]=i; //Calculate the'special codes' independence of the initial code size //and initialize the stack pointer gif->lzw->CodeSize=gif->lzw->SetCodeSize+1; gif->lzw->MaxCodeSize=gif->lzw->ClearCode<<1; gif->lzw->MaxCode=gif->lzw->ClearCode+2; gif->lzw->sp=gif->lzw->aDecompBuffer; //Read the first code from the stack after clear ingand initializing*/ do { gif->lzw->FirstCode=gif_getnextcode(gfile,gif); }while(gif->lzw->FirstCode==gif->lzw->ClearCode); gif->lzw->OldCode=gif->lzw->FirstCode; return gif->lzw->FirstCode; } if(Code==gif->lzw->EndCode)return -2;//End code Incode=Code; if(Code>=gif->lzw->MaxCode) { *(gif->lzw->sp)++=gif->lzw->FirstCode; Code=gif->lzw->OldCode; } while(Code>=gif->lzw->ClearCode) { *(gif->lzw->sp)++=gif->lzw->aPrefix[Code]; if(Code==gif->lzw->aCode[Code])return Code; if((gif->lzw->sp-gif->lzw->aDecompBuffer)>=sizeof(gif->lzw->aDecompBuffer))return Code; Code=gif->lzw->aCode[Code]; } *(gif->lzw->sp)++=gif->lzw->FirstCode=gif->lzw->aPrefix[Code]; if((Code=gif->lzw->MaxCode)<(1<<MAX_NUM_LWZ_BITS)) { gif->lzw->aCode[Code]=gif->lzw->OldCode; gif->lzw->aPrefix[Code]=gif->lzw->FirstCode; ++gif->lzw->MaxCode; if((gif->lzw->MaxCode>=gif->lzw->MaxCodeSize)&&(gif->lzw->MaxCodeSize<(1<<MAX_NUM_LWZ_BITS))) { gif->lzw->MaxCodeSize<<=1; ++gif->lzw->CodeSize; } } gif->lzw->OldCode=Incode; if(gif->lzw->sp>gif->lzw->aDecompBuffer)return *--(gif->lzw->sp); } return Code; } //DispGIFImage //Purpose: // This routine draws a GIF image from the current pointer which should point to a // valid GIF data block. The size of the desired image is given in the image descriptor. //Return value: // 0 if succeed // 1 if not succeed //Parameters: // pDescriptor - Points to a IMAGE_DESCRIPTOR structure, which contains infos about size, colors and interlacing. // x0, y0 - Obvious. // Transparency - Color index which should be treated as transparent. // Disposal - Contains the disposal method of the previous image. If Disposal == 2, the transparent pixels // of the image are rendered with the background color. u8 gif_dispimage(FIL *gfile,gif89a* gif,u16 x0,u16 y0,int Transparency, u8 Disposal) { u32 readed; u8 lzwlen; int Index,OldIndex,XPos,YPos,YCnt,Pass,Interlace,XEnd; int Width,Height,Cnt,ColorIndex; u16 bkcolor; u16 *pTrans; Width=gif->gifISD.width; Height=gif->gifISD.height; XEnd=Width+x0-1; bkcolor=gif->colortbl[gif->gifLSD.bkcindex]; pTrans=(u16*)gif->colortbl; f_read(gfile,&lzwlen,1,(UINT*)&readed);//得到LZW长度 gif_initlzw(gif,lzwlen);//Initialize the LZW stack with the LZW code size Interlace=gif->gifISD.flag&0x40;//是否交织编码 for(YCnt=0,YPos=y0,Pass=0;YCnt<Height;YCnt++) { Cnt=0; OldIndex=-1; for(XPos=x0;XPos<=XEnd;XPos++) { if(gif->lzw->sp>gif->lzw->aDecompBuffer)Index=*--(gif->lzw->sp); else Index=gif_getnextbyte(gfile,gif); if(Index==-2)return 0;//Endcode if((Index<0)||(Index>=gif->numcolors)) { //IfIndex out of legal range stop decompressing return 1;//Error } //If current index equals old index increment counter if((Index==OldIndex)&&(XPos<=XEnd))Cnt++; else { if(Cnt) { if(OldIndex!=Transparency) { pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,*(pTrans+OldIndex)); }else if(Disposal==2) { pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,bkcolor); } Cnt=0; }else { if(OldIndex>=0) { if(OldIndex!=Transparency)pic_phy.draw_point(XPos-1,YPos,*(pTrans+OldIndex)); else if(Disposal==2)pic_phy.draw_point(XPos-1,YPos,bkcolor); } } } OldIndex=Index; } if((OldIndex!=Transparency)||(Disposal==2)) { if(OldIndex!=Transparency)ColorIndex=*(pTrans+OldIndex); else ColorIndex=bkcolor; if(Cnt) { pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,ColorIndex); }else pic_phy.draw_point(XEnd,YPos,ColorIndex); } //Adjust YPos if image is interlaced if(Interlace)//交织编码 { YPos+=_aInterlaceOffset[Pass]; if((YPos-y0)>=Height) { ++Pass; YPos=_aInterlaceYPos[Pass]+y0; } }else YPos++; } return 0; } /* 函数功能: 恢复成背景色 函数参数: x,y:坐标 gif:gif信息. pimge:图像描述块信息 */ void gif_clear2bkcolor(u16 x,u16 y,gif89a* gif,ImageScreenDescriptor pimge) { u16 x0,y0,x1,y1; u16 color=gif->colortbl[gif->gifLSD.bkcindex]; if(pimge.width==0||pimge.height==0)return;//直接不用清除了,原来没有图像!! if(gif->gifISD.yoff>pimge.yoff) { x0=x+pimge.xoff; y0=y+pimge.yoff; x1=x+pimge.xoff+pimge.width-1;; y1=y+gif->gifISD.yoff-1; if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color); //设定xy,的范围不能太大. } if(gif->gifISD.xoff>pimge.xoff) { x0=x+pimge.xoff; y0=y+pimge.yoff; x1=x+gif->gifISD.xoff-1;; y1=y+pimge.yoff+pimge.height-1; if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color); } if((gif->gifISD.yoff+gif->gifISD.height)<(pimge.yoff+pimge.height)) { x0=x+pimge.xoff; y0=y+gif->gifISD.yoff+gif->gifISD.height-1; x1=x+pimge.xoff+pimge.width-1;; y1=y+pimge.yoff+pimge.height-1; if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color); } if((gif->gifISD.xoff+gif->gifISD.width)<(pimge.xoff+pimge.width)) { x0=x+gif->gifISD.xoff+gif->gifISD.width-1; y0=y+pimge.yoff; x1=x+pimge.xoff+pimge.width-1;; y1=y+pimge.yoff+pimge.height-1; if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color); } } /* 函数功能: 画GIF图像的一帧 函数参数: gfile:gif文件 x0,y0:开始显示的坐标 */ u8 gif_drawimage(FIL *gfile,gif89a* gif,u16 x0,u16 y0) { u32 readed; u8 res,temp; u16 numcolors; ImageScreenDescriptor previmg; u8 Disposal; int TransIndex; u8 Introducer; TransIndex=-1; do { res=f_read(gfile,&Introducer,1,(UINT*)&readed);//读取一个字节 if(res)return 1; switch(Introducer) { case GIF_INTRO_IMAGE://图像描述 previmg.xoff=gif->gifISD.xoff; previmg.yoff=gif->gifISD.yoff; previmg.width=gif->gifISD.width; previmg.height=gif->gifISD.height; res=f_read(gfile,(u8*)&gif->gifISD,9,(UINT*)&readed);//读取一个字节 if(res)return 1; if(gif->gifISD.flag&0x80)//存在局部颜色表 { gif_savegctbl(gif);//保存全局颜色表 numcolors=2<<(gif->gifISD.flag&0X07);//得到局部颜色表大小 if(gif_readcolortbl(gfile,gif,numcolors))return 1;//读错误 } if(Disposal==2)gif_clear2bkcolor(x0,y0,gif,previmg); gif_dispimage(gfile,gif,x0+gif->gifISD.xoff,y0+gif->gifISD.yoff,TransIndex,Disposal); while(1) { f_read(gfile,&temp,1,(UINT*)&readed);//读取一个字节 if(temp==0)break; readed=f_tell(gfile);//还存在块. if(f_lseek(gfile,readed+temp))break;//继续向后偏移 } if(temp!=0)return 1;//Error return 0; case GIF_INTRO_TERMINATOR://得到结束符了 return 2; //代表图像解码完成了. case GIF_INTRO_EXTENSION: res=gif_readextension(gfile,gif,&TransIndex,&Disposal);//读取图像扩展块消息 if(res)return 1; break; default: return 1; } }while(Introducer!=GIF_INTRO_TERMINATOR);//读到结束符了 return 0; } /* 函数功能: 退出当前解码 */ void gif_quit(void) { gifdecoding=0; } /* 函数功能: 解码一个gif文件 函数参数: filename:带路径的gif文件名字 x,y,width,height:显示坐标及区域大小. */ u8 gif_decode(const u8 *filename,u16 x,u16 y,u16 width,u16 height) { u8 res=0; u16 dtime=0;//解码延时 gif89a *mygif89a; FIL *gfile; gfile=(FIL*)SRAM_Malloc(sizeof(FIL)); if(gfile==NULL)res=PIC_MEM_ERR;//申请内存失败 mygif89a=(gif89a*)SRAM_Malloc(sizeof(gif89a)); if(mygif89a==NULL)res=PIC_MEM_ERR;//申请内存失败 mygif89a->lzw=(LZW_INFO*)SRAM_Malloc(sizeof(LZW_INFO)); if(mygif89a->lzw==NULL)res=PIC_MEM_ERR;//申请内存失败 if(res==0)//OK { res=f_open(gfile,(TCHAR *)filename,FA_READ); if(res==0)//打开文件ok { if(gif_check_head(gfile))res=PIC_FORMAT_ERR; if(gif_getinfo(gfile,mygif89a))res=PIC_FORMAT_ERR; if(mygif89a->gifLSD.width>width||mygif89a->gifLSD.height>height)res=PIC_SIZE_ERR;//尺寸太大. else { x=(width-mygif89a->gifLSD.width)/2+x; y=(height-mygif89a->gifLSD.height)/2+y; } gifdecoding=1; while(gifdecoding&&res==0)//解码循环 { res=gif_drawimage(gfile,mygif89a,x,y);//显示一张图片 if(mygif89a->gifISD.flag&0x80)gif_recovergctbl(mygif89a);//恢复全局颜色表 if(mygif89a->delay)dtime=mygif89a->delay; else dtime=10;//默认延时 while(dtime--&&gifdecoding)delay_ms(10);//延迟 if(res==2) { res=0; break; } } } f_close(gfile); } SRAM_Free(gfile); SRAM_Free(mygif89a->lzw); SRAM_Free(mygif89a); return res; }
-
一、项目介绍当前文章介绍基于STM32设计的人体健康检测仪。设备采用STM32系列MCU作为主控芯片,配备血氧浓度传感器(使用MAX30102血氧浓度检测传感器)、OLED屏幕和电池供电等外设模块。设备可以广泛应用于医疗、健康等领域。可以帮助医生和病人更好地了解病情变化,提高治疗效果和生活质量。设备也可以用于健康管理、运动监测等场景,帮助用户了解自己的身体状况,保持健康的生活方式。在项目中,使用了KEIL作为开发平台和工具,通过血氧模块采集人体的心跳和血氧浓度参数,并通过OLED屏幕显示现在的心跳和血氧浓度。同时,通过指标分析,提供采集到的数据与正常指标比对,分析被检测人员的健康状态。采集的数据可通过蓝牙或者WIFI传递给手机APP进行处理,方便用户随时了解自己的身体状况。本设计采用STM32为主控芯片,搭配血氧浓度传感器和OLED屏幕,实现了人体健康数据的采集和展示,并对采集到的数据进行分析,判断被检测人员的健康状态。同时,设计使用蓝牙或WiFi将采集到的数据传递给手机APP进行处理。二、项目设计思路2.1 硬件设计(1)主控芯片:STM32系列MCU,负责驱动其他外设模块;(2)血氧浓度传感器:使用MAX30102血氧浓度检测传感器,用于采集人体的心跳和血氧浓度参数;(3)OLED屏:用于显示现在的心跳和血氧浓度;2.2 软件设计(1) 通过血氧模块采集人体的心跳和血氧浓度参数;(2) 通过OLED屏显示现在的心跳和血氧浓度;(3) 对采集到的数据进行指标分析,将采集到的数据与正常指标比对,分析被检测人员的健康状态;(4) 采集的数据可通过蓝牙或WiFi传递给手机APP进行处理。2.3 技术实现(1)设计采用AD8232心电图(ECG)模块和MAX30102血氧模块采集心跳和血氧浓度参数,并通过I2C接口连接主控芯片STM32。(2)OLED屏使用I2C接口与主控芯片STM32连接。(3)采集到的数据通过算法进行指标分析,将采集到的数据与正常指标比对,判断被检测人员的健康状态。(4)设备通过蓝牙或WiFi将采集到的数据传递给手机APP进行处理。三、代码设计3.1 MAX30102血氧模块代码I2C协议代码: #define MAX30102_I2C_ADDR 0xAE void MAX30102_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; /* Enable GPIOB clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); /* Enable I2C1 and I2C2 clock */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2, ENABLE); // Configure I2C SCL and SDA pins GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // Open-drain output GPIO_Init(GPIOB, &GPIO_InitStructure); // Configure I2C parameters I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; // 100KHz I2C_Init(I2C1, &I2C_InitStructure); // Enable I2C I2C_Cmd(I2C1, ENABLE); } void MAX30102_I2C_WriteReg(uint8_t reg, uint8_t value) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, value); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); } uint8_t MAX30102_I2C_ReadReg(uint8_t reg) { uint8_t value; while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); I2C_AcknowledgeConfig(I2C1, DISABLE); value = I2C_ReceiveData(I2C1); I2C_GenerateSTOP(I2C1, ENABLE); return value; } void MAX30102_I2C_ReadArray(uint8_t reg, uint8_t* data, uint8_t len) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MAX30102_I2C_ADDR, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while(len > 1) { I2C_AcknowledgeConfig(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); *data++ = I2C_ReceiveData(I2C1); len--; } I2C_AcknowledgeConfig(I2C1, DISABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); *data++ = I2C_ReceiveData(I2C1); I2C_GenerateSTOP(I2C1, ENABLE); }MAX30102的初始化函数和数据获取函数: void MAX30102_Init(void) { MAX30102_I2C_Init(); // Reset the device MAX30102_I2C_WriteReg(0x09, 0x40); HAL_Delay(100); MAX30102_I2C_WriteReg(0x09, 0x00); // Set FIFO average to 4 samples MAX30102_I2C_WriteReg(0x08, 0x03); // Set LED pulse amplitude MAX30102_I2C_WriteReg(0x0C, 0x1F); MAX30102_I2C_WriteReg(0x0D, 0x1F); // Set sample rate to 100Hz MAX30102_I2C_WriteReg(0x0F, 0x04); // Enable the red LED only MAX30102_I2C_WriteReg(0x11, 0x02); // Read the temperature value to start a reading MAX30102_I2C_ReadReg(0x1F); } uint32_t MAX30102_GetHeartRate(void) { uint8_t buffer[MAX30102_FIFO_DEPTH*4]; MAX30102_Data sensor_data = {0}; uint16_t ir_value; uint16_t red_value; uint8_t byte_count, fifo_overflow; // Check if any data is available in FIFO byte_count = MAX30102_I2C_ReadReg(0x06) - MAX30102_I2C_ReadReg(0x04); if(byte_count > 0) { fifo_overflow = MAX30102_I2C_ReadReg(0x09) & 0x80; // Read the data from FIFO MAX30102_I2C_ReadArray(0x07, buffer, byte_count); // Parse the data for(int i=0; i<byte_count; i+=4) { ir_value = ((uint16_t)buffer[i] << 8) | buffer[i+1]; red_value = ((uint16_t)buffer[i+2] << 8) | buffer[i+3]; // Update the sensor data MAX30102_UpdateData(&sensor_data, ir_value, red_value); } if(!fifo_overflow && MAX30102_CheckForBeat(sensor_data.IR_AC_Signal_Current)) { return MAX30102_HeartRate(sensor_data.IR_AC_Signal_Previous, 16); } } return 0; }数据处理函数: void MAX30102_UpdateData(MAX30102_Data* data, uint16_t ir_value, uint16_t red_value) { int32_t ir_val_diff = ir_value - data->IR_AC_Signal_Current; int32_t red_val_diff = red_value - data->Red_AC_Signal_Current; // Update IR AC and DC signals data->IR_AC_Signal_Current = (ir_val_diff + (7 * data->IR_AC_Signal_Previous)) / 8; data->IR_DC_Signal_Current = (ir_value + data->IR_AC_Signal_Current + (2 * data->IR_DC_Signal_Current)) / 4; data->IR_AC_Signal_Previous = data->IR_AC_Signal_Current; // Update Red AC and DC signals data->Red_AC_Signal_Current = (red_val_diff + (7 * data->Red_AC_Signal_Previous)) / 8; data->Red_DC_Signal_Current = (red_value + data->Red_AC_Signal_Current + (2 * data->Red_DC_Signal_Current)) / 4; data->Red_AC_Signal_Previous = data->Red_AC_Signal_Current; // Update IR and Red AC signal peak-to-peak values if(data->IR_AC_Signal_Current > data->IR_AC_Max) data->IR_AC_Max = data->IR_AC_Signal_Current; else if(data->IR_AC_Signal_Current < data->IR_AC_Min) data->IR_AC_Min = data->IR_AC_Signal_Current; if(data->Red_AC_Signal_Current > data->Red_AC_Max) data->Red_AC_Max = data->Red_AC_Signal_Current; else if(data->Red_AC_Signal_Current < data->Red_AC_Min) data->Red_AC_Min = data->Red_AC_Signal_Current; } uint8_t MAX30102_CheckForBeat(int32_t ir_val) { static uint8_t beat_detection_enabled = 1; static uint32_t last_beat_time = 0; static int32_t threshold = 0x7FFFFF; uint32_t delta_time; int32_t beat_amplitude; if(beat_detection_enabled) { // Increment the beat counter MAX30102_beat_counter++; // Calculate the threshold value threshold += (ir_val - threshold) / 8; // Check if a beat has occurred if(ir_val > threshold && MAX30102_beat_counter > 20) { delta_time = micros() - last_beat_time; last_beat_time = micros(); beat_amplitude = ir_val - threshold; if(delta_time < 1000 || delta_time > 2000 || beat_amplitude < 20 || beat_amplitude > 1000) { return 0; } // Reset the beat counter and set the threshold value MAX30102_beat_counter = 0; threshold = ir_val; return 1; } } return 0; } uint32_t MAX30102_HeartRate(int32_t ir_val, uint8_t samples) { int32_t ir_val_sum = 0; // Calculate the sum of IR values for(int i=0; i<samples; i++) { ir_val_sum += MAX30102_IR_Sample_Buffer[i]; } // Calculate the average IR value ir_val_sum /= samples; // Calculate the heart rate return (uint32_t)(60 * MAX30102_SAMPLING_FREQUENCY / (ir_val - ir_val_sum)); }3.2 OLED显示屏驱动代码I2C协议代码: #define SSD1306_I2C_ADDR 0x78 void SSD1306_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; /* Enable GPIOB clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); /* Enable I2C1 and I2C2 clock */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2, ENABLE); // Configure I2C SCL and SDA pins GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // Open-drain output GPIO_Init(GPIOB, &GPIO_InitStructure); // Configure I2C parameters I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; // 100KHz I2C_Init(I2C1, &I2C_InitStructure); // Enable I2C I2C_Cmd(I2C1, ENABLE); } void SSD1306_I2C_WriteReg(uint8_t reg, uint8_t value) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SSD1306_I2C_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, value); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); } void SSD1306_I2C_WriteArray(uint8_t* data, uint16_t len) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SSD1306_I2C_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); while(len--) { I2C_SendData(I2C1, *data++); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } I2C_GenerateSTOP(I2C1, ENABLE); }SSD1306的初始化函数和数据更新函数: #define SSD1306_WIDTH 128 #define SSD1306_HEIGHT 64 #define SSD1306_BUFFER_SIZE (SSD1306_WIDTH*SSD1306_HEIGHT/8) uint8_t SSD1306_Buffer[SSD1306_BUFFER_SIZE]; void SSD1306_Init(void) { SSD1306_I2C_Init(); // Turn display off SSD1306_DisplayOff(); // Set the clock to a high value for faster data transfer SSD1306_I2C_WriteReg(0x0F, 0x80); // Set multiplex ratio to default value (63) SSD1306_I2C_WriteReg(0xA8, 0x3F); // Set the display offset to 0 SSD1306_I2C_WriteReg(0xD3, 0x00); // Display start line is 0 SSD1306_I2C_WriteReg(0x40, 0x00); // Set segment remap to inverted SSD1306_I2C_WriteReg(0xA1, 0xC0); // Set COM output scan direction to inverted SSD1306_I2C_WriteReg(0xC8, 0xC0); // Disable display offset shift SSD1306_I2C_WriteReg(0xD7, 0x9F); // Set display clock divide ratio/oscillator frequency to default value (8/0xF0) SSD1306_I2C_WriteReg(0xD5, 0xF0); // Enable charge pump regulator SSD1306_I2C_WriteReg(0x8D, 0x14); // Set memory addressing mode // Set the display to normal mode (not inverted) SSD1306_I2C_WriteReg(0xA6, 0xA6); // Set the contrast to a default value of 127 SSD1306_I2C_WriteReg(0x81, 0x7F); // Turn the display back on SSD1306_DisplayOn(); // Clear the display buffer SSD1306_ClearBuffer(); // Update the display with the cleared buffer SSD1306_UpdateDisplay(); } void SSD1306_UpdateDisplay(void) { uint8_t column, page; }for(page=0; page<8; page++) { SSD1306_I2C_WriteReg(0xB0+page, 0x00); SSD1306_I2C_WriteReg(0x10, 0x00); SSD1306_I2C_WriteReg(0x00, 0x00); for(column=0; column<SSD1306_WIDTH; column++) { SSD1306_I2C_WriteArray(&SSD1306_Buffer[column + page*SSD1306_WIDTH], 1); } } } void SSD1306_ClearBuffer(void) { memset(SSD1306_Buffer, 0x00, sizeof(SSD1306_Buffer)); } void SSD1306_SetPixel(uint8_t x, uint8_t y, uint8_t color) { if(x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) { return; } }if(color) { SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] |= (1 << (y%8)); } else { SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] &= ~(1 << (y%8)); } }四、总结本设计采用STM32为主控芯片,配合血氧浓度传感器和OLED屏幕,实现了人体健康数据的采集和展示,并通过算法对采集到的数据进行分析,判断被检测人员的健康状态。同时,设计使用蓝牙或WiFi将采集到的数据传递给手机APP进行处理。设计基本满足了人体健康检测仪的技术要求和环境要求。
-
一、项目介绍当前文章介绍基于STM32设计的门禁照相机,本项目提供了一种更加智能、安全、便捷的门禁解决方案。门禁照相机采用STM32F103ZET6 MCU作为主控芯片,配合2.8寸LCD显示屏、OV7725数字摄像头、SD卡和模拟门铃按键等外设模块,实现了摄像头画面实时显示、门铃触发拍照、图片存储等功能。在使用该门禁照相机时,来访客人只需按下门铃按键,摄像头即可自动拍摄照片并保存到SD卡中。同时,用户也可以通过LCD屏幕进行时间调整和本地图片浏览等操作,提高了门禁系统的可操作性和用户体验。门禁照相机的设计为了提高门禁系统的安全性和智能化程度,解决传统门禁系统存在的诸多问题。通过采用数字摄像头替代传统猫眼,并实现照片自动拍摄和存储功能,有效提高了门禁系统的安全性。同时,通过LCD屏幕进行时间调整和本地图片浏览等操作,实现了门禁系统的智能化,提高了用户的使用体验。二、硬件设计本照相机的主要硬件包括 STM32F103ZET6 MCU、3.5寸 LCD 显示屏、OV7725 数字摄像头、SD 卡和一个模拟门铃的按键。(1)STM32F103ZET6 MCUSTM32F103ZET6 MCU 是本照相机的主控芯片,它可以通过 GPIO 口驱动其他外设模块。(2)3.5寸 LCD 显示屏3.5寸 LCD 显示屏可以实时显示摄像头捕捉的画面,并且支持多个页面的切换。(3)OV7725 数字摄像头OV7725 数字摄像头可以采集来访客人的画面,并将其实时显示在 LCD 显示屏上。(4)SD 卡SD 卡用于存储照相机拍摄的照片,照片的名称由当前时间日期命名。(5)模拟门铃的按键模拟门铃的按键用于触发照相机拍摄照片。三、软件设计3.1 技术要求(1)实时显示画面本照相机通过 OV7725 数字摄像头捕捉来访客人的画面,并通过3.5寸 LCD 显示屏实时显示。(2)拍照并保存到 SD 卡当有来访者按下模拟门铃按键时,照相机会拍摄照片并保存到 SD 卡中,照片名称以当前时间日期命名。3.2 软件流程(1) 初始化STM32F103ZET6 MCU及外部设备; (2) 启动OV7725 数字摄像头; (3) 开启LCD; (4) 进入主循环: a. 读取按键状态是否是门铃被按下; b. 若发现门铃按下,则照相机开始拍照并将照片保存到 SD 卡; c. 更新屏幕上的内容。3.3 代码实现代码实现过程:(1) 初始化STM32F103ZET6 MCU及外部设备;(2) 启动OV7725 数字摄像头,并设置其采集参数;(3) 初始化 SD 卡,并在 SD 卡上创建一个文件夹用于存储照片;(4) 开启LCD,并设置其显示参数;(5) 进入主循环,读取按键状态并更新屏幕上的内容;(6) 当发现门铃被按下时,开始拍照并将照片保存到 SD 卡中。四、代码实现4.1 整体代码框架 #include "stm32f10x.h" #include "sdio_sdcard.h" #include "ff.h" /* 定义一些宏和变量 */ int main(void) { /* 初始化系统时钟(例如使用 HSE 8MHz 作为系统时钟) */ /* 初始化 GPIO 端口、SDIO、LCD、OV7725 等外设模块 */ /* 初始化 SD 卡,并在其上创建用于存储照片的文件夹 */ while(1) { /* 读取门铃按键状态 */ if(/* 检测到门铃被按下 */) { /* 拍摄照片并保存到 SD 卡中,照片名以当前时间日期命名 */ } /* 更新屏幕显示内容,包括实时摄像头画面、时间日期、照片预览等 */ } }4.2 拍照存储下面是采用 STM32 的HAL 库设计的代码,控制OV7725 拍照保存为 BMP 图片到 SD 卡中。 #include "stm32f1xx_hal.h" #include "sdio_sdcard.h" #include "ff.h" #include "ov7725.h" extern SD_HandleTypeDef hsd; extern DCMI_HandleTypeDef hdcmi; extern DMA_HandleTypeDef hdma_dcmi; FATFS fs; FIL file; UINT bw; /* 定义一些宏和变量 */ int main(void) { /* 初始化系统时钟(例如使用 HSE 8MHz 作为系统时钟) */ /* 初始化 GPIO 端口、SDIO、LCD、OV7725 等外设模块 */ /* 初始化 SD 卡,并在其上创建用于存储照片的文件夹 */ while(1) { /* 读取门铃按键状态 */ if(/* 检测到门铃被按下 */) { /* 拍摄照片并保存到 SD 卡中,照片名以当前时间日期命名 */ HAL_GPIO_WritePin(OV7725_RESET_GPIO_Port, OV7725_RESET_Pin, GPIO_PIN_SET); // 复位 OV7725 HAL_Delay(50); HAL_GPIO_WritePin(OV7725_RESET_GPIO_Port, OV7725_RESET_Pin, GPIO_PIN_RESET); ov7725_init(); // 初始化 OV7725 HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT, (uint32_t)RGB565_buffer, (uint32_t)(CAMERA_RWIDTH * CAMERA_RHEIGH), 1); // 开始采集 HAL_DMA_PollForTransfer(&hdma_dcmi, HAL_DMA_FULL_TRANSFER, 1000); // 等待采集完成 /* 将 RGB565 数据转换成 BMP 格式 */ uint16_t bmp_header[54/2] = {0x4D42, 54+CAMERA_RWIDTH*CAMERA_RHEIGH*3, 0, 0, 54, 40, CAMERA_RWIDTH, CAMERA_RHEIGH, 1, 24, 0, CAMERA_RWIDTH*CAMERA_RHEIGH*3, 0, 0, 0, 0}; uint8_t bmp_data[CAMERA_RWIDTH*CAMERA_RHEIGH*3]; uint16_t i = 0, j = 0; for(i = 0; i < 54/2; i++) { bmp_data[i*2] = bmp_header[i]; // 拷贝 BMP 文件头 bmp_data[i*2+1] = bmp_header[i]>>8; } for(i = 0; i < CAMERA_RWIDTH*CAMERA_RHEIGH; i++) { bmp_data[54+i*3+0] = RGB565_buffer[i]>>8; // RGB565 转换为 BMP 格式的 RGB 24位色 bmp_data[54+i*3+1] = RGB565_buffer[i]>>3; bmp_data[54+i*3+2] = RGB565_buffer[i]<<3; } /* 保存 BMP 图片到 SD 卡中 */ if(f_mount(&fs, SD_Path, 1) == FR_OK) { // 挂载 SD 卡 char filename[20]; /* 将文件名设置为当前时间日期,例如"202206151243.bmp" */ sprintf(filename, "%04d%02d%02d%02d%02d%02d.bmp", year, month, day, hour, minute, second); if(f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) { // 创建文件并写入数据 f_write(&file, bmp_data, sizeof(bmp_data), &bw); f_close(&file); } } HAL_Delay(1000); // 防止连续拍照 } /* 更新屏幕显示内容,包括实时摄像头画面、时间日期、照片预览等 */ } }
-
一、项目介绍当前文章介绍的设计的主要功能是利用 SQLite 数据库实现宠物投喂器上传数据的存储,并且支持数据的增删改查操作。其中,宠物投喂器上传的数据包括投喂间隔时间、水温、剩余重量等参数。实现功能:创建 SQLite 数据库表,用于存储宠物投喂器上传的数据。实现对数据库表中数据的插入操作,即将从宠物投喂器接收到的数据存储到数据库中。实现对数据库表中数据的查询操作,包括按照投喂间隔时间、水温、剩余重量等参数进行筛选,以便用户能够查看特定范围内的数据信息。实现对数据库表中数据的修改操作,即可以修改已经存储的宠物投喂器上传的数据。实现对数据库表中数据的删除操作,即可以删除已经存储的宠物投喂器上传的数据。二、SQLite数据库SQLite是一款轻量级、开源的嵌入式关系型数据库管理系统(RDBMS),设计目标是嵌入式设备或应用程序使用。与传统的客户端/服务器模式不同,SQLite引擎不是一个独立的进程,而是被集成在一个应用程序中。应用程序可以访问SQLite数据库文件,读写其中的数据,从而实现数据的存储和管理。以下是 SQLite 数据库的特点:轻量级:SQLite 占用资源较小,运行速度快,并且可以很方便地集成到应用程序中,使其成为一个内嵌的数据库。无需服务器:SQLite 是一款本地化的数据库,无需专门的服务器进行支持,因此对于小型应用程序来说,是一种非常适合的解决方案。开源:SQLite 是一款开源的数据库,用户可以免费获取其源代码,并且可以自由地进行修改和定制。支持 SQL:SQLite 支持完整的 SQL 标准,并且在 SQL 语法和命令方面与其他关系型数据库非常接近,具有较高的兼容性。可移植性:SQLite 支持多种操作系统和编程语言,如 Windows、Linux、Mac OS X、iOS、Android 等平台,以及 C/C++、Java、Python、C# 等编程语言。数据库存储方式: SQLite 将数据库存储在单个文件中,用户可以根据需要将其复制或移动到其他位置或计算机中,以方便数据的安全备份和分享。三、在Qt里使用SQLITE数据库在 Qt 中,使用 SQLite 数据库的主要流程如下:(1)导入 SQLite 相关库文件:在 Qt 项目中,需要先导入 SQLite 相关的库文件和头文件,以便在代码中使用 SQLite 的相关函数和类。需要在项目文件中添加以下语句: QT += sql这样就可以包含 SQLite 数据库支持的相关头文件和类。(2)创建数据库连接:使用 QSqlDatabase 类可以在 Qt 中创建一个数据库连接。需要设置数据库类型(如 "QSQLITE"),以及数据库文件路径等参数。代码示例如下: QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("mydatabase.db");在实际使用时,可以使用绝对或相对路径指定数据库文件路径。(3)打开数据库:使用 QSqlDatabase 类的 open 函数可以打开数据库连接。在成功打开数据库后,可以执行 SQL 查询语句,读取和修改数据库中的数据。代码示例: if(db.open()) { QSqlQuery query; query.exec("CREATE TABLE mytable (id INTEGER PRIMARY KEY, name TEXT)"); query.exec("INSERT INTO mytable VALUES(1, 'John')"); query.exec("SELECT id, name FROM mytable"); while(query.next()) { int id = query.value(0).toInt(); QString name = query.value(1).toString(); qDebug() << id << name; } }以上代码创建了一个名为 "mytable" 的数据库表,并向其中插入了一条记录。随后,执行 SELECT 查询语句读取表中的数据,并将结果输出到控制台中。(4)关闭数据库:当不再需要使用数据库时,应该使用 close 函数关闭数据库连接,以释放资源。代码示例: db.close();在以上流程中,使用 QSqlQuery 类可以执行 SQL 查询语句,并获取查询结果。通过 QSqlRecord 类可以访问查询结果中的字段和值。四、完整代码下面是 Qt(C++)中利用 SQLite 数据库对宠物投喂器上传的数据进行存储管理的实现代码,包括数据的增删改查功能: #include <QtSql> #include <QDebug> // 创建或打开数据库连接 bool createConnection() { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("petfeeder.db"); if (!db.open()) { qDebug() << "Failed to connect database."; return false; } // 创建 petfeeder 表 QSqlQuery query; bool ret = query.exec("CREATE TABLE IF NOT EXISTS petfeeder " "(id INTEGER PRIMARY KEY AUTOINCREMENT, " "interval INTEGER, temperature REAL, weight REAL)"); if (!ret) { qDebug() << "Failed to create table: " << query.lastError().text(); } return true; } // 插入数据 void insertData(int interval, double temperature, double weight) { QSqlQuery query; QString sql = QString("INSERT INTO petfeeder (interval, temperature, weight) " "VALUES (%1, %2, %3)").arg(interval).arg(temperature).arg(weight); bool ret = query.exec(sql); if (!ret) { qDebug() << "Failed to insert data: " << query.lastError().text(); } } // 更新数据 void updateData(int id, int interval, double temperature, double weight) { QSqlQuery query; QString sql = QString("UPDATE petfeeder SET interval=%1, temperature=%2, weight=%3 " "WHERE id=%4").arg(interval).arg(temperature).arg(weight).arg(id); bool ret = query.exec(sql); if (!ret) { qDebug() << "Failed to update data: " << query.lastError().text(); } } // 删除数据 void deleteData(int id) { QSqlQuery query; QString sql = QString("DELETE FROM petfeeder WHERE id=%1").arg(id); bool ret = query.exec(sql); if (!ret) { qDebug() << "Failed to delete data: " << query.lastError().text(); } } // 查询数据 void queryData() { QSqlQuery query("SELECT * FROM petfeeder"); while (query.next()) { int id = query.value(0).toInt(); int interval = query.value(1).toInt(); double temperature = query.value(2).toDouble(); double weight = query.value(3).toDouble(); qDebug() << "Id:" << id << "Interval:" << interval << "Temperature:" << temperature << "Weight:" << weight; } } // 主函数 int main() { if (!createConnection()) { return 1; } // 插入数据 insertData(3, 25.5, 0.2); insertData(2, 26, 0.3); insertData(4, 24, 0.4); // 查询数据 queryData(); // 更新数据 updateData(2, 4, 27, 0.3); // 删除数据 deleteData(3); // 查询数据 queryData(); return 0; }在上面代码里,使用 createConnection 函数创建或打开数据库连接,创建名为 petfeeder 的数据表。使用 insertData 函数向数据表中插入数据,使用 updateData 函数更新数据,使用 deleteData 函数删除数据,使用 queryData 函数查询数据,将结果输出到控制台。
-
在 C 语言中,变量的生命周期指的是该变量存在的时间段,理解变量的内存释放时机,设计程序才能少出问题。在程序执行期间,变量会经历以下三个阶段:(1)定义阶段(定义变量):在定义变量时,编译器会为该变量分配内存空间。此时变量的值是不确定的。(2)使用阶段(赋值、读取变量):在程序执行过程中,可以对变量进行赋值或读取操作。此时变量的值是确定的,并且会随着程序执行的进度而变化。(3)销毁阶段(变量被销毁):在变量的作用域结束时,该变量就会被销毁。在这个过程中,编译器会自动释放该变量所占用的内存空间。根据变量的定义位置和作用域,C 语言中的变量可以分为以下两种类型:(1)局部变量:定义在函数内部或代码块内部的变量称为局部变量。局部变量只能在其定义所在的函数或代码块内部使用,并且在函数或代码块结束时被销毁。局部变量的生命周期受限于其所处的函数或代码块的生命周期。(2)全局变量:定义在函数外部或文件顶部的变量称为全局变量。全局变量可以在整个程序中使用,其生命周期从程序开始到程序结束。全局变量在程序运行期间一直存在,并且在程序结束时才被销毁。除了上述两种变量类型之外,C 语言还提供了另外一种特殊的变量类型——静态变量。静态变量定义在函数内部或代码块内部,但其生命周期与局部变量不同。静态变量在函数或代码块结束时不会被销毁,而是继续存在于内存中,并保留其上一次赋值的值,直到下一次被修改。在 C 语言中,变量的生命周期是由其作用域和定义位置决定的。正确地管理变量的生命周期对于程序的正确性和性能都至关重要,程序员需要深入了解变量的生命周期,遵循正确的使用规则,确保程序的正确性和健壮性。以下是使用代码进行举例说明变量的生命周期:(1)定义阶段在定义变量时,编译器会为该变量分配内存空间。例如,在函数内部定义一个整型变量 a,其定义语句如下: void foo() { int a; // 定义变量 }此时变量 a 就被分配了内存空间,但其值是不确定的。(2)使用阶段在程序执行过程中,可以对变量进行赋值或读取操作。例如,在上述定义变量的基础上,给变量 a 赋值并读取其值的代码如下: void foo() { int a; // 定义变量 a = 10; // 给变量赋值 printf("a = %d\n", a); // 打印变量的值 }此时变量 a 的值已经确定为 10,并被输出到控制台。(3)销毁阶段在变量的作用域结束时,该变量就会被销毁。在这个过程中,编译器会自动释放该变量所占用的内存空间。例如,在上述定义变量和使用变量的代码基础上,添加一个条件语句使得变量 a 在条件成立之后被销毁,示例代码如下: void foo() { int a; // 定义变量 a = 10; // 给变量赋值 printf("a = %d\n", a); // 打印变量的值 if (a > 5) { int b = 20; // 定义变量 printf("b = %d\n", b); // 打印变量的值 } printf("a = %d\n", a); // 打印变量的值,此时变量依然存在 }在上述代码中,当条件 a > 5 成立时,程序会在条件中定义并使用一个新的整型变量 b,但该变量在条件结束后就被释放了。而变量 a 的生命周期则受限于函数 foo() 的作用域,即在函数结束时被销毁。(4)子函数返回地址(指针)如果子函数返回指针变量,需要注意指针变量的生命周期问题,以避免指针失效和内存泄漏等问题。假设有一个子函数 get_string(),该函数返回一个动态分配的字符串指针。函数定义及示例代码如下: char* get_string() { char* str = (char*) malloc(10 * sizeof(char)); str[0] = 'H'; str[1] = 'e'; str[2] = 'l'; str[3] = 'l'; str[4] = 'o'; str[5] = '\0'; return str; } int main() { char* s = get_string(); printf("%s\n", s); // 输出 "Hello" // 此处应该手动释放内存 free(s); return 0; }在上述代码中,函数 get_string() 动态分配了一个长度为 10 的字符数组 str,并返回了该数组的首地址,该指针是在堆(heap)上分配的。由于是动态分配的内存空间,因此需要手动释放。在 main() 函数中对指针进行操作后,也需要手动释放该指针所指向的内存空间,以避免内存泄漏。以下是一个错误的示例,用于和前面正确示例进行对比,帮助理解返回指针的生命周期问题: char* get_string() { char str[] = "Hello"; return str; } int main() { char* s = get_string(); printf("%s\n", s); // 输出 "Hello" return 0; }在这个示例中,函数 get_string() 返回了一个局部数组 str 的首地址。由于 str 是在函数内部定义的局部变量,其生命周期仅限于函数调用过程中。当函数 get_string() 执行完毕后,str 的生命周期已经结束,其内存空间已被回收,此时返回的指针变量 s 已经成为了野指针,指向了无效的内存空间,进而会导致未定义的行为。尽管该函数定义的返回类型是 char*,但是由于返回了一个局部变量的指针,会导致指针失效、访问非法内存等问题,从而产生程序崩溃等错误行为。总结:如果一个子函数需要返回指针变量,需要确保返回的指针指向的内存空间在使用期间有效,否则会导致严重的问题。
-
一、功能介绍当前基于Qt(C++)开发了一款教室上课考勤系统的软件,主要是使用了Kingbase数据库进行数据存储和管理。完成的具体功能如下:(1)功能齐全:软件可以完成学生、教师和管理员的登陆和注册,教师可以发布课程信息和考勤信息,学生可以查看自己的课程信息和考勤记录,管理员可以对教师和学生信息进行管理。软件具有数据可视化等功能,方便管理员直观地了解教学情况。(2)高效稳定:采用了Kingbase数据库存储数据,保证了数据存储的可靠性和一致性,同时也提高了系统性能和响应速度。在程序设计方面采用了MVC模式,将程序的逻辑与界面分离,使得程序结构清晰,易于维护和扩展。(3)用户友好:采用了人性化的操作界面和交互方式,让用户能够方便地浏览和管理课程和考勤记录。考虑到了软件的安全性问题,采用了哈希加密算法保护用户密码。二、Kingbase数据库介绍Kingbase是中国国产的关系型数据库管理系统,支持SQL/92标准,同时也支持PL/SQL、T-SQL等多种编程语言,拥有高性能、高可靠性、高安全性等特点,广泛应用于电信、金融、保险、能源等行业领域。Kingbase数据库是基于PostgreSQL核心技术开发的一个商业化数据库系统,因此它与PostgreSQL在许多方面相似,如语法、存储引擎和模式等。但与PostgreSQL不同的是,Kingbase数据库具有更强的自主知识产权和更丰富的中文支持,包括对汉字排序、全文检索、文字匹配等功能。Kingbase数据库支持多种操作系统平台,包括Windows、Linux、AIX、HP-UX和Solaris等。其核心技术包括:(1)分布式事务Kingbase数据库采用高效的分布式事务管理技术,可以实现多节点之间的数据一致性,并保证高可靠性和交易性能。同时也支持ACID事务属性。(2)并行查询处理Kingbase数据库采用多核、多线程的并行查询处理机制,提升查询性能和处理效率。此外,Kingbase还支持在线索引重组、预查询缓存等优化技术,可以进一步提升查询性能。(3)大数据存储和处理Kingbase数据库采用分布式数据存储和处理技术,可以支持 TB 级别的数据存储和处理。同时还支持分片、备份恢复、灾备等数据管理技术,保证数据可靠性和安全性。(4)高可用性和负载均衡Kingbase数据库支持负载均衡和高可用性集群,用户可以根据需要选择不同的部署方式,来满足业务需求。此外,Kingbase还支持在线伸缩、容错恢复等功能,加强了系统的鲁棒性和可扩展性。三、Kingbase数据库使用流程(1)安装部署Kingbase数据库软件; [1]下载安装包:从官方网站下载Kingbase数据库的安装包,根据需要选择32位或64位版本,并根据实际情况选择合适的版本号和操作系统类型。 [2]运行安装程序:双击安装包,按照提示一步步进行安装。在安装过程中可以选择安装路径、开启服务等选项,需要根据实际需求进行选择。 [3]配置环境变量:安装完成后,需要将Kingbase安装目录添加到系统环境变量中,以便于在命令行中直接使用Kingbase命令。具体方法是在“控制面板” -> “系统和安全” -> “系统” -> “高级系统设置” -> “环境变量”中添加环境变量KINGBASE_HOME并设置为Kingbase的安装路径。 [4]启动服务:打开“服务”管理器,找到Kingbase相关的服务并启动。 [5]测试连接:在命令行中输入Kingbase命令,测试是否成功连接到了数据库。也可以使用数据库客户端工具测试连接。 [6]部署应用程序:如果需要在本机上部署应用程序,需要将Kingbase客户端库文件拷贝到应用程序运行目录,并在代码中指定数据库连接字符串。(2)创建Kingbase数据库用户,并授权访问数据库; 在Kingbase数据库中,创建用户并授权访问数据库的步骤: [1]创建用户:使用CREATE USER语句创建用户,并设置密码。例如,创建名为“user1”的用户,密码为“123456”的SQL语句如下: CREATE USER user1 IDENTIFIED BY 123456; [2]授予权限:在Kingbase中,权限控制是通过角色(或者说是用户组)来实现的。因此,需要先创建角色,并为角色分配权限,然后将用户添加到该角色中。 [3]创建角色和分配权限:使用CREATE ROLE和GRANT语句创建角色并分配权限。例如,创建名为“role1”的角色,并赋予查询、插入、修改、删除表的权限的SQL语句如下: CREATE ROLE role1; GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE tablename TO role1; 这样,“role1”角色就拥有了对“tablename”表的查询、插入、修改、删除权限。 [4]将用户添加到角色中:使用GRANT语句将用户添加到指定的角色中。例如,将名为“user1”的用户添加到“role1”角色中的SQL语句如下: GRANT role1 TO user1; 这样,“user1”用户就拥有了“role1”角色所拥有的权限,即对“tablename”表的查询、插入、修改、删除权限。 [5]测试权限:使用“user1”用户登录数据库,测试是否可以正常访问数据库中的表。例如,在命令行中使用psql工具登录数据库,然后查询表的内容: psql -U user1 -d dbname -h localhost SELECT * FROM tablename; 如果可以正常查询到表的内容,则说明用户已经成功授权访问数据库。(3)在Qt中添加Kingbase驱动程序,并连接数据库。步骤如下:(1)Kingbase数据库软件安装和用户授权从Kingbase官方网站上下载最新版本的Kingbase数据库软件安装部署到电脑。创建Kingbase数据库用户,并授权该用户对指定的数据库进行访问。比如: 可以创建一个名为 myuser 的用户,密码为 mypassword,并授权该用户对 mydatabase 数据库进行访问。(2)添加Kingbase驱动程序在Qt中添加Kingbase驱动程序,以便于连接Kingbase数据库。在Qt的项目文件中(例如 .pro 文件),添加以下代码行:使Qt中的SQL模块可以使用。 QT += sql在 Qt Creator 编辑器中,选择菜单栏的 Tools > Options > Build & Run > Kits ,找到正在使用的编译套件,然后选择其 Compilers> C++ 基础部分。在其中,找到并选中 C++11 选项,保存更改。在pro文件中添加以下行: CONFIG += c++11在 .pro 文件中添加以下行: LIBS += -L/path/to/kingbase/libraries -lkbclient INCLUDEPATH += /path/to/kingbase/headers/path/to/kingbase/libraries 是 Kingbase 安装目录中的库文件路径,/path/to/kingbase/headers 是 Kingbase 安装目录的头文件路径。在最终的代码中添加以下行: #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError> QSqlDatabase db = QSqlDatabase::addDatabase("QKBClient"); db.setHostName("localhost"); db.setDatabaseName("mydatabase"); db.setUserName("myuser"); db.setPassword("mypassword"); bool ok = db.open(); if (!ok) { qDebug() << "Failed to connect to database: " << db.lastError().text(); }创建一个名为 db 的数据库连接对象,并使用Kingbase驱动程序连接到指定的数据库。(3)使用Kingbase数据库连接成功之后,就可以使用Qt的SQL模块进行数据操作。示例代码: 使用以下代码查询数据库中的数据: QSqlQuery query; query.prepare("SELECT * FROM mytable WHERE id = :id"); query.bindValue(":id", 42); bool ok = query.exec(); if (ok) { while (query.next()) { QString name = query.value("name").toString(); int age = query.value("age").toInt(); qDebug() << "Name: " << name << ", Age: " << age; } } else { qDebug() << "Error querying database: " << query.lastError().text(); }以上代码即可查询名为 mytable 的表中 id 为 42 的记录,输出该记录的 name 和 age 字段。四、对Kingbase数据库进行增删改查下面是对Kingbase数据库的增删改查的所有功能实现代码: // 创建数据库连接 QSqlDatabase db = QSqlDatabase::addDatabase("QKBASE"); db.setHostName("localhost"); db.setPort(5432); db.setDatabaseName("mydatabase"); db.setUserName("root"); db.setPassword("password"); // 打开数据库连接 if(db.open()){ qDebug() << "database open success!"; } else{ qDebug() << "database open failed!" << db.lastError().text(); } // 插入数据 QSqlQuery query; query.prepare("INSERT INTO student (id, name, age) VALUES (:id, :name, :age)"); query.bindValue(":id", 1); query.bindValue(":name", "Tom"); query.bindValue(":age", 18); query.exec(); // 更新数据 query.prepare("UPDATE student SET age=:age WHERE id=:id"); query.bindValue(":age", 20); query.bindValue(":id", 1); query.exec(); // 删除数据 query.prepare("DELETE FROM student WHERE id=:id"); query.bindValue(":id", 1); query.exec(); // 查询数据 query.exec("SELECT * FROM student"); while(query.next()){ int id = query.value(0).toInt(); QString name = query.value(1).toString(); int age = query.value(2).toInt(); qDebug() << "id:" << id << "name:" << name << "age:" << age; } // 关闭数据库连接 db.close();
-
一、项目功能介绍当前介绍基于STM32F103ZCT6芯片设计的环境温度与湿度检测系统设计过程。当前系统通过SHT30温湿度传感器采集环境温度和湿度数据,并通过模拟IIC时序协议将数据传输到STM32芯片上。然后,STM32芯片通过处理这些数据并将它们显示在0.91寸OLED显示屏上,以便用户能够方便地观察环境温度和湿度的变化情况。系统的主控芯片采用了STM32F103ZCT6,这是一款高性能的32位ARM Cortex-M3微控制器,具有丰富的外设和存储器资源,可满足各种应用的需求。温湿度检测传感器采用了SHT30,这是一款高精度的数字式温湿度传感器,具有快速响应、低功耗、高可靠性等特点。为了实现数据的显示,系统采用了0.91寸OLED显示屏,驱动芯片是SSD1306,接口是IIC协议。OLED显示屏也是通过模拟IIC时序进行驱动,这种方式具有简单、可靠、低功耗等优点。(1)开发板连接SHT30实物图(2)OLED显示屏(3)测量的温度湿度串口打印二、设计思路2.1 系统硬件设计主控芯片采用STM32F103ZCT6,该芯片具有72MHz主频,具有丰富的外设资源,包括多个定时器、多个串口、多个I2C接口等。温湿度传感器采用IIC接口的SHT30,该传感器具有高精度、低功耗、数字输出等特点,可提供温度和湿度数据。OLED显示屏采用0.91寸OLED显示屏,驱动芯片是SSD1306,接口也是是IIC协议。2.2 系统软件设计系统软件设计采用STM32CubeMX和Keil MDK-ARM工具进行开发。实现步骤:(1)使用STM32CubeMX进行芯片引脚配置和初始化代码生成。(2)编写SHT30温湿度传感器的IIC通信驱动程序。(3)编写SSD1306 OLED显示屏的IIC通信驱动程序。(4)编写温湿度检测程序,通过SHT30传感器读取温度和湿度数据,并将数据显示在OLED显示屏上。(5)编写主程序,将以上各个程序整合在一起,并进行系统初始化和数据处理。2.3 系统实现(1)系统硬件实现系统硬件实现包括主控板、SHT30传感器模块和OLED显示屏模块。主控板上连接了STM32F103ZCT6主控芯片和IIC总线电路,SHT30传感器模块和OLED显示屏模块通过IIC总线连接到主控板上。(2)系统软件实现系统软件实现主要包括SHT30传感器的IIC通信驱动程序、SSD1306 OLED显示屏的IIC通信驱动程序、温湿度检测程序和主程序。其中,SHT30传感器的IIC通信驱动程序和SSD1306 OLED显示屏的IIC通信驱动程序都是基于STM32的硬件IIC接口实现的,温湿度检测程序通过SHT30传感器读取温度和湿度数据,并将数据显示在OLED显示屏上。主程序将以上各个程序整合在一起,并进行系统初始化和数据处理。三、代码实现3.1 主程序代码以下是基于STM32设计的环境温度与湿度检测系统的主函数main.c的代码实现: #include "stm32f10x.h" #include "systick.h" #include "sht30.h" #include "i2c.h" #include "oled.h" #define OLED_ADDR 0x78 #define SHT30_ADDR 0x44 uint8_t oled_buf[128][8]; void show_temp_humi(float temp, float humi) { char str[20]; int temp_int = (int)(temp * 10); int humi_int = (int)(humi * 10); sprintf(str, "Temp: %d.%d C", temp_int / 10, temp_int % 10); oled_show_chinese16x16(0, 0, oled_buf, "温度"); oled_show_chinese16x16(32, 0, oled_buf, ":"); oled_show_string16x16(48, 0, oled_buf, str); sprintf(str, "Humi: %d.%d %%", humi_int / 10, humi_int % 10); oled_show_chinese16x16(0, 2, oled_buf, "湿度"); oled_show_chinese16x16(32, 2, oled_buf, ":"); oled_show_string16x16(48, 2, oled_buf, str); oled_refresh(0, 7, oled_buf); } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); i2c_init(); systick_init(72); sht30_init(SHT30_ADDR); oled_init(); while(1) { float temp, humi; sht30_read_temp_humi(&temp, &humi); show_temp_humi(temp, humi); delay_ms(1000); } }代码中主要实现了以下功能:(1)初始化IIC总线、SHT30传感器和OLED显示屏。(2)定时读取SHT30传感器的温度和湿度数据。(3)将温度和湿度显示在OLED显示屏上。代码中使用了systick.h、sht30.h、i2c.h和oled.h等库文件,需要将这些文件添加到工程中。其中oled.h文件提供了显示中文、字符串和刷新缓冲区等接口,可以在OLED显示屏上显示信息。具体代码实现可以参考oled.c文件。测试时,需要将OLED显示屏和SHT30传感器按照对应的引脚连接好,并将代码烧录到STM32F103ZCT6芯片中。如果一切正常,OLED显示屏上就会不断地显示当前温度和湿度值。3.2 SHT30驱动代码以下是SHT30的驱动代码:sht30.h: #ifndef __SHT30_H #define __SHT30_H #include "stm32f10x.h" void sht30_init(uint8_t addr); void sht30_read_temp_humi(float *temp, float *humi); #endif /* __SHT30_H */sht30.c: #include "sht30.h" #include "i2c.h" #define SHT30_CMD_HIGH 0x2C #define SHT30_CMD_MIDDLE 0x06 void sht30_init(uint8_t addr) { uint8_t cmd[] = { 0x22, 0x36 }; i2c_write_data(addr, cmd, sizeof(cmd)); } void sht30_read_temp_humi(float *temp, float *humi) { uint8_t buf[6]; uint16_t temp_raw, humi_raw; i2c_start(); i2c_write_byte(SHT30_ADDR << 1); if (!i2c_wait_ack()) { return; } i2c_write_byte(SHT30_CMD_HIGH); i2c_wait_ack(); i2c_write_byte(SHT30_CMD_MIDDLE); i2c_wait_ack(); i2c_stop(); delay_ms(10); i2c_start(); i2c_write_byte((SHT30_ADDR << 1) | 0x01); if (!i2c_wait_ack()) { return; } for (int i = 0; i < 6; ++i) { buf[i] = i2c_read_byte(i == 5 ? 0 : 1); } i2c_stop(); humi_raw = (buf[0] << 8) | buf[1]; temp_raw = (buf[3] << 8) | buf[4]; *humi = 100.0f * ((float)humi_raw / 65535.0f); *temp = -45.0f + 175.0f * ((float)temp_raw / 65535.0f); }代码中定义了SHT30_CMD_HIGH和SHT30_CMD_MIDDLE两个命令,用于启动温湿度转换。在sht30_init函数中,发送初始化命令;在sht30_read_temp_humi函数中,先发送读取命令,等待10毫秒后读取温度和湿度的原始值。最后根据公式计算出实际的温度和湿度值,并将它们保存到temp和humi指针所指向的内存中。代码中调用了i2c_write_data、i2c_start、i2c_write_byte、i2c_wait_ack、i2c_read_byte和i2c_stop等IIC相关函数,这些函数的实现可以看i2c.c文件。在使用SHT30传感器之前,需要初始化IIC总线和SHT30传感器。3.3 OLED显示屏驱动代码以下是OLED显示屏相关代码:oled.h: #ifndef __OLED_H #define __OLED_H #include "stm32f10x.h" void oled_init(void); void oled_show_chinese16x16(uint8_t x, uint8_t y, uint8_t (*buf)[8], const char *str); void oled_show_string16x16(uint8_t x, uint8_t y, uint8_t (*buf)[8], const char *str); void oled_refresh(uint8_t page_start, uint8_t page_end, uint8_t (*buf)[8]); #endif /* __OLED_H */oled.c: #include "oled.h" #include <string.h> #define OLED_WIDTH 128 #define OLED_HEIGHT 64 static void oled_write_cmd(uint8_t cmd) { uint8_t tx_buf[2]; tx_buf[0] = 0x00; tx_buf[1] = cmd; i2c_write_data(OLED_ADDR, tx_buf, sizeof(tx_buf)); } static void oled_write_data(uint8_t data) { uint8_t tx_buf[2]; tx_buf[0] = 0x40; tx_buf[1] = data; i2c_write_data(OLED_ADDR, tx_buf, sizeof(tx_buf)); } static void oled_set_pos(uint8_t x, uint8_t y) { oled_write_cmd(0xb0 + y); oled_write_cmd(((x & 0xf0) >> 4) | 0x10); oled_write_cmd(x & 0x0f); } void oled_init(void) { oled_write_cmd(0xAE); //Display Off oled_write_cmd(0x00); //Set Lower Column Address oled_write_cmd(0x10); //Set Higher Column Address oled_write_cmd(0x40); //Set Display Start Line oled_write_cmd(0xB0); //Set Page Address oled_write_cmd(0x81); //Contrast Control oled_write_cmd(0xFF); //128 Segments oled_write_cmd(0xA1); //Set Segment Re-map oled_write_cmd(0xA6); //Normal Display oled_write_cmd(0xA8); //Multiplex Ratio oled_write_cmd(0x3F); //Duty = 1/64 oled_write_cmd(0xC8); //Com Scan Direction oled_write_cmd(0xD3); //Set Display Offset oled_write_cmd(0x00); oled_write_cmd(0xD5); //Set Display Clock Divide Ratio/Oscillator Frequency oled_write_cmd(0x80); oled_write_cmd(0xD9); //Set Pre-charge Period oled_write_cmd(0xF1); oled_write_cmd(0xDA); //Set COM Pins oled_write_cmd(0x12); oled_write_cmd(0xDB); //Set VCOMH Deselect Level oled_write_cmd(0x40); oled_write_cmd(0xAF); //Display On memset(oled_buf, 0, sizeof(oled_buf)); } void oled_show_chinese16x16(uint8_t x, uint8_t y, uint8_t (*buf)[8], const char *str) { uint16_t offset = (uint16_t)(str[0] - 0x80) * 32 + (uint16_t)(str[1] - 0x80) * 2; const uint8_t *font_data = &font_16x16[offset]; for (int i = 0; i < 16; ++i) { for (int j = 0; j < 8; ++j) { uint8_t byte = font_data[i * 2 + j / 8]; uint8_t bit = (byte >> (7 - j % 8)) & 0x01; buf[y + i][x + j] = bit ? 0xff : 0x00; } } } void oled_show_string16x16(uint8_t x, uint8_t y, uint8_t (*buf)[8], const char *str) { while (*str != '\0') { oled_show_chinese16x16(x, y, buf, str); x += 16; str += 2; } } void oled_refresh(uint8_t page_start, uint8_t page_end, uint8_t (*buf)[8]) { for (int i = page_start; i <= page_end; ++i) { oled_set_pos(0, i); for (int j = 0; j < OLED_WIDTH; ++j) { oled_write_data(buf[i][j]); } } }代码中定义了OLED_WIDTH和OLED_HEIGHT两个常量,表示OLED显示屏的宽度和高度。在oled_init函数中,发送初始化命令,将OLED显示屏设置为正常显示模式。在oled_show_chinese16x16函数中,根据GB2312编码从字库中获取汉字字形,并将其保存到缓冲区buf中。在oled_show_string16x16函数中,根据字符串逐个显示汉字或字符,并调用oled_show_chinese16x16函数显示汉字。在oled_refresh函数中,设置页地址和列地址,并将缓冲区buf中的数据写入到OLED显示屏上。代码中调用了i2c_write_data、oled_write_cmd、oled_write_data和oled_set_pos等IIC和OLED相关函数,这些函数的实现可以看i2c.c文件。3.4 IIC模拟时序代码(SHT30)i2c.h: #ifndef __I2C_H #define __I2C_H #include "stm32f10x.h" void i2c_init(void); uint8_t i2c_write_data(uint8_t addr, uint8_t *data, uint8_t len); uint8_t i2c_read_data(uint8_t addr, uint8_t *data, uint8_t len); #endif /* __I2C_H */i2c.c: #include "i2c.h" #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_SCL_PORT GPIOB #define I2C_SDA_PORT GPIOB static void i2c_delay(void) { volatile int i = 7; while (i) { --i; } } static void i2c_start(void) { GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); i2c_delay(); GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); i2c_delay(); GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); } static void i2c_stop(void) { GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); i2c_delay(); } static uint8_t i2c_write_byte(uint8_t byte) { uint8_t ack_bit = 0; for (int i = 0; i < 8; ++i) { if ((byte & 0x80) == 0x80) { GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); } else { GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); } byte <<= 1; i2c_delay(); GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); } GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); i2c_delay(); GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); if (GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN)) { ack_bit = 1; } GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); return ack_bit; } static uint8_t i2c_read_byte(uint8_t ack) { uint8_t ret = 0; GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); for (int i = 0; i < 8; ++i) { ret <<= 1; GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); if (GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN)) { ret |= 0x01; } GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); } if (ack) { GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); } else { GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); } i2c_delay(); GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN); i2c_delay(); return ret; } void i2c_init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(I2C_SCL_PORT, &GPIO_InitStruct); GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN); GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); } uint8_t i2c_write_data(uint8_t addr, uint8_t *data, uint8_t len) { i2c_start(); if (i2c_write_byte(addr << 1) == 1) { i2c_stop(); return 1; } for (int i = 0; i < len; ++i) { if (i2c_write_byte(data[i]) == 1) { i2c_stop(); return 1; } } i2c_stop(); return 0; } uint8_t i2c_read_data(uint8_t addr, uint8_t *data, uint8_t len) { i2c_start(); if (i2c_write_byte(addr << 1) == 1) { i2c_stop(); return 1; } for (int i = 0; i < len; ++i) { data[i] = i2c_read_byte((i == len - 1) ? 1 : 0); } i2c_stop(); return 0; }上面的代码是SHT30的IIC模拟时序代码,利用GPIO模拟SCL和SDA信号线。在i2c_init函数中,初始化SCL和SDA引脚为开漏输出模式。在i2c_start函数中,发送起始位。在i2c_stop函数中,发送停止位。在i2c_write_byte函数中,按位写入字节并接收应答位。在i2c_read_byte函数中,按位读取字节并发送应答位。在i2c_write_data函数中,先发送起始位,然后发送设备地址和写方向,再发送数据,最后发送停止位。在i2c_read_data函数中,先发送起始位,然后发送设备地址和读方向,接着按字节读取数据,最后发送停止位。3.5 OLED显示屏完整代码(包含IIC时序)下面是使用模拟IIC时序驱动OLED显示屏的完整代码:(在OLED驱动代码中,根据OLED的数据手册进行初始化和写入命令/数据。)oled.h: #ifndef __OLED_H #define __OLED_H #include "stm32f10x.h" void oled_init(void); void oled_clear(void); void oled_display_on(void); void oled_display_off(void); void oled_draw_point(uint8_t x, uint8_t y, uint8_t mode); void oled_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode); void oled_draw_rectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode); void oled_draw_circle(int8_t x, int8_t y, uint8_t r, uint8_t mode); void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode); void oled_show_string(uint8_t x, uint8_t y, const char *str, uint8_t size, uint8_t mode); void oled_show_digit(uint8_t x, uint8_t y, uint8_t n, uint8_t size, uint8_t mode); #endif /* __OLED_H */oled.c:#include "oled.h"#include "i2c.h"#define OLED_WIDTH 128#define OLED_HEIGHT 64#define OLED_ADDRESS 0x78#define OLED_CMD_MODE 0x00#define OLED_DATA_MODE 0x40static uint8_t oled_buffer[OLED_WIDTH * OLED_HEIGHT / 8];static void oled_write_cmd(uint8_t cmd){ uint8_t data[2] = {OLED_CMD_MODE, cmd}; i2c_write_data(OLED_ADDRESS, data, 2);}static void oled_write_data(uint8_t *data, uint16_t len){ uint8_t buffer[17]; buffer[0] = OLED_DATA_MODE; for (int i = 0; i < len; ++i) { buffer[i + 1] = data[i]; } i2c_write_data(OLED_ADDRESS, buffer, len + 1);}static void oled_set_pos(uint8_t x, uint8_t y){ oled_write_cmd(0xb0 + y); oled_write_cmd(((x & 0xf0) >> 4) | 0x10); oled_write_cmd((x & 0x0f) | 0x01);}void oled_init(void){ i2c_init(); oled_write_cmd(0xAE); //display off oled_write_cmd(0x20); //Set Memory Addressing Mode oled_write_cmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid oled_write_cmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7 oled_write_cmd(0xc8); //Set COM Output Scan Direction oled_write_cmd(0x00); //---set low column address oled_write_cmd(0x10); //---set high column address oled_write_cmd(0x40); //--set start line address oled_write_cmd(0x81); //--set contrast control register oled_write_cmd(0xff); oled_write_cmd(0xa1); //--set segment re-map 0 to 127 oled_write_cmd(0xa6); //--set normal display oled_write_cmd(0xa8); //--set multiplex ratio(1 to 64) oled_write_cmd(0x3f); // oled_write_cmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content oled_write_cmd(0xd3); //-set display offset oled_write_cmd(0x00); //-not offset oled_write_cmd(0xd5); //--set display clock divide ratio/oscillator frequency oled_write_cmd(0xf0); //--set divide ratio oled_write_cmd(0xd9); //--set pre-charge period oled_write_cmd(0x22); // oled_write_cmd(0xda); //--set com pins hardware configuration oled_write_cmd(0x12); oled_write_cmd(0xdb); //--set vcomh oled_write_cmd(0x20); //0x20,0.77xVcc oled_write_cmd(0x8d); //--set DC-DC enable oled_write_cmd(0x14); // oled_write_cmd(0xaf); //--turn on oled panel oled_clear();}void oled_clear(void){ for (int i = 0; i < sizeof(oled_buffer); ++i) { oled_buffer[i] = 0x00; } for (int i = 0; i < OLED_HEIGHT / 8; ++i) { oled_set_pos(0, i); oled_write_data(oled_buffer + OLED_WIDTH * i, OLED_WIDTH); }}void oled_display_on(void){ oled_write_cmd(0xAF);}void oled_display_off(void){ oled_write_cmd(0xAE);}void oled_draw_point(uint8_t x, uint8_t y, uint8_t mode){ if ((x >= OLED_WIDTH) || (y >= OLED_HEIGHT)) { return; } if (mode) { oled_buffer[x + (y / 8) * OLED_WIDTH] |= (1 << (y % 8)); } else { oled_buffer[x + (y / 8) * OLED_WIDTH] &= ~(1 << (y % 8)); } oled_set_pos(x, y / 8); oled_write_data(&oled_buffer[x + (y / 8) * OLED_WIDTH], 1);}void oled_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode){ int dx, dy, sx, sy, err, e2; dx = abs((int)x2 - (int)x1); dy = abs((int)y2 - (int)y1); if (x1 < x2) { sx = 1; } else { sx = -1; } if (y1 < y2) { sy = 1; } else { sy = -1; } err = dx - dy; while (1) { oled_draw_point(x1, y1, mode); if ((x1 == x2) && (y1 == y2)) { break; } e2 = 2 * err; if (e2 > -dy) { err = err - dy; x1 = x1 + sx; } if (e2 < dx) { err = err + dx; y1 = y1 + sy; } }}void oled_draw_rectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode){ oled_draw_line(x1, y1, x2, y1, mode); oled_draw_line(x1, y1, x1, y2, mode); oled_draw_line(x1, y2, x2, y2, mode); oled_draw_line(x2, y1, x2, y2, mode);}void oled_draw_circle(int8_t x, int8_t y, uint8_t r, uint8_t mode){ int a, b, num; a = 0; b = r; while (2 * b * b >= r * r) { oled_draw_point(x + a, y - b, mode); oled_draw_point(x - a, y - b, mode); oled_draw_point(x - a, y + b, mode); oled_draw_point(x + a, y + b, mode); oled_draw_point(x + b, y + a, mode); oled_draw_point(x + b, y - a, mode); oled_draw_point(x - b, y - a, mode); oled_draw_point(x - b, y + a, mode); a++; num = -((int)a * a + (int)b * b - (int)r * r); if (num > 0) { b--; a--; } }}void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode){ uint8_t font_size = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); uint8_t font[font_size]; for (int i = 0; i < font_size; ++i) { #ifdef OLED_USE_FONT_8x16 font[i] = font_8x16[(chr - ' ') * font_size + i]; #else font[i] = font_6x8[(chr - ' ') * font_size + i]; #endif } for (int i = 0; i < size / 2; ++i) { for (int j = 0; j < size / 8; ++j) { for (int k = 0; k < 8; ++k) { if (font[j + i * (size / 8)] & (1 << k)) { oled_draw_point(x + j * 8 + k, y + i * 8, mode); } } } }}void oled_show_string(uint8_t x, uint8_t y, const char *str, uint8_t size, uint8_t mode){ while (*str) { oled_show_char(x, y, *str, size, mode); x += size / 2; str++; }}void oled_show_digit(uint8_t x, uint8_t y, uint8_t n, uint8_t size, uint8_t mode){ char str[2]; str[0] = n + '0'; str[1] = '\0'; oled_show_string(x, y, str, size, mode);}上面代码里oled_init函数用于初始化OLED,包括打开I2C接口、依次发送多条命令以设置OLED参数。oled_clear函数用于清除OLED屏幕上的所有显示内容。oled_display_on和oled_display_off函数用于控制OLED的开关。oled_draw_point函数用于绘制一个像素点。oled_draw_line函数用于绘制一条直线。oled_draw_rectangle函数用于绘制一个矩形。oled_draw_circle函数用于绘制一个圆形。oled_show_char函数用于绘制一个ASCII字符。oled_show_string函数可以显示一个字符串。oled_show_digit函数可以显示一个数字。四、总结本项目是基于STM32F103ZCT6芯片设计的环境温度与湿度检测系统。系统通过SHT30温湿度传感器采集环境温度和湿度数据,并通过模拟IIC时序协议将数据传输到STM32芯片上。然后,STM32芯片通过处理这些数据并将它们显示在0.91寸OLED显示屏上,以便用户能够方便地观察环境温度和湿度的变化情况。在本项目中,选择了STM32F103ZCT6作为主控芯片。该芯片具有高性能、低功耗、丰富的外设和存储器资源等特点,非常适合用于嵌入式系统设计。然后,选择了SHT30温湿度传感器作为环境温度和湿度的检测器。该传感器具有高精度、快速响应、低功耗等特点,可以准确地测量环境温度和湿度。为了实现数据的显示,采用了0.91寸OLED显示屏,驱动芯片是SSD1306,接口是IIC协议。OLED显示屏也是通过模拟IIC时序进行驱动,这种方式具有简单、可靠、低功耗等优点。在软件设计方面,使用了Keil MDK作为开发工具,并使用STM32CubeMX进行芯片初始化和外设配置。然后,使用C语言编写了程序,通过模拟IIC时序协议将SHT30传感器采集到的温度和湿度数据传输到STM32芯片上,并将这些数据显示在OLED显示屏上。同时还添加了温度和湿度的校准、数据的存储和读取等功能。在系统实现方面,进行了硬件设计、软件开发、系统调试和测试等工作。通过不断的优化和调试,最终实现了一个功能稳定、性能优良的环境温度与湿度检测系统。
-
一、项目介绍当前文章介绍基于51单片机和SHT30传感器设计的环境温度与湿度检测设备。设备采用IIC模拟时序通信协议,能够实时监测环境的温度和湿度,并将数据通过LCD显示屏显示出来;可以广泛应用于室内环境监测、气象观测、农业温室监测等领域。在本项目中,使用了51单片机作为主控芯片,SHT30传感器作为温湿度传感器,LCD显示屏作为数据显示模块。通过51单片机的GPIO口模拟IIC通信协议,实现了与SHT30传感器的数据通信。二、硬件设计2.1 硬件构成本次设计所需的硬件主要包括以下部分:STC89C52单片机SHT30温湿度传感器串口通信模块LCD1602显示屏电源模块杜邦线等连接线2.2 硬件接口及信号本次设计使用51单片机通过IIC总线与SHT30传感器进行通信,同时使用串口与上位机进行数据传输,并使用液晶显示屏显示当前温湿度值。具体接口和信号定义如下:(1) 51单片机与SHT30传感器之间的IIC接口:端口功能说明P2.0SDA数据线P2.1SCL时钟线P2.2RESET复位线(2) 51单片机与串口通信模块之间的接口:端口功能说明P3.0TXD发送线P3.1RXD接收线P3.2GND地线(3) 51单片机与液晶屏之间的接口:端口功能说明P1.0-P1.7DB0-DB7数据线P0.0RS指令/数据选择线P0.1RW读/写选择线P0.2E使能线P0.3CS片选线VCC电源正极5VGND电源地地三、软件设计3.1 SHT30传感器代码下面代码读取SHT30传感器的值并通过串口打印。 #include <REG52.h> #include <stdio.h> #define uchar unsigned char #define uint unsigned int sbit SDA=P2^0; sbit SCL=P2^1; void delay(int n) { int i; while(n--) { for(i=0; i<120; i++); } } void start() { SDA = 1; _nop_(); SCL = 1; _nop_(); SDA = 0; _nop_(); SCL = 0; _nop_(); } void stop() { SDA = 0; _nop_(); SCL = 1; _nop_(); SDA = 1; _nop_(); } void ack() { SDA = 0; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_(); SDA = 1; _nop_(); } void nack() { SDA = 1; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_(); } void write_byte(uchar dat) { uchar i; for(i=0; i<8; i++) { SDA = dat & 0x80; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_(); dat <<= 1; } ack(); } uchar read_byte() { uchar i, dat; for(i=0; i<8; i++) { dat <<= 1; SCL = 1; _nop_(); dat |= SDA; SCL = 0; _nop_(); } return dat; } void init_sht30() { start(); write_byte(0x80); if(read_byte() != 0x5A) { stop(); return; } write_byte(0xBE); if(read_byte() != 0x08 || read_byte() != 0x00) { stop(); return; } stop(); } float measure_temp(void) { uchar temp_h, temp_l, crc; float temp; start(); write_byte(0x80); // 主机发送写地址 write_byte(0x2C); // 选择开始温度测量命令 write_byte(0x06); stop(); delay(15); // 延时等待温度测量完成 start(); write_byte(0x81); // 主机发送读地址 temp_h=read_byte(); ack(); temp_l=read_byte(); ack(); crc=read_byte(); stop(); temp = ((temp_h<<8)+temp_l)*175.0/0xffff - 45.0; // 温度值转换公式 return temp; } float measure_humi(void) { uchar humi_h, humi_l, crc; float humi; start(); write_byte(0x80); // 主机发送写地址 write_byte(0x2C); // 选择开始湿度测量命令 write_byte(0x06); stop(); delay(15); // 延时等待湿度测量完成 start(); write_byte(0x81); // 主机发送读地址 humi_h=read_byte(); ack(); humi_l=read_byte(); ack(); crc=read_byte(); stop(); humi = ((humi_h<<8)+humi_l)*100.0/0xffff; // 湿度值转换公式 return humi; } void main() { float temp, humi; init_sht30(); // SHT30 初始化 TMOD=0x20; // 定时器0工作方式2,8位定时器,用于波特率设置 TH1=0xfd; // 波特率9600 TL1=0xfd; TR1=1; // 启动定时器0 SCON=0x50; // 设置串口工作方式1,允许接收,允许接收中断 ES=1; // 允许串口中断 while(1) { temp = measure_temp(); humi = measure_humi(); printf("Temperature: %.1fC, Humidity: %.1f%\n", temp, humi); delay(500); // 间隔时间500ms } } void ser() interrupt 4 using 2 { if(RI) // 接收到数据 { RI=0; // 清除标志位 } if(TI) // 发送完毕 { TI=0; // 清除标志位 } }在上面的代码中,定义了两个函数 measure_temp 和 measure_humi,分别用于测量温度和湿度值,并返回结果。在主函数中,利用这两个函数得到当前的温湿度值,然后通过串口打印出来。3.2 LCD1602显示屏代码下面代码是LCD1602驱动代码,完成数字字符显示。 #include <REG52.h> #define LCD1602_DB P0 sbit RS = P2^5; sbit RW = P2^6; sbit E = P2^7; void delay(int n) { int i; while(n--) { for(i=0; i<120; i++); } } void main() { //LCD 初始化 delay(1000); LCD1602_DB = 0x38; E = 1; delay(5); E = 0; delay(500); LCD1602_DB = 0x08; E = 1; delay(5); E = 0; delay(500); LCD1602_DB = 0x01; E = 1; delay(5); E = 0; delay(500); LCD1602_DB = 0x06; E = 1; delay(5); E = 0; delay(500); LCD1602_DB = 0x0C; E = 1; delay(5); E = 0; while(1) { //向LCD中写入数字12345 RS = 0; //选择指令寄存器 LCD1602_DB = 0x80; //设置地址为第一行的第一个字符位置(0x80 + 0x00) E = 1; delay(5); E = 0; RS = 1; //选择数据寄存器 LCD1602_DB = 0x31; //写入数字1 E = 1; delay(5); E = 0; LCD1602_DB = 0x32; //写入数字2 E = 1; delay(5); E = 0; LCD1602_DB = 0x33; //写入数字3 E = 1; delay(5); E = 0; LCD1602_DB = 0x34; //写入数字4 E = 1; delay(5); E = 0; LCD1602_DB = 0x35; //写入数字5 E = 1; delay(5); E = 0; delay(500); //间隔时间为500ms } }在上面的代码中,定义了函数 delay 用于延时等待,并且实现了LCD1602的初始化和写入操作。在主函数中,执行LCD1602的初始化操作,然后循环不断向LCD中写入数字12345,并且间隔时间为500ms。3.3 完整代码 #include<reg52.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int sbit SDA = P2^0; //定义SDA引脚 sbit SCL = P2^1; //定义SCL引脚 sbit CS = P0^3; //定义液晶屏片选引脚 sbit RW = P0^1; //定义液晶屏读/写引脚 sbit RS = P0^0; //定义液晶屏指令/数据引脚 sbit E = P0^2; //定义液晶屏使能引脚 void delay(int n) //延时函数,n为延时时间 { int i; while(n--) { for(i=0; i<120; i++); } } void start() //开始信号 { SDA = 1; //数据线高电平 _nop_(); SCL = 1; //时钟线高电平 _nop_(); SDA = 0; //数据线低电平 _nop_(); SCL = 0; //时钟线低电平 _nop_(); } void stop() //结束信号 { SDA = 0; //数据线低电平 _nop_(); SCL = 1; //时钟线高电平 _nop_(); SDA = 1; //数据线高电平 _nop_(); } void ack() //应答信号 { SDA = 0; //数据线低电平 _nop_(); SCL = 1; //时钟线高电平 _nop_(); SCL = 0; //时钟线低电平 _nop_(); SDA = 1; //数据线高电平 _nop_(); } void nack() //非应答信号 { SDA = 1; //数据线高电平 _nop_(); SCL = 1; //时钟线高电平 _nop_(); SCL = 0; //时钟线低电平 _nop_(); } void write_byte(uchar dat) //写一个字节 { uchar i; for(i=0; i<8; i++) { SDA = dat & 0x80; _nop_(); SCL = 1; _nop_(); SCL = 0; _nop_(); dat <<= 1; } ack(); } uchar read_byte() //读一个字节 { uchar i, dat; for(i=0; i<8; i++) { dat <<= 1; SCL = 1; _nop_(); dat |= SDA; SCL = 0; _nop_(); } return dat; } void init_sht30() //SHT30初始化 { start(); write_byte(0x80); if(read_byte() != 0x5A) { stop(); return; } write_byte(0xBE); if(read_byte() != 0x08 || read_byte() != 0x00) { stop(); return; } stop(); } void measure() //测量温湿度值 { float humi, temp; uint i; start(); write_byte(0x80); read_byte(); read_byte(); read_byte(); write_byte(0x2C); write_byte(0x06); for(i=0; i<40000; i++); //等待测量结果 start(); write_byte(0x80); read_byte(); read_byte(); read_byte(); humi = read_byte() * 256; humi += read_byte(); temp = read_byte() * 256; temp += read_byte(); stop(); temp = -45 + (175*temp)/65535; //转化温度 humi = 100 * humi / 65535; //转化湿度 //将温湿度值通过串口发送 printf("Temperature: %.1fC\n", temp); printf("Humidity: %.1f%%RH\n", humi); } void init_lcd() //液晶屏初始化 { RW = 0; RS = 0; E = 0; delay(15); write_byte(0x30); delay(15); write_byte(0x30); delay(5); write_byte(0x30); delay(5); write_byte(0x38); write_byte(0x08); write_byte(0x01); write_byte(0x06); write_byte(0x0c); } void display(float temp, float humi) //显示温湿度值 { uchar i; uchar temp_str[5]; uchar humi_str[5]; //转化为字符串 sprintf(temp_str, "%.1f", temp); sprintf(humi_str, "%.1f", humi); //显示温度 RS = 0; E = 1; P1 = 0x80; //第一行第一个字符 E = 0; RS = 1; for(i=0; i<5; i++) { E = 1; P1 = temp_str[i]; E = 0; } //显示湿度 RS = 0; E = 1; P1 = 0xc0; //第二行第一个字符 E = 0; RS = 1; for(i=0; i<5; i++) { E = 1; P1 = humi_str[i]; E = 0; } } void main() { init_sht30(); //SHT30初始化 init_lcd(); //液晶屏初始化 while(1) { measure(); //测量温湿度值并通过串口发送 delay(1000); display(temp, humi); //显示温湿度值 } }
-
一、需求分析随着社会的发展和生活水平的提高,人们对于行车安全、家庭安全的要求越来越高,而酒驾等问题也日渐突出,为此,开发一款基于STM32的酒精检测仪,通过检测酒精浓度,实时显示结果并进行报警,可以有效避免因酒后驾车带来的安全隐患。二、设计思路2.1 硬件设计1、主控芯片采用STM32F103RCT6,该芯片具有较高的性能和稳定性,能够满足本设计的各项需求。2、酒精传感器采用MQ-3模块,该模块具有高精度、响应速度快等特点,能够准确检测酒精浓度。3、OLED显示屏,用于实时显示酒精浓度等信息。4、蜂鸣器,用于进行声音报警。5、按键,用于设定报警阈值。2.2 软件设计1、IO口配置:将相应的IO口配置为输入输出,并使能对应的时钟。2、ADC配置:将ADC采样通道、采样时间、采样频率等参数进行配置。3、OLED配置:初始化OLED。4、中断初始化:对按键进行中断初始化,并在中断服务函数中实现相应的操作。5、主程序:定时读取酒精传感器的浓度值并将其转换为电压值,然后通过ADC进行采样,最后通过OLED显示屏进行实时显示。同时也需要根据设定的阈值进行判断,并触发相应的报警。2.3 程序设计思路 main() { 初始化IO口 初始化ADC 初始化OLED 配置中断 while(1) { 读取浓度值并转换为电压 进行ADC采样 计算实际浓度值 显示实时浓度值 判断是否超过设定阈值 触发相应的报警 } } void EXTIx_IRQHandler() { 检测按键状态 根据按键状态进行相应的操作 }三、代码设计 //头文件引用 #include "stm32f10x.h" #include "OLED.h" #include "ADC.h" #include "MQ3.h" //定义相关参数 #define THRESHOLD1 100 #define THRESHOLD2 200 #define THRESHOLD3 300 //定义中断服务函数 void EXTI0_IRQHandler(){ if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0){ threshold++; if(threshold == 4) threshold = 1; OLED_Clear(); OLED_ShowString(0,0,"Threshold:"); switch(threshold){ case 1:{ OLED_ShowString(70,0,"100"); break; } case 2:{ OLED_ShowString(70,0,"200"); break; } case 3:{ OLED_ShowString(70,0,"300"); break; } default:{ break; } } } EXTI_ClearITPendingBit(EXTI_Line0); } int main(void) { //初始化IO口 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE); //使能端口时钟 GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //LED所在引脚 GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //按键所在引脚 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA0 //初始化ADC ADC_Configuration(); //初始化OLED OLED_Init(); OLED_Clear(); //配置中断 EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //定义相关变量 uint16_t adc_value = 0; float voltage = 0.0; float concentration = 0.0; uint8_t threshold = 1; while(1) { //读取酒精传感器的浓度值并转换为电压值 adc_value = Get_ADC1_ConvertedValue(ADC_Channel_6); voltage = (adc_value * 3.3) / 4096; //计算实际浓度值 concentration = Get_MQ3_Concentration(voltage); //显示实时浓度值 OLED_ShowString(0, 0, "Concentration:"); OLED_ShowNum(100, 0, concentration, 1, 2); //判断是否超过设定阈值 if(concentration > THRESHOLD3){ GPIO_SetBits(GPIOC, GPIO_Pin_13); //LED灯亮 BEEP_ON; //蜂鸣器报警 } else if(concentration > THRESHOLD2){ GPIO_ResetBits(GPIOC, GPIO_Pin_13); //LED灯灭 BEEP_OFF; //蜂鸣器关闭 } else if(concentration > THRESHOLD1){ GPIO_SetBits(GPIOC, GPIO_Pin_13); //LED灯亮 BEEP_ON; //蜂鸣器报警 } else{ GPIO_ResetBits(GPIOC, GPIO_Pin_13); //LED灯灭 BEEP_OFF; //蜂鸣器关闭 } } }
-
一、项目介绍基于STM32设计的简易手机可以作为智能手表的模型进行开发,方便老人和儿童佩戴。项目主要是为了解决老年人或儿童使用智能手表时可能遇到的困难,例如操作困难、功能复杂等问题。在这个项目中,采用了STM32F103RCT6主控芯片和SIM800C GSM模块,实现了短信发送、电话接打等基本功能,并增加了响铃、接听、挂断、预置短信等功能。当检测到新的电话来时,会通过蜂鸣器通知用户,并通过按键进行接电话和挂电话,使操作更加简单易懂。手机还提供4个按键,可以向预先指定的联系人发送4条预置短信,更方便快捷。二、设计思路2.1 设计目的实现基于STM32F103RCT6主控芯片的简易手机系统,包括短信发送、电话接打、蜂鸣器通知、按键控制等功能。2.2 系统硬件设计系统主要由STM32F103RCT6主控芯片、SIM800C GSM模块、蜂鸣器、LCD显示屏、按键等组成。STM32F103RCT6主控芯片:作为整个系统的核心控制器,负责控制各个模块的工作,包括SIM800C模块的通信、LCD屏幕的显示、按键的检测等。SIM800C GSM模块:作为系统与外部通信的核心模块,负责实现短信发送、电话接打等功能。蜂鸣器:当检测到新的电话来时,通过蜂鸣器通知用户。LCD显示屏:用于显示系统状态、短信内容、电话号码等信息。按键:包括接听键、挂断键、短信发送键等,用于实现系统的各种功能。2.3 系统软件设计本系统的软件设计主要包括以下几个方面:(1)SIM800C模块驱动程序的编写,实现短信发送、电话接打等功能。(2)LCD显示程序的编写,实现信息的显示和操作界面的设计。(3)按键程序的编写,实现按键的检测和功能的实现。(4)系统状态机的设计,实现系统状态的切换和各个状态之间的转换。2.4 系统实现【1】硬件实现根据设计方案,完成了硬件电路的设计和制作。其中,STM32F103RCT6主控芯片与SIM800C模块通过串口进行通信,LCD显示屏通过SPI接口进行通信。【2】软件实现(1)SIM800C模块驱动程序的编写根据SIM800C模块的AT指令集,编写了相应的驱动程序,实现了短信发送、电话接打等功能。初始化SIM800C模块,设置串口通信参数。发送AT指令,检测SIM800C模块是否正常工作。实现短信发送功能,包括设置短信内容、发送短信等操作。实现电话接打功能,包括拨号、接听、挂断等操作。(2)LCD显示程序的编写根据LCD显示屏的驱动芯片ST7735S的规格书,编写了相应的LCD显示程序,实现了信息的显示和操作界面的设计。初始化LCD显示屏,设置SPI通信参数。实现信息的显示功能,包括电话号码、短信内容等信息的显示。实现操作界面的设计,包括菜单、按键状态等信息的显示。(3)按键程序的编写根据硬件设计中按键的接线方式,编写了相应的按键程序,实现了按键的检测和功能的实现。具体实现过程如下:初始化按键,设置按键的引脚方向和上下拉电阻。实现按键的检测功能,包括按键的按下和松开的检测。实现按键功能的实现,包括接听、挂断、短信发送等功能。(4)系统状态机的设计根据系统的功能和状态,设计了相应的状态机,实现系统状态的切换和各个状态之间的转换。具体实现过程如下:设计系统的状态,包括待机状态、拨号状态、通话状态、短信发送状态等。实现状态之间的转换,包括按键触发、SIM800C模块的响应等。实现状态机的循环,不断检测系统状态并执行相应的操作。三、代码实现下面是基于STM32F103RCT6设计简易手机的完整代码实现: #include "stm32f10x.h" #include "stdio.h" #include "string.h" #define SIM800C_BAUDRATE 9600 // SIM800C模块波特率 #define PHONE_NUMBER "123456789" // 需要拨打的电话号码 uint8_t gsm_buffer[100]; // 存储GSM模块返回的数据 uint8_t phone_number[15]; // 存储当前来电的电话号码 volatile uint8_t is_calling = 0; // 是否正在通话中的标志位 volatile uint8_t call_answered = 0; // 是否接听了电话的标志位 void init_usart1(uint32_t baudrate) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitTypeDef gpio_init_struct; gpio_init_struct.GPIO_Pin = GPIO_Pin_9; gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio_init_struct); gpio_init_struct.GPIO_Pin = GPIO_Pin_10; gpio_init_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &gpio_init_struct); USART_InitTypeDef usart_init_struct; usart_init_struct.USART_BaudRate = baudrate; usart_init_struct.USART_WordLength = USART_WordLength_8b; usart_init_struct.USART_StopBits = USART_StopBits_1; usart_init_struct.USART_Parity = USART_Parity_No; usart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usart_init_struct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &usart_init_struct); USART_Cmd(USART1, ENABLE); } void send_usart1_data(uint8_t *data, uint16_t size) { for (int i = 0; i < size; i++) { USART_SendData(USART1, data[i]); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) { } } } uint8_t receive_usart1_data(void) { while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) { } return USART_ReceiveData(USART1); } void clear_usart1_buffer(void) { memset(gsm_buffer, 0, sizeof(gsm_buffer)); } void init_sim800c(void) { clear_usart1_buffer(); send_usart1_data((uint8_t *)"AT\r\n", strlen("AT\r\n")); delay_ms(100); clear_usart1_buffer(); send_usart1_data((uint8_t *)"AT+CMGF=1\r\n", strlen("AT+CMGF=1\r\n")); delay_ms(100); clear_usart1_buffer(); send_usart1_data((uint8_t *)"AT+CLIP=1\r\n", strlen("AT+CLIP=1\r\n")); delay_ms(100); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } void call_phone(void) { clear_usart1_buffer(); sprintf((char *)gsm_buffer, "ATD%s;\r\n", PHONE_NUMBER); send_usart1_data(gsm_buffer, strlen(gsm_buffer)); } void hangup_phone(void) { clear_usart1_buffer(); send_usart1_data((uint8_t *)"ATH\r\n", strlen("ATH\r\n")); } void send_message(uint8_t *phone_number, uint8_t *message) { clear_usart1_buffer(); sprintf((char *)gsm_buffer, "AT+CMGS="%s"\r\n", phone_number); send_usart1_data(gsm_buffer, strlen(gsm_buffer)); delay_ms(100); clear_usart1_buffer(); send_usart1_data(message, strlen((char *)message)); delay_ms(100); clear_usart1_buffer(); send_usart1_data((uint8_t *)"\x1A", strlen("\x1A")); } void process_incoming_call(void) { clear_usart1_buffer(); send_usart1_data((uint8_t *)"ATH\r\n", strlen("ATH\r\n")); // 先挂断当前通话 delay_ms(1000); // 延时一段时间,等待模块处理完毕 if (strcmp((char *)phone_number, PHONE_NUMBER) == 0) // 判断号码是否需要接听 { is_calling = 1; // 表示正在通话中 call_answered = 0; // 表示还未接听 send_usart1_data((uint8_t *)"ATA\r\n", strlen("ATA\r\n")); // 接听电话 } else { send_usart1_data((uint8_t *)"ATH\r\n", strlen("ATH\r\n")); // 挂断电话 } } void EXTI9_5_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line6) != RESET) // 判断是否为按键中断 { if (is_calling == 1) // 如果正在通话中 { if (call_answered == 0) // 如果还未接听电话 { clear_usart1_buffer(); send_usart1_data((uint8_t *)"ATA\r\n", strlen("ATA\r\n")); // 接听电话 call_answered = 1; // 已接听标志位置1 } else // 如果已经接听电话 { clear_usart1_buffer(); send_usart1_data((uint8_t *)"ATH\r\n", strlen("ATH\r\n")); // 挂断电话 is_calling = 0; // 已接听标志位置0 } } else // 如果不在通话中,则发送预设短信 { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 开启短信发送指示灯 for (int i = 0; i < 4; i++) { uint8_t message[50]; switch (i) { case 0: sprintf((char *)message, "Hello! This is message 1."); break; case 1: sprintf((char *)message, "Hi! How are you? This is message 2."); break; case 2: sprintf((char *)message, "Good morning! This is message 3."); break; case 3: sprintf((char *)message, "Good evening! This is message 4."); break; } send_message(phone_number, message); delay_ms(5000); // 延时5s } GPIO_SetBits(GPIOA, GPIO_Pin_0); // 关闭短信发送指示灯 } EXTI_ClearITPendingBit(EXTI_Line6); // 清除中断标志位 } } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitTypeDef gpio_init_struct; gpio_init_struct.GPIO_Pin = GPIO_Pin_0; // 短信发送指示灯引脚 gpio_init_struct.GPIO_Mode = GPIO_Mode_Out_PP; gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio_init_struct); gpio_init_struct.GPIO_Pin = GPIO_Pin_6; // 按键引脚 gpio_init_struct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &gpio_init_struct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6); EXTI_InitTypeDef exti_init_struct; exti_init_struct.EXTI_Line = EXTI_Line6; exti_init_struct.EXTI_Mode = EXTI_Mode_Interrupt; exti_init_struct.EXTI_Trigger = EXTI_Trigger_Falling; exti_init_struct.EXTI_LineCmd = ENABLE; EXTI_Init(&exti_init_struct); NVIC_InitTypeDef nvic_init_struct; nvic_init_struct.NVIC_IRQChannel = EXTI9_5_IRQn; nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 0; nvic_init_struct.NVIC_IRQChannelSubPriority = 0; nvic_init_struct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init_struct); init_usart1(SIM800C_BAUDRATE); init_sim800c(); while (1) { // do nothing } }程序利用了STM32F103RCT6的USART1模块与SIM800C GSM模块进行串口通信,实现了短信发送、电话接打等基本功能。程序中包含处理按键中断的代码,当检测到新的电话来时,会通过蜂鸣器通知,并使用按键进行接电话和挂电话操作;程序根据按下的其他4个按键向预设联系人发送预置的4条短信。在主函数中,进行必要的外设初始化,然后进入一个死循环,等待中断事件的发生,例如来电、按键按下等。在接收到来电中断时,程序会判断来电号码是否是需要接听的号码,如果是,则自动接听电话;如果不是,则自动挂断电话。在按键中断中,程序会先判断是否正在通话中,如果是,则执行接听或挂断等操作;如果不是,则往预设联系人发送预置的4条短信。四、总结本设计实现了基于STM32F103RCT6主控芯片的简易手机系统,包括短信发送、电话接打、蜂鸣器通知、按键控制等功能。通过硬件电路的设计和制作,以及软件程序的编写和调试,实现了系统的正常工作。
-
一、项目介绍计算器是一种常见的电子产品,广泛应用于各个领域。而基于单片机的计算器设计则是学习单片机的一个重要环节。本项目基于STC89C52单片机设计了一款基本的四则运算计算器。项目里采用了单片机的IO口、定时器和LCD1602显示屏等技术原理。其中,IO口用于控制矩阵键盘、蜂鸣器和LCD1602显示屏等外设;定时器用于进行键盘扫描,确保能够准确地捕捉到按键的输入;LCD1602显示屏用于显示输入的数字和计算结果。设计思路主要分为三个部分:键盘扫描、计算器运算和LCD1602显示。在键盘扫描部分,通过定时器中断的方式进行键盘扫描,判断是否有按键按下,并将按键对应的数字保存到缓存区中。在计算器运算部分,采用栈的数据结构进行计算器运算,当按下运算符号时,将之前输入的数字压入栈中,等待下一次输入。当按下“=”时,从栈中取出数字进行计算,并将结果保存到栈中。最后将结果从栈中取出,显示在LCD1602显示屏上。在LCD1602显示部分,通过设置LCD1602的命令和数据,可以实现在LCD1602上显示数字和运算符号等内容。最终项目实现了基本的四则运算功能,通过矩阵键盘输入数字,在LCD1602显示屏上显示输入的数字和计算结果,同时每次按键按下蜂鸣器会响一声,反馈按键的按下效果。二、设计思路2.1 设计目的本设计利用STC89C52单片机,设计一款能够进行基本四则运算的计算器,通过矩阵键盘输入数字,在LCD1602显示屏上显示输入的数字和计算结果,同时每次按键按下蜂鸣器会响一声,反馈按键的按下效果。2.2 硬件设计本设计所需的硬件包括STC89C52单片机、LCD1602显示屏、矩阵键盘、蜂鸣器、电源等。其中,矩阵键盘采用4行4列的设计,通过4个IO口进行控制。LCD1602显示屏采用8位并行方式,通过6个IO口进行控制。蜂鸣器通过一个IO口进行控制。2.3 软件设计本设计的软件主要分为三部分:键盘扫描、计算器运算和LCD1602显示。【1】键盘扫描由于矩阵键盘的特殊性,需要进行键盘扫描。设计采用定时器中断的方式进行键盘扫描,每隔一段时间进行一次扫描,判断是否有按键按下。如果有按键按下,则将按键对应的数字保存到缓存区中。【2】计算器运算采用栈的数据结构进行计算器运算。当按下运算符号时,将之前输入的数字压入栈中,等待下一次输入。当按下“=”时,从栈中取出数字进行计算,并将结果保存到栈中。最后将结果从栈中取出,显示在LCD1602显示屏上。【3】LCD1602显示采用8位并行方式控制LCD1602显示屏。通过设置LCD1602的命令和数据,可以实现在LCD1602上显示数字和运算符号等内容。同时,通过设置光标位置,可以实现在不同位置显示不同内容。三、代码实现下面是使用STC89C52单片机设计计算器的完整代码: #include <reg52.h> #include <intrins.h> #define uint unsigned int #define uchar unsigned char sbit beep = P2^3; // 蜂鸣器引脚 sbit RS = P1^0; // LCD1602 串行/并行选择引脚 sbit RW = P1^1; // LCD1602 读/写控制引脚 sbit E = P1^2; // LCD1602 使能引脚 uchar num1 = 0; // 第一个输入数字 uchar num2 = 0; // 第二个输入数字 uchar result = 0; // 计算结果 void Delay(uint ms) { uint i; while (ms--) { for (i = 0; i < 122; i++) ; } } void Write_Command(uchar com) // 向 LCD1602 发送命令 { RS = 0; RW = 0; E = 1; P0 = com; _nop_(); E = 0; Delay(5); } void Write_Data(uchar dat) // 向 LCD1602 发送数据 { RS = 1; RW = 0; E = 1; P0 = dat; _nop_(); E = 0; Delay(5); } void Init_LCD1602() // 初始化 LCD1602 显示屏 { Write_Command(0x38); Write_Command(0x0c); Write_Command(0x06); Write_Command(0x01); Delay(5); } uchar Read_Key() // 读取矩阵键盘输入的数字 { uchar key_num = 0xff; // 初始化为无效值 P3 = 0xf0; // 第一步:P3.0~P3.3 输出 0,P3.4~P3.7 输出 1 if (P3 != 0xf0) // 若检测到有按键按下,则进入第二步 { Delay(5); // 延时一段时间,消除抖动 if (P3 != 0xf0) // 再次检测是否仍然有按键按下 { switch (P3) // 根据按键的位置确定输入的数字 { case 0xe0: key_num = 0; break; case 0xd0: key_num = 1; break; case 0xb0: key_num = 2; break; case 0x70: key_num = 3; break; } P3 = 0xff; // 复位 P3 口的状态 } } return key_num; // 返回输入的数字 } void main() { Init_LCD1602(); // 初始化 LCD1602 显示屏 Write_Command(0x80); // 光标移到左上角 Write_Data('0'); // 默认显示 0 while (1) { num1 = Read_Key(); // 读取第一个数字 if (num1 != 0xff) // 若第一个数字有效,则进行第二步 { beep = 1; // 蜂鸣器响起 Delay(10); // 延时一段时间,与蜂鸣器发声时间相匹配 beep = 0; // 蜂鸣器停止响起 Write_Data(num1 + '0'); // 在 LCD1602 显示屏上显示输入的第一个数字 num2 = Read_Key(); // 读取第二个数字 if (num2 != 0xff) // 若第二个数字有效,则进行第三步 { beep = 1; // 蜂鸣器响起 Delay(10); // 延时一段时间,与蜂鸣器发声时间相匹配 beep = 0; // 蜂鸣器停止响起 Write_Data(num2 + '0'); // 在 LCD1602 显示屏上显示输入的第二个数字 result = num1 + num2; // 进行加法运算 Write_Data('='); // 在 LCD1602 显示屏上显示“=” Write_Data(result + '0'); // 在 LCD1602 显示屏上显示计算结果 } } } }程序使用矩阵键盘输入数字,并在LCD1602显示屏上显示输入的数字和计算结果,每次按键按下蜂鸣器会响一声,反馈按键的按下效果。其中,使用P3口读取矩阵键盘输入的数字,使用P2.3口控制蜂鸣器的发声,使用P1口控制LCD1602显示屏的操作。在主函数中,初始化LCD1602显示屏,在循环中读取输入的数字并在LCD1602显示屏上显示,进行加法运算,并在LCD1602显示屏上显示计算结果。四、总结设计采用STC89C52单片机设计了一款基本的四则运算计算器,通过矩阵键盘输入数字,在LCD1602显示屏上显示输入的数字和计算结果,同时每次按键按下蜂鸣器会响一声,反馈按键的按下效果。
-
蓝牙模块第一次与手机配对时输入密码后配对成功 但是上电重启模块后,手机蓝牙设置中虽然显示之前配对过hc05,但模块本身并未进入配对状态。
-
在(Linux)ubuntu下通过GTK调用libvlc开发视频播放器cid:link_1本项目实现了一个基于GTK和libvlc的视频播放器。使用GTK创建GUI界面,使用libvlc播放视频。用户可以通过选择视频文件,然后启动播放器来观看视频。 VLC是一款自由、开放源代码的跨平台媒体播放器,支持播放几乎所有常见的音频和视频格式。最初于2001年由法国学生开发,现在已经成为了一个非常受欢迎的媒体播放器,在Windows、macOS、Linux等多个操作系统上都可用。libvlc是VLC media player使用的核心库之一。提供了一组应用程序接口(API),可以让开发人员轻松地将类似于VLC的媒体播放功能嵌入到他们自己的应用程序中。libvlc可以与多种编程语言和框架(如C、C++、Python、Java、.NET等)集成,因此被广泛应用于各种媒体相关的项目中。基于STM32的智能粮仓系统设计cid:link_2随着粮食质量要求的提高和储存方式的改变,对于粮仓环境的监测和控制也愈发重要。在过去的传统管理中,通风、防潮等操作需要定期人工进行,精度和效率都较低。而利用嵌入式技术和智能控制算法进行监测和控制,不仅能够实时掌握环境变化,还可以快速做出响应。本项目选择STM32F103RCT6作为主控芯片,采用DHT11温湿度传感器和MQ9可燃气体检测模块进行数据采集,在本地利用显示屏实时显示出来。WiFi模块则用于与手机端实现数据通信和远程控制,方便用户随时了解粮仓环境状况并进行相应的操作。同时,通过连接继电器控制通风风扇和蜂鸣器报警,实现了智能化的温湿度检测和可燃气体浓度检测。STC89C52+DS18B20实现环境温度检测(数码管显示温度)cid:link_3温度检测是工业自动化、生产线等众多领域中常见的应用场景之一,能及时准确地监测温度对于保障生产安全和提高生产效率有着非常重要的作用。而在现代的电子制造行业中,使用单片机和传感器等电子元器件进行温度检测已经成为了一个比较成熟的技术方案。本项目选择STC89C52单片机和DS18B20数字温度传感器,通过读取传感器输出的温度值,经过计算和处理后,并将结果显示在数码管上,实现环境温度的实时监测和显示。其中,STC89C52单片机为主控芯片,负责接收和处理数字温度传感器的数据,并通过数码管将温度值进行显示。基于STC89C52+PulseSensor心率传感器检测心率实时显示cid:link_4当前基于STC89C52单片机和PCF8591、PulseSensor心率传感器、SSD1306 OLED显示屏等元件实现了一个心率检测仪。检测仪可以通过采集心率传感器输出的模拟信号,并经过AD转换后计算出实时的心率值,然后将心率值通过IIC协议传输到OLED显示屏上进行展示。用户只需要将心率传感器固定在身体上,启动心率检测仪,就能够方便地实时监测自己的心率。本项目的应用范围广泛,可以用于健康管理、健身锻炼、医疗等领域。在家庭中,人们可以使用该心率检测仪,及时监测自己的心率,对身体健康进行有效管理和控制;在健身房或健身教练中心,教练可以利用该心率检测仪来监测运动员的心率变化,以便针对性地调整训练计划,提高训练效果;在医疗机构中,医护人员可以使用该心率检测仪,监测患者的心率情况,及时发现异常情况,为患者的治疗提供有力的依据和参考。基于STM32的重力感应售货机系统设计cid:link_5随着智能物联网技术的不断发展,人们的生活方式和消费习惯也正在发生改变。如今越来越多的人习惯于在线购物、自助购物等新型消费模式,因此智能零售自助柜应运而生。本项目设计开发一款基于STM32主控芯片的智能零售自助柜,通过重力传感器监测货柜内商品重量变化,并通过WiFi通信模块与手机端实现交互。用户可以通过输入账号密码,柜门自动打开,用户自取商品后关闭柜门,柜门锁定,系统根据重量变化判断用户拿取的商品并从账户自动扣费。同时,用户也可以通过手机端查看消费流水、商品库存,并进行补货和充值等操作。智能零售自助柜的应用场景非常广泛,可以应用于商场、超市、酒店、机场、车站等各类场景。通过自助购物,可以提高消费者的消费体验和购物效率,同时也降低了商家的人力成本和物流成本。OpenCV(C++)创建图片绘制图形(矩形、圆、文字、线段等等)cid:link_6OpenCV 是基于开源许可证的跨平台计算机视觉库,提供了一组丰富、广泛的图像处理和计算机视觉算法。OpenCV 支持多种编程语言,包括 C++、Python、Java 等,可以运行在 Linux、Windows、Mac OS 等平台上。OpenCV 能够在图像上绘制各种几何形状、文本和曲线,以及对图像进行调整、裁剪和旋转等操作,这些功能都为图像的分析和处理提供了很大的帮助。以下是 OpenCV 可以绘制图像的一些应用:(1)图像标注:在图像上添加标注或者注释,例如在目标检测或者图像分类任务中,通过在图像上绘制框、标签等信息来标记检测到的目标。(2)处理后显示:例如在图像处理过程中,可以在处理前和处理后的图像上绘制对比图,直观地显示图像处理的效果。(3)实时显示:通过持续不断地在屏幕上绘画来实现实时显示效果,例如在视频处理中输出处理后的视频流并将其实时渲染在屏幕上。基于51单片机设计的电动车控制器cid:link_7随着社会经济的快速发展,人们对节能环保的要求越来越高,电动车因其无污染、噪音小、使用成本低等优点逐渐成为了市场关注的焦点。同时,随着科技的不断进步和应用,电动车的技术水平也在不断提高。为了更好地满足市场需求和科技进步的要求,本项目基于51单片机设计了一款电动车控制器。主要包括电动车控制和驱动两个关键部分。其中,控制部分采用51单片机作为控制核心,通过编程实现电动车前后行驶、左右转向、加速等操作。而驱动部分则采用L298N驱动芯片驱动直流电机。当前设计的电动车,支持锂电池供电、支持按键实现电动车前后行驶、左右转向和加速等操作,电机采用直流电机,驱动芯片采用L298N。基于51单片机设计的红外遥控器cid:link_8遥控器是现代生活中必不可少的电子产品之一,目前市面上的遥控器种类繁多,应用范围广泛。而 NEC 红外遥控器协议则是目前应用最为广泛的一种协议之一,几乎所有的电视、空调等家用电器都支持该协议。本项目是基于 51 单片机设计支持 NEC 协议的红外遥控器,实现接收解码和发送功能。用户通过按下相应按键进行信号的发射,红外发射二极管向外发射红外信号,被控制设备通过红外接收头接收到这个信号,然后解码执行相应的操作。基于51单片机设计的呼吸灯cid:link_9呼吸灯是一种常见的LED灯光效果,它可以模拟人类呼吸的变化,使灯光看起来更加柔和和自然。51单片机是一种广泛使用的微控制器,具有体积小、功耗低、成本低等优点,非常适合用于控制LED呼吸灯。本项目的呼吸灯将使用PWM(脉冲宽度调制)技术控制LED亮度,从而实现呼吸灯的效果。在本项目中,将使用51单片机作为主控制器,通过编程实现呼吸灯的控制。将使用C语言编写代码,并使用Keil C51集成开发环境进行编译和调试。使用Proteus仿真软件进行电路设计和仿真,确保电路的正确性和稳定性。基于51单片机设计的花样流水灯设计cid:link_10花样流水灯是一种常见的LED灯效果,被广泛应用于舞台表演、节日庆典、晚会演出等场合。在现代智能家居、电子产品中,花样流水灯也被广泛使用,通过调整亮灭顺序和时间,可以实现各种炫酷的灯光效果,增强用户体验。而51单片机作为一种常见的嵌入式开发平台,具有体积小、功耗低、可编程性强等优点,非常适合用于开发花样流水灯及其他嵌入式应用。以下场景中流水灯得到了广泛的应用:舞台表演:花样流水灯可用于舞台背景、音乐MV等场合,配合音乐和舞蹈,营造出炫酷、动感的视觉效果。 节日庆典:在传统节日如春节、中秋节等场合,花样流水灯可以用于灯笼、彩灯等装饰,为节日增添喜庆氛围。 晚会演出:在各种晚会、派对、聚会等场合,花样流水灯可以用于舞台效果、音乐灯光秀等,增强整个活动的氛围和趣味性。 智能家居:花样流水灯可以使用在居家灯光控制中,实现远程控制、定时开关、自动调节等功能,提升居住环境的科技感和人性化。基于51单片机设计的井下瓦斯监控系统cid:link_11井下瓦斯监控系统是煤矿安全生产中非常重要的一部分,防止井下瓦斯爆炸事故的发生,保障煤矿工人的人身安全。由于地下环境特殊,需要特殊的监测系统来实时监测瓦斯浓度等关键指标,并及时报警以便采取措施进行处理。瓦斯气体,又称沼气,是一种轻质烃类气体,主要成分是甲烷(CH4),也包含少量的乙烷、丙烷等。它是在地下煤炭层与泥岩等岩石中通过微生物作用或者煤炭化学反应形成的。在煤矿等地下工程中,瓦斯常常是一种具有危险性的气体,如果采取不当的措施,就有可能发生瓦斯爆炸事故。基于51单片机的井下瓦斯监控系统,可以通过传感器检测瓦斯气体浓度,将检测到的数据通过AD转换后送入单片机处理,再通过LCD显示器显示出来。如果瓦斯浓度超过了预设阈值,系统会自动启动报警装置进行警示。同时,这种系统具有适用面广、成本低、可靠性高等特点。在目前环保意识提高的背景下,煤炭企业和政府对于井下瓦斯监控系统的需求越来越大,系统的市场潜力巨大。基于51单片机设计的热敏电阻测温系统cid:link_12当前文章介绍基于51单片机的热敏电阻测温系统的设计过程,用于实时监测环境温度,并在温度超过预设阈值时进行报警。由于采用的是热敏电阻测温技术,无需外置温度传感器,使得系统具有结构简单、成本较低等优点。主控芯片采用STC89C52,具有良好的稳定性和可靠性,适应于工业控制等领域的应用需要。ADC采集模块采用PCF8591模块,可方便地实现对热敏电阻温度数据的转换和采集,提高了系统的准确度和实用性。系统通过4位数码管显示出温度值,同时通过按键设置温度上限阀值,当温度超过阀值时,会通过蜂鸣器报警,提醒用户注意环境温度的变化情况。在项目中主要是用到了热敏电阻和PCF8591模块。热敏电阻(Thermistor)是一种基于材料的电阻元件,其电阻值随温度的变化而发生相应的变化。通常情况下,热敏电阻的电阻值随温度升高而降低,反之则随温度降低而升高,这种特性被称为负温度系数(NTC)或正温度系数(PTC)。热敏电阻的工作原理是基于材料的温度敏感性质。在热敏电阻中,存在许多导电粒子,当温度升高时,导电粒子与材料中的离子激发程度增强,导致导电粒子的数量变多,因此电阻值降低;反之,当温度降低时,导电粒子的数量变少,电阻值增加。基于51单片机设计的数字温度计cid:link_13数字温度计是一种广泛应用于日常生活和工业领域中的电子测量仪器,用于检测环境温度并将其转换为数字信号进行显示。随着现代科技的发展,数字温度计逐渐取代了传统的水银温度计等方式,具有快速响应、高精度、便携式等优点。基于51单片机设计的数字温度计具体应用于制造业中的温度检测,例如温度控制器、烤箱温度控制、食品加工、工业炉等领域。通过DS18B20这种数字温度传感器来进行温度采集,使用STC89C52这种常用的单片机控制芯片,配合4位共阳数码管实现温度数据显示,并通过按键设置温度上限阀值,一旦温度超过阀值,系统会触发蜂鸣器进行报警提示,从而保证了温度的精准控制和安全性。DS18B20是一种数字温度传感器,由Maxim Integrated公司生产。采用1-Wire总线接口,只需要一个数据线就可以同时实现数据传输和供电。主要特点是精度高、响应速度快、体积小、价格低廉,被广泛应用于各种温度测量场合。DS18B20可以测量的温度范围为-55℃~+125℃,精度为±0.5℃(在-10℃~+85℃范围内)。内部集成了温度传感器、A/D转换器和数字信号处理电路,可以直接输出数字温度值。DS18B20的工作原理是利用温度对半导体材料电阻值的影响,将温度转化为电阻值,再通过A/D转换器将电阻值转化为数字信号输出。1-Wire总线接口可以实现多个DS18B20传感器的串联,只需要一个控制器就可以同时读取多个传感器的温度数据。在热敏电阻测温系统中,可以使用DS18B20传感器来测量环境温度,并将温度值传输到控制器中进行处理和显示。基于51单片机设计的公交车LED屏cid:link_0为了提高公交车站点信息的实时性和准确性,方便乘客及时了解公交车到站信息,从而提高公交出行的便利性和舒适度。传统的公交车到站信息是通过人工喊话或者静态的站牌来实现的,这种方式存在信息不及时、不准确、不方便等问题。当前设计基于STC89C52单片机和MAX7219点阵LED驱动模块的公交车LED屏,通过SYN6288进行语音播报到站信息,可以更加准确地展示到站信息,提高公交出行的效率和便利性。通过STC89C52单片机控制MAX7219点阵LED驱动模块,将需要显示的信息转化成点阵图像,然后通过MAX7219点阵LED驱动模块控制2*8的LED显示屏显示出来。同时,通过SYN6288语音模块,将到站信息转化成语音播报出来,方便乘客听取。这样,乘客不仅可以看到到站信息,还可以听到语音播报,提高了信息的实时性和准确性,方便乘客及时了解公交车的到站信息。
-
一、项目介绍为了提高公交车站点信息的实时性和准确性,方便乘客及时了解公交车到站信息,从而提高公交出行的便利性和舒适度。传统的公交车到站信息是通过人工喊话或者静态的站牌来实现的,这种方式存在信息不及时、不准确、不方便等问题。当前设计基于STC89C52单片机和MAX7219点阵LED驱动模块的公交车LED屏,通过SYN6288进行语音播报到站信息,可以更加准确地展示到站信息,提高公交出行的效率和便利性。通过STC89C52单片机控制MAX7219点阵LED驱动模块,将需要显示的信息转化成点阵图像,然后通过MAX7219点阵LED驱动模块控制2*8的LED显示屏显示出来。同时,通过SYN6288语音模块,将到站信息转化成语音播报出来,方便乘客听取。这样,乘客不仅可以看到到站信息,还可以听到语音播报,提高了信息的实时性和准确性,方便乘客及时了解公交车的到站信息。二、设计思路2.1 硬件设计本设计采用STC89C52单片机作为主控芯片,MAX7219点阵LED驱动模块控制2*8的LED显示屏,SYN6288语音模块进行语音播报。具体硬件设计:(1)STC89C52单片机STC89C52单片机是一种高性能、低功耗的8位单片机,具有丰富的外设资源,支持ISP下载和在线仿真调试,适合于各种应用场合。(2)MAX7219点阵LED驱动模块MAX7219是一种集成电路,可以驱动8×8点阵LED显示屏,具有串行输入、并行输出的特点,可以方便地控制多个LED显示屏。本设计采用MAX7219点阵LED驱动模块控制2*8的LED显示屏,实现公交车站点信息的展示。(3)SYN6288语音模块SYN6288是一种语音合成芯片,可以将文字转换成语音输出。本设计采用SYN6288语音模块进行语音播报,实现公交车到站信息的语音提示。2.2 软件设计本设计采用Keil C51编译器进行软件开发,具体软件设计如下:(1)LED显示屏控制程序LED显示屏控制程序主要实现MAX7219点阵LED驱动模块控制2*8的LED显示屏,显示公交车站点信息。具体实现过程:① 初始化MAX7219点阵LED驱动模块,设置显示模式、扫描限制、亮度等参数。② 将需要显示的信息转换成点阵数据,存储在数组中。③ 将点阵数据通过SPI总线发送给MAX7219点阵LED驱动模块,实现LED显示屏的显示。(2)语音播报程序语音播报程序主要实现SYN6288语音模块进行语音播报,实现公交车到站信息的语音提示。具体实现过程:① 初始化SYN6288语音模块,设置波特率、语音速度、音量等参数。② 将需要播报的信息转换成语音数据,存储在数组中。③ 将语音数据通过串口发送给SYN6288语音模块,实现语音播报。2.3 设计实现本设计采用STC89C52单片机作为主控芯片,MAX7219点阵LED驱动模块控制2*8的LED显示屏,SYN6288语音模块进行语音播报。代码设计思路:(1)LED显示屏控制程序① 初始化MAX7219点阵LED驱动模块 void Init_MAX7219(void) { Send_Max7219(0x09, 0x00); // 译码方式:BCD码 Send_Max7219(0x0a, 0x03); // 亮度 Send_Max7219(0x0b, 0x07); // 扫描界限:8个数码管 Send_Max7219(0x0c, 0x01); // 关闭显示测试 Send_Max7219(0x0f, 0x00); // 正常工作模式 }② 将需要显示的信息转换成点阵数据,存储在数组中 unsigned char code LED_Data[16][8] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, // 全部亮 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} // 空 };③ 将点阵数据通过SPI总线发送给MAX7219点阵LED驱动模块,实现LED显示屏的显示 void Display_LED(unsigned char *LED_Data) { unsigned char i; for(i=1; i<=8; i++) { Send_Max7219(i, LED_Data[i-1]); } }(2)语音播报程序① 初始化SYN6288语音模块 void Init_SYN6288(void) { SCON = 0x50; // 串口工作方式1 TMOD &= 0x0f; // 定时器1工作方式0 TMOD |= 0x20; TH1 = 0xfd; // 波特率:9600 TL1 = 0xfd; TR1 = 1; // 启动定时器1 P1 = 0xff; // 关闭SYN6288语音模块 }② 将需要播报的信息转换成语音数据,存储在数组中 unsigned char code Voice_Data[10][10] = { "请注意,车辆即将到站,请乘客做好上车准备", "请乘客注意安全,文明乘车,谢谢合作", "请乘客不要在公交车上吸烟,保持车厢空气清新", "请乘客不要在公交车上大声喧哗,保持车厢安静", "请乘客不要在公交车上随意乱扔垃圾,保持车厢清洁", "请乘客不要在公交车上占用座位,给需要的人让座", "请乘客不要在公交车上打电话,尊重他人", "请乘客不要在公交车上吃东西,保持车厢整洁", "请乘客不要在公交车上拥挤,保持车厢通畅", "请乘客不要在公交车上拍照,尊重他人隐私" };③ 将语音数据通过串口发送给SYN6288语音模块,实现语音播报 void Play_Voice(unsigned char *Voice_Data) { unsigned char i; P1 = 0xfe; // 打开SYN6288语音模块 for(i=0; i<strlen(Voice_Data); i++) { SBUF = Voice_Data[i]; while(!TI); // 等待发送完成 TI = 0; } P1 = 0xff; // 关闭SYN6288语音模块 }三、代码设计3.1 Max7219控制代码 #include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int //定义Max7219端口 sbit Max7219_pinCLK = P2^2; sbit Max7219_pinCS = P2^1; sbit Max7219_pinDIN = P2^0; uchar code disp1[38][8]={ {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C},//0 {0x10,0x18,0x14,0x10,0x10,0x10,0x10,0x10},//1 {0x7E,0x2,0x2,0x7E,0x40,0x40,0x40,0x7E},//2 {0x3E,0x2,0x2,0x3E,0x2,0x2,0x3E,0x0},//3 {0x8,0x18,0x28,0x48,0xFE,0x8,0x8,0x8},//4 {0x3C,0x20,0x20,0x3C,0x4,0x4,0x3C,0x0},//5 {0x3C,0x20,0x20,0x3C,0x24,0x24,0x3C,0x0},//6 {0x3E,0x22,0x4,0x8,0x8,0x8,0x8,0x8},//7 {0x0,0x3E,0x22,0x22,0x3E,0x22,0x22,0x3E},//8 {0x3E,0x22,0x22,0x3E,0x2,0x2,0x2,0x3E},//9 {0x8,0x14,0x22,0x3E,0x22,0x22,0x22,0x22},//A {0x3C,0x22,0x22,0x3E,0x22,0x22,0x3C,0x0},//B {0x3C,0x40,0x40,0x40,0x40,0x40,0x3C,0x0},//C {0x7C,0x42,0x42,0x42,0x42,0x42,0x7C,0x0},//D {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x7C},//E {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x40},//F {0x3C,0x40,0x40,0x40,0x40,0x44,0x44,0x3C},//G {0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44},//H {0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x7C},//I {0x3C,0x8,0x8,0x8,0x8,0x8,0x48,0x30},//J {0x0,0x24,0x28,0x30,0x20,0x30,0x28,0x24},//K {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7C},//L {0x81,0xC3,0xA5,0x99,0x81,0x81,0x81,0x81},//M {0x0,0x42,0x62,0x52,0x4A,0x46,0x42,0x0},//N {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C},//O {0x3C,0x22,0x22,0x22,0x3C,0x20,0x20,0x20},//P {0x1C,0x22,0x22,0x22,0x22,0x26,0x22,0x1D},//Q {0x3C,0x22,0x22,0x22,0x3C,0x24,0x22,0x21},//R {0x0,0x1E,0x20,0x20,0x3E,0x2,0x2,0x3C},//S {0x0,0x3E,0x8,0x8,0x8,0x8,0x8,0x8},//T {0x42,0x42,0x42,0x42,0x42,0x42,0x22,0x1C},//U {0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18},//V {0x0,0x49,0x49,0x49,0x49,0x2A,0x1C,0x0},//W {0x0,0x41,0x22,0x14,0x8,0x14,0x22,0x41},//X {0x41,0x22,0x14,0x8,0x8,0x8,0x8,0x8},//Y {0x0,0x7F,0x2,0x4,0x8,0x10,0x20,0x7F},//Z {0x8,0x7F,0x49,0x49,0x7F,0x8,0x8,0x8},//中 {0xFE,0xBA,0x92,0xBA,0x92,0x9A,0xBA,0xFE},//国 }; void Delay_xms(uint x) { uint i,j; for(i=0;i<x;i++) for(j=0;j<112;j++); } //-------------------------------------------- //功能:向MAX7219(U3)写入字节 //入口参数:DATA //出口参数:无 //说明: void Write_Max7219_byte(uchar DATA) { uchar i; Max7219_pinCS=0; for(i=8;i>=1;i--) { Max7219_pinCLK=0; Max7219_pinDIN=DATA&0x80; DATA=DATA<<1; Max7219_pinCLK=1; } } //------------------------------------------- //功能:向MAX7219写入数据 //入口参数:address、dat //出口参数:无 //说明: void Write_Max7219(uchar address,uchar dat) { Max7219_pinCS=0; Write_Max7219_byte(address); //写入地址,即数码管编号 Write_Max7219_byte(dat); //写入数据,即数码管显示数字 Max7219_pinCS=1; } void Init_MAX7219(void) { Write_Max7219(0x09, 0x00); //译码方式:BCD码 Write_Max7219(0x0a, 0x03); //亮度 Write_Max7219(0x0b, 0x07); //扫描界限;8个数码管显示 Write_Max7219(0x0c, 0x01); //掉电模式:0,普通模式:1 Write_Max7219(0x0f, 0x00); //显示测试:1;测试结束,正常显示:0 } void main(void) { uchar i,j; Delay_xms(50); Init_MAX7219(); while(1) { for(j=0;j<38;j++) { for(i=1;i<9;i++) Write_Max7219(i,disp1[j][i-1]); Delay_xms(1000); } } } 3.2 完整代码下面代码里实现了MAX7219点阵LED模块和SYN6288语音模块的控制,以及实现公交车到站信息的主程序。 #include <reg52.h> #include <intrins.h> sbit DIN = P1^5; // MAX7219数据输入口 sbit CS = P1^4; // MAX7219芯片使能口 sbit CLK = P1^6; // MAX7219时钟输入口 sbit SYN_PWR = P2^0; // SYN6288语音模块电源控制口 sbit SYN_RST = P2^1; // SYN6288语音模块复位控制口 sbit SYN_BUSY = P3^7;// SYN6288语音模块忙碌指示口 sbit SYN_RXD = P3^0; // SYN6288语音模块串口接收口 sbit SYN_TXD = P3^1; // SYN6288语音模块串口发送口 unsigned char code DIGITS[10][8] = { { 0x00, 0x7E, 0x81, 0x81, 0x81, 0x7E, 0x00, 0x00 }, // 0 { 0x00, 0x00, 0x82, 0xFF, 0x80, 0x00, 0x00, 0x00 }, // 1 { 0x00, 0xE2, 0x91, 0x91, 0x91, 0x8E, 0x00, 0x00 }, // 2 { 0x00, 0x42, 0x81, 0x89, 0x89, 0x76, 0x00, 0x00 }, // 3 { 0x00, 0x1F, 0x10, 0x10, 0xFF, 0x10, 0x10, 0x00 }, // 4 { 0x00, 0x4F, 0x89, 0x89, 0x89, 0x71, 0x00, 0x00 }, // 5 { 0x00, 0x7E, 0x89, 0x89, 0x89, 0x72, 0x00, 0x00 }, // 6 { 0x00, 0x01, 0xF1, 0x09, 0x05, 0x03, 0x00, 0x00 }, // 7 { 0x00, 0x76, 0x89, 0x89, 0x89, 0x76, 0x00, 0x00 }, // 8 { 0x00, 0x4E, 0x91, 0x91, 0x91, 0x7E, 0x00, 0x00 } // 9 }; // MAX7219控制程序 void MAX7219(unsigned char address, unsigned char data) { unsigned char i; CS = 0; for(i = 1; i <= 8; i++) { CLK = 0; DIN = ((address & 0x80) > 0) ? 1 : 0; address <<= 1; CLK = 1; } for(i = 1; i <= 8; i++) { CLK = 0; DIN = ((data & 0x80) > 0) ? 1 : 0; data <<= 1; CLK = 1; } CS = 1; } // SYN6288语音控制程序 void SYN6288(unsigned char address, unsigned char value) { while(SYN_BUSY); // 等待SYN6288模块空闲 SYN_TXD = 0; // 发送起始位 _nop_(); SYN_TXD = 1; SYN_TXD = 0; // 发送地址位 _nop_(); SYN_TXD = 1; SYN_TXD = 0; // 发送命令位 _nop_(); SYN_TXD = 1; SYN_TXD = 0; // 发送数据位 _nop_(); SYN_TXD = (address | 0x7F); _nop_(); SYN_TXD = value; } // 主程序 void main() { unsigned char i, j; unsigned int count = 60; SYN_PWR = 1; // 打开语音模块电源 SYN_RST = 0; // 复位语音模块 SYN_RST = 1; MAX7219(0x09, 0x00); // 控制显示方式(0x00:采用代码B显示方式) MAX7219(0x0A, 0x0F); // 控制亮度(0x00~0x0F,共16级) MAX7219(0x0B, 0x07); // 控制扫描方式(0x07:以列为单位逐行扫描) while(1) { for(i = 0; i < 2; i++) { for(j = 0; j < 8; j++) { MAX7219(j + 1, DIGITS[count % 10][j]); MAX7219(j + 9, DIGITS[count / 10][j]); } } SYN6288(0x06, 0x0B); // 设定语音播报速度 SYN6288(0x02, 0x01); // 选择播报方案1 SYN6288(0x03, 0x03); // 选择播报语音3 SYN6288(0x05, 0x01); // 执行语音播报操作 if(count == 0) { // 倒计时完成,清零计数器 count = 60; } else { // 继续倒计时 count--; } for(i = 0; i < 200; i++) { // 延时,控制倒计时速度 for(j = 0; j < 100; j++); } } }上面代码实现了一个倒计时器,并结合MAX7219点阵LED模块和SYN6288语音模块,可以在屏幕上显示倒计时数字,并在倒计时结束时播放语音提示。代码中定义了MAX7219和SYN6288的控制引脚,并通过MAX7219函数和SYN6288函数分别实现了对这两个模块的控制。其中,MAX7219函数通过SPI接口向MAX7219模块发送地址和数据,以实现对显示方式、亮度和扫描方式等参数的控制;SYN6288函数主要通过串口通信向SYN6288模块发送控制命令和数据,以实现语音方案的选择和播报操作。在主程序中,代码先打开了SYN6288模块的电源,并将其复位。然后,通过MAX7219函数依次设置MAX7219模块的显示方式、亮度和扫描方式。接着,代码进入了一个无限循环中,在循环体内实现了如下操作:(1)显示当前倒计时数值。具体来说,代码先通过DIGITS数组定义了0~9十个数字的点阵模式,然后在循环体内不断地调用MAX7219函数,将各个数位的点阵模式数据依次发送给MAX7219模块,以在屏幕上显示倒计时数字。(2)播放语音提示。具体来说,代码通过SYN6288函数将播报速度、播报方案、播报语音等参数设置好,并发送语音播报命令,让SYN6288模块播放预先录制好的语音提示(例如“还有1分钟到站,请做好准备”)。(3)控制倒计时速度和完成情况。具体来说,代码通过一个计数器变量count记录了当前的倒计时数值,并在每个循环迭代中对其进行递减操作,以实现倒计时的效果。同时,代码也在循环中加入了一个延时操作,以控制倒计时速度。当倒计时完成时,代码会将计数器清零,从而重新开始倒计时。四、总结本设计采用STC89C52单片机设计了基于MAX7219点阵LED驱动模块控制2*8的LED显示屏,通过SYN6288语音模块进行语音播报的公交车LED屏。系统可以提高公交车站点信息的展示效果,方便乘客及时获取公交车到站信息,提高公交车站点服务质量。
-
一、项目介绍数字温度计是一种广泛应用于日常生活和工业领域中的电子测量仪器,用于检测环境温度并将其转换为数字信号进行显示。随着现代科技的发展,数字温度计逐渐取代了传统的水银温度计等方式,具有快速响应、高精度、便携式等优点。基于51单片机设计的数字温度计具体应用于制造业中的温度检测,例如温度控制器、烤箱温度控制、食品加工、工业炉等领域。通过DS18B20这种数字温度传感器来进行温度采集,使用STC89C52这种常用的单片机控制芯片,配合4位共阳数码管实现温度数据显示,并通过按键设置温度上限阀值,一旦温度超过阀值,系统会触发蜂鸣器进行报警提示,从而保证了温度的精准控制和安全性。DS18B20是一种数字温度传感器,由Maxim Integrated公司生产。采用1-Wire总线接口,只需要一个数据线就可以同时实现数据传输和供电。主要特点是精度高、响应速度快、体积小、价格低廉,被广泛应用于各种温度测量场合。DS18B20可以测量的温度范围为-55℃~+125℃,精度为±0.5℃(在-10℃~+85℃范围内)。内部集成了温度传感器、A/D转换器和数字信号处理电路,可以直接输出数字温度值。DS18B20的工作原理是利用温度对半导体材料电阻值的影响,将温度转化为电阻值,再通过A/D转换器将电阻值转化为数字信号输出。1-Wire总线接口可以实现多个DS18B20传感器的串联,只需要一个控制器就可以同时读取多个传感器的温度数据。在热敏电阻测温系统中,可以使用DS18B20传感器来测量环境温度,并将温度值传输到控制器中进行处理和显示。下面是仿真图:二、设计思路2.1 系统架构系统硬件主要由单片机控制模块、温度传感器模块、数码管显示模块、按键模块、蜂鸣器模块组成。其中单片机控制模块采用STC89C52作为主控芯片,通过连接数码管、按键、蜂鸣器、温度传感器等外围电路实现温度检测、控制和报警功能。2.2 技术方案(1)温度传感器模块 本项目采用DS18B20数字式温度传感器进行温度检测,该传感器具有精度高、响应快、可靠性强等优点。通过将其与单片机进行串口通信,实现温度数据的采集。(2)数码管显示模块 本项目采用4位共阳数码管进行温度数据的显示,通过设置单片机控制IO口实现数据的动态扫描和显示。(3)按键模块 本项目通过设置按键模块实现对温度上限阀值的设定,采用矩阵按键实现多个按键功能。(4)蜂鸣器模块 本项目采用蜂鸣器作为报警提示器,当温度超过上限阀值时,触发单片机控制后,蜂鸣器会发出一定频率的报警信号。2.3 系统实现流程(1)主程序初始化:设置IO口模式、串口配置、定时器中断等参数。(2)温度检测:通过DS18B20进行温度采集,并将采集到的数据解析为实际温度值。(3)数码管显示:将温度值通过数码管进行数据的显示。(4)上限阀值设置:通过按键设置温度上限阀值,将阀值存储在单片机内部的EEPROM中。(5)报警提示:当温度值超过阀值时,触发蜂鸣器发出报警信号。三、代码实现3.1 4位共阳极数码管显示代码下面是控制STC89C52通过P1口控制4位共阳极数码管显示数字1234的实现代码: #include <reg52.h> // 定义数码管端口连接的IO口 sbit Dig1 = P1^0; sbit Dig2 = P1^1; sbit Dig3 = P1^2; sbit Dig4 = P1^3; // 定义数码管段码 unsigned char code SegCode[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; void main() { unsigned int num = 1234; // 要显示的数字 unsigned char i, j, k, l; // 分别表示千位、百位、十位和个位 while (1) { // 将数字分解为千位、百位、十位和个位 i = num / 1000; j = num % 1000 / 100; k = num % 100 / 10; l = num % 10; // 显示千位 Dig1 = 1; P0 = SegCode[i]; Dig1 = 0; // 显示百位 Dig2 = 1; P0 = SegCode[j]; Dig2 = 0; // 显示十位 Dig3 = 1; P0 = SegCode[k]; Dig3 = 0; // 显示个位 Dig4 = 1; P0 = SegCode[l]; Dig4 = 0; } }这段代码中,定义了数码管端口连接的IO口,然后定义了数码管段码。在main函数中,将要显示的数字1234分解为千位、百位、十位和个位,并通过控制P1口的四个IO口,依次显示出来。这里使用共阳极数码管,需要将对应位的IO口置为0才能点亮数码管。3.2 数字温度计实现代码下面是数字温度计完整的代码。 #include <reg52.h> // 定义温度传感器引脚 sbit DQ = P3^7; // 定义数码管引脚 sbit DIG_1 = P2^0; sbit DIG_2 = P2^1; sbit DIG_3 = P2^2; sbit DIG_4 = P2^3; sbit SEG_A = P1^0; sbit SEG_B = P1^1; sbit SEG_C = P1^2; sbit SEG_D = P1^3; sbit SEG_E = P1^4; sbit SEG_F = P1^5; sbit SEG_G = P1^6; sbit SEG_DP = P1^7; // 定义按键引脚 sbit KEY_SET = P0^0; sbit KEY_ADD = P0^1; sbit KEY_SUB = P0^2; // 定义全局变量 unsigned char code DisplayChar[] = { 0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; // 数码管显示字符编码 unsigned char TempData[4] = {0, 0, 0, 0}; // 显示温度值的数组 unsigned char SetTemp = 25; // 设定温度上限阀值 unsigned char LastKeyStatus = 0x07; // 按键状态 unsigned char Count = 0; // 数码管扫描计数器 bit IsAlarm = 0; // 报警状态 // 定时器中断服务函数 void Timer0_ISR() interrupt 1 { TH0 = 0xfc; TL0 = 0x67; DIG_1 = DIG_2 = DIG_3 = DIG_4 = 1; // 关闭所有数码管 Count++; // 数码管扫描计数器加1 switch (Count) { case 1: // 扫描第1位数码管 DIG_1 = 0; P0 = TempData[3]; break; case 2: // 扫描第2位数码管 DIG_2 = 0; P0 = TempData[2]; break; case 3: // 扫描第3位数码管 DIG_3 = 0; P0 = TempData[1]; break; case 4: // 扫描第4位数码管 DIG_4 = 0; P0 = TempData[0]; break; default: Count = 0; break; } } // 延时函数 void Delay(unsigned int n) { unsigned int i, j; for(i=0; i<n; i++) { for(j=0; j<125; j++); } } // 数字温度计初始化函数 void Init() { TMOD |= 0x01; // 定时器0工作在模式1 TH0 = 0xfc; // 定时器0初始值 TL0 = 0x67; ET0 = 1; // 允许定时器0中断 TR0 = 1; // 启动定时器0 EA = 1; // 允许中断 } // DS18B20复位函数 bit Reset() { bit res; DQ = 0; Delay(480); DQ = 1; Delay(60); res = DQ; Delay(420); return res; } // DS18B20写字节函数 void WriteByte(unsigned char dat) { unsigned char i; for(i=0; i<8; i++) { DQ = 0; Delay(2); DQ = dat & 0x01; Delay(60); DQ = 1; Delay(2); dat >>= 1; } } // DS18B20读字节函数 unsigned char ReadByte() { unsigned char i, j, dat = 0; for(i=0; i<8; i++) { DQ = 0; Delay(2); DQ = 1; Delay(2); j = DQ; Delay(60); dat |= (j << i); } return dat; } // DS18B20温度转换函数 void TempConv() { if(!Reset()) { WriteByte(0xCC); // 跳过ROM操作,直接访问DS18B20 WriteByte(0x44); // 发送温度转换命令 } } // DS18B20读取温度函数 void ReadTemp() { unsigned char TL, TH; if(!Reset()) { WriteByte(0xCC); // 跳过ROM操作,直接访问DS18B20 WriteByte(0xBE); // 发送读取温度命令 TL = ReadByte(); // 读取温度值低8位 TH = ReadByte(); // 读取温度值高8位 if(TH > 7) { // 温度值为负数,进行补码转换 TH = ~TH; TL = ~TL; TempData[0] = ((unsigned short)(TH << 8) | TL) * -0.0625 * 10 + 0.5; // 计算温度值并保存 TempData[1] = DisplayChar[10]; // 显示字符“-” } else { // 温度值为正数 TempData[0] = ((unsigned short)(TH << 8) | TL) * 0.0625 * 10 + 0.5; // 计算温度值并保存 TempData[1] = DisplayChar[TempData[0] / 10]; // 显示整数部分 } TempData[2] = DisplayChar[TempData[0] % 10]; // 显示小数部分 } } // 按键检测函数 void KeyCheck() { unsigned char key_status = 0; if(KEY_SET == 0) { // 设定按键被按下 key_status |= 0x01; } if(KEY_ADD == 0) { // 加温按键被按下 key_status |= 0x02; } if(KEY_SUB == 0) { // 减温按键被按下 key_status |= 0x04; } if(key_status != LastKeyStatus) { // 判断是否有按键事件发生 Delay(10); // 延时去抖 if(key_status != LastKeyStatus) { // 再次判断是否有按键事件发生 switch(key_status) { case 0x01: // 设定按键被按下 SetTemp++; // 温度上限阀值加1 if(SetTemp > 50) { // 上限阀值不能超过50℃ SetTemp = 50; } break; case 0x02: // 加温按键被按下 break; case 0x04: // 减温按键被按下 break; default: break; } } LastKeyStatus = key_status; // 保存当前按键状态 } } // 报警函数 void Alarm() { if(TempData[0] > SetTemp * 10 && !IsAlarm) { // 当温度超过设定的阀值且没有报警时触发报警 IsAlarm = 1; // 设置报警标志 while(TempData[0] > SetTemp * 10) { // 循环等待 P1 = 0xff; // 关闭数码管 P0 = 0x00; // 关闭蜂鸣器 Delay(500); // 延时 P1 = 0x00; // 打开数码管 P0 = 0xff; // 打开蜂鸣器 Delay(500); // 延时 } } else if(TempData[0] <= SetTemp * 10) { // 当温度低于等于设定的阀值时,取消报警 IsAlarm = 0; // 清除报警标志 } } // 主函数 void main() { Init(); // 初始化数字温度计 while(1) { TempConv(); // 温度转换 ReadTemp(); // 读取温度值 KeyCheck(); // 按键检测 Alarm(); // 报警处理 } }这份代码的设计主要分为4个模块:(1)数码管显示模块:使用四位共阴数码管进行温度值的显示,采用定时中断扫描四个数码管的方式进行显示。(2)DS18B20模块:通过DS18B20温度传感器获取当前温度值,并将温度值保存到数组中,以便于数码管显示模块进行显示。(3)按键检测模块:通过检测按键状态,实现设定温度上限阀值、加温和减温等操作。(4)报警模块:当当前温度超过设定的温度上限阀值时,触发蜂鸣器报警。代码主要使用51单片机进行设计,其中主要包含了DS18B20温度传感器的读取、按键检测、数码管显示、蜂鸣器控制等多种功能。通过使用定时中断和循环结构,实现了各个模块之间的协作,从而一同完成数字温度计的设计。
上滑加载中
推荐直播
-
空中宣讲会 2025年华为软件精英挑战赛
2025/03/10 周一 18:00-19:00
宸睿 华为云存储技术专家、ACM-ICPC WorldFinal经验 晖哥
2025华为软挑赛空中宣讲会重磅来袭!完整赛程首曝+命题天团硬核拆题+三轮幸运抽奖赢参赛助力礼包,与全国优秀高校开发者同台竞技,直通顶尖赛事起跑线!
即将直播
热门标签