当前位置: 首页 > news >正文

Linux外设驱动开发1 - 单总线驱动开发__dht11

设备树描述硬件 + 驱动解析协议 + 应用层处理数据

硬件信息由设备树描述,硬件操作由驱动实现,业务逻辑由应用处理

一、单总线介绍

1、单总线协议是一种巧妙的设计,它只用一根数据线(外加地线)就能实现设备间的双向通信

2、通信模式半双工,主从结构,所有信号由主机发起

3、关键优势布线极简、成本低、易于扩展、支持“寄生供电”主要局限带宽受限、时序要求严格、长距离传输需抗干扰

二、DHT11测温湿度传感器

1、基本信息

核心功能同时测量环境温度湿度
输出信号单总线数字信号,与微控制器连接简单
测量范围湿度:20% ~ 90% RH;温度:0℃ ~ 50℃
测量精度湿度:±5% RH;温度:±2℃
分辨率湿度:1% RH;温度:1℃
工作电压3.3V ~ 5.5V,兼容常见的单片机逻辑电平
功耗平均工作电流约0.5mA,功耗很低
引脚封装常见为3针或4针单排引脚(4针款有一引脚悬空)

2、时序图

1)主机发送起始信号(总线空闲时状态是拉高)
  • 步骤 1:主机将总线拉低 至少 18ms
    • 唤醒 DHT11,确保传感器检测到信号
  • 步骤 2:主机释放总线(拉高),等待 20~40us
    • 给传感器准备响应的时间
  • 步骤 3:主机将总线切换为输入模式,等待传感器响应
2)从机响应信号(传感器收到主机信号并答复)
  • 步骤 1:传感器拉低总线 80us
    • 告诉主机 “已收到请求”
  • 步骤 2:传感器拉高总线 80us
    • 告诉主机 “准备发送数据”
3)数据传输时序:

        传感器响应后,连续发送 40 位数据(高位在前),40 位数据的最后 8 位是 “校验和”,其值等于前 4 字节(32 位)之和,格式为:

[8 位湿度整数] + [8 位湿度小数] + [8 位温度整数] + [8 位温度小数] + [8 位校验和]

        通常校验:校验和 = 湿度整数 + 湿度小数 + 温度整数 + 温度小数

   单个 bit 的表示方式:    

  • 数据 “0”:传感器拉低总线 50us左右,然后拉高总线 26~28us。
  • 数据 “1”:传感器拉低总线 50us左右,然后拉高总线 68-74us。
4)完整时序图:
dht11 完整时序图

三、DHT11单总线外设驱动开发过程

    1、设备树准备过程:

    1. 修改设备树,添加传感器硬件描述配置(arch/arm/boot/dts/imx6ull-alientek-emmc.dts)
      1. 设备树的根节点或对应子系统节点下:添加设备节点
      2. 设备树的iomuxc节点下:配置GPIO 引脚的 复用功能、电器特性
    2. 编译设备树
      1. make dtbs
    3. 将编译好的(arch/arm/boot/dts/imx6ull-alientek-emmc.dtb)复制到tftpboot目录下
      1. tftpboot:利用TFTP协议实现开发板的远程文件加载
        1. TFTP(简单文件传输协议):是一种基于 UDP 的轻量级协议,专为嵌入式开发设计,支持无认证的文件下载 / 上传,常用于开发板的远程启动和文件更新。
        2. 目录作用:开发板(如 IMX6ULL)在启动阶段(如 U-Boot 环境下),可通过网络从tftpboot目录下载所需文件,无需依赖本地存储(如 SD 卡、NAND Flash),极大提升开发调试效率。

    2、代码大体框架:

    • dht11_app(应用层程序)
      • dht11_app.c
      • makefile
    • dht11_drv(驱动层程序)
      • dht11_drv.c
      • makefiel
    • makefie
    1)dht11_app(应用层程序)
    • dht11_app.c

    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <unistd.h>

    int main(void)
    {
      int fd = 0;
      ssize_t nret = 0;
      unsigned char data[4] = {0};
      char hum[8] = {0};
      char temp[8] = {0};

      //名字别错了
      fd = open("/dev/dht11", O_RDWR);
      if (-1 == fd)
      {
        perror("fail to open");
        return -1;
      }

      while (1)
      {
        nret = read(fd, data, sizeof(data));
        if (4 == nret)
        {
          sprintf(hum, "%d.%d", data[0], data[1]);
          sprintf(temp, "%d.%d", data[2], data[3]);
          printf("temp = %s, hum = %s\n", temp, hum);
        }

        sleep(2);
      }

      close(fd);

      return 0;
    }

    • makefile

    modulename:=dht11_app

    cc:=arm-linux-gnueabihf-gcc


    #$()引用变量或调用函数
    $(modulename):$(modulename).c
        $(cc) $^ -o $@
        cp $(modulename) ~/nfs/imx6/rootfs


    #将可执行文件复制到该目录下

    .PHONY:
    clean:
        rm $(modulename) -r
    distclean:
        rm $(modulename) -r
        rm ~/nfs/imx6/rootfs/$(modulename) -r

    2)dht11_drv(驱动层程序)
    • dht11_drv.c

    #include <asm/delay.h>
    #include <asm/uaccess.h>
    #include <linux/cdev.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h>
    #include <linux/module.h>
    #include <linux/of.h>
    #include <linux/of_gpio.h>
    #include <linux/platform_device.h>
    #include "asm-generic/gpio.h"
    #include "asm/gpio.h"
    #include "linux/mod_devicetable.h"
    #include "linux/nodemask.h"
    #include "linux/printk.h"

    static int gpio_dht11 = 0;
    extern void msleep(unsigned int msecs);

    // -7.1- MCU发送复位函数(启动信号)
    static void dht11_reset(void)
    {
      /* "敲门告诉传感器,我要准备读数据"
      1.初始化GPIO为输出模式并置高
      2.主机拉低总线,发送起始信号
      3.保持低电平至少18ms
      4.主机释放总线,等待传感器响应*/

      gpio_direction_output(gpio_dht11, 1);//设置刚开始是空闲拉高状态
      gpio_set_value(gpio_dht11, 0);
      msleep(20);
      gpio_set_value(gpio_dht11, 1);

      return;
    }


    // -7.2- 检测传感器是否响应MCU (先拉低80微秒,在拉高80微秒)
    static int recv_dht11_respone(void)
    {
      int timeout = 0;

      gpio_direction_input(gpio_dht11);  // GPIO引脚设置为输入模式,准备读取传感器信号

      timeout = 0;
      while (gpio_get_value(gpio_dht11) && timeout <= 10)  //等待DHT11将数据线拉低(100微秒) &&逻辑与不是按位与
      {
        udelay(10);  //微秒
        timeout++;
      }
      if (timeout > 10)  //超时未处理,失败返回
      {
        return -1;
      }
      while (!gpio_get_value(gpio_dht11));  //等待结束低电平
      while (gpio_get_value(gpio_dht11));  //等待结束高电平

      return 0;
    }


    // -7.3-
    static unsigned char recv_dht11_byte(void)
    {
      int cnt = 0;
      //在计算机内部本身就是以二进制形式存储
      unsigned char value = 0;
      int n = 0;

      gpio_direction_input(gpio_dht11);

      for (n = 7; n >= 0; n--)
      {
        while (!gpio_get_value(gpio_dht11));

        cnt = 0;
        while (gpio_get_value(gpio_dht11))
        {
          udelay(10);
          cnt++;
        }

        // value的初始状态是0,当判断出某位是1时,才需要进行对应某位进行"或"
        if (cnt > 5)
        {
          value |= (0x1 << n);
        }
      }
      return value;
    }

    static void recv_dht11_data(unsigned char *pdata)
    {
      //湿度整数部分
      pdata[0] = recv_dht11_byte();
      //湿度小数部分
      pdata[1] = recv_dht11_byte();
      //温度整数部分
      pdata[2] = recv_dht11_byte();
      //温度小数部分
      pdata[3] = recv_dht11_byte();
      //校验位
      pdata[4] = recv_dht11_byte();

      return;
    }


    // -7- 调函数
    /*fp: puser: n: off:*/
    static ssize_t dht11_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
    {
      int ret = 0;
      long nret = 0;
      unsigned char data[5] = {0};

      dht11_reset();
      ret = recv_dht11_respone();
      if (ret)
      {
        return ret;
      }
      recv_dht11_data(data);
      gpio_direction_output(gpio_dht11, 1);

      if (data[0] + data[1] + data[2] + data[3] != data[4])
      {
        return -2;
      }

      nret = copy_to_user(puser, data, 4);
      if (nret)
      {
        pr_info("copy_to_user failed\n");
        return -3;
      }

      return 4;
    }

    // -6- 设备操作函数集
    static struct file_operations dht11_fops = {
        .owner = THIS_MODULE,  //安全机制,避免多次加载
        .read = dht11_read,    //调函数
    };

    // -5- 注册设备
    static struct miscdevice misc_dht11 = {
        .minor = MISC_DYNAMIC_MINOR,  //动态分配"次设备号"
        .name = "dht11",              //"设备节点名",将在/dev下生成
        .fops = &dht11_fops,          //指向"结构体"文件操作函数集(open,read)
    };

    //  -3- 注册
    static int dht11_probe(struct platform_device *pdev)
    {
      int ret = 0;
      struct device_node *pht11node = NULL;

      pr_info("dht11_probe: start\n");

      ret = misc_register(&misc_dht11);
      if (ret)
      {
        pr_err("misc_register failed, ret=%d\n", ret);
        return -1;
      }

      pht11node = of_find_node_by_path("/fddht11");
      if (NULL == pht11node)
      {
        pr_info("of_find_node_by_path failed\n");
        return -1;
      }

      gpio_dht11 = of_get_named_gpio(pht11node, "gpio-dht11", 0);
      if (gpio_dht11 < 0)
      {
        pr_info("of_get_named_gpio failed\n");
        return -1;
      }

      ret = devm_gpio_request(misc_dht11.this_device, gpio_dht11, "fd-dht11");
      if (ret)
      {
        pr_info("devm_gpio_request failed\n");
        return -1;
      }

      gpio_direction_output(gpio_dht11, 1);
      pr_info("dht11_probe: success\n");

      return 0;
    }

    // -4- 销毁
    static int dht11_remove(struct platform_device *pdev)
    {
      int ret = 0;

      ret = misc_deregister(&misc_dht11);
      if (ret)
      {
        pr_info("misc_deregister failed\n");
        return -1;
      }

      return 0;
    }

    // -2- 匹配模式
    //用于设备树匹配,匹配顺序:最高_会优先进行这个匹配
    static struct of_device_id dht11_of_match_table[] = {
        {.compatible = "fd,fddht11"},
        {},
    };
    //用于非设备树或备选匹配,匹配顺序:次高(不支持设备树的旧系统,增加兼容性)
    static struct platform_device_id dht11_id_table[] = {
        {.name = "fddht11"},
        {},
    };

    //  -1- 
    static struct platform_driver dht11_driver = {
        .probe = dht11_probe,
        .remove = dht11_remove,
        .driver =
            {
                .name = "fddht11",
                .owner = THIS_MODULE,
                .of_match_table = dht11_of_match_table,
            },
        .id_table = dht11_id_table,
    };

    module_platform_driver(dht11_driver);  //带参宏
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("FD");

    • makefile

    #定义模块名字
    modulename:=dht11_drv

    #内核源码路径
    kerdir:=/home/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

    #获得当前makefile的路径
    curdir:=$(shell pwd)

    #obj-m:表示将该文件编译为动态加载模块(.ko)
    obj-m+=$(modulename).o

    #-C :切换到指定目录下
    #M=$(curdir) 告诉内核makefile,模块源码在该目录下
    all:
        make -C $(kerdir) M=$(curdir) modules
        cp $(modulename).ko ~/nfs/imx6/rootfs

    #伪目标:强制 Make 执行目标对应的命令,忽略是否存在同名文件
    .PHONY:
    #清理过程性生成的中间文件,留下ko


    clean:
        make -C $(kerdir) M=$(curdir) modules clean

    #彻底清理
    distclean:
        make -C $(kerdir) M=$(curdir) modules clean 
        rm ~/nfs/rootfs/$(modulename).ko

    3)makefile

    modulename:=dht11

    all:
        make -C $(modulename)_app
        make -C $(modulename)_drv

    .PHONY:
    clean:
        make -C $(modulename)_app clean
        make -C $(modulename)_drv clean
    distclean:
        make -C $(modulename)_app distclean
        make -C $(modulename)_drv distclean

    3、嵌入式应用程序加载与执行

    1)通过串口通信工具(如 minicom)连接嵌入式开发板(目标板)

    2)将编译生成的内核模块(.ko 文件)加载到目标板的操作系统内核中(insmod dht11_drv.ko)

    3)随后运行在目标板上的可执行程序,以验证模块功能或应用程序逻辑(./dht11_app)

    http://www.dtcms.com/a/506881.html

    相关文章:

  • 使用高性能流式的库SpreadCheetah来添加图片和合并表格单元
  • 建设银行网站建设情况免费招聘的网站
  • 手机上怎么做微电影网站徐州做网站谁家最专业
  • 【Mathematics】椭圆眼睛跟随鼠标交互中的仿射变换数学推导
  • 【u-boot】u-boot的分区支持
  • CG-FS-A3 风速传感器 485型 体积小巧 便捷安装 三杯式 聚碳材质
  • http和https区别如何转https
  • 国外的主要电机生产厂商
  • 英伟达公司发展历史
  • 网站首页文件名通常是无锡市建设安全监督网站
  • SQL之参数类型讲解——从基础类型到动态查询的核心逻辑
  • Linux中匿名设备和安全相关以及VFS的slab缓存对象创建
  • B.NET编写不阻塞UI线程的同步延时
  • 论文泛读:DYNAPROMPT: DYNAMIC TEST-TIME PROMPT TUNING(动态测试时调优)
  • 做 58 那样的网站北京公司网页设计
  • PyTorch实战(9)——从零开始实现Transformer
  • 18.SELInux安全性
  • Layui连线题编辑器组件(ConnectQuestion)
  • 电影网站加盟可以做么网奇seo培训官网
  • 【Linux】Socket编程TCP
  • Debian编译Qt5
  • [3-03-01].第07节:搭建服务 - 服务重构cloud-consumer-ocommon
  • Ubuntu Certbot版本查询失败?Snap安装后报错终极修复指南(通用版)
  • Kafka底层解析:可靠性与高性能原理
  • 分布式链路追踪中的上下文传播与一致性维护技术
  • 为已有nextjs项目添加supabase数据库,不再需要冗余后端
  • 网站建设怎样上传程序微信网站搭建多少钱
  • rabbitmq在微服务中配置监听开关
  • 下一代时序数据库标杆:Apache IoTDB架构演进与AIoT时代的数据战略
  • k8s中的控制器