DHT11 Linux驱动 + QT

上一章移植并调试好了开发板内核 uboot 根文件系统

先开始添加设备树

1.设备树

打开linux源码  找到im6ull-14x14-emmc-4.3-800x480-c.dts

其中只添加了lcd显示相关的节点内容

找#include "imx6ull-14x14-evk-emmc.dts"中的内容

在往上找#include "imx6ull-14x14-evk.dts"

可以发现出厂源码的设备树中已经写了dht11的节点内容所以不需要修改

为了方便之后的编译设备树写一个build-dtb.sh

2.驱动编写流程

1.DHT11设备结构体

struct dht11_dev
{
    dev_t dev_id;          // 设备号 u32
    struct cdev cdev;      // 字符设备 cdev
    struct class *class;   // 类
    struct device *device; // 设备
    int dht11_gpio;        // dht11所使用的GPIO编号
};
从后往前看

2.驱动的注册和驱动的注销(insmod *.ko / rmmod *.ko)

module_init(dht11_init);
module_exit(dht11_exit);
​
static int dht11_init(void)
{
    // 注册platform_driver
    int ret = platform_driver_register(&pdrv);
    if (ret != 0)
        ERROR_PRINT("platform driver register");
​
    return 0;
}
​
static void dht11_exit(void)
{
    /*注销设备节点*/
    device_destroy(dht11.class, dht11.dev_id);
    /*注销class*/
    class_destroy(dht11.class);
    /*注销字符设备*/
    cdev_del(&dht11.cdev);
    /*注销设备号*/
    unregister_chrdev_region(dht11.dev_id, 1);
    /*注销GPIO*/
    gpio_free(dht11.dht11_gpio);
    /*注销设备节点*/
    /*注销platform_driver*/
    platform_driver_unregister(&pdrv);
    printk("rmmod\n");
}

3.platform_diver设备结构体

struct of_device_id of_match_table[] = {
    {.compatible = "alientek, dht11"},
    {}}; // 设备树匹配   
​
struct platform_driver pdrv = {
    .probe = dht11_probe,
    .remove = dht11_remove,
    .driver = {
        .name = "dht11",
        .owner = THIS_MODULE,
        .of_match_table = of_match_table},
};

匹配上设备树中的内容就会调用dht11_probe函数

4.dht11_probe函数 注册相关的设备

1.GPIO的注册

int ret;
    printk("probe start\n");
    // 获取GPIO
    dht11.dht11_gpio = of_get_named_gpio(pdev->dev.of_node, "dht11-gpio", 0);
    if (dht11.dht11_gpio < 0)
    {
        ERROR_PRINT("of_get_named_gpio");
    }
    //请求GPIO
    ret = gpio_request(dht11.dht11_gpio, "dht11_gpio");
    if (ret != 0)
    {
        ERROR_PRINT("gpio_request!");
    }
    //设置GPIo的方向
    ret = gpio_direction_output(dht11.dht11_gpio, HIGH);
    if (ret != 0)
    {
        ERROR_PRINT("gpio_direction_output");
    }

2.注册字符设备

/*注册字符设备驱动*/
    /*分配设备号*/
    ret = alloc_chrdev_region(&dht11.dev_id, 0, 1, "dht11");
    if (ret != 0)
    {
        ERROR_PRINT("alloc_chrdev_region");
    }
    /*初始化cdev*/
    dht11.cdev.owner = THIS_MODULE;
    cdev_init(&dht11.cdev, &fops);
​
    /*将cdev注册到内核*/
    ret = cdev_add(&dht11.cdev, dht11.dev_id, 1);
    if (ret != 0)
    {
        ERROR_PRINT("cdev_add");
    }
    /*创建设备class*/
    dht11.class = class_create(THIS_MODULE, "dht11_class");
    if (dht11.class == NULL)
    {
        ERROR_PRINT("class_create");
    }
    /*在class下创建设备节点*/
    dht11.device = device_create(dht11.class, NULL, dht11.dev_id, NULL, DEVICE_NAME);
    if (dht11.device == NULL)
    {
        ERROR_PRINT("device_create");
    }
    printk("probe success!\n");

5.file_operations结构体

上方的cdev_init(&dht11.cdev, &fops); 将该结构体注册了,当应用读写的时候就会调用相关的函数

struct file_operations fops = {
    .open = dht11_open,
    .release = dht11_release,
    .read = dht11_read,
    .owner = THIS_MODULE};

6.DHT11驱动

主机拉低数据线20ms  然后拉高30ms 表示开始

void DHT11_Start(void)
{
    ssleep(1);
    gpio_direction_output(dht11.dht11_gpio, HIGH);
    udelay(30);
    gpio_set_value(dht11.dht11_gpio, LOW);
    mdelay(20); // 拉低20ms
    /*拉高20-40us*/
    gpio_set_value(dht11.dht11_gpio, HIGH);
    udelay(30); // 拉高30us
    // 设置为输入
    gpio_direction_input(dht11.dht11_gpio);
}

数据采集

当数据线由低转高并且大于30us说明数据是1否则是0

unsigned char get_dht11_value(void)
{
    int i;
    unsigned char data = 0;
    for (i = 0; i < 8; i++) // 8bit
    {
        while (gpio_get_value(dht11.dht11_gpio) == LOW)
            ;
        udelay(30);
        data <<= 1;
        if (gpio_get_value(dht11.dht11_gpio) == 1)
        {
            data |= 1;
        }
        while (gpio_get_value(dht11.dht11_gpio) == 1)
            ;
    }
    return data;
}

dht11_read

每次读取5个字节

ssize_t dht11_read(struct file *file, char __user *user_buf, size_t size, loff_t *loff)
{
    unsigned char dht11_data[5];
    DHT11_Start();
    gpio_set_value(dht11.dht11_gpio, HIGH);
    /*DHT11响应信号*/
    if (gpio_get_value(dht11.dht11_gpio) == LOW)
    {
        // 80us 低电平
        while (gpio_get_value(dht11.dht11_gpio) == LOW)
            ;
        // 80us 高电平
        while (gpio_get_value(dht11.dht11_gpio) == HIGH)
            ;
        dht11_data[0] = get_dht11_value(); // 湿度整数数据
        dht11_data[1] = get_dht11_value(); // 湿度小数数据  
        dht11_data[2] = get_dht11_value(); // 温度整数数据
        dht11_data[3] = get_dht11_value(); // 温度小数数据
        dht11_data[4] = get_dht11_value(); // 校验和
    }
    else
        printk("start is error!\n");

copy_to_user(user_buf, dht11_data, size);
    // 恢复为输出模式高电平
    gpio_direction_output(dht11.dht11_gpio, HIGH);
    return size;
}

7.编译

Makefile内容

make.sh内容

使用内核编译需要引入环境变量 然后make才不会报错

8.验证内容

使用scp发送dht11.ko 发送到开发板中

如果出现上述现象

ssh-keygen -R 192.168.1.200

使用insmod  dht11.ko

可以写一个.c的应用程序验证一下成功之后再编写QT程序  我这里没有验证

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    unsigned char buf[5] = {0};
    int fd = open("/dev/dht11", O_RDWR);
    if (fd == -1)
    {
        perror("open");
        return -1;
    }
    printf("open success!\n");
    while (1)
    {
        int ret = read(fd, buf, 5);
        if (ret == -1)
        {
            perror("read");
            return -1;
        }
        printf("buf[0]%d \n", buf[0]);
        printf("buf[1]%d\n", buf[1]);
        printf("buf[2]%d\n", buf[2]);
        printf("buf[3]%d\n", buf[3]);
        printf("buf[4]%d\n", buf[4]);

        sleep(5);
    }

    close(fd);
    return 0;
}

可以看到0+1+2+3=4  说明数据正确

3.QT

QT下的页面布局就不详细说了

发现的问题:使用QT的 file.read会导致读取数据失败

所以使用C库函数read

#include "mainwindow.h"

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <QDebug>
#include <cstring>

#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
  ui->setupUi(this);
  this->resize(800, 480);

  for (int i = 0; i < BUTTON_NUM; i++) {
    pushButton[i] = new QPushButton();
  }
  pushButton[0]->setText("start");
  pushButton[1]->setText("stop");
  pushButton[2]->setText("clear");
  textBrowser = new QTextBrowser;
  hBoxLayout = new QHBoxLayout;
  widget = new QWidget;
  hWidget = new QWidget;
  vBoxLayout = new QVBoxLayout;
  timer = new QTimer(this);
  hBoxLayout->addWidget(pushButton[0]);
  hBoxLayout->addWidget(pushButton[1]);
  hBoxLayout->addWidget(pushButton[2]);
  hWidget->setLayout(hBoxLayout);
  vBoxLayout->addWidget(textBrowser);
  vBoxLayout->addWidget(hWidget);
  widget->setLayout(vBoxLayout);
  setCentralWidget(widget);
  connect(timer, SIGNAL(timeout()), this, SLOT(timerTimerOut()));
  connect(pushButton[0], SIGNAL(clicked()), this, SLOT(start()));
  connect(pushButton[1], SIGNAL(clicked()), this, SLOT(stop()));
  connect(pushButton[2], SIGNAL(clicked()), this, SLOT(clear()));
  pushButton[0]->setEnabled(true);
  pushButton[1]->setEnabled(false);
}

MainWindow::~MainWindow() { delete ui; }

void MainWindow::timerTimerOut() { getData(); }

void MainWindow::getData() {
  if (fd != -1) {
    ssize_t bytesRead = read(fd, data, sizeof(data));
    if (bytesRead == sizeof(data)) {
      // 校验读取的数据是否合理
      if (data[4] == (data[0] + data[1] + data[2] + data[3])) {
        QString dataStr = QString("Humidity: %1.%2, Temperature: %3.%4℃ ")
                              .arg(data[0])
                              .arg(data[1])
                              .arg(data[2])
                              .arg(data[3]);
        textBrowser->append(dataStr);
      } else {
        // textBrowser->append("Checksum error, data might be corrupted");
        // qDebug() << "Checksum error:" << data[4]
        //<< "!=" << (data[0] + data[1] + data[2] + data[3]);
      }
    } else {
      // textBrowser->append("Error reading data");
      // qDebug() << "Error reading data. Bytes read:" << bytesRead
      //<< "Expected:" << sizeof(data);
    }
  } else {
    textBrowser->append("File is not open");
  }
}
void MainWindow::start() {
  const char *fileName = "/dev/dht11";
  fd = open(fileName, O_RDWR);
  if (fd == -1) {
    textBrowser->append("File can't open");
    qDebug() << "File can't open. Error:" << strerror(errno);
    return;
  }
  textBrowser->append("File opened successfully");
  getData();
  timer->start(5000);  // 每5秒读取一次数据
  pushButton[0]->setEnabled(false);
  pushButton[1]->setEnabled(true);
}
void MainWindow::stop() {
  if (fd != -1) {
    ::close(fd);
    fd = -1;
    textBrowser->append("File closed successfully");
    timer->stop();
    pushButton[0]->setEnabled(true);
    pushButton[1]->setEnabled(false);
  }
}
void MainWindow::clear() { textBrowser->clear(); }

结果

先导入环境变量 

qmake  -> make

4.补充:

驱动while记得添加超时跳出条件,不然数据一有波动整个程序就会卡住

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值