笔者之前从事车载行业诊断开发,不过那时候都是基于MCU驱动或者Windows端上位机开发,未涉猎于linux can的开发,不过经历最近一段时间学习,算是把这部分完善了,本章将介绍CAN在linux上,驱动和应用是如何开发的
先前文章:
基于CAN总线的汽车诊断协议UDS–ECU 下位机设计(RT1062)
前期准备
1.imx6ull 开发板(笔者使用的是 韦东山开发板)
2.内核版本 4.9.88
3.文件系统(buildroot 2019.02工具输出)移植好qt(本章简单介绍)
4.ubuntu 安装好qt
5.交叉编译工具链:gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
驱动篇
开发板CAN物理接口选用的是FLEXCAN1,如下:
设备树配置
&flexcan1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan1>;
xceiver-supply = <®_can_3v3>;
status = "okay";
};
......
pinctrl_flexcan1: flexcan1grp{
fsl,pins = <
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x0001B020
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x0001B020
>;
};
内核中添加CAN总线
修改完成,编译,替换新的zImage和dtb文件
应用篇
QT5.8以上的版本,已经有内部的接口可以直接调用SocketCan插件,开发起来更加便捷。笔者之前从事汽车行业使用PeakCAN VectorCAN特别多(QT不支持也没关系,使用第三方库就行)。具体支持的插件如下:
- 文件系统修改
文件系统未添加相关依赖会有如下问题:
所以我们事先得完善文件系统,在buildroot qt中添加qt5serialbus
Networking applications 中添加 iproute2
- QT工程搭建
*.pro文件中注意添加 serialbus
QT += core gui serialbus
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = canTool
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
代码如下(基础的代码,网上一大把):
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QCanBusDevice>
#include <QCanBus>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QFrame>
#include <QLabel>
#include <QImage>
#include <QComboBox>
#include <QPushButton>
#include <QTextBrowser>
#include <QTextEdit>
#include <QLineEdit>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QCanBusDevice *pCanDevice;
/**********发送接收布局**********/
QGridLayout* pThransLayout;
QLabel* pTransLabel[2];
QImage* pImage[2];
QTextBrowser *pTextRecive;
QLineEdit *pTextSend;
/**********底部布局**********/
QHBoxLayout* pBotLayout;
QLabel* pBotLabel;
QComboBox* pBotComboBox;
QPushButton* pPushButton[2];
/**********主体布局**********/
QWidget* mainWidget;
QVBoxLayout* pMainWidget;
QFrame* pMainFrame;
private:
void layoutConfig(void);
// void pluginSocketCanInit(void); //我们只要 socketcan 其他插件就不遍历了
private slots:
void connectCanDevice(void);
void canSendFrameBuffer(void);
void canReceiveFrameBuffer(void);
void canDeviceErrors(QCanBusDevice::CanBusError) const;
};
#endif // MAINWINDOW_H
......
#include "mainwindow.h"
#include <QGuiApplication>
#include <QScreen>
#define CAN_CMD0 "ifconfig can0 down"
#define CAN_CMD1 "ip link set up can0 type can bitrate 500000 restart-ms 100"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
layoutConfig();
}
MainWindow::~MainWindow()
{
}
/*
********************************************************************************************************************
@ Brief : 布局初始化
@ Param : None
@ Return : None
@ Author : LYC
@ Date : 2022 - 09 - 13
********************************************************************************************************************
*/
void MainWindow::layoutConfig(void)
{
QList <QScreen *> list_screen = QGuiApplication::screens();
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
system(CAN_CMD0);
system(CAN_CMD1);
#else
this->resize(800, 480);
#endif
setWindowTitle("canTool");
/*发送接收布局*/
pThransLayout = new QGridLayout();
#if 0
QList <QString> listTrans;
listTrans<<"接收:"<<"发送:";
pTransLabel[0] = new QLabel(listTrans[0]);
pTransLabel[1] = new QLabel(listTrans[1]);
pTransLabel[0]->setAlignment(Qt::AlignCenter);//居中显示
pTransLabel[1]->setAlignment(Qt::AlignCenter);//居中显示
pTransLabel[0]->setStyleSheet("color:red;font-size:30px");
pTransLabel[1]->setStyleSheet("color:red;font-size:30px");
#else
pTransLabel[0] = new QLabel();
pTransLabel[1] = new QLabel();
pImage[0]=new QImage(); //新建一个image对象
pImage[1]=new QImage(); //新建一个image对象
pImage[0]->load("./img/00");
pImage[1]->load("./img/01");
pTransLabel[0]->setPixmap(QPixmap::fromImage(*pImage[0]));
pTransLabel[0]->resize(pImage[0]->width(),pImage[0]->height());
pTransLabel[0]->setAlignment(Qt::AlignCenter);//居中显示
pTransLabel[1]->setPixmap(QPixmap::fromImage(*pImage[1]));
pTransLabel[1]->resize(pImage[1]->width(),pImage[1]->height());
pTransLabel[1]->setAlignment(Qt::AlignCenter);//居中显示
#endif
pTextRecive = new QTextBrowser();
pTextSend = new QLineEdit();
pTextSend->setText("123 11 22 33 44 55 66 77 88"); //ID +
pThransLayout->addWidget(pTransLabel[0],0,0);
pThransLayout->addWidget(pTransLabel[1],1,0);
pThransLayout->addWidget(pTextRecive,0,1);
pThransLayout->addWidget(pTextSend,1,1);
//比例设置 1:7
pThransLayout->setColumnStretch(0,1);
pThransLayout->setColumnStretch(1,7);
/****************设置布局***************/
pBotLayout = new QHBoxLayout();
pBotLabel = new QLabel("未连接!");
pBotLabel->setStyleSheet("color:red");
const QList<int> rates =
{
20000, 50000, 100000,
500000, 1000000
};
pBotComboBox = new QComboBox();
for (int rate : rates)
{
pBotComboBox->addItem(QString::number(rate), rate);
}
pBotComboBox->setCurrentIndex(3);
pPushButton[0] = new QPushButton("连接CAN");
pPushButton[1] = new QPushButton("发送参数");
pPushButton[0]->setStyleSheet("QPushButton{background:rgb(255,176,28)}");
pPushButton[1]->setEnabled(false);
pBotLayout->addWidget(pBotLabel);
pBotLayout->addWidget(pBotComboBox);
pBotLayout->addWidget(pPushButton[0]);
pBotLayout->addWidget(pPushButton[1]);
pBotLayout->setStretch(0,2);
pBotLayout->setStretch(1,4);
pBotLayout->setStretch(2,4);
pBotLayout->setStretch(3,4);
/****************主体布局***************/
pMainWidget = new QVBoxLayout();
pMainFrame = new QFrame();
pMainFrame->setFrameShape(QFrame::HLine);
pMainFrame->setFrameShadow(QFrame::Sunken);
pMainWidget->addLayout(pThransLayout);
pMainWidget->addWidget(pMainFrame);
pMainWidget->addLayout(pBotLayout);
pMainWidget->setStretch(0,7);
pMainWidget->setStretch(1,1);
mainWidget = new QWidget();
mainWidget->setLayout(pMainWidget);
this->setCentralWidget(mainWidget);
/****************创建信号和槽***************/
connect(pPushButton[0], SIGNAL(clicked()),this, SLOT(connectCanDevice()));
connect(pPushButton[1], SIGNAL(clicked()),this, SLOT(canSendFrameBuffer()));
}
//void MainWindow::pluginSocketCanInit(void)
//{
//}
/*
********************************************************************************************************************
@ Brief : 设备连接
@ Param : None
@ Return : None
@ Author : LYC
@ Date : 2022 - 09 - 13
********************************************************************************************************************
*/
void MainWindow::connectCanDevice(void)
{
pTextRecive->clearHistory();
if (pPushButton[0]->text() == "连接CAN")
{
QString canCmd = tr("ip link set up can0 type can bitrate %1 restart-ms 100")
.arg(pBotComboBox->currentText());
system(CAN_CMD0);
system(canCmd.toStdString().c_str());
QString errorString;
/* 以设置的插件名与接口实例化canDevice */
pCanDevice = QCanBus::instance()->createDevice("socketcan","can0",&errorString);
if (!pCanDevice)
{
pBotLabel->setText(tr("Error creating device socketcan, reason: '%1'").arg(errorString));
return;
}
//连接Can设备
if (!pCanDevice->connectDevice())
{
pBotLabel->setText(tr("Connection error: %1").arg(pCanDevice->errorString()));
delete pCanDevice;
pCanDevice = nullptr;
return;
}
/****************创建信号和槽***************/
connect(pCanDevice, SIGNAL(framesReceived()),this, SLOT(canReceiveFrameBuffer()));
connect(pCanDevice,SIGNAL(errorOccurred(QCanBusDevice::CanBusError)),
this,SLOT(canDeviceErrors(QCanBusDevice::CanBusError)));
/**********/
pBotLabel->setText("连接成功!");
pBotComboBox->setEnabled(false);
pPushButton[0]->setText("断开CAN");
pPushButton[1]->setEnabled(true);
}
else
{
if (!pCanDevice) return;
/* 断开连接 */
pCanDevice->disconnectDevice();
delete pCanDevice;
pCanDevice = nullptr;
pBotLabel->setText("未连接!");
pBotComboBox->setEnabled(true);
pPushButton[0]->setText("连接CAN");
pPushButton[1]->setEnabled(false);
}
}
/*
********************************************************************************************************************
@ Brief : 发送帧Buffer
@ Param : None
@ Return : None
@ Author : LYC
@ Date : 2022 - 09 - 13
********************************************************************************************************************
*/
void MainWindow::canSendFrameBuffer(void)
{
if (!pCanDevice) return;
QString str = pTextSend->text();
QByteArray data = 0;
QString strTemp = nullptr;
/* 以空格分隔lineEdit的内容,并存储到字符串链表中 */
QStringList strlist = str.split(' ');
for (int i = 1; i < strlist.count(); i++)
{
strTemp = strTemp + strlist[i];
}
/* 将字符串的内容转为QByteArray类型 */
data = QByteArray::fromHex(strTemp.toLatin1());
bool ok;
int framId = strlist[0].toInt(&ok, 16); //帧ID
QCanBusFrame frame = QCanBusFrame(framId, data);
//发送帧 buffer
pCanDevice->writeFrame(frame);
}
/*
********************************************************************************************************************
@ Brief : 接收帧Buffer
@ Param : None
@ Return : None
@ Author : LYC
@ Date : 2022 - 09 - 13
********************************************************************************************************************
*/
void MainWindow::canReceiveFrameBuffer(void)
{
if (!pCanDevice)
return;
/* 读取帧 */
while (pCanDevice->framesAvailable())
{
const QCanBusFrame frame = pCanDevice->readFrame();
QString view;
if (frame.frameType() == QCanBusFrame::ErrorFrame)
{
view = pCanDevice->interpretErrorFrame(frame);
}
else
{
view = frame.toString();
}
const QString time = QString::fromLatin1("%1.%2 ")
.arg(frame.timeStamp()
.seconds(), 10, 10, QLatin1Char(' '))
.arg(frame.timeStamp()
.microSeconds() / 100, 4, 10, QLatin1Char('0'));
/* 接收消息框追加接收到的消息 */
pTextRecive->insertPlainText(time + view + "\n");
}
}
void MainWindow::canDeviceErrors(QCanBusDevice::CanBusError error) const
{
/* 错误处理 */
switch (error)
{
case QCanBusDevice::ReadError:
case QCanBusDevice::WriteError:
case QCanBusDevice::ConnectionError:
case QCanBusDevice::ConfigurationError:
case QCanBusDevice::UnknownError:
pBotLabel->setText(pCanDevice->errorString());
break;
default:
break;
}
}
ubuntu UI显示效果如下:
交叉编译输出适合arm的执行文件,将执行文件拷贝到开发板文件系统,开发板将CAN总线连接到CAN卡上(笔者用的PCAN),然后将CAN卡连接Windows端,启动开发板执行可执行文件,连接上位机测试。
补充:can总线使用场景很多,当年笔者出差深圳某亚迪,他们就用了个大屏显示(模拟带屏幕的CAN卡)给他们的新能源车做诊断,界面的确很酷炫,功能的确很拉跨,丢包贼严重,这诊断个鸡毛啊,好家伙UDS协议都没弄明白,界面再好也没用,万幸猪脚饭还行。。。。。。