这个博文主要介绍的是在制作这个简易的智能家居系统中学到的东西以及用到的各式各样的问题。这篇文章使用的开发板是韦东山的IMX6ULL pro开发板,实现简易的智能家居控制系统。这是我的第一个Linux的综合驱动项目,主要是想明白怎么实现一个项目的整体思维。文章内容主要提及的是关键部分,如果想实现的话参考我最后的代码链接。
最关键的部分整体思路,首先我得把每个模块的.ko文件加载实现了,然后终端如果能实现./xxx /dev/xxx 应用程序的调用的话,是不是我设计的UI界面也可以通过使用按键当按键按下后调用我的应用程序,这就是我整体的设计思路。其实是用UI界面上的按键调用驱动程序。

这篇文章的引脚配置如下所示
c
1.超声波 引脚为 trig-gpio gpio4 21 echo-gpio gpio4 20
2.舵机 引脚为 gpio4_21 pwm7
3.dht11 引脚为 gpio4_22
4.led 引脚为 gpio5 3
设备树配置
1.根节点配置如下
c
gpioled{
compatible = "ljj,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>;
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
dht11{
#address-cells = <1>;
#size-cells = <1>;
compatible = "ljj,dht11";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpiodht11>;
dht11-gpio = <&gpio4 22 GPIO_ACTIVE_HIGH>;
status = "okay";
};
hc-sg90 {
compatible = "hc-sg90";
pwms = <&pwm7 0 20000000>; /* 使用pwm2,id为0,周期20ms */
pwm-names = "sg90-pwm"; /* 可选:添加pwm名称 */
status = "okay";
};
hsr04{
#address-cells = <1>;
#size-cells = <1>;
compatible = "ljj,hsr04";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hsr04>;
trig-gpios = <&gpio4 21 GPIO_ACTIVE_HIGH>;
echo-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio4>;
interrupts = <20 IRQ_TYPE_EDGE_BOTH>;
status = "okay";
};
2.iomuxc中配置
c
pinctrl_gpioled: ledgrp{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0 /* 我写的 */
>;
};
pinctrl_gpiodht11: dht11grp{
fsl,pins = <
MX6UL_PAD_CSI_DATA01__GPIO4_IO22 0x000050B0 /* 我写的 */
>;
};
pinctrl_pwm7: pwm7grp {
fsl,pins = <
MX6UL_PAD_CSI_VSYNC__PWM7_OUT 0x000010B0 /* ljj写的 */
>;
};
pinctrl_hsr04: hsr04grp{
fsl,pins = <
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B0
MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x000010B0
>;
};
切记一定要注意引脚有没有被别的地方调用了,不然会冲突的。以上就是设备树相关的配置了。
驱动程序
驱动程序并没有涉及到引脚的配置,最多也就是节点不一样,这就实现了驱动和驱动程序的分离,我.ko文件基本上都不需要动,直接调用即可。我会把所有的驱动程序都放到最后的链接中。接下来是我在终端中测试其是否正常工作的截图。各模块的驱动代码不做过多的展示。
Hsr04 测试成功
./hsr04app /dev/hsr04irq

Dht11测试成功
./dht11_app /dev/gpiodht11

灯测试成功
./ledAPP /dev/gpioled 0


舵机测试成功

c
[root@100ask:~]# cd /sys/class/pwm/pwmchip6
[root@100ask:/sys/class/pwm/pwmchip6]# ls
device export npwm power subsystem uevent unexport
[root@100ask:/sys/class/pwm/pwmchip6]# echo 0 > export
[root@100ask:/sys/class/pwm/pwmchip6]# echo 20000000 > pwm0/period
[root@100ask:/sys/class/pwm/pwmchip6]# echo 1000000 > pwm0/duty_cycle
[root@100ask:/sys/class/pwm/pwmchip6]# echo 1 > pwm0/enable
[root@100ask:/sys/class/pwm/pwmchip6]# echo 1200000 > pwm0/duty_cycle
[root@100ask:/sys/class/pwm/pwmchip6]# echo 800000 > pwm0/duty_cycle
sh文件加载模块
sh文件主要是用于加载模块,不用我们一行行的敲。
insmod.sh
c
#!/bin/sh
insmod gpioled.ko &
insmod servo.ko &
insmod dht11.ko &
insmod hsr04.ko
c
#!/bin/sh
rmmod gpioled.ko &
rmmod servo.ko &
rmmod dht11.ko &
rmmod hsr04.ko
加载模块命令行
c
chmod +x insmod.sh
./insmod.sh
我驱动代码中写了各个部分如果测试成功输出其设置成功的内容,不同的驱动代码不一样。

驱动测试成功之后接下来就是qt ui界面的设计。
UI界面的初始化学习可以先参考韦老师的教程:百问网韦东山嵌入式Linux应用开发入门实验班(快!做出产品!而不是学一堆知识点!!)
我也非常推荐看一下我的这篇文章快速入门:linux之ubuntu qt界面开发开发点菜系统 这篇文中中有许多最开始初学者会遇到的问题提及到了以及解决方案。

首先pro文件中导入一下头文件地址怕找不到头文件。根据你的文件而定。
c
/home/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include
UI界面设计

界面设计就不做过多讲解了主要用的是PushButton按键控制开关啥的、lineEdit用于显示超声波、DHT11传输过来的文本数据内容。学习的话多参考我的这篇文章我也非常推荐看一下我的这篇文章快速入门:linux之ubuntu qt界面开发开发点菜系统 这篇文中中有许多最开始初学者会遇到的问题提及到了以及解决方案。
lineEdit用于显示超声波、DHT11传输过来的文本数据内容我并没有用slot啥的,可以参考我的代码学习一下。
首先讲一下最简单的两个一个是LED开关、一个是PWM控制舵机
LED灯开关直接用system即可
舵机控制主要是命令行实现的所有直接用system函数即可。
首先PWM单独初始化
c
system("echo 0 > /sys/class/pwm/pwmchip6/export");
c
void MainWindow::on_Servo0Button_clicked()
{
// 设置PWM周期为20ms(20000000ns)
system("echo 20000000 > /sys/class/pwm/pwmchip6/pwm0/period");
// 设置占空比为1ms(1000000ns → 0°)
system("echo 1000000 > /sys/class/pwm/pwmchip6/pwm0/duty_cycle");
system("echo 1 > /sys/class/pwm/pwmchip6/pwm0/enable");
}
void MainWindow::on_Servo90Button_clicked()
{
system("echo 20000000 > /sys/class/pwm/pwmchip6/pwm0/period");
system("echo 1500000 > /sys/class/pwm/pwmchip6/pwm0/duty_cycle");
system("echo 1 > /sys/class/pwm/pwmchip6/pwm0/enable");
}
void MainWindow::on_Servo180Button_clicked()
{
system("echo 20000000 > /sys/class/pwm/pwmchip6/pwm0/period");
system("echo 2000000 > /sys/class/pwm/pwmchip6/pwm0/duty_cycle");
system("echo 1 > /sys/class/pwm/pwmchip6/pwm0/enable");
}

后面出现红色报错没关系的,只要编译没报错就行。
LED应用程序
直接调用写好的应用程序即可,不要再写C++代码不然就工程复杂了。
c
void MainWindow::on_Led_on_Button_clicked()
{
system("./ledAPP /dev/gpioled 1");
printf("on");
}
void MainWindow::on_Led_off_Button_clicked()
{
system("./ledAPP /dev/gpioled 0");
printf("off");
}
DHT11应用程序
将思路转换成代码。首先这部分我是模仿dht11的应用程序进行改写的,所以一定会用到open函数以非阻塞模式打开设备文件,read函数读取data[4]温湿度数据。以上就是大致思路。
1.变量头文件初始化
首先在.h文件中注册一个应用程序的定时器,和sleep函数一样的作用,dht11Running 监测DHT11是否正在运行,dht11Fd查看open是否正确其实他就是应用程序中的ret。
c
private:
Ui::MainWindow *ui;
QTimer *dht11Timer; // 定时器,用于定时读取数据,是用户代码定时执行
int dht11Fd; // DHT11设备文件描述符
bool dht11Running; // DHT11是否正在运行
2.定时器,变量初始化值
然后初始化创建定时器对象,初始化dht11Fd为-1能正常open的话它肯定会大于0,初始化dht11Running为false。
c
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, dht11Timer(new QTimer(this)) // 创建定时器对象
, dht11Fd(-1) // 初始化dht11Fd为-1
, dht11Running(false) // 初始化dht11Running为false
3.定时器配置
连接定时器信号到读取数据的槽函数,设置定时器间隔(2000ms = 2秒,符合DHT11要求)因为我驱动程序是1秒1读。当 dht11Timer 发出 timeout 信号时,自动调用 this 对象的 readDHT11Data 函数
c
// 连接定时器信号到读取数据的槽函数
connect(dht11Timer, &QTimer::timeout, this, &MainWindow::readDHT11Data);
// 设置定时器间隔(2000ms = 2秒,符合DHT11要求)
dht11Timer->setInterval(2000);
4.开启dht11监测按钮函数 void MainWindow::on_Dht11_on_Button_clicked()
首先判断是否在工作了,open函数以非阻塞模式打开设备文件,初始化成功后dht11Fd就不会小于0了否则返回。在此处开启定时器,更新开启dht11监测按钮为蓝色,关闭dht11监测按钮为白色。
c
void MainWindow::on_Dht11_on_Button_clicked()
{
if (dht11Running) { // 刚开始为false
QMessageBox::information(this, "提示", "DHT11已经在运行中");
return;
}
// 以非阻塞模式打开设备文件
dht11Fd = open("/dev/gpiodht11", O_RDWR | O_NONBLOCK);
// 初始化成功后dht11Fd就不会小于0了
if (dht11Fd == -1) {
QMessageBox::critical(this, "错误",
QString("无法打开DHT11设备文件\n错误: %1").arg(strerror(errno)));
return;
}
dht11Running = true;
dht11Timer->start(); // 启动定时器
// 更新UI按钮状态
ui->Dht11_on_Button->setEnabled(false);
ui->Dht11_off_Button->setEnabled(true);
ui->Dht11_on_Button->setStyleSheet(
"QPushButton {"
" background-color: #2196F3;"
" color: white;"
" border: 2px solid #1565C0;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
ui->Dht11_off_Button->setStyleSheet(
"QPushButton {"
" background-color: white;"
" border: 2px solid #2196F3;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
// 初始显示
ui->Dht11lineEdit->setText("DHT11: 启动中...");
qDebug() << "DHT11传感器已启动";
}
5.关闭dht11监测按钮函数 void MainWindow::on_Dht11_off_Button_clicked()
停止定时器,更改按钮颜色即可。
c
void MainWindow::on_Dht11_off_Button_clicked()
{
if (!dht11Running) {
return;
}
// 停止定时器
dht11Timer->stop();
dht11Running = false;
// 更新UI按钮状态
ui->Dht11_on_Button->setEnabled(true);
ui->Dht11_off_Button->setEnabled(false);
ui->Dht11_on_Button->setStyleSheet(
"QPushButton {"
" background-color: white;"
" border: 2px solid #2196F3;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
ui->Dht11_off_Button->setStyleSheet(
"QPushButton {"
" background-color: #2196F3;"
" color: white;"
" border: 2px solid #1565C0;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
// 更新显示
ui->Dht11lineEdit->setText("DHT11: 已停止");
qDebug() << "DHT11传感器已停止";
}
6.核心函数:读取DHT11数据并更新显示
首先判断设备是否打开了,没打开就返回,使用poll进行超时控制,等待设备文件变为可读状态,ret = read(dht11Fd, data, 4);打开设备dht11Fd将其返回的内容给data,然后温湿度成功读取4字节数据,UI界面在Dht11lineEdit中显示。
c
void MainWindow::readDHT11Data()
{
if (dht11Fd == -1) {
qDebug() << "DHT11设备未打开";
return;
}
struct pollfd fds[1];
unsigned char data[4];
int ret;
int timeout_ms = 3000; // 3秒超时
// 设置poll结构
fds[0].fd = dht11Fd;
fds[0].events = POLLIN;
// 使用poll进行超时控制,等待设备文件变为可读状态
ret = poll(fds, 1, timeout_ms);
if (ret == -1) {
// 错误
QString errorMsg = QString("poll错误: %1").arg(strerror(errno));
qDebug() << errorMsg;
ui->Dht11lineEdit->setText("DHT11: " + errorMsg);
return;
} else if (ret == 0) {
// 超时
ui->Dht11lineEdit->setText("DHT11: 读取超时");
qDebug() << "DHT11读取超时";
return;
} else {
// 数据可读
if (fds[0].revents & POLLIN) {
ret = read(dht11Fd, data, 4);
if (ret == 4) {
// 成功读取4字节数据
int humidity_int = data[0];
int humidity_dec = data[1];
int temp_int = data[2];
int temp_dec = data[3];
// 构建显示字符串
QString displayText = QString("温度: %1.%2°C 湿度: %3.%4%")
.arg(temp_int)
.arg(temp_dec)
.arg(humidity_int)
.arg(humidity_dec);
// 更新UI显示
ui->Dht11lineEdit->setText(displayText);
// 可选:输出到控制台用于调试
qDebug() << "DHT11读取成功:" << displayText;
} else if (ret == -1) {
if (errno == EAGAIN) {
// 设备忙,重试
ui->Dht11lineEdit->setText("DHT11: 设备忙,重试中...");
qDebug() << "DHT11设备忙";
} else {
// 读取错误
QString errorMsg = QString("读取错误: %1").arg(strerror(errno));
qDebug() << errorMsg;
ui->Dht11lineEdit->setText("DHT11: " + errorMsg);
}
} else {
// 意外的返回值
QString errorMsg = QString("意外的返回值: %1").arg(ret);
qDebug() << errorMsg;
ui->Dht11lineEdit->setText("DHT11: " + errorMsg);
}
}
}
}
以上就是DHT11配置的核心步骤不涉及驱动只有应用程序和传统的应用程序步骤一样的。
Hsr04应用程序
1.将思路转换成代码。首先这部分我是模仿Hsr04的应用程序进行改写的,和dht11的步骤大差不差。所以一定会用到open函数以非阻塞模式打开设备文件,read函数读取int 类型的distance_cm数据。以上就是大致思路。
1.变量头文件初始化
首先在.h文件中注册一个应用程序的定时器,和sleep函数一样的作用,hsr04Running 监测hsr04是否正在运行,hsr04Fd查看open是否正确其实他就是应用程序中的ret。
c
private:
Ui::MainWindow *ui;
// 超声波HSR04相关
QTimer *hsr04Timer;
int hsr04Fd;
bool hsr04Running;
2.定时器,变量初始化值
然后初始化创建定时器对象,初始化hsr04Fd为-1能正常open的话它肯定会大于0,初始化hsr04Running为false。
c
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, hsr04Timer(new QTimer(this))
, hsr04Fd(-1)
, hsr04Running(false)
3.定时器配置
连接定时器信号到读取数据的槽函数,设置定时器间隔(2000ms = 2秒,符合DHT11要求)因为我驱动程序是1秒1读。当 hsr04Timer 发出 timeout 信号时,自动调用 this 对象的 readHSR04Data 函数
c
// 连接超声波定时器信号
connect(hsr04Timer, &QTimer::timeout, this, &MainWindow::readHSR04Data);
hsr04Timer->setInterval(1000); // 超声波每1秒读取一次
4.开启hsr04监测按钮函数 void MainWindow::on_Hsr04_on_Button_clicked()
首先判断是否在工作了,open函数以非阻塞模式打开设备文件,初始化成功后hsr04Fd就不会小于0了否则返回。在此处开启定时器,更新开启hsr04监测按钮为蓝色,关闭hsr04监测按钮为白色。
c
void MainWindow::on_Hsr04_on_Button_clicked()
{
if (hsr04Running) {
QMessageBox::information(this, "提示", "超声波已经在运行中");
return;
}
// 打开超声波设备文件(阻塞模式,与C代码一致)
hsr04Fd = open("/dev/hsr04irq", O_RDWR);
if (hsr04Fd == -1) {
QMessageBox::critical(this, "错误",
QString("无法打开超声波设备文件\n错误: %1").arg(strerror(errno)));
return;
}
hsr04Running = true;
hsr04Timer->start(); // 启动定时器
// 更新UI按钮状态
ui->Hsr04_on_Button->setEnabled(false);
ui->Hsr04_off_Button->setEnabled(true);
ui->Hsr04_on_Button->setStyleSheet(
"QPushButton {"
" background-color: #2196F3;"
" color: white;"
" border: 2px solid #1565C0;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
ui->Hsr04_off_Button->setStyleSheet(
"QPushButton {"
" background-color: white;"
" border: 2px solid #2196F3;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
// 初始显示
ui->Hsr04lineEdit->setText("HSR04: 启动中...");
qDebug() << "超声波传感器已启动";
}
5.关闭Hsr04监测按钮函数 void MainWindow::on_Hsr04_off_Button_clicked()
停止定时器,更改按钮颜色即可。
c
void MainWindow::on_Hsr04_off_Button_clicked()
{
if (!hsr04Running) {
return;
}
// 停止定时器
hsr04Timer->stop();
// 关闭设备文件
// if (hsr04Fd != -1) {
// ::close(hsr04Fd);
// hsr04Fd = -1;
// }
hsr04Running = false;
// 更新UI按钮状态
ui->Hsr04_on_Button->setEnabled(true);
ui->Hsr04_off_Button->setEnabled(false);
ui->Hsr04_on_Button->setStyleSheet(
"QPushButton {"
" background-color: white;"
" border: 2px solid #2196F3;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
ui->Hsr04_off_Button->setStyleSheet(
"QPushButton {"
" background-color: #2196F3;"
" color: white;"
" border: 2px solid #1565C0;"
" border-radius: 10px;"
" padding: 10px;"
" font-size: 14px;"
" font-weight: bold;"
"}"
);
// 更新显示
ui->Hsr04lineEdit->setText("HSR04: 已停止");
qDebug() << "超声波传感器已停止";
}
6.核心函数:读取HSR04数据并更新显示
首先判断设备是否打开了,没打开就返回,使用poll进行超时控制,等待设备文件变为可读状态,ret = read(hsr04Fd, &distance_cm, 4);打开设备hsr04Fd将其返回的内容给整数类型distance_cm,然后超声波成功读取1字节整数数据,UI界面在Hsr04lineEdit中显示。
以上就是HSR04配置的核心步骤不涉及驱动只有应用程序和传统的应用程序步骤一样的。
实验效果视频
实验不算复杂,主要是学会linux项目的整体思维,以及怎么和QT结合实现操控。 ## 代码链接:
通过网盘分享的文件:linux智能家居
链接: https://pan.baidu.com/s/1YCMi7SOSQuUhDLwCJxlWsQ?pwd=bwuh 提取码: bwuh