Linux+Qt驱动dht11温湿度传感器实验过程遇到的问题及解决办法

描述

最近想要做一个基于嵌入式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)< span="">
{
       cnt++;
       udelay(10);
}

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

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写内核文件节点的一些问题

这篇文章对我还是有很大的启发。 总之就是驱动中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    //这里写用到的C头文件
}

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

::read(fd,buf,sizeof(buf));

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

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

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

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

驱动代码参考了Linux下DHT11驱动编程,以及测试程序

在此基础上修改得到

#include 
#include 
#include 
#include 
#include 


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#include 








/*------------------字符设备内容----------------------*/
#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");
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分