简介:基于STM32环境检测综合项目资源,面向嵌入式初学者、电子竞赛选手及物联网爱好者,用于解决多传感器数据采集与环境监测系统搭建问题。项目结合DHT11温湿度、MQ-2烟雾浓度与光敏电阻光照强度三大感知单元,覆盖单总线时序控制、ADC模拟量采集与电压换算等关键知识点。资源包共303个文件、18.1MB,以45个C源文件、47个头文件为主体,另含2个hex烧录文件、2个axf调试映像、uvprojx工程配置及编译生成的o目标文件与crf文件,构成完整Keil MDK工程,从源码到可烧录产物一应俱全。已有9657人学习下载。包内提供原理图与完整STM32驱动代码,便于读者直接查看外设初始化、传感器读取与数据处理逻辑,也可对照工程配置快速搭建项目,适合作为课程设计、毕业设计或传感器入门实践的参考资料。
1. 基于STM32的环境检测方案:DHT11、MQ-2、光敏电阻三合一
很多人第一次做STM32项目,就是奔着“能测温湿度、能报烟雾、能感知光照”这个目标去的。DHT11负责温湿度,MQ-2负责烟雾浓度,光敏电阻负责光照强度,三路信号汇总到STM32F103C8T6,串口一打,环境状态一目了然。标题里带原理图和源程序文件,意味着你不需要从零画板、从零写驱动,拿到文件照着烧录就能跑。这套组合非常适合课设、毕业设计第一版,也适合想快速搭一个环境检测样板的嵌入式初学者。下面按我实际做过的流程拆开讲,包含硬件连线、驱动代码、参数设置和踩坑记录。
2. 原理图与硬件连线:三个传感器的电路设计要这样画
2.1 传感器选型逻辑与供电关系
先说选型。DHT11是数字单总线传感器,一根线同时传时钟和数据,电路简单,价格便宜,精度虽然只有±2℃和±5%RH,但做环境检测的“有/无”级别判断完全够用。MQ-2是半导体气敏传感器,内部有一个加热丝和一个二氧化锡气敏层,遇到可燃气体、烟雾时电导率变化,输出信号随之改变。光敏电阻更直接,阻值随光照强度变化,配合一个固定电阻分压就能变成电压信号。
这三个传感器放在一起,正好覆盖了环境检测里最常用的三个维度:空气温湿度、空气质量、光照。供电关系需要注意:DHT11和数据线可以走3.3V,光敏电阻模块也建议走3.3V,而MQ-2的加热丝需要5V供电,加热电流在150mA左右。如果整个系统只有一个5V输入,常见做法是用AMS1117-3.3稳压给MCU和DHT11供电,MQ-2直接吃5V。原理图上要把5V和3.3V两个电源域画清楚,地线公共,但MQ-2的大电流回路要单独引一段回主地,不要跟ADC采样地交叉,否则采集值会跟着加热电流波动。
2.2 关键电路细节:上拉电阻、分压网络和LM393光敏模块
DHT11的单总线数据线必须接一个上拉电阻,阻值4.7K到10K都可以。裸DHT11的引脚上如果没有上拉,读时序时总线释放后电平拉不高,驱动代码会一直卡在等待高电平那里。很多模块上已经焊了10K电阻,直接用模块可以省掉,但如果在原理图里自己画,一定不要漏。
MQ-2模块的输出要格外小心。市面上大多数MQ-2模块自带LM393比较器,有DO(数字输出)和AO(模拟输出)两个引脚,模块标注工作电压5V,AO输出范围是0到5V。STM32F103的ADC输入范围是0到3.3V,直接接AO引脚,浓度一高就是4095满量程,严重了还可能损坏引脚。原理图上必须加分压:AO先串一个10K电阻,再经一个10K电阻到地,中点接STM32的ADC引脚,这样最高电平被限制在2.5V以内。如果是自己画的LM393光敏电阻模块,同样处理,AO输出通过分压再接ADC。
光敏电阻有两种接法。一种是用现成的光敏模块,模块上自带LM393比较器和电位器,DO输出数字信号,AO输出模拟信号,AO接ADC引脚即可。另一种是自己用裸光敏电阻搭分压电路:3.3V经过光敏电阻,再经过一个10K固定电阻到地,中间抽头接ADC。光照越强,光敏电阻阻值越小,抽头电压越高。10K这个值的选取有一定讲究,光敏电阻暗阻大、亮阻小,10K能让暗处和亮处的电压都有明显区分,这是工程里比较常用的折中值。
2.3 引脚分配与完整连线表
下面给出一份可以直接照抄的引脚分配表。PA0接DHT11数据线,PA1接MQ-2分压后的AO,PA2接光敏模块AO,PA9和PA10留作串口输出。
| 传感器 | 信号 | STM32引脚 | 连接说明 |
|---|---|---|---|
| DHT11 | DATA | PA0 | 数据线经4.7K电阻上拉到3.3V |
| DHT11 | VCC | 3.3V | 靠近引脚加0.1uF去耦电容 |
| MQ-2模块 | AO | PA1 | AO经10K电阻分压后接入,再经10K到地 |
| MQ-2模块 | VCC | 5V | 加热丝供电,注意大电流回路 |
| 光敏模块 | AO | PA2 | 3.3V供电时AO可直接接ADC引脚 |
| 光敏模块 | DO | 留空 | 数字输出,需要报警时可接普通GPIO |
| USART1 | TX | PA9 | 串口发送,接USB转TTL的RX |
| USART1 | RX | PA10 | 串口接收,调试时可不接 |
原理图放在嘉立创EDA里画比较顺手,元件库搜DHT11、MQ-2、光敏电阻都能找到现成封装。画完连线后用“设计规则检查”跑一遍,重点看电源网络是否接错、ADC输入引脚有没有加上拉或分压。硬件这块只要供电和分压不出错,后面驱动代码基本不用返工。
3. DHT11驱动:单总线时序拆解与HAL库最小代码
3.1 DHT11时序拆解:起始信号、响应信号和数据位
DHT11用的是单总线协议,一根数据线上由主机发起通信。完整过程分三段:主机发送起始信号、DHT11返回响应信号、DHT11连续输出40位数据。
主机先把数据线拉低,保持至少18ms,然后释放。这个低电平就是起始信号,告诉DHT11准备发送数据。释放后等待20到40us,DHT11会主动把总线拉低,保持80us左右作为响应信号,再释放并输出40位数据。数据位顺序是:8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。
每一位数据的判断逻辑是看高电平持续的时间:每位先有50us的低电平,然后如果高电平持续约26到28us,表示“0”;如果高电平持续约70us,表示“1”。所以读取代码的核心就是在低电平结束后延时一段时间,再去读引脚电平。延时点选在35us左右最安全,“0”的高电平已经结束,“1”的高电平还在持续,判别窗口最大。
3.2 用DWT计数器写一个精确的delay_us
HAL库自带的HAL_Delay只精确到毫秒级,DHT11位时序需要微秒级延时,不能直接用。常见做法是用SysTick或者DWT循环计数器实现delay_us。SysTick被HAL库用作系统心跳,动它需要小心;DWT是内核调试组件里的循环计数器,不占用SysTick,是更干净的选择。
#include "stm32f1xx.h" void delay_us(uint32_t us) { uint32_t ticks; uint32_t start; CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; ticks = us * (SystemCoreClock / 1000000); start = DWT->CYCCNT; while ((DWT->CYCCNT - start) < ticks); }这段代码先把DWT计数器清零并启动,然后根据主频算出延时对应的周期数。STM32F103C8T6的主频是72MHz,1us对应72个周期。用SystemCoreClock自动适配时钟频率,换到其他主频的芯片也通用。DWT的计数器和CPU同频,延时误差在单个周期级别,比空循环稳得多。注意使用DWT前要先把CoreDebug的TRCENA位置1,这是很多移植代码会漏掉的一步。
3.3 完整读取驱动与校验逻辑
DHT11的GPIO需要频繁切换输入输出模式。用HAL_GPIO_Init反复配置会拖慢切换速度,我习惯直接用寄存器操作GPIOA的CRL寄存器,改PA0的模式,这样切换只花几个时钟周期,不会干扰微秒级时序。
#define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 // 把PA0切成推挽输出,速度50MHz void DHT11_MODE_OUTPUT(void) { GPIOA->CRL &= ~(GPIO_CRL_MODE0_0 | GPIO_CRL_MODE0_1); GPIOA->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF0_1); GPIOA->CRL |= (GPIO_CRL_MODE0_0 | GPIO_CRL_MODE0_1); } // 把PA0切成浮空输入,外部4.7K上拉负责拉高 void DHT11_MODE_INPUT(void) { GPIOA->CRL &= ~(GPIO_CRL_MODE0_0 | GPIO_CRL_MODE0_1); GPIOA->CRL &= ~GPIO_CRL_CNF0_1; GPIOA->CRL |= GPIO_CRL_CNF0_0; }CRL寄存器控制PA0到PA7,这里只改PA0对应的低四位,其他引脚不受影响。MODE位是00表示输入,输入模式下CNF位取值01表示浮空输入,10表示上下拉输入。这里用浮空输入,因为外部已经有4.7K上拉电阻。
读取单bit的函数:
uint8_t DHT11_Read_Bit(void) { uint8_t bitval = 0; uint16_t timeout = 500; // 等待50us的低电平结束 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET) { delay_us(1); if (--timeout == 0) return 0xFF; } // 延时35us再采样,落在0/1判别的安全窗口 delay_us(35); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) { bitval = 1; } // 等待高电平剩余部分结束,为下一位做准备 timeout = 500; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) { delay_us(1); if (--timeout == 0) break; } return bitval; }最后是读取40位数据的完整函数:
uint8_t DHT11_ReadData(uint8_t *temp, uint8_t *humi) { uint8_t data[5] = {0}; uint16_t timeout; // 主机起始信号:拉低至少18ms DHT11_MODE_OUTPUT(); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); // 释放总线,让上拉电阻拉高,等待DHT11响应 HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); DHT11_MODE_INPUT(); delay_us(30); // 等待DHT11把总线拉低,发出响应信号 timeout = 500; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) { delay_us(1); if (--timeout == 0) return 0; // 无响应 } // 响应信号:低电平80us + 高电平80us timeout = 1000; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET) { delay_us(1); if (--timeout == 0) return 0; } timeout = 1000; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) { delay_us(1); if (--timeout == 0) return 0; } // 读取40位数据,按8位一组存入data数组 for (int i = 0; i < 40; i++) { uint8_t bit = DHT11_Read_Bit(); if (bit == 0xFF) return 0; data[i / 8] = (data[i / 8] << 1) | (bit & 0x01); } // 校验和:前4个字节相加的低8位等于第5字节 if ((uint8_t)(data[0] + data[1] + data[2] + data[3]) == data[4]) { *humi = data[0]; *temp = data[2]; return 1; } return 0; }这段代码里有几个细节值得解释。起始信号后先切输入再延时30us,是为了让上拉电阻把总线拉高,而不是靠输出引脚强行驱动。读取每一位时,等待低电平结束之后延时35us再采样,避开了0和1的高电平时序重叠区。所有等待循环都加了超时计数,DHT11损坏或接线错误时函数能及时返回,不会让主循环卡死。校验和计算时强制转换成uint8_t,因为四个8位数据相加可能产生进位,而校验和只保留低8位。
DHT11的读取间隔必须在1秒以上,读太频繁会得到上一次的缓存数据。主循环里每次读取后至少延时1.5秒再发起下一次。
4. MQ-2与光敏电阻的ADC采集:多通道配置、滤波与串口上报
4.1 ADC1扫描模式配置:PA1和PA2双通道采集
STM32F103C8T6的ADC1支持多个通道,但同一时间的常规转换序列只有一个。要同时采PA1和PA2,常见做法是用扫描模式:启动一次ADC转换,内部按Rank顺序依次转换两个通道。用HAL库配置如下:
ADC_HandleTypeDef hadc1; void MX_ADC1_Init(void) { hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; // 扫描多个通道 hadc1.Init.ContinuousConvMode = DISABLE; // 单次扫描,软件再次触发 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 2; // 扫描2个通道 HAL_ADC_Init(&hadc1); ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_1; // PA1 sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_2; // PA2 sConfig.Rank = ADC_REGULAR_RANK_2; sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); }读取两个通道值的函数:
void Read_ADC_TwoChannels(uint16_t *smoke_adc, uint16_t *light_adc) { HAL_ADC_Start(&hadc1); // 第1次轮询完成,得到Rank1通道PA1的值 if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { *smoke_adc = HAL_ADC_GetValue(&hadc1); } // 第2次轮询完成,得到Rank2通道PA2的值 if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { *light_adc = HAL_ADC_GetValue(&hadc1); } HAL_ADC_Stop(&hadc1); }ScanConvMode使能后,每次Start会按Rank顺序把序列里的通道都转换一遍。PollForConversion第一次返回对应Rank1,第二次对应Rank2。采样时间选的55.5个ADC时钟周期,对光敏和烟雾这类慢变信号足够了,能有效抑制引脚上的高频噪声。如果信号变化快,可以改短采样时间,但这两个传感器的响应速度都在秒级,没必要。
4.2 数据滤波与阈值判断:滑动平均怎么用才不延迟
MQ-2的模拟输出本身有噪声,光敏电阻在灯光下也会有轻微波动。直接拿原始ADC值去判断阈值,会出现临界点反复跳变的情况。我一般会做一层10点滑动平均滤波。
uint16_t AD_GetFiltered(uint16_t new_value) { static uint16_t buf[10]; static uint8_t index = 0; static uint8_t filled = 0; uint32_t sum = 0; uint8_t i; buf[index] = new_value; index = (index + 1) % 10; if (filled < 10) filled++; for (i = 0; i < filled; i++) { sum += buf[i]; } return (uint16_t)(sum / filled); }这段代码维护一个10元素的环形缓冲区,新数据覆盖最旧数据。前10次采样时缓冲区未满,平均值用已有数据计算;填满后固定取最近10次的均值。每次调用都重新累加求和,虽然不做增量优化,但每秒采样一次,CPU开销可以忽略。滤波窗口10次,加上MQ-2本身秒级的响应时间,不会让数据变得迟钝。
MQ-2的阈值判断不能照抄网上的固定值。每个传感器的基线电压都不同,跟老化程度、环境湿度和供应电压都有关系。合理的做法是:开机预热5分钟后,在干净空气里连续读20次取平均,作为基线值,之后用当前值和基线的差值判断烟雾是否超标。
static uint16_t smoke_baseline = 0; void Smoke_CalibrateBaseline(void) { uint32_t sum = 0; uint16_t raw; uint8_t i; // 连续采集20次,间隔100ms for (i = 0; i < 20; i++) { Read_ADC_TwoChannels(&raw, &raw); // 实际读取时只取smoke通道 sum += AD_GetFiltered(raw); HAL_Delay(100); } smoke_baseline = (uint16_t)(sum / 20); }校准函数要放在主循环触发烟雾报警之前调用。烟雾判定写成当前ADC值大于基线值加上一个偏移量。偏移量根据具体环境调整,我常用的初值是50到80。数值越小越敏感,误报率越高,需要实际测试后确定。
4.3 串口数据协议与printf重定向
调试阶段最简单的输出方式是串口打印。重定向C库的fputc到USART1,就能直接调用printf:
#include "stdio.h" int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100); return ch; }Keil环境下需要在工程选项里勾选MicroLIB,才能让printf使用这个小尺寸的C库实现。没有勾选MicroLIB,链接时会报fputc未定义或者printf不可用之类的错误。
主循环的数据打印格式我习惯用逗号分隔的键值对:
printf("TEMP:%d,HUMI:%d,SMOKE:%d,LIGHT:%d\r\n", temp, humi, smoke_adc, light_adc);这样每一行是一条完整记录,串口助手可以直接按行查看,也可以用串口绘图工具把四个维度画成曲线。\r\n换行不能省略,否则串口助手显示的是一长串粘在一起的数据。
完整的采集主循环示例:
while (1) { uint8_t temp = 0, humi = 0; uint16_t smoke_raw = 0, light_raw = 0; uint16_t smoke_val = 0, light_val = 0; if (DHT11_ReadData(&temp, &humi)) { Read_ADC_TwoChannels(&smoke_raw, &light_raw); smoke_val = AD_GetFiltered(smoke_raw); light_val = AD_GetFiltered(light_raw); printf("TEMP:%d,HUMI:%d,SMOKE:%d,LIGHT:%d\r\n", temp, humi, smoke_val, light_val); } else { printf("DHT11:READ ERROR\r\n"); } HAL_Delay(1500); }5. 避坑:DHT11、MQ-2和光敏电阻的5个典型故障
5.1 DHT11应答超时,最可能是上拉电阻和杜邦线
现象:代码一直进入DHT11_ReadData返回0的分支,串口反复打印DHT11:READ ERROR,用示波器看数据线上根本没有响应波形。
原因:第一,裸DHT11的数据引脚没接上拉电阻,总线释放后电平拉不高,等待响应的循环直接超时。第二,DHT11和STM32之间用了很长的杜邦线,超过20cm时寄生电容会拖慢电平跳变,DHT11的80us响应信号被严重削顶。第三,GPIO配置成了推挽输出没有切回输入模式,DHT11拉不动引脚。
解决:DHT11数据引脚接4.7K到10K上拉电阻;杜邦线尽量缩短,或者直接焊接;检查引脚模式切换代码,起始信号后必须切回输入。
5.2 MQ-2读数满量程,先检查分压网络
现象:串口打印出来的SMOKE值一直是4095,拿打火机气体靠近也不变,只有离很远时才稍微降下来一点。
原因:MQ-2模块的AO引脚直接连到了STM32的ADC引脚。AO输出范围是0到5V,STM32的ADC最大量程是3.3V,超过3.3V以后所有值都饱和在4095。这类模块的AO不是标准0到3.3V接口。
解决方案:在原理图上给AO增加分压网络,用两个10K电阻串联到地,中间抽头接ADC引脚。分压后AO的5V最大值变成2.5V,留出安全余量。改完硬件后重新扫描ADC值,干净空气下应该读数在300到800之间,而不是顶到4095。
5.3 MQ-2上电后数值漂移,等多久都不稳
现象:刚上电时MQ-2的ADC值从几十慢慢往上涨,过了几分钟还在飘,拿着一罐气体测试,数值变化和浓度对不上。
原因:MQ-2的加热丝需要时间把气敏层加热到工作温度,这个预热过程通常需要3到5分钟。预热阶段传感器表面温度还没稳定,电导率持续变化,输出自然漂移。另外,模块的供电电压如果来自USB口且电流不足,加热丝工作造成压降,也会让电压基准跟着飘。
解决:程序里加预热逻辑,上电后延迟5分钟再开始正常巡检,或者在校准时做一个开机自动校准。供电要选能稳定输出500mA以上的5V电源,不要用电脑USB口同时带一堆外设。如果加热时ADC值跟随电源电压波动,就是供电回路压降太大,需要加强电源滤波电容。
5.4 DHT11比室温高2到3℃,是物理布局的问题
现象:用家用温湿度计对比,DHT11读到的温度始终偏高2℃以上,湿度也偏低。
原因:DHT11紧贴着MQ-2模块或者LDO稳压器。MQ-2的加热丝工作时表面温度很高,LDO从5V降到3.3V也会有可观功耗,这两者都是板上的热源。DHT11感温元件直接暴露在这些热源旁边,读出来的自然不是环境温度。
解决:改PCB布局时把DHT11放在板边,远离MQ-2和稳压器。用DHT11裸传感器加短引线伸出板子,或者用杜邦线把它引出到空气流通的位置。湿度偏低通常也是温度偏高引起的相对湿度计算偏差,热源问题解决后湿度读数会一起恢复正常。
5.5 串口乱码,查晶振和波特率
现象:串口助手打开后显示一堆乱码,波特率调到9600或115200都无效,偶尔能出几个数字但立刻又乱。
原因:最常见的是开发板上的晶振频率和工程配置不一致。很多STM32F103C8T6最小系统板上用的是8MHz晶振,但有些板子用了12MHz或贴了HSI内部时钟。CubeMX工程里如果HSE_VALUE写错了,系统时钟就偏了,串口波特率跟着偏,收发双方对不上。其次是串口助手的波特率、数据位、停止位和代码配置不一致。
解决:核对板子上晶振的实际频率,在CubeMX的Clock Configuration里确认HSE输入值;标准配置是8位数据、1位停止位、无校验。USB转TTL线质量不好也会造成乱码,换一根CP2102或者CH340芯片的线试一下。实在不行用示波器测TX引脚的波特率,串口助手收发双方向合拍是最基本的前提。
6. 调试与验证:示波器看时序、串口看曲线
6.1 用示波器验证DHT11时序波形
DHT11的驱动代码写得对不对,串口能读出数据不代表时序完全正确。我习惯在调试阶段用示波器抓PA0的波形,确认每一位的高低电平宽度符合规格。示波器探头接PA0,地线接GND,触发电平设到1.5V左右,时基调到100us一格。触发一次就能看到完整的40位数据帧。
重点关注三个地方:起始信号的低电平宽度是否在18ms以上;响应信号是不是先低80us再高80us;每个数据位的高电平宽度是不是明显分成27us和70us两档。如果波形里高电平宽度混乱,说明延时函数不准确或者GPIO切换太慢。示波器是DHT11调试最直接的工具,比反复改代码猜原因高效得多。
6.2 长时间记录数据,用对比法确认传感器漂移
代码烧录后不要只跑几秒就下结论。我会用串口助手把数据记录到文件,连续跑4到6个小时,观察温度和湿度曲线是否符合当天室内环境的变化规律。DHT11的温湿度曲线应该是平滑缓慢变化的,如果出现毛刺和跳变,说明信号质量有问题。MQ-2在干净空气下的ADC值应该保持在一个窄带内波动,波动超过正负50就要检查供电和滤波。
传感器标定用对比法最简单:把DHT11和家用温湿度计放在同一位置,读取两组数据对比。环境稳定后,两者偏差应该在DHT11的精度范围内。MQ-2的烟雾检测用点燃的蚊香靠近测试,观察ADC值是否快速上升,移开后是否回落。光敏电阻用手电筒照射和遮挡两个状态测试,确认ADC值的翻转范围和响应灵敏度。这三个测试做完,系统才算验收合格。
如果后续要把这个方案扩展成物联网网关,把串口数据转发到WiFi模块时要注意协议设计,当前这个逗号分隔的文本格式在嵌入式端解析和云端解析都比较方便,可以直接复用。我自己做环境检测项目的一个习惯是,任何传感器数据都要先跑一天看趋势,再谈阈值和报警,数据稳定是前提。希望这些细节能帮你把基于STM32的环境检测系统一次跑稳。
本文还有配套的精品资源,点击获取