1)实验平台:正点原子阿尔法Linux开发板
2) 章节摘自【正点原子】《I.MX6U 嵌入式Qt开发指南》
3)购买链接:https://detail.tmall.com/item.htm?id=609033604451
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子阿尔法Linux交流群:1027879335
第十八章 CAN Bus
从Qt5.8开始,提供了CAN Bus类,很庆幸,正点原子的I.MX6U出厂系统里Qt版本是QT5.12.9。我们可以直接使用Qt的提供的CAN相关类编程即可。假设您的Qt版本没有CAN Bus,可以参考Linux应用编程来操控开发板的CAN,目前我们主要讲解Qt相关的CAN编程。其实Qt也提供了相关的Qt CAN的例子,我们也可以直接参考来编程。编者根据实际情况,化繁为易,直接写了个简单的例子给大家参考。最重要的一点,读者应会使用CAN相关测试工具,我们应该提前去熟悉,并且读者手上需要有测试CAN的仪器!否则写好程序,却无法测试,这就有些尴尬了。
18.1 资源简介正点原子I.MX6U开发板底板上预留了一路CAN接口(6U芯片最大支持两路)。如下图。在正点原子【正点原子】I.MX6U用户快速体验V1.x.pdf里也有相关的CAN测试方法。这里就不多介绍CAN了,编者默认读者是会使用CAN的。同时不对CAN总线协议进行讲解,主要是讲解如何在Qt里对CAN编程。 18.2 应用实例项目简介:本例适用于正点原子I.MX6U开发板。不适用于Windows。因为Windows没有CAN设备。虽然Windows可以外接USB CAN模块,但是这些模块都是某些厂商开发的,需要有相应的固件才能驱动CAN设备。所以编写的例子不一定适用于Windows下的CAN。编者写的例子已经在正点原子I.MX6U开发板上验证了,确保正常使用! 在正点原子I.MX6U板上,需要使用CAN必须初始化CAN。它的开启与关闭都是由系统完成。I.MX6U为普通CAN,非FD CAN,最大比特率为1000kBit/s。 在系统执行要在100毫秒后自动从“总线关闭”错误中恢复,并以比特率1000000,可以使用以下命令开启CAN。 - ip link set up can0 type can bitrate 1000000 restart-ms 100
复制代码例04_socketcan,Qt CAN编程(难度:较难)。项目路径为Qt/3/04_ socketcan。 04_socketcan.pro要想使用Qt的QCanBus,需要在pro项目文件里添加相应的模块支持。同时还需要添加对应的头文件,详细请看项目里的代码。 - 1 QT += core gui serialbus
- 2
- 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 4
- 5 CONFIG += c++11
- 6
- 7 # The following define makes your compiler emit warnings if you use
- 8 # any Qt feature that has been marked deprecated (the exact warnings
- 9 # depend on your compiler). Please consult the documentation of the
- 10 # deprecated API in order to know how to port your code away from it.
- 11 DEFINES += QT_DEPRECATED_WARNINGS
- 12
- 13 # You can also make your code fail to compile if it uses deprecated APIs.
- 14 # In order to do so, uncomment the following line.
- 15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
- 16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
- 17
- 18 SOURCES += \
- 19 main.cpp \
- 20 mainwindow.cpp
- 21
- 22 HEADERS += \
- 23 mainwindow.h
- 24
- 25 # Default rules for deployment.
- 26 qnx: target.path = /tmp/${TARGET}/bin
- 27 else: unix:!android: target.path = /opt/${TARGET}/bin
- 28 !isEmpty(target.path): INSTALLS += target
复制代码第1行,添加的serialbus就是添加串行总线模块的支持。 在头文件“mainwindow.h”的代码如下。一些声明。 - /******************************************************************
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 04_socketcan
- * @brief mainwindow.h
- * [url=home.php?mod=space&uid=90321]@Author[/url] Deng Zhimao
- * [url=home.php?mod=space&uid=55957]@EMAIL[/url] 1252699831@qq.com
- * [url=home.php?mod=space&uid=28414]@net[/url] www.openedv.com
- * @date 2021-03-15
- *******************************************************************/
- 1 #ifndef MAINWINDOW_H
- 2 #define MAINWINDOW_H
- 3
- 4 #include <QMainWindow>
- 5 #include <QCanBusDevice>
- 6 #include <QCanBus>
- 7 #include <QPushButton>
- 8 #include <QTextBrowser>
- 9 #include <QLineEdit>
- 10 #include <QVBoxLayout>
- 11 #include <QLabel>
- 12 #include <QComboBox>
- 13 #include <QGridLayout>
- 14 #include <QMessageBox>
- 15 #include <QDebug>
- 16
- 17 class MainWindow : public QMainWindow
- 18 {
- 19 Q_OBJECT
- 20
- 21 public:
- 22 MainWindow(QWidget *parent = nullptr);
- 23 ~MainWindow();
- 24
- 25 private:
- 26 /* CAN设备 */
- 27 QCanBusDevice *canDevice;
- 28
- 29 /* 用作接收数据 */
- 30 QTextBrowser *textBrowser;
- 31
- 32 /* 用作发送数据 */
- 33 QLineEdit *lineEdit;
- 34
- 35 /* 按钮 */
- 36 QPushButton *pushButton[2];
- 37
- 38 /* 下拉选择盒子 */
- 39 QComboBox *comboBox[3];
- 40
- 41 /* 标签 */
- 42 QLabel *label[4];
- 43
- 44 /* 垂直布局 */
- 45 QVBoxLayout *vboxLayout;
- 46
- 47 /* 网络布局 */
- 48 QGridLayout *gridLayout;
- 49
- 50 /* 主布局 */
- 51 QWidget *mainWidget;
- 52
- 53 /* 设置功能区域 */
- 54 QWidget *funcWidget;
- 55
- 56 /* 布局初始化 */
- 57 void layoutInit();
- 58
- 59 /* 插件类型项初始化 */
- 60 void pluginItemInit();
- 61
- 62 /* 比特率项初始化 */
- 63 void bitrateItemInit();
- 64
- 65 private slots:
- 66 /* 发送消息 */
- 67 void sendFrame();
- 68
- 69 /* 接收消息 */
- 70 void receivedFrames();
- 71
- 72 /* 插件发生改变 */
- 73 void pluginChanged(int);
- 74
- 75 /* 处理can错误 */
- 76 void canDeviceErrors(QCanBusDevice::CanBusError) const;
- 77
- 78 /* 连接或者断开can */
- 79 void connectDevice();
- 80 };
- 81 #endif // MAINWINDOW_H
- 82
复制代码上面代码是在mianwindow.h里声明需要用到的变量,方法及槽函数。 mainwindow.cpp的代码如下。 - /******************************************************************
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 04_socketcan
- * @brief mainwindow.cpp
- * @author Deng Zhimao
- * @email 1252699831@qq.com
- * @net www.openedv.com
- * @date 2021-03-15
- *******************************************************************/
- 1 #include "mainwindow.h"
- 2 #include <QGuiApplication>
- 3 #include <QScreen>
- 4
- 5 MainWindow::MainWindow(QWidget *parent)
- 6 : QMainWindow(parent)
- 7 {
- 8 /* 使用系统指令比特率初始化CAN,默认为1000000bits/s */
- 9 system("ifconfig can0 down");
- 10 system("ip link set up can0 type can bitrate 1000000 restart-ms 100");
- 11
- 12 /* 布局初始化 */
- 13 layoutInit();
- 14
- 15 /* 可用插件初始化 */
- 16 pluginItemInit();
- 17
- 18 /* 可用接口项初始化 */
- 19 pluginChanged(comboBox[0]->currentIndex());
- 20
- 21 /* 比特率项初始化 */
- 22 bitrateItemInit();
- 23 }
- 24
- 25 MainWindow::~MainWindow()
- 26 {
- 27 }
- 28
- 29 static QString frameFlags(const QCanBusFrame &frame)
- 30 {
- 31 /* 格式化接收到的消息 */
- 32 QString result = QLatin1String(" --- ");
- 33
- 34 if (frame.hasBitrateSwitch())
- 35 result[1] = QLatin1Char('B');
- 36 if (frame.hasErrorStateIndicator())
- 37 result[2] = QLatin1Char('E');
- 38 if (frame.hasLocalEcho())
- 39 result[3] = QLatin1Char('L');
- 40
- 41 return result;
- 42 }
- 43
- 44 /* 发送消息 */
- 45 void MainWindow::sendFrame()
- 46 {
- 47 if (!canDevice)
- 48 return;
- 49 /* 读取QLineEdit的文件 */
- 50 QString str = lineEdit->text();
- 51 QByteArray data = 0;
- 52 QString strTemp = nullptr;
- 53 /* 以空格分隔lineEdit的内容,并存储到字符串链表中 */
- 54 QStringList strlist = str.split(' ');
- 55 for (int i = 1; i < strlist.count(); i++) {
- 56 strTemp = strTemp + strlist[i];
- 57 }
- 58 /* 将字符串的内容转为QByteArray类型 */
- 59 data = QByteArray::fromHex(strTemp.toLatin1());
- 60
- 61 bool ok;
- 62 /* 以16进制读取要发送的帧内容里第一个数据,并作为帧ID */
- 63 int framId = strlist[0].toInt(&ok, 16);
- 64 QCanBusFrame frame = QCanBusFrame(framId, data);
- 65 /* 写入帧 */
- 66 canDevice->writeFrame(frame);
- 67 }
- 68
- 69 /* 接收消息 */
- 70 void MainWindow::receivedFrames()
- 71 {
- 72 if (!canDevice)
- 73 return;
- 74
- 75 /* 读取帧 */
- 76 while (canDevice->framesAvailable()) {
- 77 const QCanBusFrame frame = canDevice->readFrame();
- 78 QString view;
- 79 if (frame.frameType() == QCanBusFrame::ErrorFrame)
- 80 view = canDevice->interpretErrorFrame(frame);
- 81 else
- 82 view = frame.toString();
- 83
- 84 const QString time = QString::fromLatin1("%1.%2 ")
- 85 .arg(frame.timeStamp()
- 86 .seconds(), 10, 10, QLatin1Char(' '))
- 87 .arg(frame.timeStamp()
- 88 .microSeconds() / 100, 4, 10, QLatin1Char('0'));
- 89
- 90 const QString flags = frameFlags(frame);
- 91 /* 接收消息框追加接收到的消息 */
- 92 textBrowser->insertPlainText(time + flags + view + "\n");
- 93 }
- 94 }
- 95
- 96 void MainWindow::layoutInit()
- 97 {
- 98 /* 获取屏幕的分辨率,Qt官方建议使用这
- 99 * 种方法获取屏幕分辨率,防上多屏设备导致对应不上
- 100 * 注意,这是获取整个桌面系统的分辨率
- 101 */
- 102 QList <QScreen *> list_screen = QGuiApplication::screens();
- 103
- 104 /* 如果是ARM平台,直接设置大小为屏幕的大小 */
- 105 #if __arm__
- 106 /* 重设大小 */
- 107 this->resize(list_screen.at(0)->geometry().width(),
- 108 list_screen.at(0)->geometry().height());
- 109 #else
- 110 /* 否则则设置主窗体大小为800x480 */
- 111 this->resize(800, 480);
- 112 #endif
- 113 /* 对象初始化 */
- 114 textBrowser = new QTextBrowser();
- 115 lineEdit = new QLineEdit();
- 116 vboxLayout = new QVBoxLayout();
- 117 funcWidget = new QWidget();
- 118 mainWidget = new QWidget();
- 119 gridLayout = new QGridLayout();
- 120
- 121 /* QList链表,字符串类型 */
- 122 QList <QString> list1;
- 123 list1<<"插件类型:"<<"可用接口:"<<"比特率bits/sec:";
- 124
- 125 for (int i = 0; i < 3; i++) {
- 126 label[i] = new QLabel(list1[i]);
- 127 /* 设置最小宽度与高度 */
- 128 label[i]->setMinimumSize(120, 30);
- 129 label[i]->setMaximumHeight(50);
- 130 /* 自动调整label的大小 */
- 131 label[i]->setSizePolicy(QSizePolicy::Expanding,
- 132 QSizePolicy::Expanding);
- 133 /* 将label[i]添加至网格的坐标(0, i) */
- 134 gridLayout->addWidget(label[i], 0, i);
- 135 }
- 136 label[3] = new QLabel();
- 137 label[3]->setMaximumHeight(30);
- 138
- 139 for (int i = 0; i < 3; i++) {
- 140 comboBox[i] = new QComboBox();
- 141 comboBox[i]->setMinimumSize(120, 30);
- 142 comboBox[i]->setMaximumHeight(50);
- 143 /* 自动调整label的大小 */
- 144 comboBox[i]->setSizePolicy(QSizePolicy::Expanding,
- 145 QSizePolicy::Expanding);
- 146 /* 将comboBox[i]添加至网格的坐标(1, i) */
- 147 gridLayout->addWidget(comboBox[i], 1, i);
- 148 }
- 149
- 150 /* QList链表,字符串类型 */
- 151 QList <QString> list2;
- 152 list2<<"发送"<<"连接CAN";
- 153
- 154 for (int i = 0; i < 2; i++) {
- 155 pushButton[i] = new QPushButton(list2[i]);
- 156 pushButton[i]->setMinimumSize(120, 30);
- 157 pushButton[i]->setMaximumHeight(50);
- 158 /* 自动调整label的大小 */
- 159 pushButton[i]->setSizePolicy(QSizePolicy::Expanding,
- 160 QSizePolicy::Expanding);
- 161 /* 将pushButton[0]添加至网格的坐标(i, 3) */
- 162 gridLayout->addWidget(pushButton[i], i, 3);
- 163 }
- 164 pushButton[0]->setEnabled(false);
- 165
- 166 /* 布局 */
- 167 vboxLayout->addWidget(textBrowser);
- 168 vboxLayout->addWidget(lineEdit);
- 169 funcWidget->setLayout(gridLayout);
- 170 vboxLayout->addWidget(funcWidget);
- 171 vboxLayout->addWidget(label[3]);
- 172 mainWidget->setLayout(vboxLayout);
- 173 this->setCentralWidget(mainWidget);
- 174
- 175 /* 设置文本 */
- 176 textBrowser->setPlaceholderText("系统时间 帧ID 长度 数据");
- 177 lineEdit->setText("123 aa 77 66 55 44 33 22 11");
- 178 label[3]->setText(tr("未连接!"));
- 179
- 180 connect(pushButton[1], SIGNAL(clicked()),
- 181 this, SLOT(connectDevice()));
- 182 connect(pushButton[0], SIGNAL(clicked()),
- 183 this, SLOT(sendFrame()));
- 184 }
- 185
- 186 /* 从系统中读取可用的插件,并显示到comboBox[0] */
- 187 void MainWindow::pluginItemInit()
- 188 {
- 189 comboBox[0]->addItems(QCanBus::instance()->plugins());
- 190 for (int i = 0; i < QCanBus::instance()->plugins().count(); i++) {
- 191 if (QCanBus::instance()->plugins().at(i) == "socketcan")
- 192 comboBox[0]->setCurrentIndex(i);
- 193 }
- 194 connect(comboBox[0], SIGNAL(currentIndexChanged(int)),
- 195 this, SLOT(pluginChanged(int)));
- 196 }
- 197
- 198 /* 插件类型改变 */
- 199 void MainWindow::pluginChanged(int)
- 200 {
- 201 QList<QCanBusDeviceInfo> interfaces;
- 202 comboBox[1]->clear();
- 203 /* 当我们改变插件时,我们同时需要将可用接口,从插件类型中读取出来 */
- 204 interfaces = QCanBus::instance()
- 205 ->availableDevices(comboBox[0]->currentText());
- 206 for (const QCanBusDeviceInfo &info : qAsConst(interfaces)) {
- 207 comboBox[1]->addItem(info.name());
- 208 }
- 209 }
- 210
- 211 /* 初始化一些常用的比特率,can的比特率不是随便设置的,有相应的计算公式 */
- 212 void MainWindow::bitrateItemInit()
- 213 {
- 214 const QList<int> rates = {
- 215 10000, 20000, 50000, 100000, 125000,
- 216 250000, 500000, 800000, 1000000
- 217 };
- 218
- 219 for (int rate : rates)
- 220 comboBox[2]->addItem(QString::number(rate), rate);
- 221
- 222 /* 默认初始化以1000000比特率 */
- 223 comboBox[2]->setCurrentIndex(8);
- 224 }
- 225
- 226 /* 连接或断开CAN */
- 227 void MainWindow::connectDevice()
- 228 {
- 229 if (pushButton[1]->text() == "连接CAN") {
- 230 /* Qt中的QCanBusDevice::BitRateKey不能设置比特率 */
- 231 QString cmd1 = tr("ifconfig %1 down")
- 232 .arg(comboBox[1]->currentText());
- 233 QString cmd2 =
- 234 tr("ip link set up %1 type can bitrate %2 restart-ms 100")
- 235 .arg(comboBox[1]->currentText())
- 236 .arg(comboBox[2]->currentText());
- 237 /* 使用系统指令以设置的比特率初始化CAN */
- 238 system(cmd1.toStdString().c_str());
- 239 system(cmd2.toStdString().c_str());
- 240
- 241 QString errorString;
- 242 /* 以设置的插件名与接口实例化canDevice */
- 243 canDevice = QCanBus::instance()->
- 244 createDevice(comboBox[0]->currentText(),
- 245 comboBox[1]->currentText(),
- 246 &errorString);
- 247
- 248 if (!canDevice) {
- 249 label[3]->setText(
- 250 tr("Error creating device '%1', reason: '%2'")
- 251 .arg(comboBox[0]->currentText())
- 252 .arg(errorString));
- 253 return;
- 254 }
- 255
- 256 /* 连接CAN */
- 257 if (!canDevice->connectDevice()) {
- 258 label[3]->setText(tr("Connection error: %1")
- 259 .arg(canDevice->errorString()));
- 260 delete canDevice;
- 261 canDevice = nullptr;
- 262
- 263 return;
- 264 }
- 265
- 266 connect(canDevice, SIGNAL(framesReceived()),
- 267 this, SLOT(receivedFrames()));
- 268 connect(canDevice,
- 269 SIGNAL(errorOccurred(QCanBusDevice::CanBusError)),
- 270 this,
- 271 SLOT(canDeviceErrors(QCanBusDevice::CanBusError)));
- 272 /* 将连接信息插入到label */
- 273 label[3]->setText(
- 274 tr("插件类型为: %1, 已连接到 %2, 比特率为 %3 kBit/s")
- 275 .arg(comboBox[0]->currentText())
- 276 .arg(comboBox[1]->currentText())
- 277 .arg(comboBox[2]->currentText().toInt() / 1000));
- 278 pushButton[1]->setText("断开CAN");
- 279 /* 使能/失能 */
- 280 pushButton[0]->setEnabled(true);
- 281 comboBox[0]->setEnabled(false);
- 282 comboBox[1]->setEnabled(false);
- 283 comboBox[2]->setEnabled(false);
- 284 } else {
- 285 if (!canDevice)
- 286 return;
- 287
- 288 /* 断开连接 */
- 289 canDevice->disconnectDevice();
- 290 delete canDevice;
- 291 canDevice = nullptr;
- 292 pushButton[1]->setText("连接CAN");
- 293 pushButton[0]->setEnabled(false);
- 294 label[3]->setText(tr("未连接!"));
- 295 comboBox[0]->setEnabled(true);
- 296 comboBox[1]->setEnabled(true);
- 297 comboBox[2]->setEnabled(true);
- 298 }
- 299 }
- 300
- 301 void MainWindow::canDeviceErrors(QCanBusDevice::CanBusError error) const
- 302 {
- 303 /* 错误处理 */
- 304 switch (error) {
- 305 case QCanBusDevice::ReadError:
- 306 case QCanBusDevice::WriteError:
- 307 case QCanBusDevice::ConnectionError:
- 308 case QCanBusDevice::ConfigurationError:
- 309 case QCanBusDevice::UnknownError:
- 310 label[3]->setText(canDevice->errorString());
- 311 break;
- 312 default:
- 313 break;
- 314 }
- 315 }
- 316
复制代码第9~10行,使用系统的CAN硬件,必须初始化系统的CAN。在项目里添加相应的开启CAN的指令。第一个指令是先关闭本地的CAN,因为只有关闭CAN,才能以新的速率再开启。 第12~22行,构造函数里界面初始化,以及QComboBox里的项初始化。 第29~42行,格式化帧处理函数。 第45~67行,发送消息,将lineEdit的文本进行处理后,第一个作为CAN的帧ID,后面8个数据作为需要要发送的数据。每帧只能发送8个数据。 第70~94行,接收消息,读取帧并格式化处理,显示到textBrowser里。 第96~184行,界面布局初始化设置,在嵌入式里,根据实际的屏的大小,设置全屏显示。其中我们用到垂直布局和网格布局,如果布局这方面内容理解不了,请回到第七章7.5小节学习布局内容。 第187~196行,可用插件初始化,检查系统QCanBus提供的插件。在Linux里使用的插件类型是SocketCAN, SocketCAN插件支持Linux内核和用于所用CAN硬件的SocketCAN设备驱动程序。下面程序遍历可用的CAN插件,并设置socketcan为当前插件。注意,只能使用SocketCAN访问本地硬件CAN,其他插件是不同类型的CAN驱动程序所使用的。请自行测试。 第199~209行,当插件类型改变时,我们需要更新可用接口。 第212~224行,常用的比特率初始化。 第227~299行,连接/断开CAN,很遗憾Qt的QCanBusDevice::BitRateKey不能设置比特率,因为系统的CAN需要使用ip指令以一个比特率才能进行初始化,Qt需要系统CAN起来才能进行操作。所以需要使用系统指令设置CAN。 第301~315行,错误处理,CAN设备可能遇到错误,打印错误的信息。
18.3 程序运行效果在Ubuntu上运行 界面效果如下,因为Ubutnu没有CAN设备,所以在可用接口处是不可选的。请把程序交叉编译到开发板上运行。与CAN仪器以相同的比特率通信,插件类型默认是(必须是)socketcan,可用接口为can0,即可发送消息与接收消息。 下图最上面的是接收消息框,“123 aa 77 66 55 44 33 22 11”这个是需要发送的帧,“123”为帧ID,后面的为8个字节数据,每个字节需要以空格隔开。点击连接后,发送按钮才能使用。
|