2. 窗口可视化设计–点亮LED

前面熟悉了Qt的工程创建和部署运行等,这章将简单学习下Qt界面设计。一般界面设计可以通过Qt Designer 进行可视化设计,或者代码化UI设计。

这章主要使用Qt Designer 进行可视化设计,设计一个非常简单的UI,控制鲁班猫板卡的GPIO,点亮一个LED。

提示

实际界面设计会混合使用代码和图形可视化,图形化设计很简单直观(所见即所得),代码化设计更灵活,可以实现一些图形化设计做不到的效果,也便于后期修改维护。

2.1. 鲁班猫板卡GPIO资源介绍

鲁班猫板卡引出了的40 pin引脚(Lubancat2为例):

gpio01.png

可以看到引脚可以复用为多种功能,这章我们只是使用普通io功能,点亮一个led。这里教程测试的板卡是lubancat2,默认使用的引脚是GPIO3_A5。 更多硬件资源或者其他板卡引脚参考下 这里

2.2. libgpiod

在Linux中,最常见的读写GPIO方式就是用GPIO sysfs interface, 是通过 /sys/class/gpio 目录下的 export 、 unexport 、gpio{N}/direction, gpio{N} /value (用实际引脚号替代{N})等文件实现, 命令行直接对data和direction等文件的读写来简便地操作IO。

从kernel 4.8开始,使用GPIO character device API,暴露到用户空间的设备文件是/dev/gpiochipN,新接口的设备文件没法像之前的sysfs接口一样使用, 而是通过作用在设备文件上的一系列ioctl实现的,这比较底层而不方便使用,因此一个建立在其上的封装库libgpiod应运而生。

相比sysfs接口,使用libgpiod操作/dev/gpiochipN有更多优势:

  • 为设置IO的上下拉提供了统一的操作;

  • 提供了用户空间中断的原生支持;

  • 可以在一个系统调用的时间内同时读取或者设置多个IO的值。此前操作多个IO必须分别对每个IO对应的设备文件进行read/write,相比之下新的接口不但节省了开销, 更可以对多个IO电平变化的时机进行精确的同步。

鲁班猫板卡系统中使用libgpiod库,需要使用apt命令安装:

# 使用命令安装libgpiod的相关库和头文件
sudo apt install libgpiod-dev

2.3. Qt Designer介绍

Qt Designer 是一个Qt工具,用于设计和构建图形用户界面(GUI)。

创建一个普通项目,默认勾选窗体(form)复选框,就会创建一个.ui文件,双击该文件就可以打开Qt Designer界面。

Qt Designer的使用参考下: 野火Qt Designer教程 , 更多的一些可以查看Qt官网的 Qt Designer教程

2.4. 点亮LED例程

2.4.1. 创建一个GPIO控制工程

打开Qt Creator,新建工程项目,创建一个qt_gpio工程,具体创建参考下前面 Hello Qt 章节。创建工程后,会在对应目录下生成:

qtgpio001

创建的项目默认添加了界面文件(默认勾选窗体(form)复选框),就会产生上面的mainwindow.ui文件。

2.4.2. 简单UI设计

双击项目资源目录的mainwindow.ui文件,进入设计界面,先点击主窗口(这里测试创建的是QMainWindow,个人测试可以使用QWidget), 在属性栏中设置大小为640x480,并设置最大值和最小值相同来固定窗口大小:

qtgpio001

从左边的控件面板中拖两个按键控件(QPushButton)到主窗口,实际就是添加两个按键对象到主窗口,我们重新修改对象名称分别为BtnRED、BtnONOFF:

qtgpio001

然后我们修改下两个按键控件的大小和相对主窗口的位置,先点击对象浏览框中的BtnONOFF,然后在属性栏然后修改, 在QWidget属性修改位置和大小:

qtgpio005

设置按钮文本和可选的:

qtgpio005

提示

前面图片中,属性栏中可以清晰的看见QPushButton类的继承关系:QObject->QWidget->QAbstractButton->QPushButton

添加资源文件,更改按键控件的样式。先到工程目录下点击 添加新文件 ,选择添加 Qt Resource File 之后设置下名称,然后一路点击完成:

qtgpio005

然后选择配套例程img文件下一些图片,添加到工程中,然后保存:

qtgpio005

再打开mainwindow.ui的Qt Designer界面,右击BtnRED或者BtnONOFF,点击 更改样式表 ,选择 添加资源 ,下拉框中选择 border-image , 然后选择前面添加图片来填充控件,BtnRED使用c_btn.png:

qtgpio005

关闭资源文件后,如果需要继续添加其他资源文件,可以右击.qrc资源文件,找到用 资源编辑器 打开 ,就可以继续添加资源文件。

修改之后,我们可以先选择虚拟机系统的默认套件: Desktop Qt %{Qt:Version} GCC 64bit ,编译运行下,查看我们设计的简单窗口:

qtgpio005

2.4.3. 编写GPIO控制程序

在工程目录右击选择 添加新文件 ,添加一个c++类,名称是GpioLed :

qtgpio005

添加之后,在类中描述,添加初始化和设置gpio输出高低电平的函数接口(使用libgpiod):

lubancat_qt_tutorial_code/QtGpio/gpioled.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef GPIOLED_H
#define GPIOLED_H

#include <QDebug>

extern "C"{
#include <gpiod.h>
}

class GpioLed : public QObject
{
   Q_OBJECT

public:
   /*测试使用引脚GPIO3_A5,实际根据鲁班猫板卡引脚修改*/
   const char *chipname = "gpiochip3";
   int line_num = 5;

   struct gpiod_chip *chip;
   struct gpiod_line *line_led;

public:
   int init();
   void set_value(bool flag);
   explicit GpioLed(QObject *parent = nullptr);
   ~GpioLed();

signals:

};

#endif // GPIOLED_H

函数具体实现在gpioled.cpp:

lubancat_qt_tutorial_code/QtGpio/gpioled.cpp(截取部分)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int GpioLed::init()
{
   int ret;

   /* 打开GPIO控制器 */
   chip = gpiod_chip_open_by_name(chipname);
   if (!chip)
   {
      qDebug("gpiod_chip_open_by_name failed!");
      return -1;
   }
   /* 获取引脚 */
   line_led = gpiod_chip_get_line(chip, line_num);
   if (!line_led)
   {
      gpiod_chip_close(chip);
      qDebug("gpiod_chip_get_line failed!");
      return -1;
   }

   /* 配置引脚为输出模式 name为“led” 初始电平为high ;教程的测试的电路,gpio输出为低电平时led亮*/
   ret = gpiod_line_request_output(line_led, "led", 1);
   if (ret)
   {
      qDebug("led request error.");
      return -1;
   }
   return 0;
}

void GpioLed::set_value(bool flag)
{
   gpiod_line_set_value(line_led, flag?1:0);
}

然后在我们的mainwindow中初始化:

lubancat_qt_tutorial_code/QtGpio/mainwindow.h(截取部分)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MainWindow : public QMainWindow
{
   Q_OBJECT

public:
// 创建对象GPIO3_A5
   GpioLed GPIO3_A5;

public:
   MainWindow(QWidget *parent = nullptr);
   ~MainWindow();
//后面省略
lubancat_qt_tutorial_code/QtGpio/mainwindow.cpp(截取部分)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
   ui->setupUi(this);

   /*设置使用的GPIO,调用函数初始化GPIO3_A5输出*/
   GPIO3_A5.chipname="gpiochip3";
   GPIO3_A5.line_num=5;
   GPIO3_A5.init();
}

以上就是一个非常简陋的gpio控制程序,实际使用可以自己添加更多的函数。

2.4.4. 关联信号与槽

前面创建了界面,实现了GPIO的控制,现在需要关联按钮点击和gpio的操作,来实现点击按键控件,就点亮LED。

进入Qt Creator设计界面,右击BtnONOFF按钮, 在弹窗中选择 转为槽... ,选中clicked(bool):

qtgpio005

会在mainwindow中自动添加on_BtnOnOff_clicked(bool checked)函数,我们在该函数下添加一些操作:

lubancat_qt_tutorial_code/QtGpio/mainwindow.cpp(截取部分)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void MainWindow::on_BtnOnOff_clicked(bool checked)
{
   if(checked)
   {
      /*按键控件按下on,checked=ture ,gpio输出低电平,led亮*/
      GPIO3_A5.set_value(!checked);
      /*改变按键样式*/
      ui->BtnOnOff->setStyleSheet(QString::fromUtf8("border-image: url(:/ic_btn_pre.png);"));
      ui->BtnRED->setStyleSheet(QString::fromUtf8("border-image: url(:/led_red.png);"));
   }
   else
   {
      /*按键控件off,checked=false ,gpio输出高电平,led灭*/
      GPIO3_A5.set_value(!checked);
      /*改变按键样式*/
      ui->BtnOnOff->setStyleSheet(QString::fromUtf8("border-image: url(:/ic_btn.png);"));
      ui->BtnRED->setStyleSheet(QString::fromUtf8("border-image: url(:/led_off.png);"));
   }
}

也许会有这样的疑问,怎么没有使用connect函数关联信号与槽? 到项目构建目录下,会看到一个中间文件:ui_mainwindow.h (对应测试默认创建的mainwindow.ui),打开文件会看到其中会有这么一句:

ui_mainwindow.h(截取)
1
QMetaObject::connectSlotsByName(MainWindow);

该函数会将递归的搜寻传入的Qt对象object的所有子对象,并把所有匹配的子对象的信号关联到object对象的符合规则的槽函数: void on_<窗口部件名称>_<信号名称>(<信号参数>), 如果窗口部件已经提供信号,就可以自动关联。更多信号与槽的知识参考下后面章节。

提示

在窗口设计中一些看不懂的函数或者不知道如何使用,可以先查看下Qt Creator中的帮助文档(快捷键F1)。

2.5. 运行测试例程

项目选择 LubanCat_RK 编译套件,然后打开qt_gpio.pro文件,修改下的下载路径和添加libgpiod链接库:

qt_gpio.pro
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
   gpioled.cpp \
   main.cpp \
   mainwindow.cpp

HEADERS += \
   gpioled.h \
   mainwindow.h

FORMS += \
   mainwindow.ui

# 添加链接库
LIBS += -lgpiod

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
#else: unix:!android: target.path = /opt/$${TARGET}/bin
# 修改下部署路径,如果不是远程部署,不需要设置
else: unix:!android: target.path = /home/cat/qt/qt_gpio
!isEmpty(target.path): INSTALLS += target

RESOURCES += \
   img/img.qrc

之后点击Qt Creator左下角锤子,只进行编译,编译后会在构建目录下生成qt_gpio可执行文件。 传输到板卡执行程序:

# 复制可执行程序到板卡
scp qt_gpio cat@192.168.103.110:/home/cat/qt

# 在debian10 带桌面的系统中运行,使用X11
./qt_gpio -platform xcb

在桌面就会弹窗,然后我们点击按钮就会点亮或者关闭LED(如果相应引脚接了LED),相应的图标也会变化:

qtgpio005

也可以远程部署,参考下前面章节。