若该文为原创文章,转载请注明原文出处。
记录开发单总线,读取DHT11温湿度
一、DHT11介绍
DHT11是串行接口(单线双向)DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分。
1、协议及数据格式
DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。
数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。
2、时序
根据时序读取DHT11流程为:
1、主机发送复位信号
2、等待DHT11 发送响应信号
3、读取数据(40bit,5个字节)
前面4字节为数据位,第5字节为校验和位
二、原理图
使用的是前面的IO口,GPIO3_PC4。IO口可以跟换,设备的节点需要对应修改。
三、创建节点
1、在设备树中创建设备节点
修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,添加gpiodht11节点
gpiodht11 {
compatible = "alientek,dht11";
pinctrl-names = "alientek,dht11";
pinctrl-0 = <&dht11_gpio>;
dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
2、创建设备的 pinctrl 节点
dht11-gpios{
/omit-if-no-ref/
dht11_gpio: dht11-pin {
rockchip,pins =
<3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
修改后,重新编译内核,生成boot.img文件,重新烧写即可。
重启后会在/proc/device-tree下找到gpiodht11节点.
四、驱动编写
1、drv_dht11.c
/*
dht11-gpios{
/omit-if-no-ref/
dht11_gpio: dht11-pin {
rockchip,pins =
<3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
gpiodht11 {
compatible = "alientek,dht11";
pinctrl-names = "alientek,dht11";
pinctrl-0 = <&dht11_gpio>;
dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DHT11DEV_CNT 1 /* 设备号长度 */
#define DHT11DEV_NAME "dtsplatdht11" /* 设备名字 */
/* dht11dev 设备结构体 */
struct dht11dev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *node; /* dht11dev 设备节点 */
int gpio_dht11; /* dht11dev 灯 GPIO 标号 */
};
struct dht11dev_dev dht11dev; /* dht11d ev 设备 */
unsigned char getdata[4] = {0xff, 0xff, 0xff, 0xff};
static void dht11_release( void )
{
gpio_direction_output(dht11dev.gpio_dht11, 1);
}
static void dht11_start(void)
{
gpio_direction_output(dht11dev.gpio_dht11, 1);
mdelay(30);
gpio_set_value( dht11dev.gpio_dht11, 0);
mdelay(20);
gpio_set_value(dht11dev.gpio_dht11, 1);
udelay(40);
gpio_direction_input(dht11dev.gpio_dht11);
}
static int dht11_wait_ack(void)
{
long timeout_us = 50000;
/* 等待低电平 */
while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
/* 现在是低电平 */
/* 等待高电平 */
timeout_us = 50000;
while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
/* 现在是高电平 */
/* 等待低电平 */
timeout_us = 20000;
while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
return 0;
}
static int dht11_read_byte( unsigned char datalist )
{
int i;
unsigned char data = 0;
int timeout_us = 20000;
u64 pre = 0, last = 0;
for (i = 0; i < 8; i++)
{
/* 现在是低电平 */
/* 等待高电平 */
timeout_us = 20000;
while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
/* 现在是高电平 */
/* 等待低电平,累加高电平的时间 */
timeout_us = 20000000;
/* set another gpio low */
pre = ktime_get_boot_ns();
while (1)
{
last = ktime_get_boot_ns();
if (last - pre >= 40000)
break;
}
if (gpio_get_value(dht11dev.gpio_dht11))
{
/* get bit 1 */
data = (data << 1) | 1;
/* 当前位的高电平未结束, 等待 */
timeout_us = 20000;
while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
}
else
{
/* get bit 0 */
data = (data << 1) | 0;
}
}
getdata[datalist] = data;
return 0;
}
static int dht11_get_value(void)
{
unsigned long flags;
int i;
local_irq_save(flags); // 关中断
/* 1. 发送高脉冲启动DHT11 */
dht11_start();
/* 2. 等待DHT11就绪 */
if (dht11_wait_ack())
{
local_irq_restore(flags); // 恢复中断
return -EAGAIN;
}
/* 3. 读5字节数据 */
for (i = 0; i < 5; i++)
{
dht11_read_byte(i);
}
/* 4. 释放总线 */
dht11_release();
local_irq_restore(flags); // 恢复中断
/* 5. 根据校验码验证数据 */
if (getdata[4] != (getdata[0] + getdata[1] + getdata[2] + getdata[3]))
{
getdata[0] = 0xff;
getdata[1] = 0xff;
getdata[2] = 0xff;
getdata[3] = 0xff;
return -1;
}
return 0;
}
static int dht11_gpio_init(struct device_node *nd)
{
int ret;
/* 从设备树中获取 GPIO */
dht11dev.gpio_dht11 = of_get_named_gpio(nd, "dht11-gpio", 0);
if(!gpio_is_valid(dht11dev.gpio_dht11)) {
printk(KERN_ERR "dht11dev: Failed to get dht11-gpio\n");
return -EINVAL;
}
/* 申请使用 GPIO */
ret = gpio_request(dht11dev.gpio_dht11, "DHT11");
if (ret) {
printk(KERN_ERR "dht11: Failed to request dht11-gpio\n");
return ret;
}
/* 将 GPIO 设置为输出模式并设置 GPIO 初始电平状态 */
gpio_direction_output(dht11dev.gpio_dht11,0);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int dht11_open(struct inode *inode, struct file *filp)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*
linux driver 驱动接口:
实现对应的open/read/write等函数,填入file_operations结构体
*/
static ssize_t dht11_drv_read ( struct file *file, char __user *buf,
size_t size, loff_t *offset)
{
int ret =0;
if( !dht11_get_value() )
{
//printk("data[0] = %d, data[1] = %d, data[2] = %d, data[3] = %d\n", getdata[0], getdata[1], getdata[2], getdata[3]);
ret = copy_to_user(buf, getdata, sizeof(getdata));
return ret;
}
return ret;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t dht11_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int dht11_drv_close(struct inode *node, struct file *file)
{
printk(" %s line %d \r\n", __FUNCTION__, __LINE__);
dht11_get_value();
return 0;
}
/* 设备操作函数 */
static struct file_operations dht11_fops = {
.owner = THIS_MODULE,
.open = dht11_open,
.read = dht11_drv_read,
.write = dht11_write,
.release = dht11_drv_close,
};
/*
* @description : flatform 驱动的 probe 函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - dev : platform 设备
* @return : 0,成功;其他负值,失败
*/
static int dht11_probe(struct platform_device *pdev)
{
int ret;
printk("dht11 driver and device was matched!\r\n");
/* 初始化 DHT11 */
ret = dht11_gpio_init(pdev->dev.of_node);
if(ret < 0)
return ret;
/* 1、设置设备号 */
ret = alloc_chrdev_region(&dht11dev.devid, 0, DHT11DEV_CNT, DHT11DEV_NAME);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DHT11DEV_NAME, ret);
goto free_gpio;
}
/* 2、初始化 cdev */
dht11dev.cdev.owner = THIS_MODULE;
cdev_init(&dht11dev.cdev, &dht11_fops);
/* 3、添加一个 cdev */
ret = cdev_add(&dht11dev.cdev, dht11dev.devid, DHT11DEV_CNT);
if(ret < 0)
goto del_unregister;
/* 4、创建类 */
dht11dev.class = class_create(THIS_MODULE, DHT11DEV_NAME);
if (IS_ERR(dht11dev.class)) {
goto del_cdev;
}
/* 5、创建设备 */
dht11dev.device = device_create(dht11dev.class, NULL, dht11dev.devid, NULL, DHT11DEV_NAME);
if (IS_ERR(dht11dev.device)) {
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(dht11dev.class);
del_cdev:
cdev_del(&dht11dev.cdev);
del_unregister:
unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
free_gpio:
gpio_free(dht11dev.gpio_dht11);
return -EIO;
}
/*
* @description : platform 驱动的 remove 函数
* @param - dev : platform 设备
* @return : 0,成功;其他负值,失败
*/
static int dht11_remove(struct platform_device *dev)
{
gpio_set_value(dht11dev.gpio_dht11, 0);/* 卸载驱动的时候 GPIO置0 */
gpio_free(dht11dev.gpio_dht11); /* 注销 GPIO */
cdev_del(&dht11dev.cdev); /* 删除 cdev */
unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
device_destroy(dht11dev.class, dht11dev.devid); /* 注销设备 */
class_destroy(dht11dev.class); /* 注销类 */
return 0;
}
/* 匹配列表 */
static const struct of_device_id dht11_of_match[] = {
{
.compatible = "alientek,dht11" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dht11_of_match);
/* platform 驱动结构体 */
static struct platform_driver dht11_driver = {
.driver = {
.name = "rk3568-dht11", /* 驱动名字,用于和设备匹配 */
.of_match_table = dht11_of_match, /* 设备树匹配表 */
},
.probe = dht11_probe,
.remove = dht11_remove,
};
/*
* @description : 驱动模块加载函数
* @param : 无
* @return : 无
*/
static int __init dht11driver_init(void)
{
return platform_driver_register(&dht11_driver);
}
/*
* @description : 驱动模块卸载函数
* @param : 无
* @return : 无
*/
static void __exit dht11driver_exit(void)
{
platform_driver_unregister(&dht11_driver);
}
module_init(dht11driver_init);
module_exit(dht11driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yifeng");
MODULE_INFO(intree, "Y");
程序是根据正点原子的注册platform平台驱动点亮LED修改,增加读功能. 这里有点需要注意,内存的使用,使用指针,不清楚为什么,会出错。
2、makefile
KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-
export ARCH CROSS_COMPILE
CURRENT_PATH := $(shell pwd)
obj-m := drv_dht11.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
直接make编译,生成ko文件,拷贝到开发板。
五、应用程序编写
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : dht11App.c
作者 : yifeng
版本 : V1.0
描述 : 测试dth11驱动程序
其他 : 无
日志 : 初版V1.0 2024/07/18
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#define DEV_FILE "/dev/dtsplatdht11"
int main(void)
{
int fd;
int count_run = 0;
int ret = 0;
unsigned char data[4];
fd = open(DEV_FILE, 0);
if (fd == -1){
printf("can not open file: %s \n", DEV_FILE);
return -1;
}
while( count_run < 50000)
{
count_run++;
if (read(fd, &data[0], 4) == 0)
{
printf("get humidity : %d.%d\n", data[0], data[1]);
printf("get temprature: %d.%d\n", data[2], data[3]);
}
else
{
printf("read dht11 device fail!\n");
}
sleep(1); // 1毫秒
}
close(fd);
return 0;
}
程序比较简单,5秒读一次数据,并打印。
编译:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc dht11App.c -o dht11App
六、测试
测试使用的是正点原子的ATK-DLRK3568板子
加载驱动
insmod drv_dht11.ko
测试
./dht11App
测试虽然有读取到温湿度,也有变化,但驱动不是很完美,只限测试使用。
如有侵权,或需要完整代码,请及时联系博主。