一、DHT11 传感器基础
DHT11 采用单总线串行通信方式,仅需一根数据线即可完成温湿度数据的双向传输,无需额外的时钟线,硬件连接简单。其通信逻辑由主机(MCU / 开发板)主动发起,从机(DHT11)响应并返回 40 位数据。
数据格式为:8 位湿度整数 + 8 位湿度小数 + 8 位温度整数 + 8 位温度小数 + 8 位校验和,其中 DHT11 的湿度小数和温度小数位始终为 0,实际有效数据为湿度整数和温度整数
二、DHT11原理图
1.电路原理图

DHT11 温湿度传感器的单总线连接电路,DATA 引脚通过 4.7K 上拉电阻接 VCC,确保总线空闲时为高电平,VCC 和 GND 分别接电源正负极。
2.通信时许

- 主机起始:主机将数据线拉低至少 18ms,再拉高 20~40us,向 DHT11 发送起始指令;
- 从机响应:DHT11 检测到起始信号后,会将数据线拉低 80us,再拉高 80us,完成响应;
3.读取0和1的时序

从机通过数据线高低电平的持续时间表示二进制位,低电平 50us 后,高电平 26~28us 表示 0,依次传输 40 位数据。

从机通过数据线高低电平的持续时间表示二进制位,低电平 50us 后,高电平 70us 表示 1,依次传输 40 位数据。
三、驱动代码核心实现解析
3.1 宏定义与全局变量
cs
#define DEV_NAME "dht11"
static int dht11_gpio;
- 定义设备名dht11,作为杂项设备和平台驱动的标识;
- 全局变量dht11_gpio存储从设备树解析到的 DHT11 数据线对应的 GPIO 编号,实现 GPIO 资源的动态获取。
3.2 核心时序操作函数
3.2.1 起始信号函数dht11_start
cs
static void dht11_start(void)
{
gpio_direction_output(dht11_gpio, 1);
mdelay(2);
gpio_set_value(dht11_gpio, 0);
msleep(20);
gpio_set_value(dht11_gpio, 1);
udelay(20);
}
该函数实现主机向 DHT11 发送起始信号,严格遵循 DHT11 时序要求:
- 先将 GPIO 配置为输出模式并拉高 2ms,保证数据线初始状态稳定;
- 拉低数据线 20ms(满足至少 18ms 的要求),发送起始指令;
- 拉高数据线 20us,等待 DHT11 的响应信号。
3.2.2 响应等待函数dht11_wait_respon
cs
static int dht11_wait_respon(void)
{
int time = 0;
gpio_direction_input(dht11_gpio);
// 等待数据线拉低
time = 30;
while(gpio_get_value(dht11_gpio) && time--) { udelay(1); }
if(time < 0) return -1;
// 等待从机响应低电平结束
time = 100;
while((!gpio_get_value(dht11_gpio)) && time--) { udelay(1); }
if(time < 0) return -2;
// 等待从机响应高电平结束
time = 100;
while(gpio_get_value(dht11_gpio) && time--) { udelay(1); }
if(time < 0) return -3;
return 0;
}
主机发送起始信号后,将 GPIO 配置为输入模式,等待 DHT11 的响应信号,通过超时计数避免死循环:
- 等待 DHT11 将数据线拉低,超时 30us 返回错误;
- 等待 DHT11 响应的 80us 低电平结束,超时 100us 返回错误;
- 等待 DHT11 响应的 80us 高电平结束,超时 100us 返回错误;
- 无超时则返回 0,表示响应成功。
3.2.3 位数据读取函数dht11_get_bit
cs
static char dht11_get_bit(void)
{
int time = 60;
while(gpio_get_value(dht11_gpio) && time--) { udelay(1); }
if(time < 0) return -4;
time = 80;
while((!gpio_get_value(dht11_gpio)) && time--) { udelay(1); }
if(time < 0) return -5;
udelay(35);
if(!gpio_get_value(dht11_gpio)) return 0;
time = 50;
while(gpio_get_value(dht11_gpio) && time--) { udelay(1); }
if(time < 0) return -6;
return 1;
}
该函数实现单个二进制位的读取,基于 DHT11 的位时序特征:
- 先等待数据位的起始低电平结束,超时则返回错误;
- 延时 35us 后检测数据线电平:若为低,表示该位为 0;若为高,表示该位为 1;
- 对高电平做超时判断,避免总线异常导致的死循环。
3.2.4 完整数据读取函数dht11_read_data
cs
static int dht11_read_data(unsigned char * data)
{
int i = 0;
int j = 0;
for(j = 0; j < 5; j++)
{
for(i = 0; i < 8; i++)
{
char bit = dht11_get_bit();
if(bit < 0) return bit;
data[j] <<= 1;
data[j] |= bit;
}
}
return 5;
}
循环读取 40 位(5 个字节)数据,通过位运算将读取到的二进制位拼接为完整的字节数据:
- 外层循环遍历 5 个字节(湿度整、湿度小、温度整、温度小、校验和);
- 内层循环遍历每个字节的 8 位,通过左移和或运算拼接位数据;
- 若位读取失败,立即返回错误码;读取成功返回 5,表示获取到 5 个字节有效数据。
3.3 文件操作接口实现
cs
static int open(struct inode * inode, struct file * file)
{
printk("dht11 open\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff)
{
int ret = 0;
unsigned char data[5];
dht11_start();
ret = dht11_wait_respon();
if(ret < 0) return ret;
ret = dht11_read_data(data);
if(ret < 0) return ret;
ret = copy_to_user(buf, data, sizeof(data));
printk("dht11 read\n");
return 5;
}
static ssize_t write(struct file * file, const char __user * buf, size_t size, loff_t * loff)
{
printk("dht11 write\n");
return 0;
}
static int close(struct inode * inode, struct file * file)
{
printk("dht11 close\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
read:核心接口,用户态调用read函数读取设备文件时,触发该接口:
- 依次执行起始信号、响应等待、数据读取;
- 通过copy_to_user将内核态的 5 字节温湿度数据拷贝到用户态缓冲区;
- 数据传输成功后返回 5,表示读取到 5 个字节数据。
3.4 杂项设备注册
cs
static struct miscdevice misc_dev =
{
.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号
.name = DEV_NAME, // 设备名,对应/dev/dht11
.fops = &fops // 绑定文件操作接口
};
在平台驱动的probe函数中通过misc_register(&misc_dev)完成注册,驱动卸载时通过misc_deregister(&misc_dev)注销。
3.5.1 设备树匹配与 probe/remove 函数
cs
static int probe(struct platform_device * pdev)
{
struct device_node * pdts;
int ret = misc_register(&misc_dev);
if(ret) goto err_misc_register;
// 解析设备树节点
pdts = of_find_node_by_path("/dht11");
if(NULL == pdts) { ret = PTR_ERR(pdts); goto err_of_find; }
// 获取GPIO编号
dht11_gpio = of_get_named_gpio(pdts, "ptdht11-gpio", 0);
if(dht11_gpio < 0) { ret = dht11_gpio; goto err_of_find; }
// 申请GPIO资源
ret = gpio_request(dht11_gpio, "dht11");
if(ret < 0) goto err_gpio_request;
gpio_direction_output(dht11_gpio, 1);
printk("######################### dht11_driver probe\n");
return 0;
// 错误处理流程
err_gpio_request:
err_of_find:
misc_deregister(&misc_dev);
err_misc_register:
printk("######################### dht11_driver misc register ret = %d\n", ret);
return ret;
}
static int remove(struct platform_device * pdev)
{
gpio_free(dht11_gpio);
misc_deregister(&misc_dev);
printk("######################### dht11_driver remove\n");
return 0;
}
// 设备树匹配表
static struct of_device_id dht11_table[] =
{
{.compatible = "pt-dht11"},
{}
};
// 平台驱动结构体
static struct platform_driver pdrv =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = dht11_table
}
};
- probe 函数:平台驱动匹配成功后执行的核心函数,完成设备初始化:
- remove 函数:驱动卸载时执行,释放 GPIO 资源并注销杂项设备,避免资源泄漏。
- 设备树匹配表:通过compatible属性(pt-dht11)与设备树中的设备节点匹配,实现驱动的自动加载。
3.5.2 模块入口与出口
cs
static int __init dht11_driver_init(void)
{
int ret = platform_driver_register(&pdrv);
if(ret) goto err_platform_driver_register;
printk("dht11 platform_driver_register success\n");
return 0;
err_platform_driver_register:
printk("dht11 platform_driver_register failed\n");
return ret;
}
static void __exit dht11_driver_exit(void)
{
platform_driver_unregister(&pdrv);
printk("dht11 platofrm_driver_unregister\n");
}
module_init(dht11_driver_init);
module_exit(dht11_driver_exit);
MODULE_LICENSE("GPL");
- 模块入口dht11_driver_init:注册平台驱动,驱动加载时执行;
- 模块出口dht11_driver_exit:注销平台驱动,驱动卸载时执行;
- MODULE_LICENSE("GPL"):声明模块遵循 GPL 协议,避免内核加载时的版权警告。
四、应用层程序
cs
int main(int argc, const char* argv[])
{
// 1. 打开驱动生成的设备节点
int fd = open("/dev/dht11", O_RDONLY);
if (fd < 0)
{
perror("open dht11 failed");
return 1;
}
unsigned char data[5] = { 0 };
while (1)
{
// 2. 读取 5 字节数据
if (read(fd, data, sizeof(data)) == 5)
{
// data[0]: 湿度整数, data[1]: 湿度小数
// data[2]: 温度整数, data[3]: 温度小数
float h = data[0] + data[1] * 0.1;
float t = data[2] + data[3] * 0.1;
printf("Humidity = %.1f %%, Temperature = %.1f C\n", h, t);
}
else
{
printf("Read error or checksum failed\n");
}
// 3. 每 3 秒读一次(DHT11 硬件限制,不能读太快)
sleep(3);
}
close(fd);
return 0;
}
五、总结
DHT11 温湿度传感器的单总线接口电路:
- DHT11 的 VCC 和 GND 分别接电源正负极,为传感器供电。
- DATA 引脚通过 4.7K 上拉电阻接 VCC,保证总线空闲时为高电平,并通过接口 P2 与 MCU 通信。
- 整个电路实现了单总线双向通信,用于 MCU 采集温湿度数据。