【IMX6ULL笔记】-- 从驱动到应用(基于Qt)- CAN总线

6 篇文章 1 订阅
4 篇文章 4 订阅

笔者之前从事车载行业诊断开发,不过那时候都是基于MCU驱动或者Windows端上位机开发,未涉猎于linux can的开发,不过经历最近一段时间学习,算是把这部分完善了,本章将介绍CAN在linux上,驱动和应用是如何开发的

先前文章:

基于CAN总线的汽车诊断协议UDS,上位机下位机开发

基于CAN总线的汽车诊断协议UDS–ECU 下位机设计(RT1062)

基于CAN总线的汽车诊断协议–Windows上位机设计

前期准备

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 = <&reg_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总线

请添加图片描述

修改完成,编译,替换新的zImagedtb文件

应用篇

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协议都没弄明白,界面再好也没用,万幸猪脚饭还行。。。。。。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值