Linux+Qt驱动dht11实验过程遇到的问题及解决办法

2021-04-08 20:21:58 浏览数 (1)

最近想要做一个基于嵌入式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里加了超时处理,防止一直死等卡死。最开始我是这样写的

代码语言:javascript复制

while(gpio_get_value(gpio)==0 &&cnt<6)< span="">
{
       cnt  ;
       udelay(10);
}

这里通过cnt来防止while死掉,也就是说最多等待60微秒就退出循环。但是直觉告诉我这样不好,因为中间延时10个微秒太长了,导致响应性不好。所以我改成了这样:

代码语言:javascript复制

while(gpio_get_value(gpio)==0 &&cnt<60)< span="">
{
       cnt  ;
       udelay(1);
}

这样的实时响应性好多了,测出的数据也更准确了。

到这里为止,驱动就基本没有问题了,使用应用程序来读取设备文件,也基本没问题,就是有时数据校验会失败,但是测出的数据基本可以,而且是有变化的,说明还是比较可靠的。

接下来是把在Qt里把数据读出来并且显示,下面说一下调试Qt遇到的问题

在写完驱动之后,很自然会写一个.c的测试程序,用来验证驱动是否能正常工作,很幸运,一下子就成功了,于是我认为在Qt中也是类似,直接用Qt里的read相关的函数去读取设备文件就好了,但是没想到在这个环节卡了我最久

起初,我使用Qfile 里的readAll方法去读,发现控制台会刷屏(刷屏就是驱动中的read一直被调用而打印出的信息刷屏),一读就停不下来,而且后面的程序也执行不了,也就是说函数没有返回。

我不太清楚是什么原因,只能换一个函数,接着我尝试了readLine方法,一样刷屏,接着尝试read方法,这个方法和C语言的read类似,参数里要填读几个字节,这和前面两个不太一样,所以我想,这回应该不会刷屏了吧。

结果确实没有刷屏,但是读取的数据是错的,体现出来的就是从机无响应(这时我还没有注意这个问题)。

虽然说数据是错的,但是好歹没有刷屏了,只要再想一想为什么会读出错的数据就行了。

我想到Qt里还有一种读文件的方式,就是使用数据流Datastream,但是效果和上面的read一样。

接着我开始思考刷屏的原因,百度了一下,有人说要在末尾加一个"",尝试,未果。

接着,我在一些技术交流群寻求帮助,因为此刻我的问题确实很奇怪,在自己写的.c测试程序里,调用read读设备文件是完全没有问题的,现在唯一的区别就是在Qt中读,驱动又不变,为什么读出来的是错的呢?我怀疑是Qt的read对数据的解析可能和C语言里不太一样,因为此刻是有数据的,会不会是因为字节对齐之类的原因导致解析数据不对呢?群里大佬建议先排查一下源数据对不对。

于是我拿出了我许久没用过的逻辑分析仪来分析波形,我先观察了我的.c测试程序的波形,和手册描述的基本一致。接着观察Qt里read时的波形,一观察发现根本没有波形,正常情况应该是主机先拉低18ms,再拉高,等待从机应答。而我观察到的波形是主机拉低了30多ms才拉高,再看一下终端打印的数据,发现驱动里的read被调用了两次

这时,我已经猜到原因了,之所以数据不对,是因为驱动里的read被连续调用了两次,导致时序根本就不对,从机没有应答。

再观察之前使用readAll函数来读取,虽然会刷屏,但是偶尔能捕捉到有效的波形。这已经很能说明问题了,就是要解决驱动里的read为什么会被调用多次这个问题,正常应该是应用层调用一次read,驱动里的read就被调用一次。

关于这个问题,这篇文章讲的不错,[使用cat读取和echo写内核文件节点的一些问题](https://blog.csdn.net/weixin_34161083/article/details/86126562?utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-2.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-2.nonecase)

这篇文章对我还是有很大的启发。总之就是驱动中read 的返回值会影响它是否被多次调用。

先来看一下驱动中read函数的参数和返回值

代码语言:javascript复制
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 混合编程,方法就是使用

代码语言:javascript复制
extern "C"{
#include    //这里写用到的C头文件
}

然后在用到的C语言的函数前加两个冒号,比如

代码语言:javascript复制
::read(fd,buf,sizeof(buf));

这样就可以直接调用C语言代码了,而且发现效果还不错,比Qt中的read系列函数稳定。(实验次数有限,从我观察到的结果来看是这样)。

所以,最终的解决方法就是:

方法一:使用Qfile 的read函数,使用方法和C语言类似,可以正确读出数据,但是要注意,如果使用这个函数,驱动中的read要返回参数列表中的count,否则会刷屏。

方法二:直接使用混合编程的方式,调用C语言中的read ,这样测出的效果是最好的,而且不必要求驱动中的read 返回count,直接返回实际读取的字节即可,也就是copy_to_user的字节数。

驱动代码参考了[Linux下DHT11驱动编程,以及测试程序](https://blog.csdn.net/qq_23922117/article/details/72861182)

在此基础上修改得到

代码语言:javascript复制

#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:%dn",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: %lldn", count, *fops);
  
  printk(KERN_ERR "--------%s---------n",__func__);
 
    /*调用DHT11_Read_TempAndHumidity读取温湿度,若成功则输出该信息*/
  if( DHT11_Read_TempAndHumidity ( & DHT11_Data ) != 0)
  {
    printk(KERN_ERR "Read DHT11 ERROR!rn");
  }
  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 = %dn ", dht11_data_pin);

  ret=gpio_request(dht11_data_pin, "DQ_OUT");
    if(ret==0)
    {
        printk(KERN_ERR "gpio request successn");
    }
    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_devnon");
        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 cdevn");
        goto add_err;
    }

  //第四步
  /*创建类 */
  class_dht11 = class_create(THIS_MODULE, DEV_NAME);
    if(class_dht11==NULL)
    {
        printk(KERN_ERR"class creat failedn");
        goto add_class;
    }
  /*创建设备*/
  device = device_create(class_dht11, NULL, dht11_devno, NULL, DEV_NAME);
    if(device==NULL)
    {
        printk(KERN_ERR"device creat failedn");
        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 %dn",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");

0 人点赞