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 上提供的波形

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/470788.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

问题:内存时序参数 CASLatency 是() #学习方法#微信#微信

问题&#xff1a;内存时序参数 CASLatency 是&#xff08;&#xff09; A&#xff0e;行地址控制器延迟时间 B&#xff0e;列地址至行地址延迟时间 C&#xff0e;列地址控制器预充电时间 D&#xff0e;列动态时间 参考答案如图所示

84 CTF夺旗-PHP弱类型异或取反序列化RCE

目录 案例1&#xff1a;PHP-相关总结知识点-后期复现案例2&#xff1a;PHP-弱类型对比绕过测试-常考点案例3&#xff1a;PHP-正则preg_match绕过-常考点案例4&#xff1a;PHP-命令执行RCE变异绕过-常考点案例5&#xff1a;PHP-反序列化考题分析构造复现-常考点涉及资源&#xf…

倒模UV树脂胶制作舞台监听耳返入耳式耳机壳可行吗?

使用倒模UV树脂胶制作舞台监听耳返入耳式耳机壳是一种可行的方法&#xff0c;能够为专业或业余的音乐制作人、DJ和舞台表演者提供定制的、高品质的监听耳返体验。 以下是一些关键步骤和注意事项&#xff1a; 耳模制作&#xff1a;首先&#xff0c;为使用者制作一个精确的耳模…

操作 Docker 存储卷的常用指令汇总

1. 什么是存储卷&#xff1f; 存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。使得可以在宿主机和容器内共享数据库内容&#xff0c;让容器直接访问宿主机中的内容&#xff0c;也可以宿主机向容器写入内容&#xff0c;容…

TMGM官网平台开户运作流程如下:

TMGM官网平台开户运作流程如下&#xff1a; 首先&#xff0c;投资者需要注册并登录TMGM官网平台。在平台上&#xff0c;投资者可以选择适合自己的交易账户类型&#xff0c;包括标准账户、高级账户等。 然后&#xff0c;投资者需要进行身份验证和资金入账操作。TMGM会要求投资…

问题:从完整的问题解决过程来看,( )是首要环节。A.理解问题 B.提出假设C.发现问题 D.检验假设 #学习方法#学习方法

问题&#xff1a;从完整的问题解决过程来看&#xff0c;&#xff08; &#xff09;是首要环节。A&#xff0e;理解问题 B&#xff0e;提出假设C&#xff0e;发现问题 D&#xff0e;检验假设 A.理解问题 B.提出假设 C&#xff0e;发现问题 参考答案如图所示

leetcode刷题之或操作使用场景

文章目录 概要题目问题分析小结 概要 今天晚上上床前刷了一个leetcode的题目&#xff0c;是leetcode的2103题&#xff0c;因为是个简单题&#xff0c;我只是想复习一下hash表的用法。结果反而让我看到了或操作的使用场景。 题目 总计有 n 个环&#xff0c;环的颜色可以是红、…

黑马Java——异常、File、综合案例

目录 一、异常 1、异常的分类 1.1、Error 1.2、Exception 1.3、小结 2、编译时异常和运行时异常 2.1、编译时异常 2.2、运行时异常 2.3、为什么异常要分成编译时异常和运行时异常&#xff1f; 2.4、小结&#xff08;运行时异常和编译时异常的区别&#xff09; 3、异…

c语言(指针进阶)

指针 一.什么是字符指针二.使用指针数组模拟二维数组三.函数指针 一.什么是字符指针 字符指针&#xff1a;指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间&#xff0c;并有唯一确定的首地址。即将字符串的首地址赋值给字符指针&#xff0c;可让字符指针…

力扣---通配符匹配

题目描述&#xff1a; 给你一个输入字符串 (s) 和一个字符模式 (p) &#xff0c;请你实现一个支持 ? 和 * 匹配规则的通配符匹配&#xff1a; ? 可以匹配任何单个字符。 * 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09;。 判定匹配成功的充要条件是&#xff…

GoJS可视化JavaScript库讲解

1.简介 GoJS是一个可视化JavaScript库&#xff0c;用于浏览器中创建交互图形&#xff0c;&#xff08;比如流程图&#xff0c;树图&#xff0c;关系图&#xff0c;力导图等等&#xff09;。允许您为用户构建各种图表&#xff0c;从简单的流程图、组织图到图表、SCADA和BPMN图表…

配置 JDK 环境变量(最简单)

前言 在通过控制台使用 javac 命令编译 &#xff0c;java 命令运行 Java 程序时&#xff0c;会出现识别不了这两个命令的情况&#xff0c;如下所示&#xff1a; 这是没有配置环境变量导致的 在控制台输入的命令&#xff0c;操作系统会去一些特定的目录中去找&#xff0c;看看是…