)
从时序图到实战用STM32裸机驱动DHT11的深度解析在嵌入式开发领域很多开发者习惯直接调用现成的库函数来操作传感器这虽然提高了开发效率却让我们失去了理解底层通信协议的宝贵机会。今天我们将以DHT11温湿度传感器为例抛开现成的库从最基础的时序图开始一步步用STM32F103的GPIO口实现单总线协议驱动。1. 理解DHT11的单总线通信本质DHT11采用单总线通信协议这意味着数据发送和接收都通过同一根线完成。这种设计节省了IO资源但也带来了严格的时序要求。让我们先拆解几个关键概念单总线特性需要4.7kΩ上拉电阻保持默认高电平 主机控制所有通信发起 从机只在主机请求后响应数据格式每次传输40位数据5字节 包含湿度整数、湿度小数、温度整数、温度小数和校验和 校验和前四个字节之和的低8位注意DHT11的小数部分通常为0但保留这部分处理能力能让代码兼容更高级的传感器如DHT222. 时序图深度解析与代码映射2.1 启动时序实现查看数据手册中的启动时序图图5我们需要将其转换为具体的代码操作void DHT11_Start(void) { GPIO_InitTypeDef GPIO_InitStruct; // 配置为推挽输出 GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); // 主机拉低至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); // 实际使用更精确的延时方式 // 主机释放总线拉高20-40us HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); delay_us(30); // 精确微秒级延时 }2.2 响应信号捕获DHT11的响应时序图5中的t3-t4阶段需要特别注意从机拉低80us表示响应从机拉高80us准备发送数据对应的代码实现uint8_t DHT11_CheckResponse(void) { uint8_t retry 0; // 切换为输入模式 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); // 等待从机拉低 while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) retry 100) { retry; delay_us(1); } if(retry 100) return 1; // 超时错误 retry 0; // 等待从机拉高 while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) retry 100) { retry; delay_us(1); } if(retry 100) return 1; // 超时错误 return 0; // 正常响应 }3. 数据位解析的艺术DHT11的数据0和数据1有完全不同的时序特征见图6、图7数据类型低电平持续时间高电平持续时间050us26-28us150us70us基于这个特征我们可以实现位读取函数uint8_t DHT11_ReadBit(void) { uint8_t retry 0; // 等待低电平结束 while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) retry 100) { retry; delay_us(1); } // 精确测量高电平时间 uint32_t start TIM5-CNT; // 使用定时器获取精确时间 while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) retry 100) { retry; delay_us(1); } uint32_t duration TIM5-CNT - start; return (duration 40) ? 1 : 0; // 阈值设为40us区分0和1 }4. 完整数据采集与校验将位读取组合成字节读取最终完成40位数据的采集uint8_t DHT11_ReadData(DHT11_Data *data) { uint8_t buf[5] {0}; uint8_t checksum 0; DHT11_Start(); if(DHT11_CheckResponse()) return 1; for(int i0; i5; i) { for(int j0; j8; j) { buf[i] 1; buf[i] | DHT11_ReadBit(); } if(i 4) checksum buf[i]; } if(checksum ! buf[4]) return 2; // 校验失败 >uint8_t DHT11_Read_Retry(DHT11_Data *data) { uint8_t retry 3; uint8_t ret 0; while(retry--) { ret DHT11_ReadData(data); if(ret 0) break; HAL_Delay(100); // 间隔至少100ms } return ret; }6. 性能优化进阶技巧对于需要高频采集的场景我们可以进一步优化1. 中断驱动法配置GPIO中断捕获边沿变化记录时间戳优点不阻塞主程序缺点实现复杂度高2. DMA捕获法使用定时器输入捕获模式配合DMA优点极高精度缺点资源占用大3. 状态机实现将通信过程分解为多个状态优点可与其他任务并行代码示例typedef enum { DHT11_STATE_IDLE, DHT11_STATE_START, DHT11_STATE_WAIT_RESPONSE_LOW, // ...其他状态 } DHT11_State; void DHT11_StateMachine(void) { static DHT11_State state DHT11_STATE_IDLE; static uint32_t timestamp 0; switch(state) { case DHT11_STATE_IDLE: // 初始化操作 break; // 其他状态处理... } }7. 从DHT11到更高级传感器的思考虽然我们以DHT11为例但这些技术同样适用于DHT22精度更高DS18B20单总线温度传感器其他单总线设备关键差异对比特性DHT11DHT22DS18B20温度范围0-50℃-40-80℃-55-125℃温度精度±2℃±0.5℃±0.5℃通信速度1Hz0.5Hz可变数据位宽40bit40bit64bit掌握了底层驱动方法后你会发现各种传感器的使用都变得简单明了。这种从时序图开始的开发方式正是嵌入式工程师的核心能力之一。