STM32F103ZET6与DHT11温湿度传感器的单总线通信及串口调试实战
1. 从零搭建STM32与DHT11的通信桥梁
第一次接触DHT11温湿度传感器时,我被它简单的三线接口吸引住了——只需要电源、地线和一根数据线就能工作。但真正上手时才发现,这个看似简单的传感器藏着不少门道。记得当时为了搞清单总线时序,我用示波器抓波形抓到凌晨三点。
单总线协议是DHT11的核心技术特点。与I2C、SPI等多线协议不同,它仅用一根数据线完成双向通信。这种设计节省了宝贵的IO资源,特别适合STM32这类引脚有限的微控制器。但代价是需要严格遵循时序要求——误差超过20us就可能导致通信失败。
在实际项目中,我推荐使用STM32F103ZET6的PB11引脚连接DHT11。这个引脚既支持5V容忍(DHT11工作电压4.5-5.5V),又方便与开发板上的USART1复用。硬件连接非常简单:
- VCC接3.3V或5V(实测5V时数据更稳定)
- GND接地
- DATA接PB11,建议加个4.7K上拉电阻
2. 深入解析DHT11通信协议
2.1 数据格式揭秘
DHT11每次传输40bit数据,包含:
- 湿度整数(8bit) + 湿度小数(8bit)
- 温度整数(8bit) + 温度小数(8bit)
- 校验和(8bit)
校验和是前四个字节的和,这个设计我在多个项目中验证过其可靠性。例如收到数据:0x01 0x00 0x1A 0x00 0x1B,校验计算0x01+0x00+0x1A+0x00=0x1B,验证通过。
2.2 关键时序控制
最让我头疼的是起始信号时序。主机(STM32)需要先拉低总线至少18ms(实测20ms最稳妥),然后拉高20-40us等待DHT11响应。这里有个坑:拉高时间超过40us可能导致DHT11无响应。
用代码实现是这样的:
void DHT11_Start()
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置为推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_ResetBits(GPIOB, GPIO_Pin_11); // 拉低
Delay_ms(20); // 保持20ms
GPIO_SetBits(GPIOB, GPIO_Pin_11); // 释放总线
Delay_us(30); // 等待30us
}
2.3 数据位识别技巧
DHT11的0和1不是用电平高低区分,而是用高电平持续时间:
- 0:26-28us高电平
- 1:116-118us高电平
我的经验是在低电平结束后延时40us采样,此时电平状态即代表数据值。这个延时很关键,太早会采样到起始低电平,太晚可能错过数据。
3. STM32硬件配置实战
3.1 GPIO模式切换技巧
DHT11通信需要动态切换GPIO输入输出模式。我推荐使用库函数快速切换:
void DHT11_IO_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void DHT11_IO_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
3.2 精准延时实现
单总线对延时要求苛刻,我常用的两种方案:
- 使用SysTick定时器(精度高但不便调试)
- 基于循环的简易延时(适合快速验证)
这里给出循环延时的实现:
void Delay_us(uint32_t us)
{
uint32_t delay = (HCLK_FREQ/8000000)*us;
while(delay--);
}
注意要根据实际时钟频率调整系数,我用72MHz主频时这个参数刚好。
4. 数据采集与串口输出
4.1 完整数据读取流程
经过多次优化,我的数据读取函数稳定如下:
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
DHT11_Start();
if(DHT11_Check() == 0)
{
for(i=0;i<5;i++)
{
buf[i] = DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi = buf[0];
*temp = buf[2];
return 0; // 成功
}
}
return 1; // 失败
}
4.2 串口调试技巧
建议使用115200波特率,格式设置为8N1。在串口助手中添加时间戳可以观察采样间隔:
void USART1_Init(void)
{
USART_InitTypeDef USART_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
// TX(PA9)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RX(PA10)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
5. 常见问题与性能优化
5.1 典型故障排查
- 无响应:检查上拉电阻,确保起始信号时长足够
- 数据错误:降低GPIO速度,或增加时序延时裕量
- 校验失败:尝试降低采样频率(DHT11需>1s间隔)
5.2 提升稳定性技巧
- 在数据线加100nF电容滤波
- 使用屏蔽线缆长距离传输
- 添加看门狗防止程序卡死
有次在工业现场遇到干扰问题,最后是通过在电源端加磁珠解决的。这也提醒我们,看似简单的传感器在实际环境中可能遇到各种意外情况。
6. 项目扩展与进阶应用
6.1 多传感器组网
通过切换GPIO可以驱动多个DHT11,我做过最多同时接8个的温湿度监测系统。关键点是:
- 每个传感器独立供电
- 设置不同的采样时间点
- 添加防冲突机制
6.2 低功耗设计
使用STM32的睡眠模式+DHT11的间歇工作,可使整体功耗降至50uA以下。我的实现方案:
void Enter_StopMode(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
SystemInit(); // 唤醒后重新初始化时钟
}
把这段代码放在主循环中,配合RTC定时唤醒,非常适合电池供电的场景。
更多推荐



所有评论(0)