摘要:综合了嵌入式处理技术、传感器技术、无线网络通信等技术,设计了一个基于 STM32 的无线环境监测系统,系统主要实现了对湿度、温度、有毒气体、烟雾浓度、空气质量等参数进行实时监测的功能。
本文分享自华为云社区《基于 STM32 设计的环境检测设备》,作者:DS 小龙哥 。
  1. 前言
随着人们生活质量的提高,对于生活环境的问题,人们的关注度进一步提高,同时政府部门采取了许多措施来改善环境状况。但是总体上来说我国的环境监测技术水平比较落后,传统上的监测手段比较单一,监测数据也不够准确,耗尽了大量的人力和财力,却成效不高。
针对上述缺点,当前文章综合了嵌入式处理技术、传感器技术、无线网络通信等技术,设计了一个基于 STM32 的无线环境监测系统,系统主要实现了对湿度、温度、有毒气体、烟雾浓度、空气质量等参数进行实时监测的功能。为了实现无线数据传输功能,采用了无线 wifi 技术。系统的测试分析表明系统整体数据采集性能良好,数据传输稳定性可靠,到达了预期目标。
系统与传统的监测技术相比,具有监测数据准确,监测范围广,智能化高等特点。且系统具有一定的创新性,在实际的工程运用和理论研究上体现出了一定的研究价值最后通过实物的调试,各项参数及功能符合设计要求,能达到预期的目的。
设计以 STM32 微控制器为平台,采用 DHT11 温湿度传感器、烟雾传感器 MQ-2、易燃气体传感器 MQ-4、空气质量检测传感器 MQ-135 对室内温湿度和危险气体进行采集。通过 wifi 无线网络将数据传送给微控制器,STM32 微控制器处理数据后,由自带 oled 液晶屏显示。当室内温度达到预警值或有危险气体时,系统将会自动警报并将警报信息通过 wifi 网络传输给客户手机。且每隔一段时间会通过 wifi 自动发送监测信息到手机,从而实现对室内环境的监测及报警功能。
基于 STM32 设计的环境检测设备视频演示地址
v2-6dadf957f59a486f51115a55624443ec_720w.jpg
  2. 实现功能与整体框架图
开发板采用 STM32 最小系统板,主控 CPU 采用 STM32F103C8T6,其他传感器采用模块的形式连接到开发板。
主要实现以下功能实现:
1、通过 DHT11 温湿度传感器、烟雾传感器 MQ-2、易燃气体传感器 MQ-4、空气质量检测传感器 MQ-135 对室内温湿度和危险气体进行采集。
2、通过传感器用 ADC 模拟数字的转换,采集到的数据显示在 oled 屏幕上。
3、当检测到的数据超过设定的安全值时,屏幕上会显示警报。
4、检测到的数据能定时通过 ESP8266 wifi 无线传输发送到所连接的用户的手机上,实现监测功能。
系统框架图如下:
v2-3992d034f264c5f903890dc7307bfe08_720w.jpg
  3. 硬件特点介绍
(1) 温湿度传感器
温湿度传感器采用 DHT11,这是一款直接输出数字信号的温湿度传感器;其精度湿度 ±5% RH, 温度 ±2℃,量程湿度 5~95% RH, 温度 - 20~+60℃。通过单总线时序输出,占用的 IO 口也比较少,工作电压 3V~5V, 单片机连接控制很方便。
(2) MQ 系列的气体检测传感器
烟雾传感器 MQ-2、易燃气体传感器 MQ-4、空气质量检测传感器 MQ-135,这些传感器都是输出模拟信号。
配置好 STM32 的 ADC 采集接口,采集数据进行处理即可。
(3) ESP8266 WIFI
联网的模块采用 ESP8266 WIFI,ESP8266 在物联网里使用非常多,有很多成熟的案例.WIFI 本身也支持二次开发,默认集成的 SDK 支持 AT 指令控制,单片机可以通过串口方式控制 ESP8266 完成网络通信,非常方便.
(4) OLED 显示屏
OLED 显示屏采用中景园电子的 0.96 寸 OLED, 分辨率是 128x64, 使用的 SPI 引脚接口屏幕,刷屏速度很快,控制简单
(5) 上位机设计
手机 APP 和 PC 端没有单独设计精美的界面,只是简单的展示了数据显示。
v2-e3f73108716cbadc4b20e2293d5b4b04_720w.jpg
  4. 核心源码 v2-92f29732cff2db59009f49e98b44141b_720w.jpg
  4.1 DHT11 温湿度代码
#include "dht11.h"
  • #include "delay.h"
  • //复位DHT11
  • void DHT11_Rst(void)
  • {
  • DHT11_IO_OUT(); //SET OUTPUT
  • DHT11_DQ_OUT=0; //拉低DQ
  • DelayMs(20); //拉低至少18ms
  • DHT11_DQ_OUT=1; //DQ=1
  • delay_us(30); //主机拉高20~40us
  • }
  • //等待DHT11的回应
  • //返回1:未检测到DHT11的存在
  • //返回0:存在
  • u8 DHT11_Check(void)  
  • {
  • u8 retry=0;
  • DHT11_IO_IN();//SET INPUT
  • while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
  • {
  • retry++;
  • delay_us(1);
  • };
  • if(retry>=100)return 1;
  • else retry=0;
  • while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
  • {
  • retry++;
  • delay_us(1);
  • };
  • if(retry>=100)return 1;
  • return 0;
  • }
  • //从DHT11读取一个位
  • //返回值:1/0
  • u8 DHT11_Read_Bit(void)  
  • {
  • u8 retry=0;
  • while(DHT11_DQ_IN&&retry<100)//等待变为低电平
  • {
  • retry++;
  • delay_us(1);
  • }
  • retry=0;
  • while(!DHT11_DQ_IN&&retry<100)//等待变高电平
  • {
  • retry++;
  • delay_us(1);
  • }
  • delay_us(40);//等待40us
  • if(DHT11_DQ_IN)return 1;
  • else return 0;
  • }
  • //从DHT11读取一个字节
  • //返回值:读到的数据
  • u8 DHT11_Read_Byte(void)
  • {
  •     u8 i,dat;
  • dat=0;
  • for (i=0;i<8;i++)
  • {
  • dat<<=1;
  • dat|=DHT11_Read_Bit();
  • }
  • return dat;
  • }
  • //从DHT11读取一次数据
  • //temp:温度值(范围:0~50°)
  • //humi:湿度值(范围:20%~90%)
  • //返回值:0,正常;1,读取失败
  • u8 DHT11_Read_Data(u8 *temp,u8 *humi)
  • {
  • u8 buf[5];
  • u8 i;
  • DHT11_Rst();
  • //printf("------------------------\r\n");
  • if(DHT11_Check()==0)
  • {
  • for(i=0;i<5;i++)//读取40位数据
  • {
  • buf[i]=DHT11_Read_Byte();
  • }
  • if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
  • {
  • *humi=buf[0];
  • *temp=buf[2];
  • }
  • }else return 1;
  • return 0;
  • }
  • //初始化DHT11的IO口 DQ 同时检测DHT11的存在
  • //返回1:不存在
  • //返回0:存在  
  • u8 DHT11_Init(void)
  • {
  • RCC->APB2ENR|=1<<2; //使能PORTG口时钟
  • GPIOA->CRL&=0XFF0FFFFF;//PORTG.11 推挽输出
  • GPIOA->CRL|=0X00300000;
  • GPIOA->ODR|=1<<5; //输出1
  • DHT11_Rst();
  • return DHT11_Check();
  • }
  • 复制代码


      4.2 ESP8266 代码
  • #include "esp8266.h"
  • extern u8  USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN字节
  • extern u8  USART3_TX_BUF[USART3_MAX_SEND_LEN]; //发送缓冲,最大USART3_MAX_SEND_LEN字节
  • extern vu16 USART3_RX_STA; //接收数据状态
  • ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  • //用户配置区
  • //连接端口号:8086,可自行修改为其他端口.
  • const u8 portnum[]="8089";
  • //WIFI STA模式,设置要去连接的路由器无线参数,请根据你自己的路由器设置,自行修改.
  • const u8 wifista_ssid[]="wbyq1";//路由器SSID号
  • const u8 wifista_encryption[]="wpa2_aes";//wpa/wpa2 aes加密方式
  • const u8 wifista_password[]="123456789"; //连接密码
  • //WIFI AP模式,模块对外的无线参数,可自行修改.
  • const u8 wifiap_ssid[]="Cortex_M3"; //对外SSID号
  • const u8 wifiap_encryption[]="wpawpa2_aes";//wpa/wpa2 aes加密方式
  • const u8 wifiap_password[]="12345678";  //连接密码
  • /*
  • 函数功能:向ESP82668266发送命令
  • 函数参数:
  • cmd:发送的命令字符串
  • ack:期待的应答结果,如果为空,则表示不需要等待应答
  • waittime:等待时间(单位:10ms)
  • 返 回 值:
  • 0,发送成功(得到了期待的应答结果)
  •          1,发送失败
  • */
  • u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime)
  • {
  • u8 res=0;
  • USART3_RX_STA=0;
  • UsartStringSend(USART3,cmd);//发送命令
  • if(ack&&waittime)//需要等待应答
  • {
  • while(--waittime)//等待倒计时
  • {
  • DelayMs(10);
  • if(USART3_RX_STA&0X8000)//接收到期待的应答结果
  • {
  • if(ESP8266_CheckCmd(ack))
  • {
  • res=0;
  • //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack);
  • break;//得到有效数据
  • }
  • USART3_RX_STA=0;
  • }
  • }
  • if(waittime==0)res=1;
  • }
  • return res;
  • }
  • /*
  • 函数功能:ESP8266发送命令后,检测接收到的应答
  • 函数参数:str:期待的应答结果
  • 返 回 值:0,没有得到期待的应答结果
  • 其他,期待应答结果的位置(str的位置)
  • */
  • u8* ESP8266_CheckCmd(u8 *str)
  • {
  • char *strx=0;
  • if(USART3_RX_STA&0X8000) //接收到一次数据了
  • {
  • USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
  • strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功
  • printf("RX=%s",USART3_RX_BUF);
  • }
  • return (u8*)strx;
  • }
  • /*
  • 函数功能:向ESP8266发送指定数据
  • 函数参数:
  • data:发送的数据(不需要添加回车)
  • ack:期待的应答结果,如果为空,则表示不需要等待应答
  • waittime:等待时间(单位:10ms)
  • 返 回 值:0,发送成功(得到了期待的应答结果)luojian
  • */
  • u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime)
  • {
  • u8 res=0;
  • USART3_RX_STA=0;
  • UsartStringSend(USART3,data);//发送数据
  • if(ack&&waittime)//需要等待应答
  • {
  • while(--waittime)//等待倒计时
  • {
  • DelayMs(10);
  • if(USART3_RX_STA&0X8000)//接收到期待的应答结果
  • {
  • if(ESP8266_CheckCmd(ack))break;//得到有效数据
  • USART3_RX_STA=0;
  • }
  • }
  • if(waittime==0)res=1;
  • }
  • return res;
  • }
  • /*
  • 函数功能:ESP8266退出透传模式
  • 返 回 值:0,退出成功;
  •          1,退出失败
  • */
  • u8 ESP8266_QuitTrans(void)
  • {
  • while((USART3->SR&0X40)==0);//等待发送空
  • USART3->DR='+';
  • DelayMs(15);//大于串口组帧时间(10ms)
  • while((USART3->SR&0X40)==0);//等待发送空
  • USART3->DR='+';
  • DelayMs(15);//大于串口组帧时间(10ms)
  • while((USART3->SR&0X40)==0);//等待发送空
  • USART3->DR='+';
  • DelayMs(500);//等待500ms
  • return ESP8266_SendCmd("AT","OK",20);//退出透传判断.
  • }
  • /*
  • 函数功能:获取ESP82668266模块的AP+STA连接状态
  • 返 回 值:0,未连接;1,连接成功
  • */
  • u8 ESP8266_ApStaCheck(void)
  • {
  • if(ESP8266_QuitTrans())return 0; //退出透传
  • ESP8266_SendCmd("AT+CIPSTATUS",":",50);//发送AT+CIPSTATUS指令,查询连接状态
  • if(ESP8266_CheckCmd("+CIPSTATUS:0")&&
  • ESP8266_CheckCmd("+CIPSTATUS:1")&&
  • ESP8266_CheckCmd("+CIPSTATUS:2")&&
  • ESP8266_CheckCmd("+CIPSTATUS:4"))
  • return 0;
  • else return 1;
  • }
  • /*
  • 函数功能:获取ESP8266模块的连接状态
  • 返 回 值:0,未连接;1,连接成功.
  • */
  • u8 ESP8266_ConstaCheck(void)
  • {
  • u8 *p;
  • u8 res;
  • if(ESP8266_QuitTrans())return 0; //退出透传
  • ESP8266_SendCmd("AT+CIPSTATUS",":",50);//发送AT+CIPSTATUS指令,查询连接状态
  • p=ESP8266_CheckCmd("+CIPSTATUS:");
  • res=*p;//得到连接状态
  • return res;
  • }
  • /*
  • 函数功能:获取ip地址
  • 函数参数:ipbuf:ip地址输出缓存区
  • */
  • void ESP8266_GetWanip(u8* ipbuf)
  • {
  •   u8 *p,*p1;
  • if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败
  • {
  • ipbuf[0]=0;
  • return;
  • }
  • p=ESP8266_CheckCmd(""");
  • p1=(u8*)strstr((const char*)(p+1),""");
  • *p1=0;
  • sprintf((char*)ipbuf,"%s",p+1);
  • }
  • /*
  • 函数功能:将收到的AT指令应答数据返回给电脑串口
  • 参 数:mode:0,不清零USART3_RX_STA;
  • 1,清零USART3_RX_STA;
  • */
  • void ESP8266_AtResponse(u8 mode)
  • {
  • if(USART3_RX_STA&0X8000)//接收到一次数据了
  • {
  • USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
  • printf("%s",USART3_RX_BUF);//发送到串口
  • if(mode)USART3_RX_STA=0;
  • }
  • }
  • /*
  • 函数功能:ESP8266 AP模式+TCP服务器模式测试
  • */
  • void ESP8266_APorServer(void)
  • {
  • u8 p[100];
  • u8 ipbuf[20];
  • while(ESP8266_SendCmd("AT\r\n","OK",20))//检查WIFI模块是否在线
  • {
  • ESP8266_QuitTrans();//退出透传
  • ESP8266_SendCmd("AT+CIPMODE=0\r\n","OK",200); //关闭透传模式
  • printf("未检测到模块,正在尝试连接模块...\r\n");
  • DelayMs(800);
  • }
  • printf("ESP8266模块检测OK!\r\n");
  • while(ESP8266_SendCmd("ATE0\r\n","OK",20)); //关闭回显
  • printf("请用设备连接WIFI热点:%s,%s,%ss\r\n",(u8*)wifiap_ssid,(u8*)wifiap_encryption,(u8*)wifiap_password);
  • /*1. 设置WIFI AP模式 */
  • ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50);
  • /*2. 重启模块 */
  • ESP8266_SendCmd("AT+RST\r\n","OK",20);
  • /*3. 延时3S等待重启成功*/
  • DelayMs(1000);
  • DelayMs(1000);
  • DelayMs(1000);
  • /*5. 配置模块AP模式无线参数*/
  • sprintf((char*)p,"AT+CWSAP="%s","%s",1,4\r\n",wifiap_ssid,wifiap_password);
  • ESP8266_SendCmd(p,"OK",1000);
  • /*4. 设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/
  • ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20);
  • /*5. 开启Server模式(0,关闭;1,打开),端口号为portnum */
  • sprintf((char*)p,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum);
  • ESP8266_SendCmd(p,"OK",50);
  • /*6. 获取当前模块的IP*/
  • ESP8266_GetWanip(ipbuf);//
  • printf("IP地址:%s 端口:%s",ipbuf,(u8*)portnum);
  • USART3_RX_STA=0; //清空串口的接收标志位
  • //while(1)
  • //{
  • //key=GetKeyVal(1);//退出测试
  • //if(key==1)
  • //{
  • //printf("退出测试!\r\n");
  • //ESP8266_QuitTrans();//退出透传
  • //ESP8266_SendCmd("AT+CIPMODE=0","OK",20);   //关闭透传模式
  • //break;
  • //}
  • //else if(key==2)//发送数据
  • //{
  • //ESP8266_SendCmd("AT+CIPSEND=0,12\r\n","OK",200); //设置发送数据长度为12个
  • //ESP8266_SendData("ESP8266测试!","OK",100);       //发送指定长度的数据
  • //DelayMs(200);
  • //}
  • //t++;
  • //DelayMs(10);
  • //if(USART3_RX_STA&0X8000)  //接收到一次数据了
  • //{
  • //rlen=USART3_RX_STA&0X7FFF;//得到本次接收到的数据长度
  • //USART3_RX_BUF[rlen]=0;  //添加结束符
  • //printf("接收的数据: rlen=%d,%s",rlen,USART3_RX_BUF);//发送到串口
  • //USART3_RX_STA=0;
  • //if(constate!=3)t=1000;  //状态为还未连接,立即更新连接状态
  • //else t=0;                   //状态为已经连接了,10秒后再检查
  • //}
  • //if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在.
  • //{
  • ////constate=ESP8266_ConstaCheck();//得到连接状态
  • ////if(!constate)printf("连接失败!\r\n");
  • //t=0;
  • //}
  • //if((t%20)==0)LED2=!LED2;
  • //ESP8266_AtResponse(1);
  • //}
  • }
  • 复制代码