因为 DHT11 驱动时要求的时序很难直接在 shell 或者 c 语言编程下实现驱动。经网友的提醒,可以自己写驱动来实现。最开始就是在网上到处搜集资料,LuckFox Pico 算是新产品,能找到的资料有限。那就找 RV1103 芯片相关的资料,再不行找找瑞芯微所有芯片的资料,功夫不负有心人,终于让我找到了这个项目,DHT11-for-RK3399-driver ,非常感谢 @jackeyt 前期写好的驱动,我这篇帖子也是基于此项目写的。
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。
传感器性能
型号 | 测量范围 | 测湿精度 | 测温精度 | 分辨力 | 封装 |
---|---|---|---|---|---|
DHT11 | 20-90%RH 0-50℃ | ±5%RH | ±2℃ | 1 | 4针单排直插 |
串行接口(单线双向)
DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:
一次完整的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集, 用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集, 如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式。
通讯过程如图所示
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后, 等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。
先低后高
总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1.格式见下面图示.如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常.当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
低电平
高电平
RK3399与RV1103的设备树基本上是一样的。可能就是设备树几个常量的区别。我这边直接复制了原项目的设备树,未做过多修改,&gpio1 RK_PC7 是55引脚编号。这个编号规则我也没摸清楚,只是在官方wiki上找到的。有了解的小伙伴一定要在评论区讲一下啊。
//...compatible = "rockchip,rv1103g-38x38-ipc-v10", "rockchip,rv1103";
humidity_sensor {
compatible = "mygpio-leds";
pinctrl-names = "default";
status = "okay";
pinctrl-0 = <&gpio1_pc7>;
led@1 {
gpios = <&gpio1 RK_PC7 GPIO_ACTIVE_HIGH>;
label = "my_led";
linux,default-trigger = "heartbeat";
linux,default-trigger-delay-ms = <0>;
};
};
//...gpio4pa2:gpio4pa2 {
这篇文章赶得及,所以基本上就是原本的代码,还没摸清楚这里GPIO干嘛的,可能是定义引脚吧。
//**********GPIO**********/
&pinctrl {
gpio1-pc7 {
gpio1_pc7:gpio1-pc7 {
rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
以及设备树最底下的pwm11需要注释
//&pwm11 {
// status = "disabled";
// pinctrl-names = "active";
// pinctrl-0 = <&pwm11m1_pins>;
// pinctrl-0 = <&pwm11m2_pins>;
// pinctrl-0 = <&pwm11m0_pins>;
//};
修改完之后,记得编译一下。
cd /home/luckfox/Luckfox-Pico/luckfox-pico
./build.sh lunch
./build.sh kernel
将编译好的 boot.img 替换原来的,进行烧录这个设备树就好了。
makefile 将驱动和测试文件的编译写一起了。
# 指定架构
ARCH=arm
# 指定编译工具链
CROSS_COMPILE=/home/luckfox/Luckfox-Pico/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-
export ARCH CROSS_COMPILE
# 指定内核根目录,需要和板子上运行的驱动一致
KERN_DIR = /home/luckfox/Luckfox-Pico/luckfox-pico/sysdrv/source/kernel
name=dht11
PWD?=$(shell pwd)
all:
make -C $(KERN_DIR) M=$(PWD) modules
$(CROSS_COMPILE)gcc $(name)_test.c -o $(name)
echo $(PWD)
clean:
rm -f *.ko *.o *.mod *.mod.o *.mod.c *.symvers *.order *.cmd
# make -C $(KERN_DIR) M=$(PWD) modules clean
# rm -rf modules.order
# rm -f $(name)_test
obj-m += $(name)_drv.o
这是驱动模块c文件
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
typedef unsigned char u8;
typedef unsigned short u16;
static int dht11_major = 0;
static int dht11_gpio;
static struct class *dht11_class; // 类
static struct device *dht11_dev; // 设备
static const char *dht11_name = "dht11";
typedef struct DHT11_SENSOR_DATA
{
u16 temp; // 温度
u16 hum; // 湿度
} dht11_data;
#define DHT11_DQ_High gpio_direction_output(dht11_gpio, 1)
#define DHT11_DQ_Low gpio_direction_output(dht11_gpio, 0)
#define DHT11_IO_IN gpio_direction_input(dht11_gpio)
#define delay_us(x) udelay(x)
#define delay_ms(x) msleep(x)
static u8 DHT11_Read_DQ(void)
{
DHT11_IO_IN;
return gpio_get_value(dht11_gpio);
}
// 复位DHT11
static void DHT11_Rst(void)
{
DHT11_DQ_Low;
delay_ms(20); // 拉低至少18ms
DHT11_DQ_High; // DQ=1
delay_us(30); // 主机拉高20~40us
}
// 等待DHT11的回应
// 返回1:未检测到DHT11的存在
// 返回0:存在
static u8 DHT11_Check(void)
{
u8 retry = 0; // 定义临时变量
DHT11_IO_IN; // SET INPUT
while ((DHT11_Read_DQ() == 1) && retry < 100) // DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if (retry >= 100)
return 1;
else
retry = 0;
while ((DHT11_Read_DQ() == 0) && retry < 100) // DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if (retry >= 100)
return 1;
return 0;
}
// 从DHT11读取一个位
// 返回值:1/0
static u8 DHT11_Read_Bit(void)
{
u8 retry = 0;
while ((DHT11_Read_DQ() == 1) && retry < 100) // 等待变为低电平
{
retry++;
delay_us(1);
}
retry = 0;
while ((DHT11_Read_DQ() == 0) && retry < 100) // 等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40); // 等待40us
if (DHT11_Read_DQ() == 1)
return 1;
else
return 0;
}
// 从DHT11读取一个字节
// 返回值:读到的数据
static u8 DHT11_Read_Byte(void)
{
u8 i, dat;
dat = 0;
for (i = 0; i < 8; i++)
{
dat <<= 1;
dat |= DHT11_Read_Bit();
}
return dat;
}
// 从DHT11读取一次数据
// temp:温度值(范围:0~50°)
// humi:湿度值(范围:20%~90%)
// 返回值:0,正常;1,读取失败
static u8 DHT11_Read_Data(u16 *temp, u16 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if (DHT11_Check() == 0)
{
for (i = 0; i < 5; i++) // 读取40位数据
{
buf[i] = DHT11_Read_Byte();
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0] << 8 | buf[1];
*temp = buf[2] << 8 | buf[3];
printk("buf=%d,%d,%d,%d,%d\n", buf[0], buf[1], buf[2], buf[3], buf[4]);
}
}
else
return 1;
return 0;
}
// 初始化DHT11的IO口 DQ 同时检测DHT11的存在
// 返回1:不存在
// 返回0:存在
static void DHT11_Init(void)
{
DHT11_DQ_High;
DHT11_Rst(); // 复位DHT11
DHT11_Check(); // 等待DHT11的回应
}
int DHT11_open(struct inode *inode, struct file *flips)
{
printk("--------------%s--------------\n", __FUNCTION__);
return 0;
}
static ssize_t DHT11_read(struct file *file, char __user *buf,
size_t nbytes, loff_t *ppos)
{
dht11_data Last_dht11_data;
printk("--------------%s--------------\n", __FUNCTION__);
if (DHT11_Read_Data(&Last_dht11_data.temp, &Last_dht11_data.hum) == 0) // 读取温湿度值
{
if (copy_to_user(buf, &Last_dht11_data, sizeof(Last_dht11_data)))
{
return EFAULT;
}
}
return 0;
}
static int DHT11_close(struct inode *inode, struct file *flip)
{
printk("--------------%s--------------\n", __FUNCTION__);
return 0;
}
static struct file_operations dht11_fops = {
.owner = THIS_MODULE,
.read = DHT11_read,
.open = DHT11_open,
.release = DHT11_close,
};
static const struct of_device_id of_dht11_match[] = {
{
.compatible = "mygpio-leds",
},
{},
};
MODULE_DEVICE_TABLE(of, of_dht11_match);
static int dht11_probe(struct platform_device *pdev)
{
int ret;
enum of_gpio_flags flag; //(flag == OF_GPIO_ACTIVE_LOW) ?
struct device_node *dht11_gpio_node = pdev->dev.of_node;
printk("-------%s-------------\n", __FUNCTION__);
dht11_gpio = of_get_named_gpio_flags(dht11_gpio_node->child, "gpios", 0, &flag);
if (!gpio_is_valid(dht11_gpio))
{
printk("dht11-gpio: %d is invalid\n", dht11_gpio);
return -ENODEV;
}
else
printk("dht11-gpio: %d is valid!\n", dht11_gpio);
if (gpio_request(dht11_gpio, "dht11-gpio"))
{
printk("gpio %d request failed!\n", dht11_gpio);
gpio_free(dht11_gpio);
return -ENODEV;
}
else
printk("gpio %d request success!\n", dht11_gpio);
// 能够读到配置信息之后就可以开始创建设备节点
dht11_major = register_chrdev(0, "dht11", &dht11_fops);
if (dht11_major < 0)
{
printk(KERN_ERR "reg error!\n");
goto err_0;
}
else
printk("dht11_major =%d\n", dht11_major);
dht11_class = class_create(THIS_MODULE, "dht11_class"); // creat class
if (IS_ERR(dht11_class))
{
printk(KERN_ERR "fail create class\n");
ret = PTR_ERR(dht11_class);
goto err_1;
}
// creat dht11_dev--->>/dev/dht11_dev
dht11_dev = device_create(dht11_class, NULL, MKDEV(dht11_major, 0), NULL, dht11_name);
if (IS_ERR(dht11_dev))
{
printk(KERN_ERR "fail create device!\n");
ret = PTR_ERR(dht11_dev);
goto err_2;
}
// init dht11
DHT11_Init();
printk("dht11 Initing...\n");
return 0;
err_2:
device_destroy(dht11_class, MKDEV(dht11_major, 0));
err_1:
class_destroy(dht11_class);
err_0:
unregister_chrdev(dht11_major, dht11_name);
return -1;
}
static int dht11_remove(struct platform_device *pdev)
{
printk("-------%s-------------\n", __FUNCTION__);
device_destroy(dht11_class, MKDEV(dht11_major, 0));
class_destroy(dht11_class);
unregister_chrdev(dht11_major, dht11_name);
return 0;
}
static void dht11_shutdown(struct platform_device *pdev)
{
printk("-------%s-------------\n", __FUNCTION__);
}
static struct platform_driver dht11_driver = {
.probe = dht11_probe,
.remove = dht11_remove,
.shutdown = dht11_shutdown,
.driver = {
.name = "dht11_driver",
.of_match_table = of_dht11_match,
},
};
module_platform_driver(dht11_driver);
MODULE_AUTHOR("jackeyt,bbs.elecfans.com");
MODULE_DESCRIPTION("DHT11 Sensor driver");
MODULE_LICENSE("GPL");
这是调用设备文件读取温湿度的示例代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef struct DHT11_SENSOR_DATA
{
u16 temp;//温度
u16 hum;//湿度
} dht11_data;
int main ( void )
{
int fd ;
int retval ;
dht11_data Curdht11_data;
fd = open ( "/dev/dht11" , O_RDONLY) ;
if ( fd == -1 )
{
perror ( "open dht11 error\n" ) ;
exit ( -1 ) ;
}
printf ( "open /dev/dht11 successfully\n" ) ;
sleep ( 2 ) ;
while ( 1 )
{
sleep ( 1 ) ;
retval = read ( fd , &Curdht11_data , sizeof(Curdht11_data) );
if ( retval == -1 )
{
perror ( "read dht11 error" ) ;
printf ( "read dht11 error" ) ;
exit ( -1 ) ;
}
if(Curdht11_data.temp != 0xffff)
printf ( "Temperature:%d.%d C,Humidity:%d.%d %%RH\n",Curdht11_data.temp>>8,Curdht11_data.temp&0xff,\
Curdht11_data.hum>>8,Curdht11_data.hum&0xff ) ;
}
close ( fd ) ;
}
执行 make
对两个文件进行编译,我们把产物中的 dht11_drv.ko
和 dht11
拷贝到开发板上。
导入模块
insmod dht11_drv.ko
查看模块日志
dmesg
可以看到dht11设备已经初始化完毕了。
读取温湿度
chmod +x dht11
./dht11
作者水平有限,难免在这篇文章中存在一些疏漏和不足之处。虽然尽力在试用报告中提供全面的信息,但由于时间和知识的限制,可能会忽略了一些重要细节或者未能涵盖所有相关方面。读者在使用LuckFox Pico系列开发板时,建议仔细阅读官方文档和参考资料,以充分了解其功能和性能,并在开发过程中进行进一步的验证和测试。希望本报告能为您提供有用的信息,但请在实际项目中进行更深入的研究和验证,以确保取得最佳的开发结果。
最后,再次感谢 @jackeyt 前期 项目
更多回帖