【正点原子Linux连载】 第十七章 CAN Bus 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子Linux感兴趣的同学可以加群讨论:935446741

第十七章 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编程。
【正点原子Linux连载】 第十七章 CAN Bus 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2_第1张图片

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
    * @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设备可能遇到错误,打印错误的信息。

18.3 程序运行效果

在Ubuntu上运行 界面效果如下,因为Ubutnu没有CAN设备,所以在可用接口处是不可选的。请把程序交叉编译到开发板上运行。与CAN仪器以相同的比特率通信,插件类型默认是(必须是)socketcan,可用接口为can0,即可发送消息与接收消息。
下图最上面的是接收消息框,“123 aa 77 66 55 44 33 22 11”这个是需要发送的帧,“123”为帧ID,后面的为8个字节数据,每个字节需要以空格隔开。点击连接后,发送按钮才能使用。
【正点原子Linux连载】 第十七章 CAN Bus 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2_第2张图片

你可能感兴趣的:(LINUX,linux,qt,运维)