前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux+Qt驱动dht11实验过程遇到的问题及解决办法

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

作者头像
飞哥
发布2021-04-08 20:21:58
1.1K0
发布2021-04-08 20:21:58
举报

最近想要做一个基于嵌入式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一样。

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

接着,我在一些技术交流群寻求帮助,因为此刻我的问题确实很奇怪,在自己写的.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:%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");
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档