资讯动态

STM32 HAL库实现温湿度监测报警系统

发布时间:2026/9/16 2:02:19 来源:尧图企业网站定制
简介本资源是一套基于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驱动DHT11OLED蜂鸣器串口上报是一套闭环感知报警系统你手头有一块STM32F103C8T6最小系统板接了DHT11温湿度传感器、0.96寸I²C OLEDSSD1306、无源蜂鸣器和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会损坏。OLEDI²C接口SCL接PB6I²C1_SCLSDA接PB7I²C1_SDAVCC接3.3VGND接地RES引脚接PB8软复位控制DC引脚接PB9数据/命令选择。注意0.96寸SSD1306模块默认I²C地址为0x3C若焊接了A0跳线则为0x3D。无源蜂鸣器一端接GND另一端接TIM2_CH1PA0或TIM3_CH1PA6——必须用PWM驱动不可直接GPIO推挽输出否则只有“咔”声。需串联100Ω限流电阻。串口USART1TX接PA9RX接PA10通过CH340转换后接入PC。波特率固定为1152008N1无硬件流控。提示DHT11的PA0与蜂鸣器的PA0冲突解决方案是——蜂鸣器改用PA6TIM3_CH1DHT11独占PA0。这是实际布线中最常被忽略的引脚资源冲突Keil编译不报错但运行时DHT11永远返回0xFF。2.2 CubeMX关键配置项HAL库工程生成打开STM32CubeMX选择STM32F103C8T6按以下顺序配置顺序错误将导致DHT11初始化失败System Core → SYS → Debug → Serial Wire启用SWD下载System Core → RCC → High Speed Clock (HSE) → Crystal/Ceramic Resonator外部8MHz晶振DHT11时序依赖精准定时Connectivity → USART1 → Mode → Asynchronous设置Baud Rate 115200Word Length 8 BitsParity NoneStop Bits 1→开启Global InterruptNVIC Settings中勾选USART1 global interruptConnectivity → I2C1 → Mode → Standard (100kHz)Clock Source APB1Prescaler 1 →勾选Enable DMA非必需但提升OLED刷屏效率Timers → TIM3 → Channel 1 → PWM Generation CH1Counter Period 999对应1kHz载波Prescaler 71系统时钟72MHz ÷ (711) 1MHz计数频率Clock Division 0Channel 1 Polarity Active High无源蜂鸣器需高电平驱动发声GPIO → PA0 → GPIO_Output → Pull-up → No pull-upDHT11数据线切勿设为Output Push-Pull必须设为Open-Drain External Pull-upGPIO → PB6/PB7/PB8/PB9 → I2C1_SCL/SDA/RES/DC → Alternate Function Open-DrainOLED 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(i0; i18000; i) __NOP(); // 粗略延时18ms72MHz下约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(j0; j40; 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脉宽。生产环境必须启用DWTData 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驱动精简版适配HALOLED初始化易失败主因是I²C时钟拉伸未处理或RESET时序不足。以下为关键初始化序列// oled.c基于SSD1306128x64分辨率 #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, 0x3C1, 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全黑优先检查PB8RES是否接线、PB9DC是否在发送命令前正确切换。3. 数据融合与报警逻辑温湿度阈值判断、蜂鸣器PWM控制、串口结构化输出3.1 温湿度解析与报警条件定义DHT11返回的5字节中dht11_data[0]为湿度整数部分dht11_data[1]为湿度小数部分恒为0dht11_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 i0; iALARM_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字节temphumialarm_flagchecksum uint8_t temp; uint8_t humi; uint8_t alarm; uint8_t checksum; // headlentemphumialarm 之和 } 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通信硬件层用万用表通断档测PB8RES与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 HighGPIO复用功能未使能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号设备管理器中查看BaudRate115200必须与HAL配置一致DataBits8ParityNoneStopBits1FlowControlNone接收区设置页接收区显示勾选“自动换行”匹配\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并绘图XCOMv2.0支持数据日志与图表适合长期监测开启日志点击“Log”按钮 → 设置保存路径如D:\sensor_log.csv→ 格式选“Text”正则提取数据在“Filter”页输入正则表达式T:(\d),H:(\d),A:(\d)→ 勾选“Use Regex”点击“Add Column”自动创建三列Temp、Humi、Alarm实时绘图切换到“Chart”页 → X轴选“Time”Y轴选“Temp”和“Humi” → 点击“Start Chart”此时XCOM会将每行T:25,H:45,A:0解析为时间戳数值并生成双Y轴折线图。无需额外编写Python脚本工程师现场调试即可获得趋势分析。5.3 用串口数据验证报警逻辑的真值表法不要依赖肉眼观察蜂鸣器用串口输出反向验证报警状态时间点DHT11读数T/HOLED显示串口输出预期蜂鸣器实际行为t0s25/50NormalT:25,H:50,A:0无声✅t10s36/50连续5次ALARMING!T:36,H:50,A:1响✅t15s34/50第1次回落NormalT:34,H:50,A:0停✅此表可打印贴在工位每次修改报警阈值后按表测试5分钟内完成闭环验证。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价