1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子Linux感兴趣的同学可以加群讨论:935446741
从Qt5.8开始,提供了CAN Bus类,很庆幸,正点原子的I.MX6U出厂系统里Qt版本是QT5.12.9。我们可以直接使用Qt的提供的CAN相关类编程即可。假设您的Qt版本没有CAN Bus,可以参考Linux应用编程来操控开发板的CAN,目前我们主要讲解Qt相关的CAN编程。其实Qt也提供了相关的Qt CAN的例子,我们也可以直接参考来编程。编者根据实际情况,化繁为易,直接写了个简单的例子给大家参考。最重要的一点,读者应会使用CAN相关测试工具,我们应该提前去熟悉,并且读者手上需要有测试CAN的仪器!否则写好程序,却无法测试,这就有些尴尬了。
正点原子I.MX6U开发板底板上预留了一路CAN接口(6U芯片最大支持两路)。如下图。在正点原子【正点原子】I.MX6U用户快速体验V1.x.pdf里也有相关的CAN测试方法。这里就不多介绍CAN了,编者默认读者是会使用CAN的。同时不对CAN总线协议进行讲解,主要是讲解如何在Qt里对CAN编程。
项目简介:本例适用于正点原子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
* @author Deng Zhimao
* @email [email protected]
* @net 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 [email protected]
* @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设备可能遇到错误,打印错误的信息。
在Ubuntu上运行 界面效果如下,因为Ubutnu没有CAN设备,所以在可用接口处是不可选的。请把程序交叉编译到开发板上运行。与CAN仪器以相同的比特率通信,插件类型默认是(必须是)socketcan,可用接口为can0,即可发送消息与接收消息。
下图最上面的是接收消息框,“123 aa 77 66 55 44 33 22 11”这个是需要发送的帧,“123”为帧ID,后面的为8个字节数据,每个字节需要以空格隔开。点击连接后,发送按钮才能使用。