QT实现自定义Canbus plugin

前言

前段时间老板给了个 广成科技 的USB Can盒子,说是要做can总线通信,以前也没弄过这东西,上网一搜,有现成的QT代码,这不分分钟搞定嘛。

#include <QCoreApplication>
#include <QCanBus>
#include <QCanBusDevice>
#include <QCanBusFrame>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 查看可用的 CAN 接口
    qDebug() << "Available CAN plugins:" << QCanBus::instance()->plugins();

    // 创建 CAN 设备
    QCanBusDevice *device = QCanBus::instance()->createDevice("socketcan", "can0");
    if (!device) {
        qDebug() << "Failed to create CAN device";
        return -1;
    }

    // 连接到 CAN 设备
    if (!device->connectDevice()) {
        qDebug() << "Failed to connect to CAN device";
        delete device;
        return -1;
    }

    // 创建 CAN 帧并发送
    QCanBusFrame frame;
    frame.setFrameId(0x123);
    frame.setPayload(QByteArray("Hello, CAN!"));

    if (device->writeFrame(frame)) {
        qDebug() << "Frame sent successfully";
    } else {
        qDebug() << "Failed to send frame";
    }

    // 读取接收到的 CAN 帧
    while (device->framesAvailable()) {
        QCanBusFrame receivedFrame = device->readFrame();
        qDebug() << "Received frame ID:" << receivedFrame.frameId()
                 << "Payload:" << receivedFrame.payload();
    }

    // 断开连接并释放资源
    device->disconnectDevice();
    delete device;

    return a.exec();
}


 

问题来了,socketcan是linux才支持的东西,windows并不支持。咨询了下业内人士,windows下都要基于usb can盒子的厂商的sdk做二次开发,于是乎有找了下QT有框架,只要把二次开发的东西做成qt canbus plugin上面的代码就可以复用,QT默认支持的canbus硬件厂商有"passthrucan", "peakcan", "systeccan", "tinycan", "vectorcan"。

在github上也找到一个第3方厂商的plugin实现

​​​​​​​https://github.com/viteo/QtIXXATCANBus-plugin

Can设备要通信需要2个以上的设备挂在can总线上,我现在手头上只有一个 广成 的can,还要大几百块,有点小贵。那就上淘宝花30大洋买个支持peakcan,一不小心买到了一个刷kvaser固件的。那就只能含泪再给它写个plugin了。。。

QT官方文档

Qt CAN Bus | Qt Serial Bus 6.6.1

从官方文档得知,实现一个自定义的canbus plugin需要完成以下东西:

1.  添加一个 plugin.json 在load plugin的时候会使用到

2. 实现某些接口

3. 生成的dll文件要部署到QT的安装目录 $QTDIR/plugins/canbus

前期准备

在kvaser官网把二次开发的sdk包下载下来,是个exe文件,canlib.exe

Kvaser Downloads | Kvaser

安装后到安装目录取出我们需要的库文件和头文件

C:\Program Files (x86)\Kvaser\Canlib\Lib\x64\canlib32.lib

C:\Program Files (x86)\Kvaser\Canlib\INC

创建Plugin工程

QMake文件如下

#! [0]
TEMPLATE        = lib
CONFIG         += plugin
QT             += serialbus core

HEADERS         = kvaserplugin.h \
    canevt.h \
    canlib.h \
    canstat.h \
    kvaserbackend.h \
    kvaserbackend_p.h \
    obsolete.h \
    predef.h
SOURCES         = kvaserbackend.cpp
TARGET          = $$qtLibraryTarget(kvaserplugin)

DISTFILES += \
    kvaserplugin.json

win32 {
LIBS += -L$$PWD/KvaserSDKLib -lcanlib32
}

x64 {
LIBS += -L$$PWD/KvaserSDKLib -lcanlib64
}

LIBS += -lSetupapi

DESTDIR = $$[QT_INSTALL_PLUGINS]/canbus
message( can plugin install dir is $$DESTDIR)
#! [0]

下面2行搞定了QT文档中要求的json文件

DISTFILES += \
    kvaserplugin.json
 

下面1行搞定了QT文档中要求的部署目录

DESTDIR = $$[QT_INSTALL_PLUGINS]/canbus

至此所有的工作已经完成2/3

实现Backend

新建kvaserplugin.h

填充availableDevices 和 createDevice

class KvaserPlugin : public QObject, QCanBusFactoryV2
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QCanBusFactory" FILE "kvaserplugin.json")
    Q_INTERFACES(QCanBusFactoryV2)

public:
    QList<QCanBusDeviceInfo> availableDevices(QString* errorMessage) const override
    {
        Q_UNUSED(errorMessage);
        return KvaserBackend::interfaces();
    }

    // Name passed from availableDevices() list
    QCanBusDevice* createDevice(const QString& interfaceName, QString* errorMessage) const override
    {
        Q_UNUSED(errorMessage);

        qDebug() << "createDevice for kvaser";

        auto device = new KvaserBackend(interfaceName);
        return device;
    }

};

新建kvaserbackend.h

这部分为QT文档中提到了需要实现的纯虚函数,里面的代码实现直接拷贝其他canbus plugin的源码。

class KvaserBackendPrivate;

class KvaserBackend : public QCanBusDevice
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(KvaserBackend)
    Q_DISABLE_COPY(KvaserBackend)
public:
    explicit KvaserBackend(const QString &name, QObject *parent = nullptr);
    ~KvaserBackend();

    bool open() override;
    void close() override;
    void setConfigurationParameter(int key, const QVariant &value) override;
    bool writeFrame(const QCanBusFrame& newData) override;
    QString interpretErrorFrame(const QCanBusFrame& errorFrame) override;

    static QList<QCanBusDeviceInfo> interfaces();

private:
    void resetController();

    KvaserBackendPrivate * const d_ptr;


};

以close为例,真正的实现为d->close(); 即KvaserBackendPrivate里面需要真正的实现调用kvaser sdk对can盒子进行close

void KvaserBackend::close()
{
    Q_D(KvaserBackend);

    d->close();

    setState(QCanBusDevice::UnconnectedState);
}

新建kvaserbackend_p.h

class QTimer;

class KvaserBackendPrivate
{
    Q_DECLARE_PUBLIC(KvaserBackend)
public:
    KvaserBackendPrivate(KvaserBackend *q);
    ~KvaserBackendPrivate();

    bool open();
    void close();
    bool setConfigurationParameter(int key, const QVariant &value);

    void startWrite();
    void startRead();
    void resetController();

    bool setBitRate(int bitrate);
    void checkDeviceStatus();

    KvaserBackend * const q_ptr;

    bool m_isOpen = false;
    int m_channelIndex = 0;
    canHandle m_hnd;
    int m_baudrate = 200*1000;
    QTimer *writeNotifier = nullptr;
    QThread *m_readThread;
    QTimer *m_checkDeviceTimer;
};

如上d->close(); 调用的是KvaserBackendPrivate::close,在这里调用kvaser sdk中的canBusOff(m_hnd);  canClose(m_hnd); 关闭总线

void KvaserBackendPrivate::close()
{

    if (m_hnd >= 0) {
        canBusOff(m_hnd);
        canClose(m_hnd);
    }

    m_readThread->exit();

    delete writeNotifier;
    writeNotifier = nullptr;


    qDebug()<<"closed";

    m_isOpen = false;
}

setConfigurationParameter实现设置波特率

bool KvaserBackendPrivate::setConfigurationParameter(int key, const QVariant &value)
{
    Q_Q(KvaserBackend);

    switch (key) {
    case QCanBusDevice::BitRateKey:
        m_baudrate = value.toInt();
        return setBitRate(m_baudrate);
    default:
        q->setError(KvaserBackend::tr("Unsupported configuration key: %1").arg(key),
                    QCanBusDevice::ConfigurationError);
        return false;
    }

    return true;
}

以下为往canbus写数据的过程

1. 将数据入队到发送队列

2. 启动writeNotifier

3.writeNotifier继承于QTimer,这里用这个有两个好处,

    一、不会阻塞主线程

    二、当发送队列已经发送完时调用stop把自己停掉

bool KvaserBackend::writeFrame(const QCanBusFrame &newData)
{
    Q_D(KvaserBackend);

    ... ...

    enqueueOutgoingFrame(newData);

    if (!d->writeNotifier->isActive())
        d->writeNotifier->start();

    return true;
}

class KvaserWriteNotifier : public QTimer
{
    // no Q_OBJECT macro!
public:
    KvaserWriteNotifier(KvaserBackendPrivate *d, QObject *parent)
        : QTimer(parent)
        , dptr(d)
    {
    }

protected:
    void timerEvent(QTimerEvent *e) override
    {
        if (e->timerId() == timerId()) {
            dptr->startWrite();
            return;
        }
        QTimer::timerEvent(e);
    }

private:
    KvaserBackendPrivate * const dptr;
};


void KvaserBackendPrivate::startWrite()
{
    Q_Q(KvaserBackend);

    if (!q->hasOutgoingFrames()) {
        writeNotifier->stop();
        return;
    }

    const QCanBusFrame frame = q->dequeueOutgoingFrame();
    const QByteArray payload = frame.payload();

    if (Q_UNLIKELY(payload.size() > 8)) {
        return;
    }

    long id = frame.frameId();

    unsigned int flag = 0;
    if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) {
        flag |= canMSG_RTR;
    }

    if (frame.hasExtendedFrameFormat())
    {
        flag |= canMSG_EXT;
    }

    canWrite(m_hnd, id, (void *)payload.data(), payload.size(), flag);

    if (q->hasOutgoingFrames() && !writeNotifier->isActive())
        writeNotifier->start();
}

读取数据过程

二次开发sdk中canReadWait是一个同步函数,这里另外起一个读线程读取数据。

数据读取到后 q->enqueueReceivedFrames(newFrames);  入队接收队列。

KvaserBackendPrivate::KvaserBackendPrivate(KvaserBackend *q)
    : q_ptr(q)
{
    canInitializeLibrary();

    m_readThread = new QThread;
    QObject::connect(m_readThread, &QThread::started, [=]() {
        while (true) {
            startRead();
        }
    });

}

void KvaserBackendPrivate::startRead()
{
    Q_Q(KvaserBackend);

    QVector<QCanBusFrame> newFrames;


    long id ;
    uint8_t msg[16];
    unsigned int  dlc;
    unsigned int  flag;
    unsigned long time;
    canStatus ret = canReadWait(m_hnd, &id, (void *)msg, &dlc, &flag, &time, 0xFFFFFFFF);
    if (ret != canOK) {
        return;
    }

    QCanBusFrame frame(id, QByteArray(reinterpret_cast<char *>(msg),
                                      int(dlc)));
    frame.setTimeStamp(QCanBusFrame::TimeStamp(time));

    if (flag & canMSG_EXT) {
        frame.setExtendedFrameFormat(true);
    }

    if (flag & canMSG_RTR)
        frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
    else
        frame.setFrameType(QCanBusFrame::DataFrame);

    newFrames.append(std::move(frame));

    qDebug() << "enqueueReceivedFrames:";
    q->enqueueReceivedFrames(newFrames);
}

查看QT源码qcanbusdevice.cpp看到数据接收到的信号发送出去了。

void QCanBusDevice::enqueueReceivedFrames(const QVector<QCanBusFrame> &newFrames)
{
    Q_D(QCanBusDevice);

    if (Q_UNLIKELY(newFrames.isEmpty()))
        return;

    d->incomingFramesGuard.lock();
    d->incomingFrames.append(newFrames);
    d->incomingFramesGuard.unlock();
    emit framesReceived();
}

测试

在QT欢迎界面搜索can bus example,用这个工程进行测试

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值