所学来自百问网
目录
1.DHT11 简介
DHT11 是一款可测量温度和湿度的传感器。比如市面上一些空气加湿器,会测量空气中湿度,再根据测量结果决定是否继续加湿。
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,具有超小体积、极低功耗的特点,使用单根总线与主机进行双向的串行数 据传输。DHT11测量温度的精度为±2℃,检测范围为-20℃-60℃。湿度的精度为±5%RH,检测范围为5%RH-95%RH,常用于对精度和实时性要求不高的温湿度测量场合。
2.硬件设计
主机通过一条数据线与DH11连接,主机通过这条线发命令给DHT11,DHT11再通过这条线把数据发送给主机。
3.软件设计
3.1 通信协议
主机需要先发一个开始信号DHT11,才能接收数据。
下图为一次完整的传输示例:
其中深黑色信号表示由主机驱动,即主机向 DHT11 发信号,浅灰色信号表示 DHT11 驱动,即DHT11发向主机发信号。
* 当主机没有与DHT11通信时,总线处于空闲状态,此时总线电平由于上拉电阻的作用处于高电平。
* 当主机与DHT11正在通信时,总线处于通信状态,一次完整的通信过程如下:
a) 主机将对应的GPIO管脚配置为输出,准备向DHT11发送数据;
b) 主机发送一个开始信号:
开始信号 = 一个低脉冲 + 一个高脉冲。低脉冲至少持续18ms,高脉冲持续20-40us。
c) 主机将对应的GPIO管脚配置为输入,准备接受DHT11传来的数据,这时信号由上拉电阻拉高;
d) DHT11发出响应信号:
响应信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续80us,高脉冲持续80us。
e) DHT11发出数据信号:
◼ 数据为0的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续 50us,高脉冲持续26~28us。
◼ 数据为1的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续 50us,高脉冲持续70us。
f) DHT11发出结束信号:
最后1bit数据传送完毕后,DHT11拉低总线50us,然后释放总线,总线由上拉电阻拉高进入空闲状态。
3.2 数据格式
数据传送正确时,校验和等于“8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据”所得结果的末8位。
4.示例代码
4.1 驱动代码
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
#include <linux/delay.h>
#include <linux/ktime.h>
static int major;
static struct class* dht11_class;
static struct gpio_desc *dht11_gpio_pin;
int us_array[40];
int time_array[40];
int us_index;
// 复位信号
void dht11_reset(void)
{
gpiod_direction_output(dht11_gpio_pin, 1);
}
// 起始信号
void dht11_start(void)
{
mdelay(30);
gpiod_set_value(dht11_gpio_pin, 0);
mdelay(20);
gpiod_set_value(dht11_gpio_pin, 1);
udelay(40);
// 配置引脚为输入,准备接收DHT11传来的数据
gpiod_direction_input(dht11_gpio_pin);
udelay(2);
}
// 响应信号
static int dht11_wait_for_ready(void)
{
int timeout = 200;
// 等待低电平
while(gpiod_get_value(dht11_gpio_pin) && --timeout)
{
udelay(1);
}
if(!timeout)
{
return -1;
}
// 等待高电平
while(!gpiod_get_value(dht11_gpio_pin) && --timeout)
{
udelay(1);
}
if(!timeout)
{
return -1;
}
// 等待低电平
while(gpiod_get_value(dht11_gpio_pin) && --timeout)
{
udelay(1);
}
if(!timeout)
{
return -1;
}
return 0;
}
static int dht11_read_byte(unsigned char *buf)
{
int i;
unsigned char data = 0;
int timeout_us = 200;
u64 pre, last;
int ns;
for (i = 0; i <8; i++)
{
/* 现在是低电平 */
/* 等待高电平 */
timeout_us = 400;
while (!gpiod_get_value(dht11_gpio_pin) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
/* 现在是高电平 */
/* 等待低电平,累加高电平的时间 */
timeout_us = 20000000;
pre = ktime_get_boot_ns();
while (gpiod_get_value(dht11_gpio_pin) && --timeout_us)
{
//udelay(1); /* 实际耗时: 1.6us */
//us++;
}
last = ktime_get_boot_ns();
//printk("udelay 1000 ns = %d\n", last-pre);
ns = last - pre;
if (!timeout_us)
{
return -1;
}
us_array[us_index] = ns;
time_array[us_index++] = 20000000 - timeout_us;
if (ns > 40000)
{
/* get bit 1 */
data = (data << 1) | 1;
}
else
{
/* get bit 0 */
data = (data << 1) | 0;
}
}
*buf = data;
return 0;
}
static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
unsigned long flags;
int i,err;
unsigned char data[5];
if (size != 4)
return -EINVAL;
local_irq_save(flags); // 关中断
/* 1. 发送高脉冲启动DHT11 */
dht11_reset();
dht11_start();
/* 2. 等待DHT11就绪 */
if (dht11_wait_for_ready())
{
local_irq_restore(flags); // 恢复中断
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EAGAIN;
}
/* 3. 读5字节数据 */
for (i = 0; i < 5; i++)
{
if (dht11_read_byte(&data[i]))
{
local_irq_restore(flags); // 恢复中断
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EAGAIN;
}
}
dht11_reset();
local_irq_restore(flags); // 恢复中断
/* 4. 根据校验码验证数据 */
if (data[4] != (data[0] + data[1] + data[2] + data[3]))
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
/* 5. copy_to_user */
/* data[0]/data[1] : 湿度 */
/* data[2]/data[3] : 温度 */
err = copy_to_user(buf, data, 4);
return 4;
}
static unsigned int dht11_poll (struct file *file, struct poll_table_struct *wait)
{
return 0;
}
static struct file_operations dht11_opes = {
.owner = THIS_MODULE,
.read = dht11_read,
.poll = dht11_poll,
};
int dht11_probe(struct platform_device *pdev)
{
dht11_gpio_pin = gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH);
device_create(dht11_class, NULL, MKDEV(major, 0), NULL, "mydht11");
return 0;
}
int dht11_remove(struct platform_device *pdev)
{
gpiod_put(dht11_gpio_pin);
device_destroy(dht11_class, MKDEV(major, 0));
return 0;
}
static struct of_device_id ask100_dht11[] = {
{ .compatible = "100ask,dht11" },
{},
};
static struct platform_driver dht11_drv = {
.probe = dht11_probe,
.remove = dht11_remove,
.driver = {
.name = "100ask_dht11",
.of_match_table = ask100_dht11,
},
};
static int __init dht11_init(void)
{
int err;
major = register_chrdev(0, "dht11", &dht11_opes);
dht11_class = class_create(THIS_MODULE,"dht11_class");
err = platform_driver_register(&dht11_drv);
return err;
}
static void __exit dht11_exit(void)
{
unregister_chrdev(major, "dht11");
class_destroy(dht11_class);
platform_driver_unregister(&dht11_drv);
}
module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
4.2 应用代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
#include <unistd.h>
/*
* ./dht11_test /dev/mydht11
*
*/
int main(int argc, char **argv)
{
int fd;
unsigned char data[4];
int i;
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
// fd = open(argv[1], O_RDWR | O_NONBLOCK);
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
while (1)
{
if (read(fd, data, 4) == 4)
{
printf("get humidity : %d.%d\n", data[0], data[1]);
printf("get temprature: %d.%d\n", data[2], data[3]);
}
else
{
printf("get humidity/temprature: -1\n");
}
sleep(5);
}
close(fd);
return 0;
}
4.3 Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dht11_test dht11_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dht11_test
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += dht11_drv.o