海哥 发表于 2024-11-12 01:43:23

基于STM32的气体检测系统--有上位机小程序

       


 毕设搞完了,来分享一下,第一次搞,多包涵!
设计要求:
        技术要求: 1、原始数据:采集的气体浓度数据,包括CO2、可燃气体、有毒气体等;系统运行时的各种状态数据。 2、选择合适的气体传感器,并进行性能测试和评估;设计稳定且精确的模仿信号采集电路;嵌入式程序设计,实现数据采集、处理和界面显示;实现报警功能,确保及时预警和用户安全。 3、到达的指标:正确测量和及时监测不同气体浓度;检测范围和敏捷度符合相关要求; 数据显示界面友好且直观;报警功能正确可靠,能够及时提示用户;数据传输和远程监测功能稳定可靠。 4、 实验仿真等:原始数据采集和处理实验,验证嵌入式程序的功能和算法;报警功能实验,测试报警的正确性和相应速度;电源管理实验,评估电池寿命和充电效率;通信功能实验,测试数据传输的稳定性和可靠性。

        废话不多说直接软硬件设计!
硬件设计:

        本文所设计的便携式气体检测系统接纳STM32F103C8T6作为焦点处理单元,通过模块化系统,实现了对四周环境的全面监控和管理。
  通过温甲醛、一氧化碳、烟雾、可燃性等气体传感器对气体质量进行多方面监测,检测到的气体数据传输到主控芯片,再通过主控芯片的ADC数模转化模块转换和处理气体浓度信号。此外,用户还可以通过按键控制显示不同测量数据的气体浓度变化情况曲线,从而更直观地了解气体浓度的变化趋势。
          当气体浓度值超过设定的报警阈值时,STM32芯片通过启动蜂鸣器和闪烁红灯的方式,提示用户当前环境有害气体超标,并在LCD屏幕上显示及时报警信息。
          为了实现仪器的智能化和数据的远程传输,本文使用WIFI通信模块将检测数据上传到云端。上位机小程序是另一部分,通过与云端的通信,从云端获取数据并存入当地数据库,并展示这些数据,借助云端和上位机小程序,用户可以随时随地监测和分析室内空气质量,从而做出相应的措施。此外,系统接纳稳压模块5V供电,确保了电源的稳定性和系统的稳定运行。
https://i-blog.csdnimg.cn/direct/e5563bdb92e24bdb8bd614c9322963e8.png
硬件选型:
        stm32f103c8t6主控,DHT11温湿度传感器,SGP30甲醛浓度检测模块,MQ-2烟雾浓度检测模块,MQ-9可燃气浓度检测模块,MQ-7一氧化碳气浓度检测模块,TFT液晶显示模块,ESP8266-01S WIFI模块,声光报警模块:蜂鸣器和LED,按键
PCB板绘制:
        本文基于STM32的便携式气体检测系统硬件PCB板设计使用嘉立创eda设计,PCB板由嘉立创免费提供。
https://i-blog.csdnimg.cn/direct/1c7b6aea9317451d97d608c606c7acf5.png
https://i-blog.csdnimg.cn/direct/6fdb15ad620f4af8b4273656f95c5495.pnghttps://i-blog.csdnimg.cn/direct/ae2e6b65394449689d6fd72ad8812e75.png
软件设计:

        本文基于STM32的便携式气体检测系统软件设计使用C语言编程设计,程序代码接纳keil软件编写,程序注有清晰的中文解释。
主程序设计

        初始化完成后,系统进入主循环,焦点是 timeCount 时间管理变量。timeCount 小于 40 时,系统进行及时监控和相应,包括检测 LED 和蜂鸣器状态、显示气体数据、判定报警等,根据 edit 变量确定后续操作。随着 timeCount 增加,到达 10 的倍数读取气体传感器数据并转换,40 的倍数向 上位机发送数据,超过 200 向服务器发送数据并归零。然后判定服务器是否下发数据,最后 timeCount 加 1 回到主循环出发点继承执行对应操作,以实现高效正确监测和通信。 
https://i-blog.csdnimg.cn/direct/b907cadecdee44ee8d12a72f29d6dfa4.png
main函数代码
/*************************************版本2*********************************/

/*   USART1                   TX--PA10                RX--PA9

       ESP8266-01s        TX--PA3                        RX--PA2   RST--PC14
       
       1.8TFT+KEY
//            GND   电源地
//            VCC   接5V或3.3v电源
//            SCL   接PA0(SCL)        PB8
//            SDA   接PA1(SDA)        PB9
//            RES   接PB12
//            DC    接PB5
//            CS    接PA7
//                                BL          接PB13

按键
//                                K1    接PB0
//                                K2    接PB1
//                                K3    接PB3
//                                K4    接PB4

蜂鸣器                        IO    接PB15

DHT11         DATA接PB14

SGP30         SDA--PB11    SCL---PB10

MQ7一氧化碳   AO--PA4    DO--

MQ2烟雾                        AO--PA5    DO--

MQ9可燃气                AO--PA6    DO--

OLED                         SCL--PB6SDA--PB7

/***************************************************************************/
//单片机头文件
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "bmp.h"
#include "DHT11.h"
#include "usart.h"
#include "sgp30.h"
#include "adc.h"
#include "buzzer.h"
#include "key.h"
#include "led.h"
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"
#include "exti.h"
#include "function.h"
//#include "lanya.h"
#include "timer.h"
#include "oled.h"
//网络协议层
#include "onenet.h"

//网络设备
#include "esp8266.h"

//C库
#include <string.h>
#include <stdio.h>
#include "math.h"

                u8 Num;
          intkey_count;
                u8 voltage;

                u8 temp;
                u8 humi;
                u16 len;
                u32 CO2Data,TVOCData;                                //定义CO2浓度变量与TVOC浓度变量
          
                float CO_data,PM25_data,CH4_data;
                int HCHO_ppm,CO2_ppm,CO_ppm,PM25_ppm,CH4_ppm;
                int edit;
                       
                unsigned long sgp30_dat;
          u16 adc_CO,adc_CH4,adc_PM25;
               
               
                u8 LED_Status = 0;
                u8 alarm_flag = 0;                                //是否报警标志
       
                char oled_buf;                //上传数据的buf
               
                char PUB_BUF;                //上传数据的buf
                const char *devSubtopic[] = {"/myGas_detection/sub"};
                const char devPubtopic[] = "/myGas_detection/pub";
               
                unsigned short timeCount = 0;        //发送间隔变量
               
                int count = 0;        //发送间隔变量
       
                unsigned char *dataPtr = NULL;

               
int main(void)
{

                NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级

                LED_Init();
       
               
                Adc_Init();                                  //ADC初始化       
                delay_init();                 //延时函数初始化
               
                KEY_Init();//按键初始化
                LCD_Init();//LCD初始化
                LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
          SGP30_Init();   //初始化SGP30
                BEEP_Init();
       
       
                /* 初始化函数,如果DHT11存在响应则返回1,否则0 */
                DHT11_Init();
               
               
                Usart1_Init(115200);                                                        //串口1,打印信息用
       
                Usart2_Init(115200);                                                        //串口2,驱动ESP8266用
               
       
                UsartPrintf(USART_DEBUG, " Hardware init OK\r\n");
               
       
                ESP8266_Init();                                        //初始化ESP8266
               
                OneNet_DevLink();
                //while(OneNet_DevLink())                        //接入OneNET
                delay_ms(50);
       
                OneNet_Subscribe(devSubtopic, 1);       
               
                EXTIX_Init();
               
       
        while(1)
        {               

                        SHOW_zhujiem();
                        baojing ();

                        if (edit==1)
                        {
                        SHOW_curve(key_count);
                        }
                        else SHOW_flag(key_count);

        if(timeCount % 5 ==0)
        {
                        DHT11_Read_Data(&temp, &humi);
                                               
                        SGP30_Write(0x20,0x08);
                        sgp30_dat = SGP30_Read();//读取SGP30的值
                        CO2Data = (sgp30_dat & 0xffff0000) >> 16;//取出CO2浓度值
                        CO2_ppm = KalmanFilter_CO2(CO2Data);
                        TVOCData = sgp30_dat & 0x0000ffff;       //取出TVOC值
                        HCHO_ppm = KalmanFilter_HCHO(TVOCData);
       
                        //CO_ppm = MQ7_GetPPM();
                        CO_ppm = KalmanFilter_CO( MQ7_GetPPM() );
       

                        //PM25_ppm = MQ2_GetPPM();
                        PM25_ppm = KalmanFilter_PM25(MQ2_GetPPM() );
                       

                       
                        CH4_ppm =KalmanFilter_CH4( MQ9_GetPPM() );

        }               

                if(timeCount >= 8)//5000ms/25=200 发送间隔5000ms                1min        8---10s                                                //发送间隔5s
                {
               
                        UsartPrintf(USART_DEBUG, "OneNet_Publish\r\n");
                        UsartPrintf(USART_DEBUG, "温度:%d\r\n 湿度:%d\r\n 甲醛:%d\r\n CO2:%d\r\n",temp,humi,TVOCData,CO2Data);
                        UsartPrintf(USART_DEBUG, "CO:%d\r\n 烟雾:%d\r\n 可燃气:%d\r\n",CO_ppm,PM25_ppm,CH4_ppm);
                       
                        sprintf(PUB_BUF,"{\"Temp\":%d,\"Humi\":%d,\"HCHO\":%d,\"CO2\":%d,\"CO\":%d,\"PM2_5\":%d,\"CH4\":%d,\"LED\":%d,\"BEEP\":%d}",
                        temp,humi,TVOCData,CO2Data,CO_ppm,PM25_ppm,CH4_ppm,LED_Status,alarm_flag);
                       
                        OneNet_Publish(devPubtopic, PUB_BUF);
                       
                        timeCount = 0;
                        ESP8266_Clear();
                }
               
                dataPtr = ESP8266_GetIPD(3);
                if(dataPtr != NULL)
                OneNet_RevPro(dataPtr);
               
                delay_ms(10);
               
                timeCount++;
               
}
}

报警子程序设计

        系统进入报警子程序时先检查上位机控制,若有则报警器为手动控制模式,更新 alarm_is_free 变量并进行十秒声光自检以验证报警功能。若上位机未操作,系统检查气体数据,达阈值则启动声光报警器发出信号,让操作职员采取措施避免安全事故。

https://i-blog.csdnimg.cn/direct/b9e6383d4f584b948112a3ebfdd076d6.png
void baojing (void)
{
        if(alarm_is_free== 10)                                //报警器自动控制是否空闲
        {
        if (temp < 50 && humi < 90 && TVOCData < 100 &&CO2Data < 1000 && CO_ppm < 100 && PM25_ppm < 500 && CH4_ppm <1000)
        {
                LED = 1;
                BEEP = 0;
        }else{
               
                LED = 0;
                BEEP = 1;
        }
}
        if (alarm_is_free < 10)alarm_is_free ++;
        delay_ms(1000);
       
//        UsartPrintf(USART_DEBUG, "alarm_is_free %d\r\n",alarm_is_free);
       
} MQ系列数据的读取(以MQ2烟雾传感器为例)

        MQ-2烟雾传感器输出为0-5V的模仿电压信号,烟雾浓度越高电压越高,而STM32F103C8T6单片机的ADC输入通道的模仿量输入范围为0-3.3V,因此必要串联电阻进行分压,保护AD转换芯片。这样,将传感器的模仿输出引脚接随处理器的一个ADC引脚进行模数转换,即可得烟雾传感器的输出电压。又因烟雾浓度与电压信号关系为非线性,因此需拟合出气体浓度与输出电压的关系曲线,从而计算气体浓度。
https://i-blog.csdnimg.cn/direct/d4b1f9457c104fc79bc33290c2a4f4ba.png
https://i-blog.csdnimg.cn/direct/5c4091314bbc47469d9e3de81bf21eb1.png
        其中,x为RS/R0的比值,R0元件在洁净空气中的阻值,通常取6.64,RS=(Vc-VI)*RI/V,VC为回路电压,该系统取5V,为传感器模仿输出电中压,RL为5。
WiFi数据传输设计

        在进行物联网项目开发时,通常必要将微控制器与Wi-Fi模块相共同,以实现数据的采集、处理和远程传输。数据传输流程图如图4. 8所示,其配置步骤如下:

[*]初始化过程:
串口初始化:设置串口波特率、数据位、制止位和校验位等。
ESP8266-01s初始化: Wi-Fi毗连设置和MQTT客户端的初始化。

[*]网络接入检测:
判定ESP8266-01s是否乐成接入到OneNet平台,若未接入,则尝试毗连,直至乐成毗连为止。

[*]MQTT主题订阅:
网络毗连乐成后,订阅指定的OneNet主题,接收来自MQTT服务器的消息。

[*]数据打包与传输:
        STM32负责采集和处理传感器数据,将这些数据打包成MQTT消息格式,然后ESP8266-01s通过Wi-Fi网络将这些消息传输至MQTT服务器。
https://i-blog.csdnimg.cn/direct/adae88fb45b649709d59ceebe72a4d02.png
//esp8266.c
//单片机头文件
#include "stm32f10x.h"

//网络设备驱动
#include "esp8266.h"

//硬件驱动
#include "delay.h"
#include "usart.h"

//C库
#include <string.h>
#include <stdio.h>


#define ESP8266_WIFI_INFO                "AT+CWJAP=\"ONENET\",\"88888888\"\r\n"

#define ESP8266_ONENET_INFO                "AT+CIPSTART=\"TCP\",\"broker.emqx.io\",1883\r\n"

//#define ESP8266_ONENET_INFO                "AT+CIPSTART=\"TCP\",\"47.97.44.102\",1883\r\n"

//#define ESP8266_ONENET_INFO                "AT+CIPSTART=\"TCP\",\"emqx_Mjc0OT\",1883\r\n"


unsigned char esp8266_buf;
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;


//==========================================================
//        函数名称:        ESP8266_Clear
//
//        函数功能:        清空缓存
//
//        入口参数:        无
//
//        返回参数:        无
//
//        说明:               
//==========================================================
void ESP8266_Clear(void)
{

        memset(esp8266_buf, 0, sizeof(esp8266_buf));
        esp8266_cnt = 0;

}
//==========================================================
//        函数名称:        ESP8266_WaitRecive
//
//        函数功能:        等待接收完成
//
//        入口参数:        无
//
//        返回参数:        REV_OK-接收完成                REV_WAIT-接收超时未完成
//
//        说明:                循环调用检测是否接收完成
//==========================================================
_Bool ESP8266_WaitRecive(void)
{

        if(esp8266_cnt == 0)                                                         //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
                return REV_WAIT;
               
        if(esp8266_cnt == esp8266_cntPre)                                //如果上一次的值和这次相同,则说明接收完毕
        {
                esp8266_cnt = 0;                                                        //清0接收计数
                       
                return REV_OK;                                                                //返回接收完成标志
        }
               
        esp8266_cntPre = esp8266_cnt;                                        //置为相同
       
        return REV_WAIT;                                                                //返回接收未完成标志

}

//==========================================================
//        函数名称:        ESP8266_SendCmd
//
//        函数功能:        发送命令
//
//        入口参数:        cmd:命令
//                                res:需要检查的返回指令
//
//        返回参数:        0-成功        1-失败
//
//        说明:               
//==========================================================
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
       
        unsigned char timeOut = 200;

        Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
       
        while(timeOut--)
        {
                if(ESP8266_WaitRecive() == REV_OK)                                                        //如果收到数据
                {
                        if(strstr((const char *)esp8266_buf, res) != NULL)                //如果检索到关键词
                        {
                                ESP8266_Clear();                                                                        //清空缓存
                               
                                return 0;
                        }
                }
               
                delay_ms(10);
        }
       
        return 1;

}

//==========================================================
//        函数名称:        ESP8266_SendData
//
//        函数功能:        发送数据
//
//        入口参数:        data:数据
//                                len:长度
//
//        返回参数:        无
//
//        说明:               
//==========================================================
void ESP8266_SendData(unsigned char *data, unsigned short len)
{

        char cmdBuf;
       
        ESP8266_Clear();                                                                //清空接收缓存
        sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);                //发送命令
        if(!ESP8266_SendCmd(cmdBuf, ">"))                                //收到‘>’时可以发送数据
        {
                Usart_SendString(USART2, data, len);                //发送设备连接请求数据
        }

}

//==========================================================
//        函数名称:        ESP8266_GetIPD
//
//        函数功能:        获取平台返回的数据
//
//        入口参数:        等待的时间(乘以10ms)
//
//        返回参数:        平台返回的原始数据
//
//        说明:                不同网络设备返回的格式不同,需要去调试
//                                如ESP8266的返回格式为        "+IPD,x:yyy"        x代表数据长度,yyy是数据内容
//==========================================================
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{

        char *ptrIPD = NULL;
       
        do
        {
                if(ESP8266_WaitRecive() == REV_OK)                                                                //如果接收完成
                {
                        ptrIPD = strstr((char *)esp8266_buf, "IPD,");                                //搜索“IPD”头
                        if(ptrIPD == NULL)                                                                                        //如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
                        {
                                //UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
                        }
                        else
                        {
                                ptrIPD = strchr(ptrIPD, ':');                                                        //找到':'
                                if(ptrIPD != NULL)
                                {
                                        ptrIPD++;
                                        return (unsigned char *)(ptrIPD);
                                }
                                else
                                        return NULL;
                               
                        }
                }
               
                delay_ms(5);                                                                                                        //延时等待
                timeOut--;
        } while(timeOut>0);
       
        return NULL;                                                                                                                //超时还未找到,返回空指针

}

//==========================================================
//        函数名称:        ESP8266_Init
//
//        函数功能:        初始化ESP8266
//
//        入口参数:        无
//
//        返回参数:        无
//
//        说明:               
//==========================================================
void ESP8266_Init(void)
{
       
        GPIO_InitTypeDef GPIO_Initure;
       
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

        //ESP8266复位引脚
        GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_Initure.GPIO_Pin = GPIO_Pin_14;                                        //GPIOC14-复位
        GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &GPIO_Initure);
       
        GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET);
        delay_ms(250);
        GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET);
        delay_ms(500);
       
        ESP8266_Clear();
       
        UsartPrintf(USART_DEBUG, "0. AT\r\n");
        while(ESP8266_SendCmd("AT\r\n", "OK"))
        delay_ms(500);
       
       
        UsartPrintf(USART_DEBUG, "1. RST\r\n");
        ESP8266_SendCmd("AT+RST\r\n", "");
        delay_ms(500);
        ESP8266_SendCmd("AT+CIPCLOSE\r\n", "");
        delay_ms(500);
       
        UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
        while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
                delay_ms(500);
       
        UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
        while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
                delay_ms(500);
       
        UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
        while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "OK"))//GOT IP
                delay_ms(500);
       
        UsartPrintf(USART_DEBUG, "5. CIPSTART\r\n");
        while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
                delay_ms(500);
       
        UsartPrintf(USART_DEBUG, "6. ESP8266 Init OK\r\n");

}

//==========================================================
//        函数名称:        USART2_IRQHandler
//
//        函数功能:        串口2收发中断
//
//        入口参数:        无
//
//        返回参数:        无
//
//        说明:               
//==========================================================
void USART2_IRQHandler(void)
{

        if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
        {
                if(esp8266_cnt >= sizeof(esp8266_buf))        esp8266_cnt = 0; //防止串口被刷爆
                esp8266_buf = USART2->DR;
               
                USART_ClearFlag(USART2, USART_FLAG_RXNE);
        }

}         其与部分较为简朴,暂不列出,有题目欢迎批评交流!


上位机小程序设计

        便携式气体检测微信小程序是一款智能软件,可及时监测和显示气体浓度,并为用户的操作提供界面,该软件应用于各种环境监测、工业生产和公共安全,可以资助用户及时发现和处理潜伏的气体安全风险。
功能与特点


[*]及时监测:通过Wi-Fi与便携式气体检测系统通信,获取及时气体浓度数据,并通过图表的方式向用户展示。
[*]数据展示:上位机显示气体浓度数据,支持折线图显示,使用户了解气体数据变化趋势。
[*]用户界面:提供简洁明了的主界面、图表界面、数据界面等,方便用户操作和查找数据。
[*]数据存储:将数据存入当地数据库,防止数据丢失,包管数据安全。
[*]报警功能:当检测到气体浓度超过设定阈值时,上位机会发出警报消息,关照用户采取相应处理措施。
[*]装备管理:支持对毗连的便携式气体检测仪进行管理,包括装备报警装置自检等操作。
界面设计

        1.主界面:显示及时气体浓度数据、装备毗连状态、气体类型、报警状态等信息,方便用户快速了解装备运行情况和气体浓度变化。
https://i-blog.csdnimg.cn/direct/c79b225ee56945e087e45f6da0a58a52.png
https://i-blog.csdnimg.cn/direct/076d1f770b2c4feb87d9182de2475128.png

        2. 图表界面:有数据表和气体浓度曲线图供用户选择,其中气体浓度曲线图展示了气体变化情况,方便用户直观地查看气体浓度数据的变化趋势。
https://i-blog.csdnimg.cn/direct/ff068d8ded924da6897e1ef7646bcd06.png
https://i-blog.csdnimg.cn/direct/5933be05ac3f42069eb65bcfeb0ffd98.png
         3.设置界面:提供装备参数设置、报警阈值设置等操作界面,方便用户根据必要进行个性化配置。
https://i-blog.csdnimg.cn/direct/f6b0edcf20594a9aba34b633b4ddf5b8.png
开发环境与工具

1. 开发语言:使用Java编程语言进行开发,具有良好的性能和可维护性。
2. 开发工具:使用微信开发者工具进行开发,提高开发效率和代码质量。
3. 数据库工具:使用MySQL数据库工具进行数据存储和管理,包管数据的安全性和可靠性。

 最终实现结果 !         

   基于STM32的气体检测系统设计--含上位机小程序


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 基于STM32的气体检测系统--有上位机小程序