手把手教你imx6ull dht11驱动编写
·
imx6ull dht11驱动
dht11采用单总线通信协议,有vcc,gnd,sda三根引脚。湿度的精度是1,温度精度可以达到小数位。dht11驱动主要涉及到单总线协议,驱动框架采用platform + misc杂项设备驱动。
1、设备树编写
查看手册:

要求我们使用的gpio必须接有上拉电阻,使得空闲状态的时候是高电平。我们选用gpio1_io2引脚作为sda
- 在设备树的iomux节点下添加:
/*车载终端 dht11*/
pinctrl_dht11: dht11grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x000010B0
>;
};
- 添加dht11结点
/*车载终端 dht11*/
dht11 {
compatible = "wuyiqiang,dht11";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_dht11>;
dht11-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; //单总线待机状态是高电平
status = "okay";
};
注意需要检查一下使用的gpio是否被占用
2、dht11复位

- 查看手册的:初始化流程
单总线协议需要保证在空闲状态时gpio输出高电平。
①、主机发送起始信号(Tbe + Tgo):主机先将gpio设置为输出模式,默认高电平,拉低20ms,再拉高13us,将gpio设置为输入模式。
②、从机响应(Trel + Treh):从机(dht11)将总线拉低83us之后再拉高87us代表接收到主机的请求。我们需要再驱动程序里判断从机是否响应。
③、从机开始发送数据:单总线协议利用一个波形周期内高电平的占空比不同来区分传输 0 还是 1。
申请引脚,编写功能函数:
//申请gpio
dht11.gpios = of_get_named_gpio(pdev->dev.of_node, "dht11-gpios", 0);
if (!gpio_is_valid(dht11.gpios))
{
ret = -EINVAL;
printk("获取gpio失败\n");
goto failed;
}
ret = devm_gpio_request_one(&pdev->dev, dht11.gpios, GPIOF_OUT_INIT_HIGH, "dht11-gpio");//申请gpio并设置输出高,devm会自动释放资源
if (ret)
{
printk("申请gpio失败\n");
ret = -EINVAL;
goto failed;
}
static void dht11_set_output(struct dht11_dev* dev, int val)
{
if (val)
gpio_direction_output(dev->gpios, 1);
else
gpio_direction_output(dev->gpios, 0);
}
static void dht11_set_input(struct dht11_dev* dev)
{
gpio_direction_input(dev->gpios);
}
static unsigned char dht11_get_input(struct dht11_dev* dev)
{
int ret = 0;
ret = gpio_get_value(dev->gpios);
return ret;
}
dht11初始化:
//dht11初始化
//成功返回0,失败负数
static int dht11_dev_init(struct dht11_dev* dev)
{
unsigned char times;
dht11_set_output(dev, 1);
udelay(5);
dht11_set_output(dev, 0); //主机拉低20ms
mdelay(20);
dht11_set_output(dev, 1); //主机释放13us
udelay(13);
dht11_set_input(dev); //设置输入等待从机响应
//从机响应低电平时间78~88us
times = 0;
while (dht11_get_input(dev) != 0)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 响应超时 -1 \n");
return -1;
}
}
//从机响应高电平时间80~92us
times = 0;
while (dht11_get_input(dev) == 0)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 响应超时 -2 \n");
return -2;
}
}
times = 0;
while (dht11_get_input(dev) == 1)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 未开始传输数据 -3\n");
return -3;
}
}
return 0;
}
最好那里判断从机是否开始传输数据,如果开始传输数据那么一定会有低电平,如果一直是高那么代表从机输出出错了。
3、dht11 读取数据
3、1读取一个字节

- 查看手册:dht11的数据由于40bit构成,最后8bit是校验字节,我们可以在读取完数据之后用来校验。、

可以看到单总线协议通过区分高电平的时间来区分数据
/*dht11 读取一个字节*/
/*成功0 失败 1*/
static unsigned char dht11_read_byte(struct dht11_dev* dev)
{
unsigned char i, times = 0, data = 0;
for (i = 0; i < 8; i++)
{
times = 0;
while (dht11_get_input(dev) == 0) //判断数据传输是否出错,没错的一定会拉高
{
times++;
udelay(1);
if (times > 100)
return 1;
}
udelay(45); //拉高时间 23~27:0, 68~74:1
if (dht11_get_input(dev) == 1)
{
data = data | 0x01 << (7 - i); //该bit为1
times = 0;
while (dht11_get_input(dev) == 1) //判断数据传输是否出错,没错的一定会拉低
{
times++;
udelay(1);
if (times > 100)
return 1;
}
}
}
return data;
}
3、2读取数据

可以看到我们每次读取完40bit的数据之后都需要调用一下 dht11_init 来发送起始信号唤醒dht11才能再次读取数据。并且读取一次之后需要间隔 2s 才能再次读取。
static int dht11_read_data(struct dht11_dev* dev, unsigned char* data)
{
int ret, retry = 3;
mutex_lock(&dev->lock);
while (retry--)
{
ret = dht11_dev_init(dev);
if(ret)
continue;
data[0] = dht11_read_byte(dev);
data[1] = dht11_read_byte(dev);
data[2] = dht11_read_byte(dev);
data[3] = dht11_read_byte(dev);
data[4] = dht11_read_byte(dev);
//校验
if (data[4] != data[0] + data[1] + data[2] + data[3])
{
continue;
} else {
mutex_unlock(&dev->lock);
return 0;
}
}
mutex_unlock(&dev->lock);
return ret;
}
驱动框架完善
下面就是platform + misc字符驱动框架的编写了。本驱动可以采用3中方式来获取温湿度。分别是 通过cat /sys/class/misc/dht11/value,系统调用read,和ioctl。
完整程序如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#define DHT11_NAME "dht11"
#define DHT11_MINOR 1
#define DHT11_GET_VALUE _IOR(0xEF, 0, unsigned char[5])
struct dht11_dev {
struct miscdevice mdev;
int gpios;
struct mutex lock;
};
static struct dht11_dev dht11;
static void dht11_set_output(struct dht11_dev* dev, int val)
{
if (val)
gpio_direction_output(dev->gpios, 1);
else
gpio_direction_output(dev->gpios, 0);
}
static void dht11_set_input(struct dht11_dev* dev)
{
gpio_direction_input(dev->gpios);
}
static unsigned char dht11_get_input(struct dht11_dev* dev)
{
int ret = 0;
ret = gpio_get_value(dev->gpios);
return ret;
}
//dht11初始化
//成功返回0,失败负数
static int dht11_dev_init(struct dht11_dev* dev)
{
unsigned char times;
dht11_set_output(dev, 1);
udelay(5);
dht11_set_output(dev, 0); //主机拉低20ms
mdelay(20);
dht11_set_output(dev, 1); //主机释放13us
udelay(13);
dht11_set_input(dev); //设置输入等待从机传数据
//从机响应低电平时间78~88us
times = 0;
while (dht11_get_input(dev) != 0)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 等待超时 -1 \n");
return -1;
}
}
//从机响应高电平时间80~92us
times = 0;
while (dht11_get_input(dev) == 0)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 等待超时 -2 \n");
return -2;
}
}
times = 0;
while (dht11_get_input(dev) == 1)
{
udelay(1);
times++;
if (times >= 100)
{
printk("dht11 未开始传输数据 -3\n");
return -3;
}
}
return 0;
}
/*dht11 读取一个字节*/
/*成功0 失败 1*/
static unsigned char dht11_read_byte(struct dht11_dev* dev)
{
unsigned char i, times = 0, data = 0;
for (i = 0; i < 8; i++)
{
times = 0;
while (dht11_get_input(dev) == 0) //判断数据传输是否出错,没错的一定会拉高
{
times++;
udelay(1);
if (times > 100)
return 1;
}
udelay(45); //拉高时间 23~27:0, 68~74:1
if (dht11_get_input(dev) == 1)
{
data = data | 0x01 << (7 - i); //该bit为1
times = 0;
while (dht11_get_input(dev) == 1) //判断数据传输是否出错,没错的一定会拉低
{
times++;
udelay(1);
if (times > 100)
return 1;
}
}
}
return data;
}
static int dht11_read_data(struct dht11_dev* dev, unsigned char* data)
{
int ret, retry = 3;
mutex_lock(&dev->lock);
while (retry--)
{
ret = dht11_dev_init(dev);
if(ret)
continue;
data[0] = dht11_read_byte(dev);
data[1] = dht11_read_byte(dev);
data[2] = dht11_read_byte(dev);
data[3] = dht11_read_byte(dev);
data[4] = dht11_read_byte(dev);
//校验
if (data[4] != data[0] + data[1] + data[2] + data[3])
{
continue;
} else {
mutex_unlock(&dev->lock);
return 0;
}
}
mutex_unlock(&dev->lock);
return ret;
}
static int dht11_open(struct inode* inode, struct file* filp)
{
filp->private_data = &dht11;
return 0;
}
static ssize_t dht11_read(struct file* filp, char* __user buf, size_t cnt, loff_t* offt)
{
int ret = 0;
unsigned char data[5];
struct dht11_dev* dev = (struct dht11_dev*)filp->private_data;
ret = dht11_read_data(dev, data);
if (ret)
return ret;
ret = copy_to_user(buf, data, sizeof(data)); //返回未拷贝的字节数
if (ret)
return -EFAULT;
return 5;
}
static int dht11_release(struct inode* inode, struct file* filp)
{
return 0;
}
static long dht11_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
struct dht11_dev* dev = (struct dht11_dev*)filp->private_data;
unsigned char data[5];
int ret = 0;
switch(cmd)
{
case DHT11_GET_VALUE:
ret = dht11_read_data(dev, data);
break;
default:
ret = -1;
break;
}
if (copy_to_user((void*) arg, data, sizeof(data)))
return -1;
return ret;
}
static struct file_operations dht11_fops = {
.owner = THIS_MODULE,
.open = dht11_open,
.read = dht11_read,
.release = dht11_release,
.unlocked_ioctl = dht11_ioctl,
};
/* sysfs 属性文件回调:直接输出温湿度 */
static ssize_t show_rh_temp(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned char data[5];
int ret;
ret = dht11_read_data(&dht11, data);
if (ret) {
return sprintf(buf, "Error: DHT11 read failed, ret=%d\n", ret);
}
/* 格式:湿度,温度 (DHT11小数位恒为0) */
return sprintf(buf, "%d.%d,%d.%d\n",
data[0], data[1], data[2], data[3]);
}
static DEVICE_ATTR(value, S_IRUGO, show_rh_temp, NULL);
static struct attribute *dht11_attrs[] = {
&dev_attr_value.attr,
NULL
};
static const struct attribute_group dht11_attrs_group = {
.attrs = dht11_attrs,
};
static const struct attribute_group *dht11_attr_groups[] = {
&dht11_attrs_group,
NULL
};
static int dht11_probe(struct platform_device* pdev)
{
int ret = 0;
struct miscdevice *mdev = &dht11.mdev;
mdev->name = DHT11_NAME; //设备名
mdev->minor = MISC_DYNAMIC_MINOR; //杂项设备次设备号
mdev->fops = &dht11_fops;
mdev->groups = dht11_attr_groups;
mutex_init(&dht11.lock);
//申请gpio
dht11.gpios = of_get_named_gpio(pdev->dev.of_node, "dht11-gpios", 0);
if (!gpio_is_valid(dht11.gpios))
{
ret = -EINVAL;
printk("获取gpio失败\n");
goto failed;
}
ret = devm_gpio_request_one(&pdev->dev, dht11.gpios, GPIOF_OUT_INIT_HIGH, "dht11-gpio");//申请gpio并设置输出高,devm会自动释放资源
if (ret)
{
printk("申请gpio失败\n");
ret = -EINVAL;
goto failed;
}
ret = misc_register(mdev); //注册misc设备
if (ret)
{
printk("注册miscdev失败\n");
ret = -EINVAL;
goto failed;
}
printk("dht11 probe success\n");
failed:
return ret;
}
static int dht11_remove(struct platform_device* pdev)
{
misc_deregister(&dht11.mdev);
printk("dht11 remove success\n");
return 0;
}
static struct of_device_id dht11_of_match[] = {
{.compatible = "wuyiqiang,dht11"},
{}
};
static struct platform_driver dht11_driver = {
.driver = {
.name = "dht11",
.owner = THIS_MODULE,
.of_match_table = dht11_of_match,
},
.probe = dht11_probe,
.remove = dht11_remove,
};
static int __init dht11_init(void)
{
int ret = 0;
ret = platform_driver_register(&dht11_driver);
if (ret)
{
printk("dht11驱动注册失败\n");
} else {
printk("dht11驱动注册成功\n");
}
return ret;
}
static void __exit dht11_exit(void)
{
platform_driver_unregister(&dht11_driver);
printk("dht11驱动卸载成功\n");
}
module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wuyiqiang");
编写测试app
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
// ioctl 命令码(和驱动一致)
#define DHT11_GET_VALUE _IOR(0xEF, 0, unsigned char[5])
// sysfs 属性文件固定路径
#define SYSFS_VALUE_PATH "/sys/class/misc/dht11/value"
int main(int argc, char* argv[])
{
char *filename;
unsigned char data[5]; // 存储read/ioctl数据
char buf[32]; // 存储sysfs读取的文本
int fd, ret;
int rh_int, rh_dec, temp_int, temp_dec; // 解析sysfs用
// 1. 参数检查
if (argc != 2)
{
printf("用法: %s /dev/dht11\n", argv[0]);
return -1;
}
filename = argv[1];
// 2. 打开设备节点(/dev/dht11)
fd = open(filename, O_RDWR);
if (fd < 0)
{
perror("open 设备失败");
return -1;
}
printf("=== 成功打开 DHT11 设备 ===\n\n");
// ====================== 1. 测试 read 接口 ======================
ret = read(fd, data, sizeof(data));
if (ret != sizeof(data))
{
printf("read 读取错误!\n");
} else {
printf("---------- read 读取 ----------\n");
printf("湿度:%d.%d 温度:%d.%d\n", data[0], data[1], data[2], data[3]);
}
sleep(2);
// ====================== 2. 测试 ioctl 接口 ======================
ret = ioctl(fd, DHT11_GET_VALUE, data);
if (ret < 0)
{
printf("ioctl 读取错误!\n");
} else {
printf("---------- ioctl 读取 ----------\n");
printf("湿度:%d.%d 温度:%d.%d\n", data[0], data[1], data[2], data[3]);
}
sleep(2);
// ====================== 3. 新增:读取 sysfs 文件 ======================
printf("---------- sysfs 读取 ----------\n");
FILE *sysfs_fp = fopen(SYSFS_VALUE_PATH, "r");
if (!sysfs_fp)
{
perror("打开 sysfs 文件失败");
}
else
{
// 读取文本内容(格式:25.0,26.5)
fgets(buf, sizeof(buf), sysfs_fp);
// 解析文本,提取4个整数
sscanf(buf, "%d.%d,%d.%d", &rh_int, &rh_dec, &temp_int, &temp_dec);
printf("sysfs 数据:%s", buf);
printf("解析结果:湿度:%d.%d 温度:%d.%d\n", rh_int, rh_dec, temp_int, temp_dec);
fclose(sysfs_fp);
}
// 关闭设备
close(fd);
printf("\n=== 三种接口测试完成 ===\n");
return 0;
}
结果测试

更多推荐
所有评论(0)