最近想要做一个基于嵌入式Linux+Qt驱动dht11温湿度传感器的实验。想要实现的功能是通过野火的imx6ull开发板控制dht11传感器,然后使用Qt做一个上位机,在上位机上面把数据显示出来。
这里把我在做的过程中遇到的一些问题先记录一下,免得日后忘记。
在网上关于这方面的资料不多,大多数都是基于stm32来控制的,所以在做的过程中遇到一些问题解决起来也比较麻烦。
下面简述一下我做的过程及遇到的问题
首先查看原理图看使用到了哪个管脚,然后在设备树里添加相应的节点。这里用到了gpio子系统和pinctrl子系统。
接着参考网上的相关代码,进行了改写,因为这个传感器的时序也比较简单,所以有关时序的部分基本上可以不用改。
遇到的第一个问题:写好驱动后,在应用程序中使用read函数来读取设备文件,如果只读取一次,可以得到结果,但是如果使用while(1)来尝试反复读取,就会失败。
按照手册来说,只要两次读取间隔超过1秒就行了,但是我使用while(1)即使休眠sleep(3)之类的依然会在第二次读取的失败,而且整个函数会卡死在读取这里,这个进程怎么也杀不死,kill -9杀不死,kill -15 也杀不死。这里很快把问题定位在了read函数。
后面,我在代码中做了如下修改:本来在驱动程序里面有使用while函数来等待管脚电平的跳变,我认为这样是不合理的,因为没有超时处理,容易卡死,所以我加了一个计数,当超过一定计数值时就跳出while循环。后来这个问题就解决了。虽然我是不确定一开始是不是因为这个原因,因为中间过了挺久的时间,我不确定有没有别的因素存在,总之后来就不会卡死了,可以使用while循环来反复读取。
遇到的第二个问题:在解决了上面的问题之后,insmod安装驱动,可以工作,然后rmmod卸载驱动,再次insmod安装驱动就会发现安装不上去。
使用dmesg命令查看内核打印的信息,比较容易猜到应该是卸载驱动的时候没有卸载干净,然后仔细看了一下驱动,在结合网上查找资料,发现我的驱动里没有写remove函数。所以我添加了remove函数,在remove函数里注销掉那些东西。而且要注意注销的顺序,和注册是相反的,比如在驱动中最先是申请设备号,在注销的时候就是最后注销它,否则会出现很多错误,包括段错误。
遇到的第三个问题:在解决了第二个问题之后,已经可以反复卸载和安装驱动了,但是发现一个问题,就是在第二次安装的时候,总是会出现gpio_request失败,按道理讲我已经在remove函数里使用gpio_free释放掉了,不应该会失败才对,后来发现是在gpio_request的时候还没拿到引脚号,全局变量没有初始化默认是0,所以request的是0,后来通过一个函数(忘记叫什么了,总之是gpio子系统的那些函数)从设备树中拿到引脚号,这个引脚号是2,所以后面free的是2,也就是说request和free的不是同一个引脚,当然会出错了。
这属于粗心的错,把这个问题解决了之后,这个驱动总算可以正常工作了,也完全可以反复卸载和安装。
遇到的第四个问题:在第一个问题里提到我在while里加了超时处理,防止一直死等卡死。最开始我是这样写的
while(gpio_get_value(gpio)==0 &&cnt<6)
{
cnt++;
udelay(10);
}
这里通过cnt来防止while死掉,也就是说最多等待60微秒就退出循环。但是直觉告诉我这样不好,因为中间延时10个微秒太长了,导致响应性不好。所以我改成了这样:
while(gpio_get_value(gpio)==0 &&cnt<60)
{
cnt++;
udelay(1);
}
这样的实时响应性好多了,测出的数据也更准确了。
到这里为止,驱动就基本没有问题了,使用应用程序来读取设备文件,也基本没问题,就是有时数据校验会失败,但是测出的数据基本可以,而且是有变化的,说明还是比较可靠的。
接下来是把在Qt里把数据读出来并且显示,下面说一下调试Qt遇到的问题。
在写完驱动之后,很自然会写一个.c的测试程序,用来验证驱动是否能正常工作,很幸运,一下子就成功了,于是我认为在Qt中也是类似,直接用Qt里的read相关的函数去读取设备文件就好了,但是没想到在这个环节卡了我最久
起初,我使用Qfile 里的readAll方法去读,发现控制台会刷屏(刷屏就是驱动中的read一直被调用而打印出的信息刷屏),一读就停不下来,而且后面的程序也执行不了,也就是说函数没有返回。
我不太清楚是什么原因,只能换一个函数,接着我尝试了readLine方法,一样刷屏,接着尝试read方法,这个方法和C语言的read类似,参数里要填读几个字节,这和前面两个不太一样,所以我想,这回应该不会刷屏了吧。
结果确实没有刷屏,但是读取的数据是错的,体现出来的就是从机无响应(这时我还没有注意这个问题)。
虽然说数据是错的,但是好歹没有刷屏了,只要再想一想为什么会读出错的数据就行了。
我想到Qt里还有一种读文件的方式,就是使用数据流Datastream,但是效果和上面的read一样。
接着我开始思考刷屏的原因,百度了一下,有人说要在末尾加一个"\0",尝试,未果。
接着,我在一些技术交流群寻求帮助,因为此刻我的问题确实很奇怪,在自己写的.c测试程序里,调用read读设备文件是完全没有问题的,现在唯一的区别就是在Qt中读,驱动又不变,为什么读出来的是错的呢?我怀疑是Qt的read对数据的解析可能和C语言里不太一样,因为此刻是有数据的,会不会是因为字节对齐之类的原因导致解析数据不对呢?群里大佬建议先排查一下源数据对不对。
于是我拿出了我许久没用过的逻辑分析仪来分析波形,我先观察了我的.c测试程序的波形,和手册描述的基本一致。接着观察Qt里read时的波形,一观察发现根本没有波形,正常情况应该是主机先拉低18ms,再拉高,等待从机应答。而我观察到的波形是主机拉低了30多ms才拉高,再看一下终端打印的数据,发现驱动里的read被调用了两次。
这时,我已经猜到原因了,之所以数据不对,是因为驱动里的read被连续调用了两次,导致时序根本就不对,从机没有应答。
再观察之前使用readAll函数来读取,虽然会刷屏,但是偶尔能捕捉到有效的波形。这已经很能说明问题了,就是要解决驱动里的read为什么会被调用多次这个问题,正常应该是应用层调用一次read,驱动里的read就被调用一次。
关于这个问题,这篇文章讲的不错,使用cat读取和echo写内核文件节点的一些问题
这篇文章对我还是有很大的启发。总之就是驱动中read 的返回值会影响它是否被多次调用。
先来看一下驱动中read函数的参数和返回值
ssize_t dht11_chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
我经过很多实验,发现以下规律:
对于Qt中的readAll、readLine函数,不管驱动返回什么,readAll都会刷屏,readLine会调用驱动多次。
对于Qt中的read函数,如果驱动返回的是count,将不会刷屏,否则,也会刷屏。(这一点确实很奇怪)
更奇怪的是同样的实验条件,在多次实验中甚至可能得到不同的结果,但是上面这几点结论是反复实验得到的结论。
最后,我发现可以在Qt中使用C和C++混合编程,方法就是使用
extern "C"{
#include <stdio> //这里写用到的C头文件
}
然后在用到的C语言的函数前加两个冒号,比如
::read(fd,buf,sizeof(buf));
这样就可以直接调用C语言代码了,而且发现效果还不错,比Qt中的read系列函数稳定。(实验次数有限,从我观察到的结果来看是这样)。
所以,最终的解决方法就是:
方法一:使用Qfile 的read函数,使用方法和C语言类似,可以正确读出数据,但是要注意,如果使用这个函数,驱动中的read要返回参数列表中的count,否则会刷屏。
方法二:直接使用混合编程的方式,调用C语言中的read ,这样测出的效果是最好的,而且不必要求驱动中的read 返回count,直接返回实际读取的字节即可,也就是copy_to_user的字节数。
另外,还有一个非常重要的问题需要注意:就是在Qt里面,用connect连接信号和槽,如果连接多次,那么一个信号就会触发多次的槽函数,连接几次就会触发几次,这一点非常重要。所以,如果发现槽函数被反复执行,需要检查一下connect是不是执行了多次。
驱动代码参考了Linux下DHT11驱动编程,以及测试程序
在此基础上修改得到
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
/*------------------字符设备内容----------------------*/
#define DEV_NAME "dht11"
#define DEV_CNT (1)
typedef struct
{
uint8_t humi_int; //湿度的整数部分
uint8_t humi_deci; //湿度的小数部分
uint8_t temp_int; //温度的整数部分
uint8_t temp_deci; //温度的小数部分
uint8_t check_sum; //校验和
} DHT11_Data_TypeDef;
//定义字符设备的设备号
static dev_t dht11_devno;
//定义字符设备结构体chr_dev
static struct cdev dht11_chr_dev;
struct class *class_dht11; //保存创建的类
struct device *device; // 保存创建的设备
struct device_node *dht11_device_node; //dht11的设备树节点
int dht11_data_pin; // 保存获取得到的dht11引脚编号
DHT11_Data_TypeDef DHT11_Data;
//从DHT11读取1byte数据,MSB先行
uint8_t DHT11_ReadByte(void)
{
uint8_t i, temp=0;
int cnt=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(gpio_get_value(dht11_data_pin) == 0 && cnt<60)
{
cnt++;
udelay(1);
}
cnt =0;
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
udelay(40); //延时x us 这个延时需要大于数据0持续的时间即可
if(gpio_get_value(dht11_data_pin))/* x us后仍为高电平表示数据“1” */
{
/* 等待数据1的高电平结束 */
while(gpio_get_value(dht11_data_pin) && cnt<50)
{
cnt++;
udelay(1);
}
temp|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return temp;
}
/**
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和的末8位
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
// int ret;
int cnt=0;
printk(KERN_ERR"DHT11_Read_TempAndHumidity 被调用\n");
/*主机拉低*/
gpio_direction_output(dht11_data_pin, 0);
/*延时18ms,(>=18ms)*/
mdelay(18);
/*总线拉高 主机延时30us*/
gpio_direction_output(dht11_data_pin, 1);
udelay(30); //延时30us,(20~40us)
/*主机设为输入 判断从机响应信号*/
gpio_direction_input(dht11_data_pin);
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(gpio_get_value(dht11_data_pin) == 0)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(gpio_get_value(dht11_data_pin) == 0 && cnt<100)
{
cnt++;
udelay(1);
}
cnt = 0;
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(gpio_get_value(dht11_data_pin) && cnt<100)
{
cnt++;
udelay(1);
}
/*开始接收数据*/
DHT11_Data->humi_int= DHT11_ReadByte();
DHT11_Data->humi_deci= DHT11_ReadByte();
DHT11_Data->temp_int= DHT11_ReadByte();
DHT11_Data->temp_deci= DHT11_ReadByte();
DHT11_Data->check_sum= DHT11_ReadByte();
/*读取结束,引脚改为输出模式,主机拉高*/
gpio_direction_output(dht11_data_pin, 1);
printk("humi: %d.%d, temp: %d.%d,check:%d\n",DHT11_Data->humi_int,\
DHT11_Data->humi_deci,DHT11_Data->temp_int,DHT11_Data->temp_deci,DHT11_Data->check_sum);
/*检查读取的数据是否正确*/
//DHT11_Data->check_sum的正确的结果是温湿度总和的末8位,结构体也有定义check_sum为uint8_t类型
if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)
return 0;
else {
printk(KERN_ERR " ERROR 数据校验失败");
return -1;
}
}
else
{
printk(KERN_ERR "ERROR 从机无响应");
return -1;
}
}
/*字符设备操作函数集,open函数*/
static int dht11_chr_dev_open(struct inode *inode, struct file *filp)
{
printk("\n open form driver \n");
return 0;
}
/*字符设备操作函数集,write函数*/
static ssize_t dht11_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned char write_data; //用于保存接收到的数据
int error = copy_from_user(&write_data, buf, cnt);
if(error < 0) {
return -1;
}
return 0;
}
ssize_t dht11_chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int size=sizeof(DHT11_Data_TypeDef);
printk(KERN_ERR " count: %d, fops: %lld\n", count, *fops);
printk(KERN_ERR "--------%s---------\n",__func__);
/*调用DHT11_Read_TempAndHumidity读取温湿度,若成功则输出该信息*/
if( DHT11_Read_TempAndHumidity ( & DHT11_Data ) != 0)
{
printk(KERN_ERR "Read DHT11 ERROR!\r\n");
}
else
{
if(copy_to_user(buf, &DHT11_Data, size)!=0)
{
printk(KERN_ERR " 拷贝失败\n");
// return 0;
}
else
printk(KERN_ERR " 拷贝成功\n");
}
// ret= simple_read_from_buffer(buf, count, fops, &DHT11_Data, sizeof(DHT11_Data_TypeDef));
// *fops=0;
return count;
// return size;
}
/*字符设备操作函数集*/
static struct file_operations dht11_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = dht11_chr_dev_open,
.write = dht11_chr_dev_write,
.read = dht11_chr_dev_read,
};
/*----------------平台驱动函数集-----------------*/
static int dht11_probe(struct platform_device *pdv)
{
int ret = 0; //用于保存申请设备号的结果
printk(KERN_EMERG "\t match successed \n");
/*获取dht11的设备树节点*/
dht11_device_node = of_find_node_by_path("/dht11");
if(dht11_device_node == NULL)
{
printk(KERN_EMERG "\t get dht11 failed! \n");
}
dht11_data_pin = of_get_named_gpio(dht11_device_node, "dht11_data_pin", 0);
printk("dht11_data_pin = %d\n ", dht11_data_pin);
ret=gpio_request(dht11_data_pin, "DQ_OUT");
if(ret==0)
{
printk(KERN_ERR "gpio request success\n");
}
else
{
printk(KERN_ERR "gpio request failed \n");
}
gpio_direction_output(dht11_data_pin, 1);
/*---------------------注册 字符设备部分-----------------*/
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为rgb-leds,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&dht11_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc dht11_devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
dht11_chr_dev.owner = THIS_MODULE;
cdev_init(&dht11_chr_dev, &dht11_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&dht11_chr_dev, dht11_devno, DEV_CNT);
if(ret < 0)
{
printk(KERN_ERR"fail to add cdev\n");
goto add_err;
}
//第四步
/*创建类 */
class_dht11 = class_create(THIS_MODULE, DEV_NAME);
if(class_dht11==NULL)
{
printk(KERN_ERR"class creat failed\n");
goto add_class;
}
/*创建设备*/
device = device_create(class_dht11, NULL, dht11_devno, NULL, DEV_NAME);
if(device==NULL)
{
printk(KERN_ERR"device creat failed\n");
goto add_device;
}
return 0;
// device_destroy(class_dht11,dht11_devno);
add_device:
class_destroy(class_dht11);
printk(KERN_EMERG "\t 删除类成功 \n");
add_class:
cdev_del(&dht11_chr_dev);
printk(KERN_EMERG "\t 删除设备成功 \n");
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(dht11_devno, DEV_CNT);
printk(KERN_EMERG"\n 注销设备号成功! \n");
alloc_err:
return -1;
}
int dht11_remove(struct platform_device *dht11_dev)
{
printk(KERN_EMERG"开始释放资源");
gpio_free(dht11_data_pin);
device_destroy(class_dht11,dht11_devno);
class_destroy(class_dht11);
cdev_del(&dht11_chr_dev);
unregister_chrdev_region(dht11_devno, DEV_CNT);
printk(KERN_EMERG"释放资源完毕");
return 0;
}
static const struct of_device_id dht11[] = {
{
.compatible = "dht11"},
{
/* sentinel */ }
};
/*定义平台设备结构体*/
struct platform_driver dht11_platform_driver = {
.probe = dht11_probe,
.remove = dht11_remove,
.driver = {
.name = "dht11-platform",
.owner = THIS_MODULE,
.of_match_table = dht11,
}
};
/*
*驱动初始化函数
*/
static int __init dht11_platform_driver_init(void)
{
int DriverState;
DriverState = platform_driver_register(&dht11_platform_driver);
printk(KERN_EMERG "\tDriverState is %d\n",DriverState);
return 0;
}
/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{
printk(KERN_EMERG "dht11 module exit!\n");
platform_driver_unregister(&dht11_platform_driver);
}
module_init(dht11_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
转载:https://blog.csdn.net/dafeigehaha/article/details/115434452