21
社区成员




DHT11 是一种数字温湿度传感器,采用单总线协议通信,结构简单,易于使用,非常适合STM32微控制器进行学习和开发。本文基于STM32F103C8T6单片机,使用PB3口连接DHT11,详细讲解硬件连接、GPIO配置、单总线通信时序、数据接收与校验,并配备超详细注释代码和自定义延时函数。
DHT11的数据通信只有一根数据线,采用开漏结构,主机和从机通过电平的高低及持续时间传递信息:
DHT11数据时序图如下:
/*****************************************
函数功能:微秒级延时函数
参 数:xus - 延时的微秒数
返 回 值:无
备 注:使用SysTick计数器,时钟为72MHz/8=9MHz
计数器每个tick约0.111us,LOAD=9*xus-1实现xus微秒延时
******************************************/
void delay_us(u32 xus)
{
SysTick->CTRL &= ~(1 << 2); // 选择内部时钟HCLK/8(9MHz)
SysTick->LOAD = 9 * xus - 1; // 设定重装载值,计数9*xus个tick
SysTick->VAL = 0; // 清空当前计数值
SysTick->CTRL &= ~(1 << 1); // 禁止SysTick中断
SysTick->CTRL |= (1 << 0); // 启动SysTick计数器
while (!(SysTick->CTRL & (1 << 16))); // 等待计数器计数到0,自动清标志
SysTick->CTRL &= ~(1 << 0); // 关闭计数器
}
/*****************************************
函数功能:毫秒级延时函数
参 数:xms - 延时的毫秒数
返 回 值:无
备 注:循环调用delay_us实现毫秒延时
******************************************/
void delay_ms(u32 xms)
{
while (xms--)
{
delay_us(1000); // 1ms = 1000us
}
}
#include "stm32f10x.h" // STM32F10x 标准库头文件
/*****************************************
函数功能:配置PB3为开漏输出模式(主机驱动数据线)
参 数:无
返 回 值:无
备 注:开漏输出允许总线被多个设备共享,未驱动时线被上拉
******************************************/
void DHT11_SetOutput(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 初始化结构体清零
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3; // 选择PB3引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 最高输出速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化GPIOB口
}
/*****************************************
函数功能:配置PB3为浮空输入模式(接收数据线电平)
参 数:无
返 回 值:无
备 注:用于检测DHT11拉低或拉高状态,浮空输入避免内部上拉影响电平
******************************************/
void DHT11_SetInput(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 初始化结构体清零
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3; // 选择PB3引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 速度参数无实际意义
GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化GPIOB口
}
/*****************************************
函数功能:DHT11初始化,开启GPIO时钟,配置PB3引脚,默认拉高总线
参 数:无
返 回 值:无
备 注:禁用JTAG复用,释放PB3,保证引脚可用
使用开漏输出,延时1秒,等待DHT11稳定
******************************************/
void DHT11_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟
// 禁用JTAG复用,保留SWD调试,释放PB3引脚,避免复用冲突
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
DHT11_SetOutput(); // 配置PB3为开漏输出
GPIO_SetBits(GPIOB, GPIO_Pin_3); // 拉高数据线,释放总线
delay_ms(1000); // 延时1秒,等待传感器稳定
}
/*****************************************
函数功能:主机发送起始信号,通知DHT11开始传输
参 数:无
返 回 值:无
备 注:拉低至少18ms,然后拉高,等待DHT11响应
******************************************/
void DHT11_StartSignal(void)
{
DHT11_SetOutput(); // 配置为开漏输出
GPIO_ResetBits(GPIOB, GPIO_Pin_3); // 拉低数据线,主机驱动
delay_ms(20); // 保持20ms以上,满足18ms最低要求
GPIO_SetBits(GPIOB, GPIO_Pin_3); // 释放总线,拉高数据线
delay_us(30); // 等待20~40us,为DHT11响应做准备
}
/*****************************************
函数功能:等待DHT11拉低再拉高响应信号
参 数:无
返 回 值:0 - 响应成功
1 - 无响应(拉低超时)
2 - 拉低持续超时(等待低电平超时)
3 - 拉高持续超时(等待高电平超时)
备 注:防止程序死循环卡死
******************************************/
u8 DHT11_WaitResponse(void)
{
u16 timeout = 0;
DHT11_SetInput(); // 切换为输入,监听数据线
// 等待DHT11拉低开始响应,低电平持续约80us
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
{
delay_us(1);
if (++timeout > 100) return 1; // 超时,未检测到拉低信号
}
timeout = 0;
// 等待DHT11释放低电平,开始拉高
while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
{
delay_us(1);
if (++timeout > 100) return 2; // 低电平持续超时
}
timeout = 0;
// 等待DHT11拉高结束
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
{
delay_us(1);
if (++timeout > 100) return 3; // 高电平持续超时
}
return 0; // 响应成功
}
/*****************************************
函数功能:接收DHT11传来的40位数据
参 数:data - 长度为5的数组,用于存放接收的5字节数据
返 回 值:无
备 注:通过高电平宽度判别0或1,高电平持续时间长为1,短为0
高位优先传输,循环按字节和位读取
******************************************/
void DHT11_ReceiveData(u8* data)
{
u8 i, j;
for (i = 0; i < 5; i++) // 5字节循环
{
u8 byte = 0;
for (j = 0; j < 8; j++) // 8位循环
{
// 等待低电平结束(确保是新一位开始)
while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3));
delay_us(40); // 延时40us判断电平持续时间
// 判断当前高电平状态
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
{
// 高电平持续时间长,认为是1
byte |= (1 << (7 - j)); // 按位设置,高位先传
// 等待高电平结束,防止干扰下一位
while (GPIO\_ReadInputDataBit(GPIOB, GPIO\_Pin\_3));
}
// 若40us后低电平,认为0,不需设置位
}
data[i] = byte; // 存入对应字节
}
}
/*****************************************
函数功能:读取温湿度数据(整数部分)
参 数:temp_int - 指向温度整数变量指针
humi_int - 指向湿度整数变量指针
返 回 值:0 - 读取成功
1 - 无响应
2 - 校验失败
备 注:忽略小数部分和负温度,可根据需要扩展
******************************************/
u8 DHT11_Read(u8* temp_int, u8* humi_int)
{
u8 buf[5];
u8 sum;
DHT11_StartSignal(); // 主机发送起始信号
if (DHT11_WaitResponse()) // 等待DHT11响应,非0为失败
return 1;
DHT11_ReceiveData(buf); // 接收5字节数据
sum = buf[0] + buf[1] + buf[2] + buf[3]; // 计算校验和
if (sum != buf[4]) // 校验和错误
return 2;
*humi_int = buf[0]; // 湿度整数部分
*temp_int = buf[2]; // 温度整数部分
return 0; // 成功
}
#include "stm32f10x.h"
#include "dht11.h"
#include <stdio.h>
// 重定向printf到串口1
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
int main(void)
{
u8 ret;
u8 temperature = 0, humidity = 0;
SystemInit();
delay_init(); // 初始化延时函数
DHT11_Init(); // 初始化DHT11
USART1_Init(115200); // 初始化串口1
printf("System Init Complete, Starting DHT11 Reading...\r\n");
while (1)
{
ret = DHT11_Read(&temperature, &humidity);
if (ret == 0)
{
printf("Temperature: %d C, Humidity: %d%%\r\n", temperature, humidity);
}
else if (ret == 1)
{
printf("DHT11 no response!\r\n");
}
else if (ret == 2)
{
printf("DHT11 checksum error!\r\n");
}
delay_ms(2000); // 2秒刷新一次
}
}
这套代码适合STM32F103C8T6开发板,用PB3口接DHT11,实测稳定可靠,代码注释详细,欢迎读者复制粘贴调试
(完)