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节点

gpiodht11 {
  compatible = "alientek,dht11";
  pinctrl-names = "alientek,dht11";
  pinctrl-0 = <&dht11_gpio>;
  dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
  status = "okay";
};

2、创建设备的 pinctrl 节点

dht11-gpios{
  /omit-if-no-ref/
  dht11_gpio: dht11-pin {
  rockchip,pins =
    <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
  };
};

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

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

四、驱动编写

1、drv_dht11.c

/* 
dht11-gpios{
  /omit-if-no-ref/
  dht11_gpio: dht11-pin {
  rockchip,pins =
    <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
  };
};

gpiodht11 {
  compatible = "alientek,dht11";
  pinctrl-names = "alientek,dht11";
  pinctrl-0 = <&dht11_gpio>;
  dht11-gpio = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
  status = "okay";
};
*/


#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.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 DHT11DEV_CNT           1              /* 设备号长度 */
#define DHT11DEV_NAME          "dtsplatdht11"   /* 设备名字 */


/* dht11dev 设备结构体 */
struct dht11dev_dev{
  dev_t devid;                  /* 设备号 */
  struct cdev cdev;             /* cdev */
  struct class *class;          /* 类 */
  struct device *device;        /* 设备 */ 
  struct device_node *node;     /* dht11dev 设备节点 */
  int gpio_dht11;                 /* dht11dev 灯 GPIO 标号 */
};

struct dht11dev_dev dht11dev; /* dht11d ev 设备 */

unsigned char getdata[4] = {0xff, 0xff, 0xff, 0xff};

static void dht11_release( void )
{
    gpio_direction_output(dht11dev.gpio_dht11, 1);
}

static void dht11_start(void)
{
    gpio_direction_output(dht11dev.gpio_dht11, 1);
    mdelay(30);
    
    gpio_set_value( dht11dev.gpio_dht11, 0);
    mdelay(20);
    
    gpio_set_value(dht11dev.gpio_dht11, 1);
    udelay(40);
    
    gpio_direction_input(dht11dev.gpio_dht11);
}


static int dht11_wait_ack(void)
{
    long timeout_us = 50000;
 
    /* 等待低电平 */
    while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
    {
        udelay(1);
    }
    if (!timeout_us)
    {
        return -1;
    }
 
    /* 现在是低电平 */
    /* 等待高电平 */
    timeout_us = 50000;
    
    while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
    {
        udelay(1);
    }
    
    if (!timeout_us)
    {
        return -1;
    }
 
    /* 现在是高电平 */
    /* 等待低电平 */
    timeout_us = 20000;
    while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
    {
        udelay(1);
    }
    
    if (!timeout_us)
    {
        return -1;
    }

    
    return 0;
}
 
 
static int dht11_read_byte( unsigned char datalist )
{
    int i;
    unsigned char data = 0;
    int timeout_us = 20000;
    u64 pre = 0, last = 0;
    
    for (i = 0; i < 8; i++)
    {
        /* 现在是低电平 */
        /* 等待高电平 */
        timeout_us = 20000;
        while (!gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
        {
            udelay(1);
        }
        
        if (!timeout_us)
        {
            return -1;
        }
 
        /* 现在是高电平 */
        /* 等待低电平,累加高电平的时间 */
        timeout_us = 20000000;
 
        /* set another gpio low  */
        pre = ktime_get_boot_ns();
        while (1) 
        {
            last = ktime_get_boot_ns();
            if (last - pre >= 40000)
                break;
        }
 
        if (gpio_get_value(dht11dev.gpio_dht11))
        {
            /* get bit 1 */
            data = (data << 1) | 1;
            /* 当前位的高电平未结束, 等待 */
            timeout_us = 20000;
            while (gpio_get_value(dht11dev.gpio_dht11) && --timeout_us)
            {
                udelay(1);
            }
            if (!timeout_us)
            {
                return -1;
            }
        }
        else
        {
            /* get bit 0 */
            data = (data << 1) | 0;
        }
    }
 
    getdata[datalist] = data;
    
    return 0;
}
 
 
static int dht11_get_value(void)
{
	unsigned long flags;
	int i;
	
	
	local_irq_save(flags);  // 关中断
	
	/* 1. 发送高脉冲启动DHT11 */
    dht11_start();
	
	/* 2. 等待DHT11就绪 */
    if (dht11_wait_ack())
    {
        local_irq_restore(flags); // 恢复中断
        return -EAGAIN;
    }
	
	/* 3. 读5字节数据 */
    for (i = 0; i < 5; i++)
    {
		dht11_read_byte(i); 
    }
	
    /* 4. 释放总线 */
    dht11_release();
	
	local_irq_restore(flags); // 恢复中断
	
	/* 5. 根据校验码验证数据 */
    if (getdata[4] != (getdata[0] + getdata[1] + getdata[2] + getdata[3]))
    {
		getdata[0] = 0xff;
		getdata[1] = 0xff;
		getdata[2] = 0xff;
		getdata[3] = 0xff;
		
        return -1;
    }	
    
    return 0;
}


static int dht11_gpio_init(struct device_node *nd)
{
  int ret;
  
  /* 从设备树中获取 GPIO */
  dht11dev.gpio_dht11 = of_get_named_gpio(nd, "dht11-gpio", 0);
  if(!gpio_is_valid(dht11dev.gpio_dht11)) {
    printk(KERN_ERR "dht11dev: Failed to get dht11-gpio\n");
    return -EINVAL;
  }
  
  /* 申请使用 GPIO */
  ret = gpio_request(dht11dev.gpio_dht11, "DHT11");
  if (ret) {
    printk(KERN_ERR "dht11: Failed to request dht11-gpio\n");
    return ret;
  }
  
  /* 将 GPIO 设置为输出模式并设置 GPIO 初始电平状态 */
  gpio_direction_output(dht11dev.gpio_dht11,0);

  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  
  return 0;
}

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int dht11_open(struct inode *inode, struct file *filp)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  
  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)
{
    int ret =0;

    
    if( !dht11_get_value() )
	{
		//printk("data[0] = %d, data[1] = %d, data[2] = %d, data[3] = %d\n", getdata[0], getdata[1], getdata[2], getdata[3]);
		
        ret = copy_to_user(buf, getdata, sizeof(getdata));
		
        return ret;
    }
	
	return ret;

}
 


/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t dht11_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  
  return 0;
}

static int dht11_drv_close(struct inode *node, struct file *file)
{
    printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);
	
	dht11_get_value();
 
    return 0;
}


/* 设备操作函数 */
static struct file_operations dht11_fops = {
  .owner   = THIS_MODULE,
  .open    = dht11_open,
  .read    = dht11_drv_read,
  .write   = dht11_write,
  .release = dht11_drv_close,
};

/*
* @description : flatform 驱动的 probe 函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - dev : platform 设备
* @return : 0,成功;其他负值,失败
*/
static int dht11_probe(struct platform_device *pdev)
{ 
  int ret;
  
  printk("dht11 driver and device was matched!\r\n");
  
  /* 初始化 DHT11 */
  ret = dht11_gpio_init(pdev->dev.of_node);
  if(ret < 0)
    return ret;
  
  /* 1、设置设备号 */
  ret = alloc_chrdev_region(&dht11dev.devid, 0, DHT11DEV_CNT, DHT11DEV_NAME);
  if(ret < 0) {
    pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DHT11DEV_NAME, ret);
    goto free_gpio;
  }
  
  /* 2、初始化 cdev */
  dht11dev.cdev.owner = THIS_MODULE;
  cdev_init(&dht11dev.cdev, &dht11_fops);
  
  /* 3、添加一个 cdev */
  ret = cdev_add(&dht11dev.cdev, dht11dev.devid, DHT11DEV_CNT);
  if(ret < 0)
    goto del_unregister;
  
  /* 4、创建类 */
  dht11dev.class = class_create(THIS_MODULE, DHT11DEV_NAME);
  if (IS_ERR(dht11dev.class)) {
    goto del_cdev;
  }
  
  /* 5、创建设备 */
  dht11dev.device = device_create(dht11dev.class, NULL, dht11dev.devid, NULL, DHT11DEV_NAME);
  if (IS_ERR(dht11dev.device)) {
    goto destroy_class;
  }

  return 0;

destroy_class:
  class_destroy(dht11dev.class);
del_cdev:
  cdev_del(&dht11dev.cdev);
del_unregister:
  unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
free_gpio:
  gpio_free(dht11dev.gpio_dht11);
  
  return -EIO;
}

/*
* @description : platform 驱动的 remove 函数
* @param - dev : platform 设备
* @return : 0,成功;其他负值,失败
*/
static int dht11_remove(struct platform_device *dev)
{
  gpio_set_value(dht11dev.gpio_dht11, 0);/* 卸载驱动的时候 GPIO置0 */
  gpio_free(dht11dev.gpio_dht11); /* 注销 GPIO */
  cdev_del(&dht11dev.cdev); /* 删除 cdev */
  unregister_chrdev_region(dht11dev.devid, DHT11DEV_CNT);
  device_destroy(dht11dev.class, dht11dev.devid); /* 注销设备 */
  class_destroy(dht11dev.class); /* 注销类 */
  
  return 0;
}

/* 匹配列表 */
static const struct of_device_id dht11_of_match[] = {
{
  .compatible = "alientek,dht11" },
  { /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, dht11_of_match);

/* platform 驱动结构体 */
static struct platform_driver dht11_driver = {
    .driver = {
    .name = "rk3568-dht11", /* 驱动名字,用于和设备匹配 */
    .of_match_table = dht11_of_match, /* 设备树匹配表 */
  },
  
  .probe = dht11_probe,
  .remove = dht11_remove,
};

/*
* @description : 驱动模块加载函数
* @param : 无
* @return : 无
*/
static int __init dht11driver_init(void)
{
  return platform_driver_register(&dht11_driver);
}

/*
* @description : 驱动模块卸载函数
* @param : 无
* @return : 无
*/
static void __exit dht11driver_exit(void)
{
  platform_driver_unregister(&dht11_driver);
}

module_init(dht11driver_init);
module_exit(dht11driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yifeng");
MODULE_INFO(intree, "Y");



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

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-

export  ARCH  CROSS_COMPILE

CURRENT_PATH := $(shell pwd)
obj-m := drv_dht11.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

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

五、应用程序编写

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     : dht11App.c
作者       : yifeng
版本       : V1.0
描述       : 测试dth11驱动程序
其他       : 无
日志       : 初版V1.0 2024/07/18
***************************************************************/
#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/dtsplatdht11"
 
 
int main(void)
{
    int fd;
    int count_run = 0;
	int ret = 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 < 50000)
    {
        count_run++;

        if (read(fd, &data[0], 4) == 0) 
		{
            printf("get humidity  : %d.%d\n", data[0], data[1]);
            printf("get temprature: %d.%d\n", data[2], data[3]);
        } 
        else 
		{
            printf("read dht11 device fail!\n");
        }
		
        sleep(1);  // 1毫秒
    }
 
    close(fd);
 
    return 0;
}

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

编译:

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

六、测试

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

加载驱动

insmod drv_dht11.ko

测试

./dht11App

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

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

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

殷忆枫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值