STM32多传感器同步采集:DHT22与BH1750时序协同设计
发布时间:2026/10/20 13:45:25 锦皓数字建站

简介本资源是一套基于STM32F103单片机的嵌入式环境监测项目完整工程面向嵌入式初学者与课程实践者解决多传感器协同采集与本地可视化显示的核心问题——即同时驱动DHT22单总线温湿度和BH1750I²C光照强度传感器并将数据实时呈现于TFTLCD屏。压缩包含150个文件以37个.h头文件和35个.c源文件为主体涵盖STM32标准外设库如stm32f10x_i2c.c、stm32f10x_tim.c、LCD驱动lcd.c、传感器读取逻辑及Keil工程配置文件uvprojx、axf、hex等整体3.08MB结构完整、模块清晰便于逐层理解硬件初始化、协议时序解析与数据显示流程。目前已有8875人学习下载读者可直接导入Keil MDK编译运行获取可复现的软硬协同范例、关键时序处理代码如DHT22脉冲捕获与校验、I²C通信配置模板及ILI9341 LCD图形化显示实现是掌握ARM Cortex-M3平台传感器开发的高实用性入门套件。1. 为什么温湿度光照要一起采不是加个传感器就行而是得让三者时序不打架很多同学做毕设或课设时看到“DHT22 BH1750 STM32F103”就直接抄代码DHT22用延时读、BH1750用I²C读、主循环里轮询——结果一上电温湿度偶尔乱码、光照值跳变、串口打印卡死。这不是传感器坏了是没意识到DHT22是单总线协议靠精确微秒级延时握手BH1750是标准I²C设备依赖硬件I²C外设或模拟时序而STM32F103的SysTick和GPIO翻转精度在不同系统时钟配置下会放大时序误差。我带过三个类似项目有A同学在答辩现场演示时DHT22连续三次返回85℃实际室温26℃最后发现是AHB预分频设成2导致SysTick中断响应延迟了12μs刚好踩在DHT22数据采样窗口边缘。这套方案不是教你怎么接线而是帮你把“单总线抗干扰”“I²C地址冲突规避”“多传感器时间片调度”这三道坎一次性跨过去。适合正在做嵌入式课程设计、毕业设计需要交完整工程文件含原理图、Keil工程、测试报告的同学也适合刚转嵌入式的工程师快速复用稳定采集模块。2. 总体架构设计三层结构与四个核心模块的职责划分整个系统采用“硬件抽象层HAL 传感器驱动层 应用调度层”三层架构避免裸机编程中常见的寄存器魔数硬编码和时序耦合问题。关键不是堆功能而是让每个模块只干一件事DHT22驱动只负责原始字节解析不碰串口BH1750驱动只管I²C通信不处理光照单位换算应用层通过统一接口获取数据再决定是发串口、存Flash还是触发LED。这种解耦方式在答辩时能清晰讲清模块边界也能在后期扩展CO₂或气压传感器时只新增一个驱动文件不动主逻辑。2.1 硬件连接与引脚资源分配避开复位冲突与I²C上拉陷阱DHT22使用PA0作为单总线数据引脚BH1750使用PB6I²C1_SCL和PB7I²C1_SDA。这里必须强调两个易被忽略的细节DHT22的供电引脚不能直连3.3V虽然标称支持3~5.5V但实测在STM32F103系统中若DHT22 VDD接3.3V且未加100nF退耦电容上电瞬间电流尖峰会导致MCU复位。我们统一改用5V LDO独立供电并在DHT22 VDD端并联4.7μF钽电容100nF陶瓷电容。BH1750的I²C上拉电阻必须用4.7kΩ不能用10kΩ这是血泪经验。某次调试中用10kΩ上拉时示波器测到SCL上升沿达1.8μs超手册最大1.0μs导致BH1750在100kHz模式下偶发NACK。换成4.7kΩ后上升沿压到0.65μs通信误码率从3.2%降至0。提示PCB布线时DHT22数据线长度严格控制在15cm以内超过需加75Ω终端匹配电阻BH1750的SCL/SDA走线必须等长与相邻高速信号线间距≥3WW为线宽。2.2 时钟树配置与外设使能为什么SysTick必须设为1μs基准STM32F103C8T6默认HSE8MHz经PLL倍频后系统时钟为72MHz。但DHT22的时序要求极为苛刻主机拉低80μs启动信号随后释放并等待80μs再读取80μs低电平响应——这三个阶段的误差必须控制在±5μs内。若SysTick中断周期设为10μs仅一次中断延迟就可能超限。因此我们在system_stm32f10x.c中强制配置// SysTick初始化1μs计数精度无中断纯计数器模式 if (SysTick_Config(SystemCoreClock / 1000000)) { while (1); // 配置失败则死循环 }这样SysTick_GetCurrentValue()返回值可直接换算为微秒所有DHT22延时函数均基于此而非delay_ms()这类粗粒度函数。实测在72MHz下该配置使DHT22数据有效率从89%提升至99.7%。2.3 传感器驱动层接口定义统一数据结构与错误码体系为避免后续扩展时修改大量调用代码我们定义了标准化传感器接口// sensor_interface.h typedef enum { SENSOR_OK 0, SENSOR_TIMEOUT, SENSOR_CHECKSUM_ERR, SENSOR_I2C_ERR, SENSOR_BUSY } sensor_status_t; typedef struct { float temperature; // ℃ float humidity; // %RH float illuminance; // lx uint32_t timestamp; // ms, from system uptime } sensor_data_t; sensor_status_t dht22_read_data(sensor_data_t *data); sensor_status_t bh1750_read_data(sensor_data_t *data);注意timestamp字段由驱动内部调用HAL_GetTick()填充确保多传感器时间戳对齐。所有错误码统一用sensor_status_t枚举杜绝return -1这类模糊返回值——答辩时老师问“怎么判断DHT22没响应”你就能指着代码说“查SENSOR_TIMEOUT它对应DHT22拉高响应信号超200μs”。3. DHT22单总线驱动实现微秒级延时与状态机防抖设计DHT22的难点从来不是“读不到数据”而是“读到错数据却以为对”。它的单总线协议没有ACK机制全靠电平持续时间判别0/1而STM32F103的GPIO翻转速度受编译器优化等级影响极大。我们放弃传统while循环延时改用SysTick计数状态机把整个通信过程拆解为5个确定性状态。3.1 状态机流程与关键定时参数状态动作持续时间判定条件STATE_IDLEPA0设为推挽输出拉低80μsSysTick计数达80STATE_STARTPA0设为浮空输入等待上升沿≤80μs输入检测到高电平STATE_RESPONSE读取80μs低电平响应80μs计数达80STATE_DATA连续读取40bit每位50μs高27μs低77μs周期40×77μs每bit采样点在下降沿后28~30μsSTATE_CHECKSUM校验前4字节和—sum data[4]关键点在于每位数据的采样点必须落在下降沿后28~30μs区间。若提前采样可能把0误判为1若延后可能把1误判为0。我们用SysTick倒计时在下降沿触发中断后立即启动28μs倒计时到期即读取PA0电平。3.2 核心读取函数实现带注释// dht22_driver.c sensor_status_t dht22_read_data(sensor_data_t *data) { uint8_t raw[5] {0}; uint8_t i, j; uint32_t start_time; // 1. 发送启动信号拉低80μs HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); delay_us(80); // 此delay_us基于SysTick精度±0.5μs // 2. 释放总线等待DHT22响应 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); delay_us(30); // 3. 检测80μs低电平响应DHT22拉低 if (!wait_low_pulse(GPIOA, GPIO_PIN_0, 80)) { return SENSOR_TIMEOUT; } // 4. 读取40bit数据每位先等50μs高电平再测27μs低电平宽度 for (i 0; i 40; i) { if (!wait_high_pulse(GPIOA, GPIO_PIN_0, 50)) return SENSOR_TIMEOUT; // 在下降沿后28μs采样高电平结束即启动计时 start_time SysTick-VAL; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_SET) { if ((start_time - SysTick-VAL) 50) // 超时保护 return SENSOR_TIMEOUT; } delay_us(28); // 精确等待28μs raw[i/8] 1; if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_SET) { raw[i/8] | 0x01; } } // 5. 校验和 uint8_t sum raw[0] raw[1] raw[2] raw[3]; if (sum ! raw[4]) { return SENSOR_CHECKSUM_ERR; } // 6. 解析数据DHT22格式byte0-1湿度整数小数byte2-3温度整数小数 >// bh1750_driver.c uint8_t bh1750_detect_address(void) { uint8_t addr_list[] {0x23, 0x5C}; for (int i 0; i 2; i) { if (i2c_write_byte(addr_list[i], 0x00) HAL_OK) { // 发送POWER_DOWN指令 HAL_Delay(10); if (i2c_write_byte(addr_list[i], 0x10) HAL_OK) { // 连续测量模式 return addr_list[i]; } } } return 0xFF; // 未识别 }该函数在main()初始化时调用将识别出的地址存入全局变量bh1750_i2c_addr后续所有读写均用此地址。答辩时可演示“拔掉模块再插回系统自动重识别地址”非常直观。4.2 连续测量模式下的数据就绪等待用I²C读取状态位而非盲等BH1750在连续测量模式下每120ms更新一次数据但官方文档未明确说明如何判断数据就绪。常见错误是HAL_Delay(120)后直接读但实测在高温环境转换时间可能延长至135ms。正确做法是读取状态寄存器地址0x00// 等待数据就绪读取状态寄存器bit71表示数据有效 uint8_t wait_data_ready(uint8_t addr) { uint8_t status; for (int i 0; i 20; i) { // 最大等待200ms if (i2c_read_byte(addr, 0x00, status) HAL_OK) { if (status 0x80) return HAL_OK; // bit7置位 } HAL_Delay(10); } return HAL_ERROR; } sensor_status_t bh1750_read_data(sensor_data_t *data) { uint8_t addr bh1750_i2c_addr; if (addr 0xFF) return SENSOR_I2C_ERR; if (wait_data_ready(addr) ! HAL_OK) { return SENSOR_TIMEOUT; } // 读取2字节数据MSB在前 uint8_t buf[2]; if (i2c_read_bytes(addr, 0x00, buf, 2) ! HAL_OK) { return SENSOR_I2C_ERR; } uint16_t raw_lx (buf[0] 8) | buf[1]; >RCC-CFGR3 | RCC_CFGR3_SWAS; // 使能SWO CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; ITM-LAR 0xC5ACCE55; // 解锁ITM ITM-TCR | ITM_TCR_ITMENA_Msk; // 使能ITM同时Keil中设置Trace Clock 72MHzSWO Clock 72MHz。6. 验收与答辩技巧用三张表搞定老师所有质疑答辩不是背稿而是用数据证明你真的搞懂了。我带的学生用以下三张表90%的提问都能当场回应不用翻代码6.1 传感器性能实测对比表实验室环境25℃/50%RH/300lx项目DHT22实测值标准值误差BH1750实测值标准值误差温度24.9℃25.0℃-0.1℃———湿度49.2%RH50.0%RH-0.8%RH———光照———298lx300lx-0.67%单次采集耗时4.2ms——1.8ms——连续100次成功率99.7%——100%——表格数据必须真实测量用Fluke 17B万用表测温度/湿度用Extech HD450照度计测光照。老师若质疑直接打开仪器拍照。6.2 关键参数配置核查表答辩前必填参数当前值是否符合手册依据章节备注SysTick计数周期1μs是DHT22时序要求SystemCoreClock/1000000DHT22数据线电阻100Ω是抗反射设计原理图R3BH1750上拉电阻4.7kΩ是I²C上升沿≤1.0μs原理图R5/R6BH1750测量模式连续模式0x10是避免单次模式延迟bh1750_init()DHT22重试次数3次是高湿环境鲁棒性dht22_read_data()这张表的作用是当老师问“为什么用100Ω电阻”你立刻指向“依据章节”列说“根据DHT22 datasheet第5.2节信号反射抑制要求”而不是支吾“好像别人这么用”。6.3 故障注入测试记录表证明你考虑了异常故障类型注入方式系统响应日志截图是否修复DHT22断线拔掉DHT22杜邦线串口打印ERR:DHT22 TIMEOUT继续读BH1750log_01.png是BH1750短路SDA与GND短接I²C扫描失败自动跳过BH1750log_02.png是电源波动输入电压从3.3V突降至2.8VDHT22返回校验错BH1750读0log_03.png是加LDO稳压高温环境置于40℃烘箱2小时温度漂移0.3℃湿度-2.1%RHlog_04.png是软件补偿从那以后我每次带学生做嵌入式项目都强制他们在答辩前三天完成这三张表的实测和填写。不是为了应付老师而是逼自己直面硬件的真实脾气——传感器不会按理想模型工作但你的方案必须让它在真实世界里活下来。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。