1. 环境准备与工具链配置

在开始MQTT库的交叉编译之前,我们需要先搭建好开发环境。我使用的是Ubuntu 18.04系统,这个版本比较稳定,与imx6ull的开发工具链兼容性也很好。如果你用的是其他版本的Ubuntu,可能需要注意一些依赖库的版本差异。

首先安装一些基础开发工具:

sudo apt update
sudo apt install build-essential git make cmake

接下来配置交叉编译工具链。imx6ull使用的是ARM Cortex-A7架构,我们需要安装对应的交叉编译器。正点原子提供的工具链是基于poky的,可以直接从官方获取:

# 创建工具链目录
sudo mkdir -p /opt/fsl-imx-x11
cd /opt/fsl-imx-x11

# 下载工具链(具体版本号请根据实际情况调整)
wget https://www.nxp.com/lgfiles/sdk/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi

设置环境变量很重要,每次打开新的终端都需要执行:

source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi

验证工具链是否配置成功:

arm-poky-linux-gnueabi-gcc --version

如果看到正确的版本信息,说明工具链已经就绪。我在第一次配置时遇到过环境变量没生效的问题,后来发现是因为source命令只在当前终端有效,所以每次开新终端都要重新执行。

2. Qt MQTT库源码获取与准备

Qt从5.10版本开始将MQTT模块移到了官方扩展库中,我们需要从GitHub获取源码。这里有个坑要注意:必须选择与你的Qt版本匹配的MQTT库版本。我用的Qt5.12.9,所以就选择对应的5.12.9版本的MQTT库。

# 创建工作目录
mkdir -p ~/imx6ull_qt_mqtt
cd ~/imx6ull_qt_mqtt

# 下载Qt MQTT源码
wget https://github.com/qt/qtmqtt/archive/refs/tags/v5.12.9.tar.gz
tar -xzf v5.12.9.tar.gz
cd qtmqtt-5.12.9

解压后的目录结构是这样的:

qtmqtt-5.12.9/
├── src/
│   └── mqtt/
│       ├── *.h 头文件
│       └── *.cpp 源文件
├── examples/ 示例代码
└── tests/ 测试代码

在开始编译前,还需要检查一下依赖。MQTT模块需要QtNetwork的支持,确保你的Qt版本包含了这个模块。可以用以下命令检查:

qmake -query | grep QT_INSTALL_LIBS

这会显示Qt库的安装路径,进去看看有没有libQt5Network.so文件。如果没有,可能需要重新安装Qt并包含Network模块。

3. 交叉编译Qt MQTT库

现在开始实际的交叉编译过程。首先需要设置交叉编译的环境变量,这是最关键的一步:

source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi

验证qmake版本是否正确:

qmake -v

应该看到使用的是arm-poky-linux-gnueabi版本的qmake,而不是x86_64版本。

创建编译目录并配置:

mkdir build-arm
cd build-arm
../configure -prefix /opt/qtmqtt-5.12.9-arm \
    -opensource \
    -confirm-license \
    -nomake examples \
    -nomake tests

这里有几个重要参数:

  • -prefix 指定安装目录,我习惯放在/opt下方便管理
  • -nomake examples 和 -nomake tests 可以跳过示例和测试编译,加快速度

开始编译:

make -j4
sudo make install

编译过程大概需要10-20分钟,取决于你的电脑性能。我在第一次编译时遇到了一个常见错误:找不到-lts库。这是因为tslib没正确配置,解决方法是指定tslib的路径:

export TS_LIBRARY=/usr/local/tslib/lib
export TS_INCLUDE=/usr/local/tslib/include

编译完成后,检查生成的库文件:

file /opt/qtmqtt-5.12.9-arm/lib/libQt5Mqtt.so.5.12.9

应该显示为ARM架构的共享库。如果显示x86_64,说明环境变量没设置对,需要重新检查。

4. 开发板环境部署与测试

编译好的库需要部署到开发板上。我通常用nfs共享的方式,这样调试起来比较方便。首先在开发板上配置nfs挂载:

# 在开发板上执行
mkdir /mnt/nfs
mount -t nfs 192.168.1.100:/home/share /mnt/nfs -o nolock

将编译好的库文件拷贝到nfs共享目录:

sudo cp -r /opt/qtmqtt-5.12.9-arm /home/share/

在开发板上设置库路径:

export LD_LIBRARY_PATH=/mnt/nfs/qtmqtt-5.12.9-arm/lib:$LD_LIBRARY_PATH

创建一个简单的测试程序验证MQTT库是否能正常工作:

// mqtt_test.cpp
#include <QtMqtt/QMqttClient>
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QMqttClient client;
    client.setHostname("test.mosquitto.org");
    client.setPort(1883);
    
    QObject::connect(&client, &QMqttClient::connected, []() {
        qDebug() << "Connected to MQTT broker!";
    });
    
    client.connectToHost();
    return a.exec();
}

交叉编译测试程序:

arm-poky-linux-gnueabi-g++ -o mqtt_test mqtt_test.cpp \
    -I/opt/qtmqtt-5.12.9-arm/include \
    -L/opt/qtmqtt-5.12.9-arm/lib \
    -lQt5Mqtt -lQt5Network -lQt5Core

将生成的可执行文件拷贝到开发板运行:

./mqtt_test

如果看到"Connected to MQTT broker!"的输出,说明库移植成功了。

5. Qt Creator项目配置

现在我们来配置Qt Creator,让它可以交叉编译使用MQTT的项目。首先打开Qt Creator,进入Tools -> Options -> Kits。

添加新的Qt版本:

  • Name: Qt 5.12.9 ARM
  • Path: /opt/qtmqtt-5.12.9-arm/bin/qmake

添加新的编译器:

  • Name: ARM Poky GCC
  • Path: /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc

创建新的Kit:

  • Name: imx6ull Kit
  • Device: Generic Linux Device
  • Sysroot: /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi
  • Compiler: ARM Poky GCC
  • Qt version: Qt 5.12.9 ARM

在项目配置文件(.pro)中添加MQTT支持:

QT += mqtt network

INCLUDEPATH += /opt/qtmqtt-5.12.9-arm/include
LIBS += -L/opt/qtmqtt-5.12.9-arm/lib -lQt5Mqtt

# 交叉编译设置
contains(QT_ARCH, arm) {
    target.path = /home/root
    INSTALLS += target
}

配置完成后,在Qt Creator中选择imx6ull Kit,点击编译。如果一切正常,应该能成功生成ARM版本的可执行文件。

6. 常见问题与解决方案

在移植过程中我遇到过不少问题,这里分享几个典型的:

问题1:编译时报错找不到mqtt头文件

fatal error: QtMqtt/QMqttClient: No such file or directory

解决方案:确保在.pro文件中正确设置了INCLUDEPATH,并且安装了对应版本的Qt MQTT库。

问题2:运行时提示找不到libQt5Mqtt.so

error while loading shared libraries: libQt5Mqtt.so.5: cannot open shared object file

解决方案:将编译好的库文件拷贝到开发板的/usr/lib目录下,或者设置LD_LIBRARY_PATH环境变量。

问题3:MQTT连接失败

QMqttConnection::SocketError: Connection refused

解决方案:检查网络连接,确认MQTT broker地址和端口正确,开发板防火墙设置允许出站连接。

问题4:性能问题 如果发现MQTT通信速度慢,可以调整QoS等级和keepalive时间:

client.setQualityOfService(1);
client.setKeepAlive(60);

调试技巧:可以在Qt Creator中启用远程调试,这样可以在开发板上直接调试程序。需要在开发板上安装gdbserver:

gdbserver :1234 ./your_program

然后在Qt Creator中配置远程调试,连接开发板IP和端口1234。

7. 实际项目应用示例

现在我们来做一个完整的温度监控示例,这个例子综合使用了MQTT的发布和订阅功能。

首先创建发布者程序,定时发布温度数据:

// publisher.cpp
#include <QtMqtt/QMqttClient>
#include <QCoreApplication>
#include <QTimer>
#include <QRandomGenerator>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QMqttClient publisher;
    publisher.setHostname("broker.hivemq.com");
    publisher.setPort(1883);
    
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&publisher]() {
        double temperature = 20 + QRandomGenerator::global()->generateDouble() * 10;
        QString message = QString("Temperature: %1 C").arg(temperature, 0, 'f', 1);
        publisher.publish(QMqttTopicName("imx6ull/temperature"), message.toUtf8());
        qDebug() << "Published:" << message;
    });
    
    QObject::connect(&publisher, &QMqttClient::connected, [&timer]() {
        qDebug() << "Publisher connected";
        timer.start(5000); // 每5秒发布一次
    });
    
    publisher.connectToHost();
    return a.exec();
}

再创建订阅者程序,接收温度数据:

// subscriber.cpp
#include <QtMqtt/QMqttClient>
#include <QtMqtt/QMqttSubscription>
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QMqttClient subscriber;
    subscriber.setHostname("broker.hivemq.com");
    subscriber.setPort(1883);
    
    QObject::connect(&subscriber, &QMqttClient::connected, [&subscriber]() {
        qDebug() << "Subscriber connected";
        auto subscription = subscriber.subscribe(QMqttTopicFilter("imx6ull/temperature"));
        QObject::connect(subscription, &QMqttSubscription::messageReceived, [](QMqttMessage msg) {
            qDebug() << "Received:" << msg.payload();
        });
    });
    
    subscriber.connectToHost();
    return a.exec();
}

编译这两个程序并部署到开发板上运行。你可以看到发布者每5秒发送一次温度数据,订阅者实时接收并显示。这个例子展示了MQTT在物联网设备中的典型应用场景。

在实际项目中,你可能还需要处理网络断开重连、消息持久化、安全认证等问题。Qt MQTT模块提供了相应的接口,可以根据具体需求进行配置。

记得在实际部署时使用安全的MQTT连接(MQTT over SSL/TLS),特别是在生产环境中。Qt同样支持SSL/TLS,只需要在代码中启用即可:

client.setTransportType(QMqttClient::SecureTransport);
QSslConfiguration sslConfig;
// 配置SSL证书等
client.setSslConfiguration(sslConfig);

更多推荐