Linux platform tree下的单总线驱动程序设计(DHT11)

2024-02-15 17:12

本文主要是介绍Linux platform tree下的单总线驱动程序设计(DHT11),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

概述

1 认识DHT11

1.1 DHT11特性

1.2 DHT11数据格式

1.3 DHT11与MCU通信

1.4 DHT11信号解析

1.4.1 起始信号

1.4.2 解析信号0

1.4.3 解析信号1

2 驱动开发

2.1 硬件接口

2.2 更新设备树

2.2.1 添加驱动节点

2.2.2 编译.dts

2.2.3 更新板卡中的.dtb

2.3 驱动程序实现

2.3.1 编写驱动程序

 2.3.2 编写Makefile 

3 测试程序

3.1 编写测试程序

3.2 编写Makefile

4 编译和运行

4.1 编译和安装驱动程序

4.2 编译和运行测试程序

5 波形分析

5.1 起始信号波形

5.2 信息bit = 0波形

5.3 信息bit = 1波形


概述

        本文介绍platform tree下,如何设计一个单总线设备的驱动,根据datasheeet提供的波形图,使用代码来实现该驱动程序。然后用逻辑分析仪捕捉信号波形,分析其是否和datasheet中的波形一致。

1 认识DHT11

1.1 DHT11特性

        DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。 它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。

由上表可得:

温度范围: 0~ 50℃, 低于或者高于这个范围的温度不能测量

湿度范围:20~90%RH

1.2 DHT11数据格式

       DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,读一次数据总共包括8bytes( 40 bit )具体格式如下:

Byte-0: 8bit 湿度 整数 数据

Byte-1: 8bit 湿度 小数 数据

Byte-2: 8bit 温度 整数 数据

Byte-3: 8bit 温度 小数 数据

byte-4: 8bit校验和( Byte-0 + Byte-1 + Byte-2 + Byte-3)

1.3 DHT11与MCU通信

Step-1: Master 发送起始信号------->dth11, 信号变化规律为 1 - > 0 -> 1

Step-2: dht11发出响应信号,信号特征为 0 ->1

Step-3:dht11发送数据bit位,总共40个bit

1.4 DHT11信号解析

1.4.1 起始信号

Step-1: Master 发出触发信号:1 -> 0, 该信号至少持续18ms

step-2: Master电平0 ->1,该电平持续20~40us

Step-3: dht11发送响应信号0->1,该电平持续80us

step-4: dht11发送信号1,准备发送数据信息,该电平持续时间80us

1.4.2 解析信号0

信号0特征:

1)0 ->1持续 50us

2)1->0持续26~28us

1.4.3 解析信号1

信号1特征:

1)0 ->1持续 50us

2)1->0持续70us

2 驱动开发

2.1 硬件接口

DHT-11与MCU之间的连接图:

在板卡ATK-DL6Y2C上DTH-11的对应接口:

GPIO4_19:  DHT11-IO  

硬件实物图:

DHT11引脚说明:

2.2 更新设备树

2.2.1 添加驱动节点

DHT11引脚和IMX.6ULL引脚对应关系:

GPIO4_19:  DHT11-IO  

.dts文件路径:

/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c/arch/arm/boot/dts/imx6ull-14x14-evk.dts

 在.dts文件中添加如下代码:

  //mftang: user's dht11, 2024-2-14// IO: GPIO-4-PIN19mftangdht11 {compatible = "atk-dl6y2c,dht11";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_mftang_1_wire>;gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;status = "okay";};

其在imx6ull-14x14-evk.dts中位置:

2.2.2 编译.dts

编译.dts文件,并把编译生成的.dtb文件发送到NFS共享目录下,便于在板卡中操作该文件。

1)在内核根目录下使用如下命令编译.dts文件

make dtbs

2) 复制 .dtb 文件至NFS共享目录

cp arch/arm/boot/dts/imx6ull-14x14-emmc-4.3-480x272-c.dtb  /home/mftang/nfs/atk_dl6y2c/

2.2.3 更新板卡中的.dtb

复制.dtb文件到相应的运行目录,然后重新板卡

cp /mnt/atk_dl6y2c/imx6ull-14x14-emmc-4.3-480x272-c.dtb /run/media/mmcblk1p1

       reboot板卡后,内核会重新读取.dtb文件。然后在/proc/device-tree目录下查看板卡device tree,使用如下命令:

cd /proc/device-tree 
ls -l

 运行该命令后,在该目录下可以看见sensor信息,说明device已经加载到内核:

2.3 驱动程序实现

2.3.1 编写驱动程序

创建drv_dht11.c,并在该文件中编写驱动程序,驱动程序代码地址

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     : drv_14_dht11.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : dht11 驱动程序, GPIO4_PIN19-----DHT11 IO port
其他       : 无
日志       : 初版V1.0 2024/1/30  使用方法:
1) 在.dts文件中定义节点信息//mftang: user's dht11, 2024-2-14// IO: GPIO-4-PIN19mftangdht11 {compatible = "atk-dl6y2c,dht11";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_mftang_1_wire>;gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;status = "okay";};2) 在驱动匹配列表 
static const struct of_device_id dht11_of_match[] = {{ .compatible = "atk-dl6y2c,dht11" },{ } // Sentinel
};
***************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ktime.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 DEVICE_NAME      "treedht11"     // dev/treedht11/* dht11dev设备结构体 */
struct dht11stru_dev{dev_t   devid;                /* 设备号         */struct  cdev cdev;            /* cdev           */struct  class *class;         /* 类             */struct  device *device;       /* 设备           */int     major;                /* 主设备号       */struct  device_node *node;    /* dht11设备节点 */int     userdht11;            /* dht11 GPIO标号*/struct  gpio_desc *pin;
};struct dht11stru_dev dht11dev;    /* dht11设备 */ int us_low_array[40];
int us_low_index;
int us_array[40];
int time_array[40];
int us_index;/*dht11 driver 
*/
static void dht11_release( void )
{gpiod_direction_output(dht11dev.pin, 1);
}static void dht11_start(void)
{gpiod_direction_output(dht11dev.pin, 1);mdelay(30);gpiod_set_value( dht11dev.pin, 0);mdelay(20);gpiod_set_value(dht11dev.pin, 1);udelay(40);gpiod_direction_input(dht11dev.pin);
}static int dht11_wait_ack(void)
{int timeout_us = 20000;/* 等待低电平 */while (gpiod_get_value(dht11dev.pin) && --timeout_us){udelay(1);}if (!timeout_us){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}/* 现在是低电平 *//* 等待高电平 */timeout_us = 200;while (!gpiod_get_value(dht11dev.pin) && --timeout_us){udelay(1);}if (!timeout_us){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}/* 现在是高电平 *//* 等待低电平 */timeout_us = 200;while (gpiod_get_value(dht11dev.pin) && --timeout_us){udelay(1);}if (!timeout_us){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}return 0;
}static int dht11_read_byte( unsigned char *datalist )
{int i;int us = 0;unsigned char data = 0;int timeout_us = 200;u64 pre, last;for (i = 0; i < 8; i++){/* 现在是低电平 *//* 等待高电平 */timeout_us = 400;us = 0;while (!gpiod_get_value(dht11dev.pin) && --timeout_us){udelay(1);us++;}if (!timeout_us){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}us_low_array[us_low_index++] = us;/* 现在是高电平 *//* 等待低电平,累加高电平的时间 */timeout_us = 20000000;us = 0;/* set another gpio low  */pre = ktime_get_boot_ns();while (1) {last = ktime_get_boot_ns();if (last - pre >= 40000)break;}if (gpiod_get_value(dht11dev.pin)){/* get bit 1 */data = (data << 1) | 1;/* 当前位的高电平未结束, 等待 */timeout_us = 400;us = 0;while (gpiod_get_value(dht11dev.pin) && --timeout_us){udelay(1);us++;}if (!timeout_us){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}}else{/* get bit 0 */data = (data << 1) | 0;}}*datalist = data;return 0;
}static int dht11_get_value( unsigned char *data )
{unsigned long flags;int i;local_irq_save(flags);  // 关中断us_index = 0;us_low_index = 0;/* 1. 发送高脉冲启动DHT11 */dht11_start();/* 2. 等待DHT11就绪 */if (dht11_wait_ack()){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;}}/* 4. 释放总线 */dht11_release();local_irq_restore(flags); // 恢复中断/* 5. 根据校验码验证数据 */if (data[4] != (data[0] + data[1] + data[2] + data[3])){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return -1;}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)
{unsigned char data[4];int err;if( !dht11_get_value( data ) ){printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);err = copy_to_user(buf, data, 4);return 4;}return -1;
}static int dht11_drv_close(struct inode *node, struct file *file)
{printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);return 0;
}static int dht11_drv_open(struct inode *inode, struct file *filp)
{filp->private_data = &dht11dev; /* 设置私有数据  */return 0;
}/* 定义driver的file_operations结构体
*/
static struct file_operations dht11_fops = {.owner   = THIS_MODULE,.read    = dht11_drv_read,.open    = dht11_drv_open,.release = dht11_drv_close,
};/* 1. 从platform_device获得GPIO* 2. gpio=>irq* 3. request_irq*/
static int dht11_probe(struct platform_device *pdev)
{printk("dht11 driver and device was matched!\r\n");/* 1. 获得硬件信息 */dht11dev.pin = gpiod_get(&pdev->dev, NULL, 0);if (IS_ERR(dht11dev.pin)){printk("%s line %d get pin parameter error! \n", __FUNCTION__, __LINE__);}/* 2. device_create */device_create( dht11dev.class, NULL, MKDEV( dht11dev.major, 0 ), NULL, DEVICE_NAME);        // device name return 0;
}static int dht11_remove(struct platform_device *pdev)
{printk("%s line %d\n", __FUNCTION__, __LINE__);device_destroy( dht11dev.class, MKDEV( dht11dev.major, 0));gpiod_put(dht11dev.pin);return 0;
}static const struct of_device_id atk_dl6y2c_dht11[] = {{ .compatible = "atk-dl6y2c,dht11" },{ },
};/* 1. 定义platform_driver */
static struct platform_driver dht11_driver = {.probe      = dht11_probe,.remove     = dht11_remove,.driver     = {.name   = "atk_dht11",.of_match_table = atk_dl6y2c_dht11,},
};/* 2. 在入口函数注册platform_driver 
*/
static int __init dht11_init(void)
{int err;printk("%s line %d\n",__FUNCTION__, __LINE__);/* register file_operations  */dht11dev.major = register_chrdev( 0, DEVICE_NAME,     /* device name */&dht11_fops);  /* create the device class  */dht11dev.class = class_create(THIS_MODULE, "dht11_class");if (IS_ERR(dht11dev.class)) {printk("%s line %d\n", __FUNCTION__, __LINE__);unregister_chrdev( dht11dev.major, DEVICE_NAME);return PTR_ERR( dht11dev.class );}err = platform_driver_register(&dht11_driver); return err;
}/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*    卸载platform_driver*/
static void __exit dht11_exit(void)
{printk("%s line %d\n", __FUNCTION__, __LINE__);platform_driver_unregister(&dht11_driver);class_destroy(dht11dev.class);unregister_chrdev(dht11dev.major, DEVICE_NAME);
}/*4. 驱动入口和出口函数
*/
module_init(dht11_init);
module_exit(dht11_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("tangmingfei2013@126.com");

 2.3.2 编写Makefile 

在驱动程序同级目录中创建Makefile,然后编写如下代码

PWD := $(shell pwd)KERNEL_DIR=/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c
ARCH=arm
CROSS_COMPILE=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-export  ARCH  CROSS_COMPILEobj-m:= drv_14_dht11.oall:$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modulesclean:rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *.symvers

3 测试程序

3.1 编写测试程序

编写一个测试程序,目的是验证驱动程序是否能正常工作

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     : test_14_dht11.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : 测试dth11驱动程序
其他       : 无
日志       : 初版V1.0 2024/02/15***************************************************************/
#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/treedht11"int main(void)
{int fd;int count_run = 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 < 10000){count_run++;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 {perror("read dht11 device fail!\n");}sleep(1);}close(fd);return 0;
}

3.2 编写Makefile

CFLAGS= -Wall -O2
CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-striptest_14_dht11: test_14_dht11.o$(CC) $(CFLAGS) -o test_14_dht11 test_14_dht11.o$(STRIP) -s test_14_dht11clean:rm -f test_14_dht11 test_14_dht11.o

4 编译和运行

4.1 编译和安装驱动程序

1) 编译驱动程序,并将其copy到NFS的共享目录中,方便在板卡中安装该程序

2) 在板卡中安装该驱动程序 , 使用命令

insmod dev_14_dth11.ko

安装成功后,使用命令查看驱动

ls /dev -l

4.2 编译和运行测试程序

1) 编译测试程序,并将其copy到NFS的共享目录中,方便在板卡中运行该程序

2)在板卡中运行测试程序

5 波形分析

在板卡上运行测试程序,然后使用逻辑分析仪捕捉DHT11-IO上的波形,分析其信号特征,以更好的理解驱动程序。

5.1 起始信号波形

datasheet 上提供的波形

逻辑分析仪上捕捉的波形:

查看电平持续时间:

5.2 信息bit = 0波形

datasheet 上提供的波形

逻辑分析仪上捕捉的波形:

5.3 信息bit = 1波形

datasheet 上提供的波形

逻辑分析仪上捕捉的波形:

这篇关于Linux platform tree下的单总线驱动程序设计(DHT11)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/712011

相关文章

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用