应用——DHT11温湿度传感器驱动
DHT11温湿度传感器驱动
一、DHT11传感器通信协议
1. 单总线协议特点
-
单总线:只有一根数据线,双向传输
-
时序要求严格:微秒级延时控制
-
通信过程:主机发送开始信号 → DHT11响应 → 数据传送
2. DHT11数据格式
5字节数据(40位): 字节0:湿度整数部分 字节1:湿度小数部分(DHT11为0) 字节2:温度整数部分 字节3:温度小数部分(DHT11为0) 字节4:校验和(= 字节0+字节1+字节2+字节3)
二、设备树配置详解
1. DHT11设备节点
dts
dht11 {
#address-cells = <1>; // 地址单元格大小为1
#size-cells = <1>; // 大小单元格大小为1
compatible = "pt-dht11"; // 与驱动匹配的兼容字符串
pinctrl-names = "default"; // 引脚控制状态名称
pinctrl-0 = <&pinctrl_ptdht11>; // 引脚配置引用
ptdht11-gpio = <&gpio1 2 GPIO_ACTIVE_HIGH>; // GPIO引脚定义
status = "okay"; // 设备启用状态
};
字段解释:
-
compatible:驱动匹配关键字,必须与驱动中的
.compatible = "pt-dht11"一致 -
pinctrl-0:引用引脚复用配置节点
-
ptdht11-gpio:自定义属性,指定GPIO控制器、引脚号和有效电平
-
&gpio1:GPIO1控制器 -
2:GPIO1_IO02引脚 -
GPIO_ACTIVE_HIGH:高电平有效
-
2. 引脚复用配置
dts
&iomuxc {
pinctrl_ptdht11: ptdht11grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10B0 /* 配置为GPIO功能 */
>;
};
};
配置说明:
-
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02:将引脚复用为普通GPIO
-
0x10B0:电气特性配置值(上下拉、驱动能力等)
三、DHT11驱动核心时序分析
1. 开始信号(主机发送)
static void dht11_start(void)
{
// 1. 设置为输出,初始高电平
gpio_direction_output(dht11_gpio, 1);
mdelay(2); // 稳定高电平2ms
// 2. 拉低总线,发送开始信号
gpio_set_value(dht11_gpio, 0);
msleep(20); // 保持低电平20ms
// 3. 释放总线,准备接收响应
gpio_set_value(dht11_gpio, 1);
udelay(20); // 等待20微秒
}
延时函数区别:
-
mdelay(2):毫秒级延时,2ms
-
msleep(20):进程睡眠20ms,可被调度
-
udelay(20):微秒级延时,20us,忙等待
时序要求:
-
高电平稳定:>1ms
-
开始信号低电平:18-30ms
-
释放后等待:20-40us
2. 等待DHT11响应
static int dht11_wait_respon(void)
{
int time = 0;
// 1. 切换为输入模式
gpio_direction_input(dht11_gpio);
// 2. 等待DHT11拉低总线(响应信号开始)
time = 30;
while(gpio_get_value(dht11_gpio) && time--)
{
udelay(1);
}
if(time < 0) return -1; // 超时错误
// 3. 等待低电平结束(响应信号持续)
time = 100;
while((!gpio_get_value(dht11_gpio)) && time--)
{
udelay(1);
}
if(time < 0) return -2; // 超时错误
// 4. 等待高电平结束(准备发送数据)
time = 100;
while(gpio_get_value(dht11_gpio) && time--)
{
udelay(1);
}
if(time < 0) return -3; // 超时错误
return 0; // 响应正常
}
响应信号时序:
主机发送开始信号后释放总线
↓
DHT11拉低总线80us(响应开始)
↓
DHT11拉高总线80us(响应结束)
↓
开始发送40位数据
3. 读取单个数据位
static char dht11_get_bit(void)
{
int time = 60;
// 1. 等待高电平开始(数据位开始)
while(gpio_get_value(dht11_gpio) && time--)
{
udelay(1);
}
if(time < 0) return -4;
// 2. 等待低电平结束(数据位持续)
time = 80;
while((!gpio_get_value(dht11_gpio)) && time--)
{
udelay(1);
}
if(time < 0) return -5;
// 3. 延迟35us,在高电平中间采样
udelay(35);
// 4. 判断数据位是0还是1
if(!gpio_get_value(dht11_gpio))
return 0; // 低电平:数据0
// 5. 等待高电平结束(数据1)
time = 50;
while(gpio_get_value(dht11_gpio) && time--)
{
udelay(1);
}
if(time < 0) return -6;
return 1; // 高电平:数据1
}
数据位识别:
数据0:50us低电平 + 26-28us高电平
↓ 采样点在35us时,读到低电平
数据1:50us低电平 + 70us高电平
↓ 采样点在35us时,读到高电平
4. 读取完整数据
static int dht11_read_data(unsigned char * data)
{
int i = 0, j = 0;
// 读取5个字节(40位)
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; // 左移1位
data[j] |= bit; // 低位填入新位
}
}
return 5; // 返回读取的字节数
}
数据组装过程:
第1位:bit=1 → data=00000000 << 1 = 00000000, |=1 → 00000001 第2位:bit=0 → data=00000001 << 1 = 00000010, |=0 → 00000010 第3位:bit=1 → data=00000010 << 1 = 00000100, |=1 → 00000101 ... 最终得到完整的8位数据
四、驱动核心操作
1. 读取函数完整流程
c
static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff)
{
int ret = 0;
unsigned char data[5];
// 1. 发送开始信号
dht11_start();
// 2. 等待DHT11响应
ret = dht11_wait_respon();
if(ret < 0)
return ret; // 响应超时
// 3. 读取5字节数据
ret = dht11_read_data(data);
if(ret < 0)
return ret; // 数据读取错误
// 4. 将数据复制到用户空间
ret = copy_to_user(buf, data, sizeof(data));
printk("dht11 read\n");
return 5; // 返回读取的字节数
}
2. Probe函数(设备探测)
static int probe(struct platform_device * pdev)
{
struct device_node * pdts;
// 1. 注册杂项设备
int ret = misc_register(&misc_dev);
if(ret) goto err_misc_register;
// 2. 查找设备树节点
pdts = of_find_node_by_path("/dht11");
if(NULL == pdts) {
ret = PTR_ERR(pdts);
goto err_of_find;
}
// 3. 获取GPIO编号
dht11_gpio = of_get_named_gpio(pdts, "ptdht11-gpio", 0);
if(dht11_gpio < 0) {
ret = dht11_gpio;
goto err_of_find;
}
// 4. 申请GPIO
ret = gpio_request(dht11_gpio, "dht11");
if(ret < 0) goto err_gpio_request;
// 5. 初始化为输出,高电平
gpio_direction_output(dht11_gpio, 1);
printk("######################### dht11_driver probe\n");
return 0;
// 错误处理标签
err_gpio_request:
err_of_find:
err_misc_register:
misc_deregister(&misc_dev);
return ret;
}
错误处理流程:标签从下往上排列,错误发生时跳到对应标签,逐级释放已申请的资源
五、应用程序分析
1. 应用程序代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
// 1. 打开设备
int fd = open("/dev/dht11", O_RDWR);
if(fd < 0)
{
perror("open dht11 failed");
return 1;
}
// 2. 循环读取数据
while(1)
{
unsigned char data[5] = {0};
int ret = read(fd, data, sizeof(data));
if(ret < 0)
{
printf("ret = %d\n", ret); // 打印错误码
}
// 3. 打印原始数据(十六进制)
int i = 0;
for(i = 0; i < 5; i++)
{
printf("%#x\t", data[i]); // %#x:带0x前缀的十六进制
}
printf("\n");
sleep(3); // DHT11读取间隔至少1秒
}
close(fd);
return 0;
}
2. 应用程序执行流程
1. open("/dev/dht11")
↓
2. 驱动open函数(仅打印"dht11 open")
↓
3. read(fd, data, 5)
↓
4. 驱动read函数
├─ dht11_start() ← 发送开始信号
├─ dht11_wait_respon() ← 等待响应
├─ dht11_read_data() ← 读取40位数据
└─ copy_to_user() ← 返回数据给应用程序
↓
5. 打印数据(十六进制格式)
↓
6. sleep(3) 等待
↓
7. 重复步骤3-6
六、错误码说明
| 错误码 | 发生位置 | 含义 |
|---|---|---|
| -1 | wait_respon | 等待DHT11拉低总线超时(30us内无响应) |
| -2 | wait_respon | 等待低电平结束超时(100us内未结束) |
| -3 | wait_respon | 等待高电平结束超时(100us内未结束) |
| -4 | get_bit | 等待数据位高电平开始超时 |
| -5 | get_bit | 等待数据位低电平结束超时 |
| -6 | get_bit | 等待数据1高电平结束超时 |
七、U-Boot启动命令配置
1. 环境变量操作
bash
# 打印所有环境变量 printenv # 设置环境变量(字符串类型) setenv bootcmd_bk '原有bootcmd命令' # 删除环境变量(设为空值) setenv bootcmd_bk # 保存环境变量到MMC saveenv
2. 网络启动内核配置
bash
# 设置TFTP下载并启动内核
setenv bootcmd 'tftp 0x80800000 zImage; \
tftp 0x83000000 pt.dtb; \
bootz 0x80800000 - 0x83000000'
# 命令解析:
# tftp 0x80800000 zImage ← 从TFTP服务器下载zImage到内存0x80800000
# tftp 0x83000000 pt.dtb ← 下载设备树到内存0x83000000
# bootz 0x80800000 - 0x83000000 ← 启动内核(zImage地址 - dtb地址)
内存地址说明:
-
0x80800000:内核zImage加载地址
-
0x83000000:设备树dtb加载地址
-
-:表示没有initrd(初始RAM磁盘)
八、数据解析示例
原始数据输出
text
0x1c 0x00 0x1e 0x00 0x3a
解析过程
| 字节 | 十六进制 | 十进制 | 含义 |
|---|---|---|---|
| data[0] | 0x1c | 28 | 湿度整数部分:28% |
| data[1] | 0x00 | 0 | 湿度小数部分:0% |
| data[2] | 0x1e | 30 | 温度整数部分:30°C |
| data[3] | 0x00 | 0 | 温度小数部分:0°C |
| data[4] | 0x3a | 58 | 校验和 |
校验验证:
text
28 + 0 + 30 + 0 = 58 ✓ 校验通过
实际温湿度:
-
湿度:28.0% RH
-
温度:30.0°C
九、驱动框架总结
1. 驱动分层结构
text
应用程序 (app)
↓
文件操作接口 (open/read/close)
↓
DHT11设备驱动 (dht11.c)
├─ 开始信号 (dht11_start)
├─ 等待响应 (dht11_wait_respon)
├─ 读取位数据 (dht11_get_bit)
└─ 读取完整数据 (dht11_read_data)
↓
GPIO子系统 (gpio_request/set_value/get_value)
↓
硬件引脚 (GPIO1_IO02)
↓
DHT11传感器
2. 关键机制
| 机制 | 作用 | 实现函数 |
|---|---|---|
| 平台驱动 | 设备树匹配 | platform_driver_register() |
| 杂项设备 | 简化字符设备注册 | misc_register() |
| GPIO控制 | 引脚电平操作 | gpio_set_value()/gpio_get_value() |
| 精确延时 | 微秒级时序控制 | udelay()/mdelay()/msleep() |
| 数据拷贝 | 内核-用户空间数据交换 | copy_to_user() |
3. 时序关键点
-
开始信号低电平:20ms(msleep)
-
响应等待超时:30us/100us
-
位采样点:35us
-
读取间隔:≥1秒(sleep(3))
总结:
-
DHT11通信:单总线协议,严格的微秒级时序要求
-
驱动核心:精确控制GPIO电平变化和延时,模拟DHT11通信时序
-
设备树配置:定义GPIO引脚和匹配属性,完成硬件描述
-
应用程序:通过标准文件操作接口读取温湿度原始数据
-
错误处理:完善的超时检测和错误返回机制
-
启动配置:U-Boot环境变量设置,网络启动内核
DHT11驱动是单总线设备驱动的典型实现,展示了如何在Linux驱动中使用GPIO模拟复杂通信时序,以及如何通过设备树完成硬件配置。
更多推荐



所有评论(0)