STM32 HAL库实现温湿度监测报警系统
2026/9/16 2:02:16 网站建设 项目流程

简介:本资源是一套基于STM32单片机的温湿度监测与报警系统完整工程代码,面向嵌入式初学者及STM32实践开发者,解决环境参数采集、本地可视化、越限声光提醒与串口数据上传等典型物联网感知层开发问题。压缩包含272个文件,总计8.4MB,其中C源文件(36个)与头文件(36个)构成主程序逻辑,OLED.c等外设驱动模块清晰分离;.o、.d、.axf等编译中间文件与Keil工程配置文件(uvprojx、uvoptx、sct、bat等)齐全,支持开箱即用调试;另有大量.bak、.zbak备份文件,体现迭代开发过程。已有109人学习下载。读者可直接获取完整可运行工程:涵盖DHT11时序读取、OLED图形化显示、蜂鸣器阈值报警、USART串口协议封装及调试助手通信验证等核心功能模块,代码结构规范、注释充分,适合作为STM32传感器项目入门范例与二次开发基础框架。

1. 这不是“跑个例程”——STM32驱动DHT11+OLED+蜂鸣器+串口上报,是一套闭环感知报警系统

你手头有一块STM32F103C8T6最小系统板,接了DHT11温湿度传感器、0.96寸I²C OLED(SSD1306)、无源蜂鸣器和USB转串口芯片(CH340),目标不是让屏幕亮一下、蜂鸣器响一声,而是构建一个可实时监控、阈值触发、本地声光提示、同步串口上报原始数据的嵌入式感知节点。它不依赖上位机逻辑,所有判断(比如温度>35℃且湿度<30%持续5秒)都在单片机内完成;它输出的串口数据不是乱码,而是带帧头、校验、换行符的标准ASCII格式,能被SSCOM、XCOM、SecureCRT等任意串口调试助手直接解析并存为CSV;它用HAL库而非标准库,但规避了HAL_Delay阻塞式延时对DHT11时序的破坏。这套方案适用于毕业设计、环境监测模块、智能鱼缸控制器、实验室温控终端等真实场景,新手能照着接线烧录即用,老手能快速定位DHT11读取失败是GPIO初始化顺序问题、OLED闪屏是I²C时钟拉伸未处理、蜂鸣器无声是TIM输出比较极性设反——这些都不是理论问题,是每天在Keil里debug时真实踩过的坑。


2. 从硬件连接到HAL外设配置:确保DHT11能读、OLED能显、蜂鸣器能响、串口能发

2.1 硬件引脚分配与电气细节(以STM32F103C8T6为例)

DHT11、OLED、蜂鸣器、串口四者共用同一块开发板,引脚冲突是第一道关卡。必须明确每个外设的物理约束:

  • DHT11:单总线协议,仅需1个GPIO(如PA0),但该引脚必须支持开漏输出+上拉(内部上拉无效,必须外接10kΩ上拉电阻到3.3V)。DHT11供电严格要求3.3V,接5V会损坏。
  • OLED(I²C接口):SCL接PB6(I²C1_SCL),SDA接PB7(I²C1_SDA),VCC接3.3V,GND接地,RES引脚接PB8(软复位控制),DC引脚接PB9(数据/命令选择)。注意:0.96寸SSD1306模块默认I²C地址为0x3C,若焊接了A0跳线则为0x3D。
  • 无源蜂鸣器:一端接GND,另一端接TIM2_CH1(PA0)或TIM3_CH1(PA6)——必须用PWM驱动,不可直接GPIO推挽输出(否则只有“咔”声)。需串联100Ω限流电阻。
  • 串口(USART1):TX接PA9,RX接PA10,通过CH340转换后接入PC。波特率固定为115200,8N1,无硬件流控。

提示:DHT11的PA0与蜂鸣器的PA0冲突!解决方案是——蜂鸣器改用PA6(TIM3_CH1),DHT11独占PA0。这是实际布线中最常被忽略的引脚资源冲突,Keil编译不报错,但运行时DHT11永远返回0xFF。

2.2 CubeMX关键配置项(HAL库工程生成)

打开STM32CubeMX,选择STM32F103C8T6,按以下顺序配置,顺序错误将导致DHT11初始化失败

  1. System Core → SYS → Debug → Serial Wire(启用SWD下载)
  2. System Core → RCC → High Speed Clock (HSE) → Crystal/Ceramic Resonator(外部8MHz晶振,DHT11时序依赖精准定时)
  3. Connectivity → USART1 → Mode → Asynchronous,设置Baud Rate = 115200,Word Length = 8 Bits,Parity = None,Stop Bits = 1
    开启Global Interrupt(NVIC Settings中勾选USART1 global interrupt)
  4. Connectivity → I2C1 → Mode → Standard (100kHz),Clock Source = APB1,Prescaler = 1 →勾选"Enable DMA"(非必需但提升OLED刷屏效率)
  5. Timers → TIM3 → Channel 1 → PWM Generation CH1,Counter Period = 999(对应1kHz载波),Prescaler = 71(系统时钟72MHz ÷ (71+1) = 1MHz计数频率),Clock Division = 0,Channel 1 Polarity =Active High(无源蜂鸣器需高电平驱动发声)
  6. GPIO → PA0 → GPIO_Output → Pull-up → No pull-up(DHT11数据线,切勿设为Output Push-Pull!必须设为Open-Drain + External Pull-up
  7. GPIO → PB6/PB7/PB8/PB9 → I2C1_SCL/SDA/RES/DC → Alternate Function Open-Drain(OLED I²C引脚必须开漏)

生成代码前,在Project Manager中勾选Generate peripheral initialization as a pair of '.c/.h' files per peripheral,避免HAL_I2C_MspInit被覆盖。

2.3 DHT11 HAL驱动层关键实现(非阻塞式读取)

DHT11的80μs低电平启动+80μs高电平响应时序,HAL_Delay(1)最小精度为1ms,无法满足。必须用SysTick微秒级延时+GPIO寄存器直写

// dht11.c #include "dht11.h" #include "main.h" #define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 static uint8_t dht11_data[5] = {0}; // 存储4字节数据+1字节校验 uint8_t DHT11_Read_Data(void) { uint8_t i, j; uint32_t timeout = 0; // 1. 主机拉低至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); for(i=0; i<18000; i++) __NOP(); // 粗略延时18ms(72MHz下约1 NOP = 13.9ns) // 2. 拉高80us HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 1000); // 3. 等待DHT11响应(80us低+80us高) timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && timeout++ < 1000); if(timeout >= 1000) return 1; // 响应超时 timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 1000); if(timeout >= 1000) return 2; // 响应失败 // 4. 读取40位数据(每位50us低+27/70us高表示0/1) for(j=0; j<40; j++) { timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && timeout++ < 1000); // 等低电平结束 if(timeout >= 1000) return 3; timeout = 0; uint32_t high_start = HAL_GetTick(); while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 1000); uint32_t pulse_width = HAL_GetTick() - high_start; // 实际用DWT_CYCCNT更准,此处简化 if(pulse_width > 30) dht11_data[j/8] |= (1 << (7 - j%8)); // 高电平>30us判为1 } // 5. 校验 if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] != dht11_data[4]) return 4; return 0; // 成功 }

注意:HAL_GetTick()精度为1ms,无法精确测50us脉宽。生产环境必须启用DWT(Data Watchpoint and Trace)模块获取CPU cycle计数。本例为降低入门门槛暂用HAL_GetTick(),但需在main.c中添加:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

2.4 OLED SSD1306 I²C驱动精简版(适配HAL)

OLED初始化易失败,主因是I²C时钟拉伸未处理或RESET时序不足。以下为关键初始化序列:

// oled.c(基于SSD1306,128x64分辨率) #include "oled.h" #include "main.h" #define OLED_CMD 0x00 #define OLED_DATA 0x40 void OLED_WR_Byte(uint8_t dat, uint8_t mode) { uint8_t buf[2]; buf[0] = mode; buf[1] = dat; HAL_I2C_Master_Transmit(&hi2c1, 0x3C<<1, buf, 2, 100); // 地址左移1位 } void OLED_Init(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); // RES拉低 HAL_Delay(10); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // RES拉高 HAL_Delay(10); OLED_WR_Byte(0xAE, OLED_CMD); // 关闭显示 OLED_WR_Byte(0xD5, OLED_CMD); OLED_WR_Byte(0x80, OLED_CMD); // 设置时钟分频 OLED_WR_Byte(0xA8, OLED_CMD); OLED_WR_Byte(0x3F, OLED_CMD); // 设置MUX比率 OLED_WR_Byte(0xD3, OLED_CMD); OLED_WR_Byte(0x00, OLED_CMD); // 设置显示偏移 OLED_WR_Byte(0x40, OLED_CMD); // 设置显示起始行 OLED_WR_Byte(0x8D, OLED_CMD); OLED_WR_Byte(0x14, OLED_CMD); // 启用充电泵 OLED_WR_Byte(0xAF, OLED_CMD); // 开启显示 }

提示:HAL_I2C_Master_Transmit超时设为100ms而非默认10ms,避免I²C总线忙时初始化失败。若OLED全黑,优先检查PB8(RES)是否接线、PB9(DC)是否在发送命令前正确切换。


3. 数据融合与报警逻辑:温湿度阈值判断、蜂鸣器PWM控制、串口结构化输出

3.1 温湿度解析与报警条件定义

DHT11返回的5字节中,dht11_data[0]为湿度整数部分,dht11_data[1]为湿度小数部分(恒为0),dht11_data[2]为温度整数部分,dht11_data[3]为温度小数部分(恒为0)。因此实际值为整数:

uint8_t humidity = dht11_data[0]; // 20~90% RH uint8_t temperature = dht11_data[2]; // 0~50℃

报警逻辑不能简单“超限即响”,需防误触发。采用滑动窗口+持续时间判定

#define ALARM_WINDOW_SIZE 5 // 连续5次采样均超限才报警 #define ALARM_DURATION_MS 5000 // 报警持续5秒 typedef struct { uint8_t temp_history[ALARM_WINDOW_SIZE]; uint8_t humi_history[ALARM_WINDOW_SIZE]; uint8_t window_idx; uint32_t alarm_start_ms; uint8_t is_alarming; } AlarmState_t; AlarmState_t alarm_state = {0}; void Check_Alarm_Condition(uint8_t temp, uint8_t humi) { // 更新滑动窗口 alarm_state.temp_history[alarm_state.window_idx] = temp; alarm_state.humi_history[alarm_state.window_idx] = humi; alarm_state.window_idx = (alarm_state.window_idx + 1) % ALARM_WINDOW_SIZE; // 检查是否连续5次超限:温度>35℃ OR 湿度<30% uint8_t temp_exceed = 1, humi_low = 1; for(uint8_t i=0; i<ALARM_WINDOW_SIZE; i++) { if(alarm_state.temp_history[i] <= 35) temp_exceed = 0; if(alarm_state.humi_history[i] >= 30) humi_low = 0; } if((temp_exceed || humi_low) && !alarm_state.is_alarming) { // 触发报警 HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); alarm_state.is_alarming = 1; alarm_state.alarm_start_ms = HAL_GetTick(); } // 报警超时关闭 if(alarm_state.is_alarming && (HAL_GetTick() - alarm_state.alarm_start_ms) > ALARM_DURATION_MS) { HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1); alarm_state.is_alarming = 0; } }

注意:HAL_TIM_PWM_Start必须在htim3已初始化且htim3.Instance->CR1 & TIM_CR1_CEN为1的前提下调用。若蜂鸣器无声,用示波器测PA6是否有1kHz方波——没有则检查TIM3时钟使能(__HAL_RCC_TIM3_CLK_ENABLE())是否遗漏。

3.2 OLED动态刷新与数据显示格式

OLED显示需兼顾可读性与刷新效率。每帧只更新变化区域,避免全屏重绘:

// oled.c 中添加 char disp_buf[16]; void OLED_Show_Temp_Humi(uint8_t temp, uint8_t humi, uint8_t is_alarm) { // 第1行:标题 OLED_ShowString(0,0,"STM32-DHT11",12); // 第2行:温度(带告警图标) sprintf(disp_buf, "Temp:%d C", temp); OLED_ShowString(0,16,disp_buf,12); if(is_alarm) OLED_ShowChar(88,16,'!',12); // 在温度后加!号 // 第3行:湿度 sprintf(disp_buf, "Humi:%d %%", humi); OLED_ShowString(0,32,disp_buf,12); // 第4行:状态 OLED_ShowString(0,48, is_alarm ? "ALARMING!" : "Normal",12); }

OLED_ShowString函数需支持中文字模(本例用ASCII),若需中文需预加载字库并修改坐标计算逻辑。

3.3 串口发送结构化数据帧(SSCOM/XCOM可直接解析)

串口输出不是printf("Temp=%d\n",temp),而是带帧头、长度、校验的二进制帧,同时兼容ASCII调试:

// main.c 中定义帧结构 #pragma pack(1) typedef struct { uint8_t head; // 0xAA uint8_t len; // 数据长度(4字节:temp+humi+alarm_flag+checksum) uint8_t temp; uint8_t humi; uint8_t alarm; uint8_t checksum; // head+len+temp+humi+alarm 之和 } SensorFrame_t; #pragma pack() SensorFrame_t frame = {0}; void Send_Sensor_Frame(uint8_t temp, uint8_t humi, uint8_t alarm) { frame.head = 0xAA; frame.len = 4; frame.temp = temp; frame.humi = humi; frame.alarm = alarm; frame.checksum = frame.head + frame.len + frame.temp + frame.humi + frame.alarm; HAL_UART_Transmit(&huart1, (uint8_t*)&frame, sizeof(frame), 100); // 同时发送ASCII格式供调试(SSCOM可直接看) char ascii_buf[64]; sprintf(ascii_buf, "T:%d,H:%d,A:%d\r\n", temp, humi, alarm); HAL_UART_Transmit(&huart1, (uint8_t*)ascii_buf, strlen(ascii_buf), 100); }

提示:HAL_UART_Transmit超时设为100ms防止死锁。若SSCOM收不到数据,先用万用表测PA9电压——空闲时应为3.3V,发送时有电平翻转;再检查PC端串口助手是否选对COM口、波特率、数据位。


4. Keil工程编译与调试技巧:解决DHT11读取失败、OLED花屏、蜂鸣器无声三大高频问题

4.1 DHT11读取失败的逐层排查表

现象可能原因验证方法解决方案
DHT11_Read_Data()始终返回1(启动超时)PA0未外接10kΩ上拉电阻用万用表测PA0对地电阻,应≈10kΩ补焊10kΩ电阻至3.3V
返回2(响应超时)DHT11供电非3.3V或虚焊测DHT11 VDD引脚电压改用LDO稳压3.3V,检查嘉立创原理图中DHT11电源路径
返回3(数据位超时)GPIO模式设为Push-Pull而非Open-Drain查CubeMX生成的MX_GPIO_Init()中PA0配置在CubeMX中将PA0设为"Open-Drain",重新生成
返回4(校验失败)DHT11数据线受干扰或线长>20cm示波器抓取PA0波形,观察是否畸变缩短线缆,加磁环滤波,避开电机/继电器走线

4.2 OLED花屏/不显示的硬件与软件交叉验证

OLED问题80%源于RESET或I²C通信:

  • 硬件层:用万用表通断档测PB8(RES)与OLED RES引脚是否导通;测PB6/PB7对地电阻,正常应为∞(开路),若为0Ω说明I²C总线被短路。
  • 软件层:在OLED_Init()后插入HAL_Delay(100); OLED_WR_Byte(0xAF, OLED_CMD);—— 若此时屏幕亮起,说明初始化序列中某条命令执行失败,需逐条注释排查。
  • I²C地址错误:用I²C扫描工具(如Bus Pirate)确认OLED地址是0x3C还是0x3D,修改OLED_WR_Byte中地址参数。

4.3 蜂鸣器无声的TIM输出极性与GPIO复用冲突诊断

无源蜂鸣器需要方波驱动,常见错误:

错误类型表现检测方式修正操作
TIM通道极性设反PA6恒为低电平示波器测PA6,无波形CubeMX中TIM3_CH1 → Channel Configuration → Polarity =High
GPIO复用功能未使能PA6输出恒定3.3V万用表测PA6电压__HAL_RCC_GPIOA_CLK_ENABLE()后,__HAL_RCC_TIM3_CLK_ENABLE()
PWM占空比为0PA6有1kHz波形但幅值0示波器看波形幅度__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);(设为50%占空比)

注意:__HAL_TIM_SET_COMPARE必须在HAL_TIM_PWM_Start之后调用,否则无效。若仍无声,更换蜂鸣器——无源蜂鸣器需1~5kHz方波,有源蜂鸣器只需直流。


5. 串口调试助手实测技巧:用SSCOM/XCOM高效解析温湿度数据流

5.1 SScom串口调试助手关键设置(适配本项目ASCII输出)

SSCOM是Windows下最轻量的串口工具,针对本项目需调整三项:

  • 串口设置页
    • Port:选择对应CH340的COM号(设备管理器中查看)
    • BaudRate:115200(必须与HAL配置一致)
    • DataBits:8,Parity:None,StopBits:1,FlowControl:None
  • 接收区设置页
    • 接收区显示:勾选“自动换行”(匹配\r\n
    • 显示格式:ASCII(非Hex,因项目发送的是可读字符串)
    • 接收缓冲区:设为10240(避免高速采样时丢包)
  • 发送区设置页
    • 发送格式:ASCII
    • 发送间隔:0ms(本项目无需发送指令)

提示:若接收区出现乱码,90%概率是波特率不匹配。用示波器测PA9波形,数10个bit宽度,计算实际波特率:T_bit ≈ 8.7μs → Baud = 1/8.7e-6 ≈ 115200

5.2 XCOM高级功能:将温湿度数据自动保存为CSV并绘图

XCOM(v2.0+)支持数据日志与图表,适合长期监测:

  1. 开启日志:点击“Log”按钮 → 设置保存路径(如D:\sensor_log.csv)→ 格式选“Text
  2. 正则提取数据:在“Filter”页输入正则表达式:T:(\d+),H:(\d+),A:(\d+)
    → 勾选“Use Regex”,点击“Add Column”自动创建三列:Temp、Humi、Alarm
  3. 实时绘图:切换到“Chart”页 → X轴选“Time”,Y轴选“Temp”和“Humi” → 点击“Start Chart”

此时XCOM会将每行T:25,H:45,A:0解析为时间戳+数值,并生成双Y轴折线图。无需额外编写Python脚本,工程师现场调试即可获得趋势分析

5.3 用串口数据验证报警逻辑的真值表法

不要依赖肉眼观察蜂鸣器,用串口输出反向验证报警状态:

时间点DHT11读数(T/H)OLED显示串口输出预期蜂鸣器实际行为
t=0s25/50NormalT:25,H:50,A:0无声
t=10s36/50(连续5次)ALARMING!T:36,H:50,A:1
t=15s34/50(第1次回落)NormalT:34,H:50,A:0

此表可打印贴在工位,每次修改报警阈值后按表测试,5分钟内完成闭环验证。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询