1. 前言
随着人们生活质量的提高,对于生活环境的问题,人们的关注度进一步提高,同时政府部门采取了许多措施来改善环境状况。但是总体上来说我国的环境监测技术水平比较落后,传统上的监测手段比较单一,监测数据也不够准确,耗尽了大量的人力和财力,却成效不高。
针对上述缺点,当前文章综合了嵌入式处理技术、传感器技术、无线网络通信等技术,设计了一个基于STM32的无线环境监测系统,系统主要实现了对湿度、温度、有毒气体、烟雾浓度、空气质量等参数进行实时监测的功能。为了实现无线数据传输功能,采用了无线wifi技术。系统的测试分析表明系统整体数据采集性能良好,数据传输稳定性可靠,到达了预期目标。
系统与传统的监测技术相比,具有监测数据准确,监测范围广,智能化高等特点。且系统具有一定的创新性,在实际的工程运用和理论研究上体现出了一定的研究价值最后通过实物的调试,各项参数及功能符合设计要求,能达到预期的目的。
设计以STM32微控制器为平台,采用DHT11温湿度传感器、烟雾传感器MQ-2、易燃气体传感器MQ-4、空气质量检测传感器MQ-135对室内温湿度和危险气体进行采集。通过wifi无线网络将数据传送给微控制器,STM32微控制器处理数据后,由自带oled液晶屏显示。当室内温度达到预警值或有危险气体时,系统将会自动警报并将警报信息通过wifi网络传输给客户手机。且每隔一段时间会通过wifi自动发送监测信息到手机,从而实现对室内环境的监测及报警功能。
软件源码完整下载地址: https://download.csdn.net/download/xiaolong1126626497/63979263
视频演示地址: https://live.csdn.net/v/182605
2. 实现功能与整体框架图
开发板采用STM32最小系统板,主控CPU采用STM32F103C8T6,其他传感器采用模块的形式连接到开发板。
主要实现以下功能实现:
1、通过DHT11温湿度传感器、烟雾传感器MQ-2、易燃气体传感器MQ-4、空气质量检测传感器MQ-135对室内温湿度和危险气体进行采集。
2、通过传感器用ADC模拟数字的转换,采集到的数据显示在oled屏幕上。
3、当检测到的数据超过设定的安全值时,屏幕上会显示警报。
4、检测到的数据能定时通过ESP8266 wifi无线传输发送到所连接的用户的手机上,实现监测功能。
系统框架图如下:
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端没有单独设计精美的界面,只是简单的展示了数据显示。
4. 核心源码
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);
// }
}