上一章移植并调试好了开发板内核 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记得添加超时跳出条件,不然数据一有波动整个程序就会卡住