若该文为原创文章,转载请注明原文出处。
记录开发单总线,读取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_COMPILECURRENT_PATH := $(shell pwd)
obj-m := drv_dht11.obuild: kernel_moduleskernel_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
测试虽然有读取到温湿度,也有变化,但驱动不是很完美,只限测试使用。
如有侵权,或需要完整代码,请及时联系博主。