【IMX6ULL驱动开发学习】06.DHT11温湿度传感器驱动程序编写与测试

目录

一、DHT11简介

1.1 DHT11模块硬件设计

1.2 DHT11模块软件设计

1.3 DHT11通讯协议

1.4 DHT11数据格式

二、相关代码

2.1 驱动代码

2.2 测试代码

2.3 上板子测试


一、DHT11简介

DHT11是一款可测量温度和湿度的传感器。比如市面上一些空气加湿器,会测量空气中湿度,再根据测量结果决定是否继续加湿。
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,具有超小体积、极低功耗的特点,使用单根总线与主机进行双向的串行数据传输。DHT11测量温度的精度为± 2℃,检测范围为-20℃ -60℃。湿度的精度为± 5%RH,检测范围为 5%RH-95%RH,常用于对精度和实时性要求不高的温湿度测量场合。

1.1 DHT11模块硬件设计

主机通过一条数据线与DH11连接,主机通过这条线发命令给DHT11,DHT11再通过这条线把数据发送给主机。

1.2 DHT11模块软件设计

DHT11的硬件电路比较简单,核心要点就是:主机发给DHT11的命令格式和DHT11返回的数据格式。

1.3 DHT11通讯协议

通讯过程如图所示:

 

当主机没有与 DHT11 通信时,总线处于空闲状态,此时总线电平由于上拉电阻的作用处于高电平

当主机与 DHT11 正在通信时,总线处于通信状态,一次完整的通信过程如下:

a) 主机将对应的 GPIO 管脚配置为输出,准备向 DHT11 发送数据;

b)主机发送一个开始信号:开始信号 = 一个低脉冲 + 一个高脉冲。低脉冲至少持续 18ms,高脉冲持续 20-40us。

c) 主机将对应的 GPIO 管脚配置为输入,准备接受 DHT11 传来的数据,这时信号由上拉电阻拉高;

d) DHT11 发出响应信号:响应信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续 80us,高脉冲持续 80us。
e) DHT11 发出数据信号:

  • 数据为 0 的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续50us,高脉冲持续 26~28us。
  • 数据为 1 的一位信号 = 一个低脉冲 + 一个高脉冲。低脉冲持续50us,高脉冲持续 70us。

f) DHT11 发出结束信号: 最后 1bit 数据传送完毕后, DHT11 拉低总线 50us,然后释放总线,总线由上拉电阻拉高进入空闲状态

1.4 DHT11数据格式

 8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bi 温度整数数据 + 8bit 温度小数数据 + 8bit 校验和。数据传送正确时,校验和等于“8bit 湿度整数数据+8bit 湿度小数数据+8bi温度整数数据+8bit 温度小数数据”所得结果的末 8 位。

二、相关代码

2.1 驱动代码

#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "linux/jiffies.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[] = {
    {115, 0, "dht11", },
};

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

static u64 g_dht11_irq_time[84];
static int g_dht11_irq_cnt = 0;

/* 环形缓冲区 */
#define BUF_LEN 128
static char g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

static irqreturn_t dht11_isr(int irq, void *dev_id);
static void parse_dht11_datas(void);

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(char key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static char get_key(void)
{
	char key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	// 解析数据, 放入环形buffer, 唤醒APP
	parse_dht11_datas();
}


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kern_buf[2];

	if (size != 2)
		return -EINVAL;

	g_dht11_irq_cnt = 0;

	/* 1. 发送18ms的低脉冲 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	gpio_direction_output(gpios[0].gpio, 0);
	gpio_free(gpios[0].gpio);

	mdelay(18);
	/* 引脚变为输入方向, 由上拉电阻拉为1,*/
	/* 当主机没有与DHT11通信时,总线处于空闲状态,
	此时总线电平由于上拉电阻的作用处于高电平*/
	gpio_direction_input(gpios[0].gpio);  

	/* 2. 注册中断 后面的语句执行时可能会导致前几次的中断丢失*/
	err = request_irq(gpios[0].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[0].name, &gpios[0]);
	mod_timer(&gpios[0].key_timer, jiffies + 10);修改定时器的超时时间= jiffies(当前时间) + 10	

	/* 3. 休眠等待数据 */
	//等待 条件为真(有数据时),才会被唤醒并执行后面语句
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());

	free_irq(gpios[0].irq, &gpios[0]);

	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 设置DHT11 GPIO引脚的初始状态: output 1 保险起见手动设置高电平
	最后 1bit 数据传送完毕后, DHT11 拉低总线 50us,然后释放总线,总线由
    上拉电阻拉高进入空闲状态*/
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err)
	{
		printk("%s %s %d, gpio_request err\n", __FILE__, __FUNCTION__, __LINE__);
	}
	gpio_direction_output(gpios[0].gpio, 1);
	gpio_free(gpios[0].gpio);


	/* 4. copy_to_user */
	kern_buf[0] = get_key();
	kern_buf[1] = get_key();

	printk("get val : 0x%x, 0x%x\n", kern_buf[0], kern_buf[1]);
	if ((kern_buf[0] == (char)-1) && (kern_buf[1] == (char)-1))
	{
		printk("get err val\n");
		return -EIO;
	}

	err = copy_to_user(buf, kern_buf, 2);
	
	return 2;
}

static int dht11_release (struct inode *inode, struct file *filp)
{
	return 0;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations dht11_drv = {
	.owner	 = THIS_MODULE,
	.read    = dht11_read,
	.release = dht11_release,
};

//解析数据
static void parse_dht11_datas(void)
{
	int i;
	u64 high_time;
	unsigned char data = 0;
	int bits = 0;
	unsigned char datas[5];//40位 5个字节
	int byte = 0;
	unsigned char crc;

	/* 中断发生次数: 可能是81、82、83、84 */
	if (g_dht11_irq_cnt < 81)
	{
		/* 出错 */
		put_key(-1);
		put_key(-1);

		// 唤醒APP
		wake_up_interruptible(&gpio_wait);
		g_dht11_irq_cnt = 0;
		return;
	}

	// 解析数据, 81、82、83、84 
	for (i = g_dht11_irq_cnt - 80; i < g_dht11_irq_cnt; i += 2)
	{
		high_time = g_dht11_irq_time[i] - g_dht11_irq_time[i-1];//高脉冲时间

		data <<= 1;//左移一位

		//50us = 50000ns
		//如果数据是高电平
		if (high_time > 50000) /* data 1 */
		{
			data |= 1;//或
		}

		bits++;

		if (bits == 8)
		{
			datas[byte] = data;
			data = 0;
			bits = 0;
			byte++;
		}
	}

	// 放入环形buffer
	crc = datas[0] + datas[1] + datas[2] + datas[3];
	if (crc == datas[4])
	{
		put_key(datas[0]);
		put_key(datas[2]);
	}
	else
	{
		put_key(-1);
		put_key(-1);
	}

	g_dht11_irq_cnt = 0;
	// 唤醒APP
	wake_up_interruptible(&gpio_wait);
}

static irqreturn_t dht11_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	u64 time;
	
	/* 1. 记录中断发生的时间 */
	time = ktime_get_ns();//单位ns 精准的到当前时间
	//static u64 g_dht11_irq_time[84]; static int g_dht11_irq_cnt = 0;
	g_dht11_irq_time[g_dht11_irq_cnt] = time;

	/* 2. 累计次数 */
	g_dht11_irq_cnt++;

	/* 3. 次数足够: 解析数据, 放入环形buffer, 唤醒APP */
	if (g_dht11_irq_cnt == 84)
	{
		del_timer(&gpio_desc->key_timer);
		parse_dht11_datas();//解析数据
	}

	return IRQ_HANDLED;
}


/* 在入口函数 */
static int __init dht11_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);//count = 1
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);

		/* 设置DHT11 GPIO引脚的初始状态: output 1 */
		err = gpio_request(gpios[i].gpio, gpios[i].name); //申请gpio
		gpio_direction_output(gpios[i].gpio, 1);		  //输出方向,高电平
		gpio_free(gpios[i].gpio);						  //释放引脚

		//设置定时器:定时器结构体,定时器超时函数,传给超时函数的参数
		setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
		//gpios[i].key_timer.expires = ~0;
		//add_timer(&gpios[i].key_timer);
		//err = request_irq(gpios[i].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_dht11", &dht11_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_dht11_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_dht11");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "mydht11"); /* /dev/mydht11 */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit dht11_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_dht11");

	for (i = 0; i < count; i++)
	{
		//free_irq(gpios[i].irq, &gpios[i]);
		del_timer(&gpios[i].key_timer);
	}
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(dht11_init);
module_exit(dht11_exit);

MODULE_LICENSE("GPL");


2.2 测试代码


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

static int fd;

/*
 * ./button_test /dev/mydht11
 *
 */
int main(int argc, char **argv)
{
	char buf[2];
	int ret;

	int i;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	while (1)
	{
		if (read(fd, buf, 2) == 2)
			printf("get Humidity: %d, Temperature : %d\n", buf[0], buf[1]);
		else
			printf("get dht11: -1\n");

		sleep(5);
	}
	close(fd);
	return 0;
}


2.3 上板子测试

可以看到测到的温度和湿度数值都是正常的,但偶尔会测得失败的数据,这是因为在驱动程序里注册中断函数后,后面的语句执行花时间,可能会导致前几次的中断丢失。经测试如果中断发生次数小于81,则会测得错误数据;中断发生次数在81~84(含等于)就可测得准确数值。

好的,这是一个嵌入式系统的开发任务,需要进行以下几个步骤: 1. 硬件连接:将DHT11传感器与IMX6ULL ALPHA v2.0开发板相连,需要连接传感器的VCC(正极)、GND(负极)和DATA(数据)引脚。可以通过查看传感器的数据手册来确定引脚的连接方式。 2. 编写Linux驱动程序:在Linux系统中,可以通过编写驱动程序来访问传感器数据。驱动程序需要实现对传感器的读取和解析,并将湿度数据传递给应用程序。在编写驱动程序时,可以使用内核提供的GPIO和I2C驱动模块来操作IMX6ULL ALPHA v2.0开发板的GPIO和I2C接口。可以参考Linux内核的相关文档和示例代码。 3. 编写QT应用程序:QT是一个跨平台的GUI开发框架,可以用来开发嵌入式系统的应用程序。在QT应用程序中,可以通过调用Linux驱动程序来获取传感器数据,并将数据显示在屏幕上。可以使用QT提供的图形控件和布局管理器来设计应用程序的界面。 4. 调试和测试:在开发完成后,需要进行系统的调试和测试,确保应用程序能够正常运行并显示正确的湿度数据。可以使用调试器和日志输出工具来进行调试,或者使用虚拟机和模拟器来模拟硬件环境。 需要注意的是,在开发过程中需要考虑系统的稳定性和可靠性,确保系统能够长期运行并处理各种异常情况。同时,需要遵循良好的编程规范和安全性要求,确保系统不会受到外部攻击或者数据泄露等安全问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿龙还在写代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值