零、概述

记录在linxu上不同的驱动DHT11的方法。

一、使用平台驱动platform_driver 来注册设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static const struct of_device_id dht11_of_match[] = {
{ .compatible = "alientek,dht11" },
{ /* Sentinel */ }
};

static struct platform_driver dht11_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "dht11",
.of_match_table = dht11_of_match,
},
.probe = dht11_probe,
.remove = dht11_remove,
};

module_platform_driver(dht11_driver);

MODULE_LICENSE("GPL");

二、完成probe和remove函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
* @description : 驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - pdev : pdev设备
* @return : 0表示转换成功,其它值表示转换失败
*/
static int dht11_probe(struct platform_device *pdev)
{
struct miscdevice *mdev;
int ret;

dev_info(&pdev->dev, "dht11 device and driver matched successfully!\n");

ret = dht11_request_gpio(pdev);
if (ret)
return ret;

/* 初始化 MISG设备 */
mdev = &dht11_device.mdev;
mdev->name = "dht11";
mdev->minor = MISC_DYNAMIC_MINOR;
mdev->fops = &dht11_fops;

/* 初始化定时器 */
timer_setup(&dht11_device.timer, dht11_timer_callback, 0);
dht11_device.timer.expires=jiffies + msecs_to_jiffies(1500);
add_timer(&dht11_device.timer);

/* 初始化工作队列 */
INIT_WORK(&dht11_device.work, dht11_work_callback);

/* MISG 设备注册 */
return misc_register(mdev);
}

/*
* @description : 驱动的remove函数,移除驱动的时候此函数会执行
* @param - pdev : pdev设备
* @return : 0,成功;其他负值,失败
*/
static int dht11_remove(struct platform_device *pdev)
{
gpio_set_value(dht11_device.gpio, 0);

/* 卸载MISG设备 */
misc_deregister(&dht11_device.mdev);
/* 卸载定时器 */
del_timer(&dht11_device.timer);
/* 卸载工作队列 */
cancel_work_sync(&dht11_device.work);

dev_info(&pdev->dev, "DHT11 driver has been removed!\n");
return 0;
}

三、dht11_request_gpio中是去设备树获取节点并request请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* @description : GPIO的初始化函数
* @param pdev : platform设备
* @return : 0表示转换成功,其它值表示转换失败
*/
static int dht11_request_gpio(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;

dht11_device.gpio = of_get_named_gpio(dev->of_node, "dht11-gpio", 0);
if (!gpio_is_valid(dht11_device.gpio)) {
dev_err(dev, "Failed to get gpio");
return -EINVAL;
}

ret = devm_gpio_request(dev, dht11_device.gpio, "DHT11 Gpio");
if (ret) {
dev_err(dev, "Failed to request gpio");
return ret;
}

return 0;
}

四、初始化定时器和工作队列,使用定时器定时去调度工作队列(schedule_work)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    /* 初始化定时器 */
timer_setup(&dht11_device.timer, dht11_timer_callback, 0);
dht11_device.timer.expires=jiffies + msecs_to_jiffies(1500);
add_timer(&dht11_device.timer);

/* 初始化工作队列 */
INIT_WORK(&dht11_device.work, dht11_work_callback);

/*
* @description : 定时器的操作函数,每1s去获取一次数据
* @param - asg : 定时器的结构体
* @return : 无
*/
static void dht11_timer_callback(struct timer_list *arg)
{
schedule_work(&dht11_device.work);
mod_timer(&dht11_device.timer, jiffies + (1500 * HZ/1000));
}

/*
* @description : GPIO的初始化函数
* @param pdev : platform设备
* @return : 0表示转换成功,其它值表示转换失败
*/
static int dht11_request_gpio(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;

dht11_device.gpio = of_get_named_gpio(dev->of_node, "dht11-gpio", 0);
if (!gpio_is_valid(dht11_device.gpio)) {
dev_err(dev, "Failed to get gpio");
return -EINVAL;
}

ret = devm_gpio_request(dev, dht11_device.gpio, "DHT11 Gpio");
if (ret) {
dev_err(dev, "Failed to request gpio");
return ret;
}

return 0;
}

五、其中dht11_init是初始化函数dht11_read_byte就是主要的dht11读取函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* @description : DHT11的初始化
* @param : 无
* @return : 0,初始化成功; 其它表示失败
*/
static int dht11_init(void)
{
dht11_set_output(HIGH); /* 把拉高Duot */
udelay(30); /* 拉高30us */

dht11_set_output(LOW); /* 把拉低Duot */
mdelay(20); /* 拉低20us */

dht11_set_output(HIGH); /* 把拉高Duot */
udelay(30); /* 拉高30us */

dht11_set_input(); /* 设置Duot为输入模式 */
udelay(200); /* 延时200us */
if(!dht11_get_io()) { /* 不是高电平,DHT11就没有响应 */
return -ENODEV;
}
return 0;
}


//读取函数
/*
* @description : 读取一个字节的数据
* @param : 无
* @return : 读取到的数据
*/
static unsigned char dht11_read_byte(void)
{
unsigned char i, time = 0, data = 0;
local_irq_disable();

for(i = 0; i < 8; i++) {
time = 0;

while(dht11_get_io() == 0) {
udelay(1);
time++;
if (time > 100) {
return -EINVAL;
}
}
udelay(45); /* 延时45us */
if (dht11_get_io() == 1) { /* 获取到高电平,数据就为1,否则就是0 */
data |= 1<<(7 - i);
time = 0;
while(dht11_get_io() == 1) {
udelay(1);
time++;
if (time > 100)
return -EINVAL;
}
}
}
local_irq_enable();

return data;
}

数据发送流程

首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(20~ 40us)时间,然后读取 DHT11 的响应,正常的话,DHT11 会拉低数据线,保持 t3(40~50us)时间,作为响应信号,然后 DHT11 拉高数据线,保持 t4(40 ~50us)时间后,开始输出数据。

主机需要先发送一个开始信号给DHT11才可以开始采集数据

开始信号=18ms低电平+20-40us高电平

然后将GPIO设置成输入,等待DHT11的响应信号

DHT11响应信号=40-50us低电平+40-50us高电平

DHT11发出数据信号:

数据为0的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续50us,高脉冲持续26~28us。

数据为1的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续50us,高脉冲持续70us。

DHT11发出结束信号:

最后1bit数据传送完毕后,DHT11拉低总线50us,然后释放总线,总线由上拉电阻拉高进入空闲状态。

读到的数据格式是5个字节数据

8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和

校验和=前4个字节相加