STM32F103+SGP30的空气检测仪

发布时间:2026/10/2 1:37:26
STM32F103+SGP30的空气检测仪 本课程设计的空气检测仪采用STM32F103作为主控芯片结合DHT11模块、蜂鸣器模块、SGP30气体传感器模块、OLED等电路模块实现温湿度的检测、检测CO2、甲烷等有害气体浓度、报警功能设计一款具有显示温湿度、气体浓度的空气检测仪。电路设计本次设计的空气检测仪主要由STM32F103作为主控芯片OLED显示模块GP30气体传感器模块DHT11模块蜂鸣器模块组成。1相关模块介绍OLCD显示模块在单片机应用系统中常用的显示设备有单个发光二极管、八段LED显示器、液晶显示器LCD、屏幕显示器CRT等。本次设计中基于设计功能和节约成本等实际情况采用OLCD作为本次设计的显示器。图1OLED图图2OLED接线图DHT11模块在这次基于STM32的空气检测仪设计中通过控制DHT11模块来实现系统的温湿度检测功能。DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器它应用专用的数字模块采集技术和温湿度传感技术确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。单线制串行接口使系统集成变得简易快捷。超小的体积、极低的功耗使其成为该类应用中在苛刻应用场合的最佳选择。图3DHT11模块接线图(图4)DHT11蜂鸣器模块通过控制蜂鸣器的发音来实现系统的报警功能。蜂鸣器是一种采用直流电压供电的电子讯响器。下图是用蜂鸣器图。当P3.7口有脉冲信号输入时蜂鸣器SOUNDER即会发音。通过控制输入脉冲的频率还能控制蜂鸣器的发音频率SGP30气体传感器模块SGP30是数字型气体传感器能轻松集成到空气净化器通风系统和物联网应用中。 采用Sensirion独有的CMOSens®技术在单个芯片上提供了完整的传感器系统具有数字I2C接口温度控制的微型加热板和两个预处理的室内空气质量信号确保传感器的稳定和精准。图6SGP30气体传感器接线图图7SGP30气体传感器2使用介绍OLED模块实时显示温湿度与气体浓度数据。当CO2浓度一直等于400ppm且TVCO浓度一直等于0ppd时说明该模块还未开始检测当STM32获取到的CO2浓度和TVCO浓度值开始变化了说明模块开始检测此时STM32单片机每500ms获取SGP30模块检测到的空气中CO2浓度值和TVCO浓度值CO2DATA存储CO2和TVOCDATA 存储TCVO的浓度值。当温湿度超过阈值时蜂鸣器发出警报声。2.软件设计2.1OLED模块DHT11模块和SGP30气体传感器模块检测到数据后反馈到OLED显示屏中显示温湿度值和CO2、甲烷浓度。主要流程图如下用取模软件取字顺序如下图主要代码// IIC Write Command void Write_IIC_Command(unsigned char IIC_Command) { IIC_Start(); Write_IIC_Byte(0x78); //Slave address,SA00 IIC_Wait_Ack(); Write_IIC_Byte(0x00); //write command中文翻译 IIC_Wait_Ack(); Write_IIC_Byte(IIC_Command); IIC_Wait_Ack(); IIC_Stop(); } // IIC Write Data void Write_IIC_Data(unsigned char IIC_Data) { IIC_Start(); Write_IIC_Byte(0x78); //D/C#0; R/W#0 IIC_Wait_Ack(); Write_IIC_Byte(0x40); //写入数据 IIC_Wait_Ack(); Write_IIC_Byte(IIC_Data); IIC_Wait_Ack(); IIC_Stop(); } void OLED_WR_Byte(unsigned dat,unsigned cmd) { if(cmd){ Write_IIC_Data(dat);} else { Write_IIC_Command(dat); } }// fill_Picture void fill_picture(unsigned char fill_Data) { unsigned char m,n; for(m0;m8;m) { OLED_WR_Byte(0xb0m,0); //page0-page1 OLED_WR_Byte(0x00,0); //low column start address OLED_WR_Byte(0x10,0); //high column start address for(n0;n128;n) { OLED_WR_Byte(fill_Data,1); } } } /***********************Delay****************************************/ void Delay_50ms(unsigned int Del_50ms) { unsigned int m; for(;Del_50ms0;Del_50ms--) for(m6245;m0;m--); } void Delay_1ms(unsigned int Del_1ms) { unsigned char j; while(Del_1ms--) { for(j0;j123;j); } } //坐标设置 void OLED_Set_Pos(unsigned char x, unsigned char y) { OLED_WR_Byte(0xb0y,OLED_CMD); OLED_WR_Byte(((x0xf0)4)|0x10,OLED_CMD); OLED_WR_Byte((x0x0f),OLED_CMD); } //开启OLED显示 void OLED_Display_On(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON } //关闭OLED显示 void OLED_Display_Off(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF } //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! void OLED_Clear(void) { u8 i,n; for(i0;i8;i) { OLED_WR_Byte (0xb0i,OLED_CMD); //设置页地址0~7 OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n0;n128;n)OLED_WR_Byte(0,OLED_DATA); } //更新显示 } void OLED_On(void) { u8 i,n; for(i0;i8;i) { OLED_WR_Byte (0xb0i,OLED_CMD); //设置页地址0~7 OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n0;n128;n)OLED_WR_Byte(1,OLED_DATA); } //更新显示 } //在指定位置显示一个字符,包括部分字符 //x:0~127 //y:0~63 //mode:0,反白显示;1,正常显示 //size:选择字体 16/12 void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size) { unsigned char c0,i0; cchr- ;//得到偏移后的值 if(xMax_Column-1){x0;yy2;} if(Char_Size 16) { OLED_Set_Pos(x,y); for(i0;i8;i) OLED_WR_Byte(F8X16[c*16i],OLED_DATA); OLED_Set_Pos(x,y1); for(i0;i8;i) OLED_WR_Byte(F8X16[c*16i8],OLED_DATA); } else { OLED_Set_Pos(x,y); for(i0;i6;i) OLED_WR_Byte(F6x8[c][i],OLED_DATA); } } //m^n函数 u32 oled_pow(u8 m,u8 n) { u32 result1; while(n--)result*m; return result; } //显示2个数字 //x,y :起点坐标 //len :数字的位数 //size:字体大小 //mode:模式 0,填充模式;1,叠加模式 //num:数值(0~4294967295); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2) { u8 t,temp; u8 enshow0; for(t0;tlen;t) { temp(num/oled_pow(10,len-t-1))%10; if(enshow0t(len-1)) { if(temp0) { OLED_ShowChar(x(size2/2)*t,y, ,size2); continue; }else enshow1; } OLED_ShowChar(x(size2/2)*t,y,temp0,size2); } } //显示一个字符号串 void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size) { unsigned char j0; while (chr[j]!\0) { OLED_ShowChar(x,y,chr[j],Char_Size); x8; if(x120){x0;y2;} j; } } //显示汉字 void OLED_ShowCHinese(u8 x,u8 y,u8 no) { u8 t,adder0; OLED_Set_Pos(x,y); for(t0;t16;t) { OLED_WR_Byte(Hzk[2*no][t],OLED_DATA); adder1; } OLED_Set_Pos(x,y1); for(t0;t16;t) { OLED_WR_Byte(Hzk[2*no1][t],OLED_DATA); adder1; } } /***********功能描述显示显示BMP图片128×64起始点坐标(x,y),x的范围0127y为页的范围07*****************/ void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]) { unsigned int j0; unsigned char x,y; if(y1%80) yy1/8; else yy1/81; for(yy0;yy1;y) { OLED_Set_Pos(x0,y); for(xx0;xx1;x) { OLED_WR_Byte(BMP[j],OLED_DATA); } } }2.2DHT11模块检测温湿度值并传送到OLED显示屏中。流程图如下主要代码void DHT11_IO_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); GPIO_InitStructure.GPIO_Pin DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStructure); } void DHT11_IO_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); GPIO_InitStructure.GPIO_Pin DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStructure); } //复位DHT11 void DHT11_Rst(void) { DHT11_IO_OUT(); DHT11_DQ_OUT0; delay_ms(20); //拉低至少18ms DHT11_DQ_OUT1; delay_us(30); //主机拉高30us } //等待DHT11的回应 //返回1:未检测到DHT11的存在 //返回0:存在 uint8_t DHT11_Check(void) { uint8_t retry0; DHT11_IO_IN(); while (DHT11_DQ_INretry100)//DHT11会拉低40~80us { retry; delay_us(1); }; if(retry100)return 1; else retry0; while (!DHT11_DQ_INretry100)//DHT11拉低后会再次拉高40~80us { retry; delay_us(1); }; if(retry100)return 1; return 0; } //从DHT11读取一个位 //返回值1/0 uint8_t DHT11_Read_Bit(void) { uint8_t retry0; while(DHT11_DQ_INretry100)//等待变为低电平 { retry; delay_us(1); } retry0; while(!DHT11_DQ_INretry100)//等待变高电平 { retry; delay_us(1); } delay_us(40);//等待40us if(DHT11_DQ_IN)return 1; else return 0; } //从DHT11读取一个字节 //返回值读到的数据 uint8_t DHT11_Read_Byte(void) { uint8_t i,dat; dat0; for (i0;i8;i) { dat1; dat|DHT11_Read_Bit(); } return dat; } //从DHT11读取一次数据 //temp:温度值(范围:0~50°) //humi:湿度值(范围:20%~90%) //返回值0,正常;1,读取失败 uint8_t Read_DHT_Data(uint8_t *temp,uint8_t *humi) { uint8_t buf[5]; uint8_t i; DHT11_Rst(); if(DHT11_Check()0) { for(i0;i5;i)//读取40位数据 { buf[i]DHT11_Read_Byte(); } if((buf[0]buf[1]buf[2]buf[3])buf[4]) { *humibuf[0]; *(humi 1)buf[1]; *tempbuf[2]; *(temp 1)buf[3]; } } else { return 1; } return 0; } uint8_t DHT11_INIT(void) { DHT11_Rst(); return DHT11_Check(); }2.3SGP30气体传感器模块检测气体浓度值并传送到OLED显示屏中流程图如下主要代码void SGP30_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(SGP30_SCL_GPIO_CLK | SGP30_SDA_GPIO_SDA, ENABLE); GPIO_InitStructure.GPIO_Pin SGP30_SCL_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(SGP30_SCL_GPIO_PORT, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin SGP30_SDA_GPIO_PIN; GPIO_Init(SGP30_SDA_GPIO_PORT, GPIO_InitStructure); } void SDA_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin SGP30_SDA_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(SGP30_SDA_GPIO_PORT, GPIO_InitStructure); } void SDA_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin SGP30_SDA_GPIO_PIN; GPIO_InitStructure.GPIO_Speed GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(SGP30_SDA_GPIO_PORT, GPIO_InitStructure); } //产生IIC起始信号 void SGP30_IIC_Start(void) { SDA_OUT(); SGP30_SDA 1; SGP30_SCL 1; delay_us(20); SGP30_SDA 0; //START:when CLK is high,DATA change form high to low delay_us(20); SGP30_SCL 0; //钳住I2C总线准备发送或接收数据 } //产生IIC停止信号 void SGP30_IIC_Stop(void) { SDA_OUT(); SGP30_SCL 0; SGP30_SDA 0; //STOP:when CLK is high DATA change form low to high delay_us(20); SGP30_SCL 1; SGP30_SDA 1; //发送I2C总线结束信号 delay_us(20); } //等待应答信号到来 //返回值1接收应答失败 // 0接收应答成功 uint8_t SGP30_IIC_Wait_Ack(void) { uint8_t ucErrTime 0; SDA_IN(); SGP30_SDA 1; delay_us(10); SGP30_SCL 1; delay_us(10); while(SGP30_SDA_READ()) { ucErrTime; if(ucErrTime 250) { SGP30_IIC_Stop(); return 1; } } SGP30_SCL 0; //时钟输出0 return 0; } //产生ACK应答 void SGP30_IIC_Ack(void) { SGP30_SCL 0; SDA_OUT(); SGP30_SDA 0; delay_us(20); SGP30_SCL 1; delay_us(20); SGP30_SCL 0; } //不产生ACK应答 void SGP30_IIC_NAck(void) { SGP30_SCL 0; SDA_OUT(); SGP30_SDA 1; delay_us(20); SGP30_SCL 1; delay_us(20); SGP30_SCL 0; } //IIC发送一个字节 //返回从机有无应答 //1有应答 //0无应答 void SGP30_IIC_Send_Byte(uint8_t txd) { uint8_t t; SDA_OUT(); SGP30_SCL 0; //拉低时钟开始数据传输 for(t 0; t 8; t) { if((txd 0x80) 7) SGP30_SDA 1; else SGP30_SDA 0; txd 1; delay_us(20); SGP30_SCL 1; delay_us(20); SGP30_SCL 0; delay_us(20); } delay_us(20); } //读1个字节ack1时发送ACKack0发送nACK u16 SGP30_IIC_Read_Byte(uint8_t ack) { uint8_t i; u16 receive 0; SDA_IN(); for(i 0; i 8; i ) { SGP30_SCL 0; delay_us(20); SGP30_SCL 1; receive 1; if(SGP30_SDA_READ()) receive; delay_us(20); } if (!ack) SGP30_IIC_NAck();//发送nACK else SGP30_IIC_Ack(); //发送ACK return receive; } //初始化IIC接口 void SGP30_Init(void) { SGP30_GPIO_Init(); SGP30_Write(0x20, 0x03); } void SGP30_Write(uint8_t a, uint8_t b) { SGP30_IIC_Start(); SGP30_IIC_Send_Byte(SGP30_write); //发送器件地址写指令 SGP30_IIC_Wait_Ack(); SGP30_IIC_Send_Byte(a); //发送控制字节 SGP30_IIC_Wait_Ack(); SGP30_IIC_Send_Byte(b); SGP30_IIC_Wait_Ack(); SGP30_IIC_Stop(); delay_ms(100); } uint32_t SGP30_Read(void) { uint32_t dat; uint8_t crc; SGP30_IIC_Start(); SGP30_IIC_Send_Byte(SGP30_read); //发送器件地址读指令 SGP30_IIC_Wait_Ack(); dat SGP30_IIC_Read_Byte(1); dat 8; dat SGP30_IIC_Read_Byte(1); crc SGP30_IIC_Read_Byte(1); //crc数据舍去 crc crc; //为了不让出现编译警告 dat 8; dat SGP30_IIC_Read_Byte(1); dat 8; dat SGP30_IIC_Read_Byte(0); SGP30_IIC_Stop(); return(dat); }2.4蜂鸣器模块达到温湿度阈值时蜂鸣器发出警报声流程如下图3.调试及结论测试条件完成STM32单片机的所有外设搭建程序的的正常运行。测试目的测试程序能否进行温湿度和气体浓度的检测显示到OLED显示屏操作。步骤说明1.打开Keil5调试运行生成可执行文件2.打开FiyMcu下载可执行文件到单片机中下载前记得擦除芯片3.OLED屏幕显示温湿度值、CO2浓度和甲烷浓度值4.超过温湿度阈值蜂鸣器响预期结果程序能够进行检测温湿度和气体浓度。实际结果实际结果与预期结果相符测试通过。本次测试主要是测试程序是否能完成以下功能检测温湿度值、CO2浓度和甲烷浓度值、报警。流程如下图注意在OLED模块中出现显示屏显示不出字体错误和数值不出现原因是接电源时应接5v不然会出现数值混乱显示屏被烧毁而OLED是取字模时要注意排列和顺序。