当前位置:   article > 正文

RK3568笔记四十一:DHT11驱动开发测试

RK3568笔记四十一:DHT11驱动开发测试

若该文为原创文章,转载请注明原文出处。

记录开发单总线,读取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节点

  1. gpiodht11 {
  2. compatible = "alientek,dht11";
  3. pinctrl-names = "alientek,dht11";
  4. pinctrl-0 = <&dht11_gpio>;
  5. dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
  6. status = "okay";
  7. };

2、创建设备的 pinctrl 节点

  1. dht11-gpios{
  2. /omit-if-no-ref/
  3. dht11_gpio: dht11-pin {
  4. rockchip,pins =
  5. <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
  6. };
  7. };

修改后,重新编译内核,生成boot.img文件,重新烧写即可。

重启后会在/proc/device-tree下找到gpiodht11节点.

四、驱动编写

1、drv_dht11.c

  1. /*
  2. dht11-gpios{
  3. /omit-if-no-ref/
  4. dht11_gpio: dht11-pin {
  5. rockchip,pins =
  6. <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
  7. };
  8. };
  9. gpiodht11 {
  10. compatible = "alientek,dht11";
  11. pinctrl-names = "alientek,dht11";
  12. pinctrl-0 = <&dht11_gpio>;
  13. dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
  14. status = "okay";
  15. };
  16. */
  17. #include <linux/types.h>
  18. #include <linux/kernel.h>
  19. #include <linux/delay.h>
  20. #include <linux/ide.h>
  21. #include <linux/init.h>
  22. #include <linux/module.h>
  23. #include <linux/errno.h>
  24. #include <linux/gpio.h>
  25. #include <linux/cdev.h>
  26. #include <linux/device.h>
  27. #include <linux/of_gpio.h>
  28. #include <linux/semaphore.h>
  29. #include <linux/timer.h>
  30. #include <linux/irq.h>
  31. #include <linux/wait.h>
  32. #include <linux/poll.h>
  33. #include <linux/fs.h>
  34. #include <linux/fcntl.h>
  35. #include <linux/platform_device.h>
  36. //#include <asm/mach/map.h>
  37. #include <asm/uaccess.h>
  38. #include <asm/io.h>
  39. #define DHT11DEV_CNT 1 /* 设备号长度 */
  40. #define DHT11DEV_NAME "dtsplatdht11" /* 设备名字 */
  41. /* dht11dev 设备结构体 */
  42. struct dht11dev_dev{
  43. dev_t devid; /* 设备号 */
  44. struct cdev cdev; /* cdev */
  45. struct class *class; /**/
  46. struct device *device; /* 设备 */
  47. struct device_node *node; /* dht11dev 设备节点 */
  48. int gpio_dht11; /* dht11dev 灯 GPIO 标号 */
  49. };
  50. struct dht11dev_dev dht11dev; /* dht11d ev 设备 */
  51. unsigned char getdata[4] = {0xff, 0xff, 0xff, 0xff};
  52. static void dht11_release( void )
  53. {
  54. gpio_direction_output(dht11dev.gpio_dht11, 1);
  55. }
  56. static void dht11_start(void)
  57. {
  58. gpio_direction_output(dht11dev.gpio_dht11, 1);
  59. mdelay(30);
  60. gpio_set_value( dht11dev.gpio_dht11, 0);
  61. mdelay(20);
  62. gpio_set_value(dht11dev.gpio_dht11, 1);
  63. udelay(40);
  64. gpio_direction_input(dht11dev.gpio_dht11);
  65. }
  66. static int dht11_wait_ack(void)
  67. {
  68. long timeout_us = 50000;
  69. /* 等待低电平 */
  70. while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
  71. {
  72. udelay(1);
  73. }
  74. if (!timeout_us)
  75. {
  76. return -1;
  77. }
  78. /* 现在是低电平 */
  79. /* 等待高电平 */
  80. timeout_us = 50000;
  81. while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
  82. {
  83. udelay(1);
  84. }
  85. if (!timeout_us)
  86. {
  87. return -1;
  88. }
  89. /* 现在是高电平 */
  90. /* 等待低电平 */
  91. timeout_us = 20000;
  92. while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
  93. {
  94. udelay(1);
  95. }
  96. if (!timeout_us)
  97. {
  98. return -1;
  99. }
  100. return 0;
  101. }
  102. static int dht11_read_byte( unsigned char datalist )
  103. {
  104. int i;
  105. unsigned char data = 0;
  106. int timeout_us = 20000;
  107. u64 pre = 0, last = 0;
  108. for (i = 0; i < 8; i++)
  109. {
  110. /* 现在是低电平 */
  111. /* 等待高电平 */
  112. timeout_us = 20000;
  113. while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
  114. {
  115. udelay(1);
  116. }
  117. if (!timeout_us)
  118. {
  119. return -1;
  120. }
  121. /* 现在是高电平 */
  122. /* 等待低电平,累加高电平的时间 */
  123. timeout_us = 20000000;
  124. /* set another gpio low */
  125. pre = ktime_get_boot_ns();
  126. while (1)
  127. {
  128. last = ktime_get_boot_ns();
  129. if (last - pre >= 40000)
  130. break;
  131. }
  132. if (gpio_get_value(dht11dev.gpio_dht11))
  133. {
  134. /* get bit 1 */
  135. data = (data << 1) | 1;
  136. /* 当前位的高电平未结束, 等待 */
  137. timeout_us = 20000;
  138. while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
  139. {
  140. udelay(1);
  141. }
  142. if (!timeout_us)
  143. {
  144. return -1;
  145. }
  146. }
  147. else
  148. {
  149. /* get bit 0 */
  150. data = (data << 1) | 0;
  151. }
  152. }
  153. getdata[datalist] = data;
  154. return 0;
  155. }
  156. static int dht11_get_value(void)
  157. {
  158. unsigned long flags;
  159. int i;
  160. local_irq_save(flags); // 关中断
  161. /* 1. 发送高脉冲启动DHT11 */
  162. dht11_start();
  163. /* 2. 等待DHT11就绪 */
  164. if (dht11_wait_ack())
  165. {
  166. local_irq_restore(flags); // 恢复中断
  167. return -EAGAIN;
  168. }
  169. /* 3. 读5字节数据 */
  170. for (i = 0; i < 5; i++)
  171. {
  172. dht11_read_byte(i);
  173. }
  174. /* 4. 释放总线 */
  175. dht11_release();
  176. local_irq_restore(flags); // 恢复中断
  177. /* 5. 根据校验码验证数据 */
  178. if (getdata[4] != (getdata[0] + getdata[1] + getdata[2] + getdata[3]))
  179. {
  180. getdata[0] = 0xff;
  181. getdata[1] = 0xff;
  182. getdata[2] = 0xff;
  183. getdata[3] = 0xff;
  184. return -1;
  185. }
  186. return 0;
  187. }
  188. static int dht11_gpio_init(struct device_node *nd)
  189. {
  190. int ret;
  191. /* 从设备树中获取 GPIO */
  192. dht11dev.gpio_dht11 = of_get_named_gpio(nd, "dht11-gpio", 0);
  193. if(!gpio_is_valid(dht11dev.gpio_dht11)) {
  194. printk(KERN_ERR "dht11dev: Failed to get dht11-gpio\n");
  195. return -EINVAL;
  196. }
  197. /* 申请使用 GPIO */
  198. ret = gpio_request(dht11dev.gpio_dht11, "DHT11");
  199. if (ret) {
  200. printk(KERN_ERR "dht11: Failed to request dht11-gpio\n");
  201. return ret;
  202. }
  203. /* 将 GPIO 设置为输出模式并设置 GPIO 初始电平状态 */
  204. gpio_direction_output(dht11dev.gpio_dht11,0);
  205. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  206. return 0;
  207. }
  208. /*
  209. * @description : 打开设备
  210. * @param – inode : 传递给驱动的 inode
  211. * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
  212. * 一般在 open 的时候将 private_data 指向设备结构体。
  213. * @return : 0 成功;其他 失败
  214. */
  215. static int dht11_open(struct inode *inode, struct file *filp)
  216. {
  217. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  218. return 0;
  219. }
  220. /*
  221. linux driver 驱动接口:
  222. 实现对应的open/read/write等函数,填入file_operations结构体
  223. */
  224. static ssize_t dht11_drv_read ( struct file *file, char __user *buf,
  225. size_t size, loff_t *offset)
  226. {
  227. int ret =0;
  228. if( !dht11_get_value() )
  229. {
  230. //printk("data[0] = %d, data[1] = %d, data[2] = %d, data[3] = %d\n", getdata[0], getdata[1], getdata[2], getdata[3]);
  231. ret = copy_to_user(buf, getdata, sizeof(getdata));
  232. return ret;
  233. }
  234. return ret;
  235. }
  236. /*
  237. * @description : 向设备写数据
  238. * @param - filp : 设备文件,表示打开的文件描述符
  239. * @param - buf : 要写给设备写入的数据
  240. * @param - cnt : 要写入的数据长度
  241. * @param - offt : 相对于文件首地址的偏移
  242. * @return : 写入的字节数,如果为负值,表示写入失败
  243. */
  244. static ssize_t dht11_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
  245. {
  246. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  247. return 0;
  248. }
  249. static int dht11_drv_close(struct inode *node, struct file *file)
  250. {
  251. printk(" %s line %d \r\n", __FUNCTION__, __LINE__);
  252. dht11_get_value();
  253. return 0;
  254. }
  255. /* 设备操作函数 */
  256. static struct file_operations dht11_fops = {
  257. .owner = THIS_MODULE,
  258. .open = dht11_open,
  259. .read = dht11_drv_read,
  260. .write = dht11_write,
  261. .release = dht11_drv_close,
  262. };
  263. /*
  264. * @description : flatform 驱动的 probe 函数,当驱动与
  265. * 设备匹配以后此函数就会执行
  266. * @param - dev : platform 设备
  267. * @return : 0,成功;其他负值,失败
  268. */
  269. static int dht11_probe(struct platform_device *pdev)
  270. {
  271. int ret;
  272. printk("dht11 driver and device was matched!\r\n");
  273. /* 初始化 DHT11 */
  274. ret = dht11_gpio_init(pdev->dev.of_node);
  275. if(ret < 0)
  276. return ret;
  277. /* 1、设置设备号 */
  278. ret = alloc_chrdev_region(&dht11dev.devid, 0, DHT11DEV_CNT, DHT11DEV_NAME);
  279. if(ret < 0) {
  280. pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DHT11DEV_NAME, ret);
  281. goto free_gpio;
  282. }
  283. /* 2、初始化 cdev */
  284. dht11dev.cdev.owner = THIS_MODULE;
  285. cdev_init(&dht11dev.cdev, &dht11_fops);
  286. /* 3、添加一个 cdev */
  287. ret = cdev_add(&dht11dev.cdev, dht11dev.devid, DHT11DEV_CNT);
  288. if(ret < 0)
  289. goto del_unregister;
  290. /* 4、创建类 */
  291. dht11dev.class = class_create(THIS_MODULE, DHT11DEV_NAME);
  292. if (IS_ERR(dht11dev.class)) {
  293. goto del_cdev;
  294. }
  295. /* 5、创建设备 */
  296. dht11dev.device = device_create(dht11dev.class, NULL, dht11dev.devid, NULL, DHT11DEV_NAME);
  297. if (IS_ERR(dht11dev.device)) {
  298. goto destroy_class;
  299. }
  300. return 0;
  301. destroy_class:
  302. class_destroy(dht11dev.class);
  303. del_cdev:
  304. cdev_del(&dht11dev.cdev);
  305. del_unregister:
  306. unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
  307. free_gpio:
  308. gpio_free(dht11dev.gpio_dht11);
  309. return -EIO;
  310. }
  311. /*
  312. * @description : platform 驱动的 remove 函数
  313. * @param - dev : platform 设备
  314. * @return : 0,成功;其他负值,失败
  315. */
  316. static int dht11_remove(struct platform_device *dev)
  317. {
  318. gpio_set_value(dht11dev.gpio_dht11, 0);/* 卸载驱动的时候 GPIO置0 */
  319. gpio_free(dht11dev.gpio_dht11); /* 注销 GPIO */
  320. cdev_del(&dht11dev.cdev); /* 删除 cdev */
  321. unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
  322. device_destroy(dht11dev.class, dht11dev.devid); /* 注销设备 */
  323. class_destroy(dht11dev.class); /* 注销类 */
  324. return 0;
  325. }
  326. /* 匹配列表 */
  327. static const struct of_device_id dht11_of_match[] = {
  328. {
  329. .compatible = "alientek,dht11" },
  330. { /* Sentinel */ }
  331. };
  332. MODULE_DEVICE_TABLE(of, dht11_of_match);
  333. /* platform 驱动结构体 */
  334. static struct platform_driver dht11_driver = {
  335. .driver = {
  336. .name = "rk3568-dht11", /* 驱动名字,用于和设备匹配 */
  337. .of_match_table = dht11_of_match, /* 设备树匹配表 */
  338. },
  339. .probe = dht11_probe,
  340. .remove = dht11_remove,
  341. };
  342. /*
  343. * @description : 驱动模块加载函数
  344. * @param : 无
  345. * @return : 无
  346. */
  347. static int __init dht11driver_init(void)
  348. {
  349. return platform_driver_register(&dht11_driver);
  350. }
  351. /*
  352. * @description : 驱动模块卸载函数
  353. * @param : 无
  354. * @return : 无
  355. */
  356. static void __exit dht11driver_exit(void)
  357. {
  358. platform_driver_unregister(&dht11_driver);
  359. }
  360. module_init(dht11driver_init);
  361. module_exit(dht11driver_exit);
  362. MODULE_LICENSE("GPL");
  363. MODULE_AUTHOR("yifeng");
  364. MODULE_INFO(intree, "Y");

程序是根据正点原子的注册platform平台驱动点亮LED修改,增加读功能. 这里有点需要注意,内存的使用,使用指针,不清楚为什么,会出错。

2、makefile

  1. KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
  2. ARCH=arm64
  3. CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-
  4. export ARCH CROSS_COMPILE
  5. CURRENT_PATH := $(shell pwd)
  6. obj-m := drv_dht11.o
  7. build: kernel_modules
  8. kernel_modules:
  9. $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
  10. clean:
  11. $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

直接make编译,生成ko文件,拷贝到开发板。

五、应用程序编写

  1. /***************************************************************
  2. Copyright 2024-2029. All rights reserved.
  3. 文件名 : dht11App.c
  4. 作者 : yifeng
  5. 版本 : V1.0
  6. 描述 : 测试dth11驱动程序
  7. 其他 : 无
  8. 日志 : 初版V1.0 2024/07/18
  9. ***************************************************************/
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <linux/types.h>
  13. #include <linux/i2c-dev.h>
  14. #include <linux/i2c.h>
  15. #include <sys/ioctl.h>
  16. #include <unistd.h>
  17. #include <stdio.h>
  18. #include <string.h>
  19. #include <fcntl.h>
  20. #include <stdlib.h>
  21. #include <linux/fs.h>
  22. #include <errno.h>
  23. #include <assert.h>
  24. #include <string.h>
  25. #include <time.h>
  26. #define DEV_FILE "/dev/dtsplatdht11"
  27. int main(void)
  28. {
  29. int fd;
  30. int count_run = 0;
  31. int ret = 0;
  32. unsigned char data[4];
  33. fd = open(DEV_FILE, 0);
  34. if (fd == -1){
  35. printf("can not open file: %s \n", DEV_FILE);
  36. return -1;
  37. }
  38. while( count_run < 50000)
  39. {
  40. count_run++;
  41. if (read(fd, &data[0], 4) == 0)
  42. {
  43. printf("get humidity : %d.%d\n", data[0], data[1]);
  44. printf("get temprature: %d.%d\n", data[2], data[3]);
  45. }
  46. else
  47. {
  48. printf("read dht11 device fail!\n");
  49. }
  50. sleep(1); // 1毫秒
  51. }
  52. close(fd);
  53. return 0;
  54. }

程序比较简单,5秒读一次数据,并打印。

编译:

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc dht11App.c -o dht11App

六、测试

测试使用的是正点原子的ATK-DLRK3568板子

加载驱动

insmod drv_dht11.ko

测试

./dht11App

测试虽然有读取到温湿度,也有变化,但驱动不是很完美,只限测试使用。

如有侵权,或需要完整代码,请及时联系博主。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/883086
推荐阅读
相关标签
  

闽ICP备14008679号