ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32环境监测实战:20元开发板搭建温湿度烟雾报警系统

STM32环境监测实战:20元开发板搭建温湿度烟雾报警系统 简介面向物联网与嵌入式初学者的家庭环境监测系统完整工程基于STM32F103C8T6结合DHT11温湿度传感器、MQ-2烟雾传感器与0.9寸OLED屏实现数据采集、实时显示与阈值报警。适合毕业设计、课程实训或智能家居入门覆盖GPIO、ADC、定时器、I2C等典型外设应用。压缩包共231个文件约5.32MB以C源文件、H头文件、Keil工程文件及启动汇编为主此外还包含编译生成的hex、map、crf等辅助文件便于直接打开工程烧录验证。资源中还保留了J-Link调试日志其中可看到USB连接失败的报错记录对排查下载器连接问题有一定参考价值。目前已有3758人学习下载。借助这套资料读者不仅能获得可运行的温湿度与烟雾监测代码还能学习外设驱动封装、阈值判断与报警逻辑理解从传感器采样到显示输出的完整开发流程。1. 一块 20 元开发板搭家庭环境监测的完整链路把 STM32F103C8T6、DHT11、0.9 寸 OLED 和 MQ-2 拼在一起很多人第一反应是“这不过是个毕业设计”。但真正动手后你会发现这块 20 元出头的国产 ARM Cortex-M3 最小系统板上最考验人的反而不是写逻辑而是怎么在 3.3V 供电、有限 GPIO 和不断抖动的传感器时序之间找到一个稳定平衡点。DHT11 要 18ms 以上的总线占用才能读到温湿度MQ-2 的模拟电压会随预热时间缓慢漂移蜂鸣器一响还会把板载稳压器的纹波拉高——这些问题堆在一起恰好构成了一条从配环境到调参、从看波形到做联动的完整嵌入式训练路径。这篇文章按“硬件连接 → 温湿度采集 → OLED 显示 → 烟雾检测与告警联动 → 参数校准与验证”的顺序推进每一步都能直接抄板、烧录、看现象。目标读者是正在做物联网课设或入门比赛的人也适合已经写过点 Arduino、想切换到 STM32 原生开发、顺便搞清 HAL 库底层配置的工程师。文章里所有代码均在 STM32F103C8T6 最小系统板上以 HAL 库方式验证时钟配置为 72MHz 主频、8MHz 外部晶振GPIO 多数为推挽输出或浮空输入具体参数说明跟着代码走。2. 硬件连接别急着接线先理清供电和引脚分配的边界2.1 模块供电的 3.3V 与 5V 之争DHT11 和 MQ-2 的常见模块上通常都带有板载上拉电阻和电源指示灯但它们的推荐工作电压并不一致。DHT11 传感器本身工作电压是 3.3V 至 5.5V而 MQ-2 模块上的比较器 LM393 需要 5V 才能保证比较阈值稳定0.9 寸 OLED 的 SSD1306 驱动芯片则明确支持 3.3V接 5V 反而有损坏风险。初次做这个项目的人最容易犯的错是把所有模块的 VCC 全部接到 STM32F103C8T6 板载的 3.3V 输出上结果 MQ-2 加热丝电流不足传感器输出始终是低电平。正确的接法是板子通过 USB 或 ST-Link 供电时5V 和 3.3V 两个引脚都已经有稳定输出DHT11 和 OLED 接 3.3VMQ-2 模块接 5V。STM32F103C8T6 的 GPIO 全部兼容 5V 输入数据手册标注为 FT 引脚所以 MQ-2 模块的数字输出 DO 可以直接连到 PA1 上不需要电平转换。蜂鸣器模块如果是低电平触发的有源蜂鸣器同样接 5V 供电控制脚由 PA2 输出。// 引脚分配总表以标准最小系统板为准 // PA0 - DHT11 DATA // PA1 - MQ-2 DO数字输出 // PA2 - 蜂鸣器控制脚低电平触发 // PB8 - OLED SCL // PB9 - OLED SDA // GND - 所有模块共地引脚说明PA0 配置为开漏输出加外部上拉是为了模拟 DHT11 需要的单总线时序PA1 配置为浮空输入即可因为 MQ-2 模块上的 LM393 已经输出明确的 0V 或 3.3V 逻辑电平PB8 和 PB9 是 STM32F103C8T6 的 I2C1 引脚硬件上直接复用。蜂鸣器接 5V 是因为有源蜂鸣器内部有振荡电路5V 驱动声压更足而 STM32 的推挽输出拉到 3.3V 也能触发但声音会偏小。2.2 蜂鸣器控制脚为什么不能直接推挽驱动蜂鸣器模块上虽然自带了一个 S8550 三极管做驱动但很多卖家为了省成本模块上并没有加续流二极管。当蜂鸣器是感性负载时断电瞬间会产生反电动势如果直接让 STM32 的 PA2 推挽输出去关断这个反向尖峰可能会灌进 GPIO长期运行容易导致引脚内部钳位二极管老化。常见做法是让 STM32 的 PA2 只控制一个外部 NPN 三极管如 S8050的基极三极管集电极接蜂鸣器负极蜂鸣器正极接 5V同时在蜂鸣器两端反向并联一个 1N4148 二极管。这样 STM32 的 GPIO 只提供毫安级的基极电流关断时反电动势被二极管吸收MCU 本身得到保护。如果手头没有外部三极管也可以直接用模块上的低电平触发引脚但至少要在 PA2 上串联一个 1kΩ 电阻限流。3. 用 HAL 库驱动 DHT11 温湿度传感器时序逻辑比代码更关键3.1 DHT11 的 40 位数据帧究竟怎么读DHT11 单总线协议是理解整个系统的分水岭它没有时钟线完全靠主机拉低总线发起一次握手、然后从机连续输出高低电平来表示 0 和 1。一次完整读取需要这几个步骤主机拉低总线至少 18ms然后释放并拉高 20 至 40μs接下来从机响应一个 80μs 的低电平和一个 80μs 的高电平之后开始输出 40 位数据每位数据都是 50μs 的低电平加上 26 至 28μs代表 0或 70μs代表 1的高电平。这里最容易踩的坑是延时精度。HAL 库的 HAL_Delay() 基于 SysTick 中断实现最小分辨率是 1ms完全无法用于微秒级时序。因此在读取 DHT11 时需要使用 DWT 或 SysTick 的 CYCCNT 寄存器来做微秒延时。// 使用 DWT 实现微秒级延时适用于 DHT11 时序 static void DWT_Delay_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static void DWT_Delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); } // 读取单个位高电平持续时间长则为 1短则为 0 uint8_t DHT11_ReadBit(void) { while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET); DWT_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) { while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET); return 1; } else { return 0; } }延时说明DWT_Delay_Init 使能了内核调试单元的周期计数器DWT-CYCCNT 在每个时钟周期加一因此 ticks 是用 SystemCoreClock72MHz换算出的周期数DHT11_ReadBit 里的 40μs 延时是为了避开位起始的低电平窗口再判断此刻总线电平是 0 还是 1逻辑上等效于采样高电平宽度。这种方法比 for 循环空转精确得多而且可移植到任何 Cortex-M3/M4 内核。3.2 读取结果的数据校验DHT11 返回的 40 位数据中前 16 位是湿度整数和小数部分中间 16 位是温度整数和小数部分最后 8 位是校验和。DHT11 的温度小数位默认读出 0湿度小数位可能有值因此拼接时要把整数和小数分开处理。typedef struct { uint8_t humi_int; uint8_t humi_dec; uint8_t temp_int; uint8_t temp_dec; uint8_t checksum; } DHT11_Data; uint8_t DHT11_Read(DHT11_Data *data) { uint8_t buffer[5] {0}; HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET); DWT_Delay_us(20000); // 主机拉低至少 18ms HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET); DWT_Delay_us(30); // 等待从机响应低电平 if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) return 1; while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET); for (int i 0; i 40; i) { buffer[i / 8] 1; if (DHT11_ReadBit()) buffer[i / 8] | 1; } if ((uint8_t)(buffer[0] buffer[1] buffer[2] buffer[3]) ! buffer[4]) { return 2; // 校验失败 } >// I2C 地址扫描函数用于确认 OLED 实际地址 void OLED_ScanAddress(void) { for (uint8_t addr 0x30; addr 0x80; addr) { if (HAL_I2C_IsDeviceReady(hi2c1, addr, 1, 100) HAL_OK) { printf(Found device at 0x%02X\r\n, addr); } } }这块屏幕的刷新思路是基于 1KB 显存128×32 像素每像素 1 位往显存写数据时先按页8 像素一行组织写完整个显存后通过 I2C 一次性送入屏幕。显示字符串时要先做 ASCII 字模取模取模方式为纵向取模、字节倒序因为 SSD1306 的显存排列是从左上角开始、纵向跨越 8 页。4.2 显示温湿度数值的格式化输出在嵌入式场景中直接调用 sprintf 会把 stdio 库的完整格式化逻辑拉进来代码体积增加不少。0.9 寸 OLED 只有 32 像素高显示两行文本比较合适第一行显示湿度第二行显示温度。为了速度可以自己写一个整数转字符串的简单函数或者用有限精度的 snprintf。char line1[16]; char line2[16]; // 将温湿度按一位小数格式化为字符串 snprintf(line1, sizeof(line1), Humi:%d.%d%%, dht.humi_int, dht.humi_dec); snprintf(line2, sizeof(line2), Temp:%d.%d C, dht.temp_int, dht.temp_dec); OLED_Clear(); OLED_ShowString(0, 0, line1, 8); OLED_ShowString(0, 16, line2, 8); OLED_Refresh();显示注意事项每次刷新前先调 OLED_Clear 清空显存否则上次残留的“Humi:”前缀会覆盖在新字符串前造成叠字OLED_ShowString 的第三个参数是字模在闪存中的起始地址显示完要 OLED_Refresh 才会真正把显存通过 I2C 刷到屏上。由于 I2C 速率设为 400kHz整屏刷新约需 30ms肉眼感觉不到闪烁但不要在 OLED_Refresh 期间去读 DHT11否则 I2C 时序可能被拉长传感器读数出错。5. 烟雾传感器 MQ-2 读取与蜂鸣器联动告警5.1 MQ-2 的数字输出与模拟输出的选择MQ-2 模块上其实有两个输出引脚AO 是模拟电压输出DO 是数字电平输出。DO 引脚旁边通常有一个蓝色电位器用来调节比较器阈值——顺时针旋转会提高触发阈值逆时针调低也就是说模块上电后LED 指示灯亮起时 DO 输出低电平表示当前烟雾浓度已经超过预设阈值。项目中标题写的是“烟雾传感器”但很多手头只有模块的人只接了 DO这样系统只能告诉你“超标了”还是“没超标”看不到浓度变化的趋势曲线。若 MCU 还有空闲 ADC 通道把 AO 接到 PA3配合 STM32 的 12 位 ADC 连续采样可以在 OLED 上显示一个 0 至 4095 的浓度百分比条同时用 DO 作为硬阈值快速触发蜂鸣器。这样双通道并用的做法在真实环境监测里更常见。// 使用 ADC1 通道 4 读取 MQ-2 的 AO 引脚 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); uint16_t smoke_adc HAL_ADC_GetValue(hadc1); // 映射到 0-100 浓度百分比 uint8_t smoke_percent (smoke_adc * 100) / 4095; // 判断数字输出引脚低电平表示烟雾超标 uint8_t smoke_alarm (HAL_GPIO_ReadPin(MQ2_GPIO_Port, MQ2_Pin) GPIO_PIN_RESET); if (smoke_alarm) { HAL_GPIO_WritePin(BUZZER_Port, BUZZER_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(BUZZER_Port, BUZZER_Pin, GPIO_PIN_SET); }ADC 参数说明STM32F103C8T6 的 ADC 是 12 位分辨率转换结果范围 0 至 4095对应 0V 至 3.3V注意 AO 接 5V 供电时输出高电平可达 4V 左右已经超过 3.3V 量程建议把 MQ-2 供电改接 3.3V并接受灵敏度略微下降的代价或者用电阻分压后再进 PA3。PollForConversion 设置 100ms 超时如果转换未完成则返回超时错误码实际项目中可把这段放在定时器中断里做周期性采样。5.2 蜂鸣器节奏与误报抑制蜂鸣器联动不能只做布尔判断否则人在厨房炒个菜油烟一飘就响个不停系统会被当成狼来了。常见做法是加一个“连续超标 N 次才告警”的计数窗口同时让蜂鸣器以 1Hz 频率间歇鸣叫而不是长鸣。uint8_t alarm_count 0; // 主循环周期约 200ms 执行一次 void Smoke_Task(void) { if (smoke_alarm) { if (alarm_count 5) alarm_count; if (alarm_count 5) { // 500ms 响500ms 停 if (HAL_GetTick() % 1000 500) { HAL_GPIO_WritePin(BUZZER_Port, BUZZER_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(BUZZER_Port, BUZZER_Pin, GPIO_PIN_SET); } } } else { alarm_count 0; HAL_GPIO_WritePin(BUZZER_Port, BUZZER_Pin, GPIO_PIN_SET); } }逻辑解释alarm_count 是连续检测到烟雾超过阈值的次数达到 5 次约 1 秒才触发告警避免瞬时干扰。HAL_GetTick() 返回系统上电以来的毫秒数用取模 1000 判断当前处于鸣叫窗口还是静音窗口。如果直接让蜂鸣器长鸣不仅刺耳还会让用户因为烦躁而干脆关掉电源。想要不同告警等级可以改成火警两短一长、烟雾一短一长的节奏。6. 调参与验证把传感器数据校准到可信再谈智能化6.1 DHT11 与标准温湿度计对比的偏移修正DHT11 的精度本来就只有 ±2℃ 和 ±5% RH在小空间里连续工作半小时后PCB 上的走线发热可能导致测到的温度比环境实际高 1 至 2℃。常见的校准方法是把系统放在通风处旁边放一个标准温湿度计等待 10 分钟后记录两组数据计算固定偏移量然后在代码里做加法修正。// 温度偏移修正示例实测标准温度为 25.0℃DHT11 读数为 26.3℃ // 则 int8_t temp_offset 26 - 25 1后续显示时减去 1 int8_t temp_offset 1; int8_t display_temp dht.temp_int - temp_offset; // 如果偏移量为负同样可以直接相加如果 DHT11 读数波动剧烈不要先怀疑算法——优先检查 PA0 到传感器之间的杜邦线长度线长超过 20cm 时总线电容会明显拉长电平跳变沿导致 0/1 误判此时把上拉电阻从 10kΩ 换成 4.7kΩ 能直接改善波形。另外传感器探头不要贴近 STM32 芯片的背面部那里是发热区。6.2 MQ-2 预热漂移与阈值自校准MQ-2 是半导体气敏传感器内部加热丝需要持续通电才能保持敏感层温度初次上电的 1 至 3 分钟里输出电压会持续上升最后趋于稳定。很多人在上电后马上测试报警功能发现一用打火机靠近就报拿走之后却要等很久才恢复原因不是传感器坏而是加热丝尚未达到工作温度区间敏感层表面吸附的气体分子还在慢慢脱附。一个可靠的自校准流程是MCU 上电后先等 60 秒然后连续读取 10 次 MQ-2 的数字输出和模拟值作为基准。假设环境是洁净空气将这 10 次的平均值作为阈值基准线之后任何时刻读到的值超过基准线 1.2 倍就判定为烟雾事件。这样做还有个额外好处就是同一块板子换到不同海拔或不同环境都能自动适应本底浓度而不是依赖模块上那颗电位器的出厂位置。// 上电自校准取前 10 次读数的平均值作为基准 uint16_t baseline 0; for (uint8_t i 0; i 10; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); baseline HAL_ADC_GetValue(hadc1); HAL_Delay(100); } baseline / 10; // 动态告警阈值 uint16_t alarm_threshold baseline * 1.2;参数怎么调alarm_threshold 的 1.2 倍系数适合家庭厨房环境若放在灰尘较大的木材加工间建议提高到 1.5若做的是酒精、油漆这类挥发性气体检测MQ-2 本身对乙醇、甲烷都有响应可以把系数放低到 1.1同时增加 6.2 节提到的“连续 5 次超标”确认窗口避免检测到香水、酒精湿巾这类瞬时挥发物时误响。6.3 整机验证步骤所有代码都烧好之后的验证顺序建议固定下来第一步用 ST-Link 连接板子打开串口助手波特率 115200确认串口能周期性打印温湿度和烟雾 ADC 原始值这一步能定位传感器接线和时序问题第二步对着 DHT11 呵一口气观察串口输出湿度是否上升、温度是否微升响应时间通常在 2 秒左右第三步用打火机不点火、靠近 MQ-2 放气观察烟雾浓度值是否有台阶式上升蜂鸣器是否在约 1 秒后开始间歇鸣叫第四步用棉签蘸少量酒精靠近 MQ-2由于 MQ-2 对酒精同样敏感应看到与烟雾类似的反应但停止靠近后报警应在 5 秒内解除。若 DHT11 长时间返回校验错误用示波器挂 PA0 看波形正常数据位的高电平应分别是 26μs 和 70μs 两簇如果整个波形有重影大概率是 DWT_Delay_us 的时钟频率算错检查 SystemCoreClock 变量是否为 72MHz。OLED 屏不亮时先用 I2C 扫描程序确认地址如果地址是 0x3C 或 0x3D7 位模式说明模块使用了不同 I2C 地址映射需要把驱动里的地址改为对应值。系统长时间运行后 OLED 出现花屏多半是 I2C 总线被 DHT11 读取时的关闭外部中断抢占过长可以在 OLED_Refresh 前临时关闭中断或改用 DMA 方式发送显存数据。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表