介绍 Linux自带socket CAN 驱动,可以使用VCAN
功能来模拟CAN数据的收发,这个在原型开发阶段非常有用,拿一台笔记本直接调试CAN设备
使用 虚拟CAN 加载vcan.ko,这个驱动是操作系统自带的,位置参考:/usr/lib/modules/5.15.0-131-generic
/kernel/drivers/net/can
如果你的Linux操作系统没有vcan驱动,请下载kernel编译一个,VCAN驱动模块配置如下:
添加vcan设备:vcan0
1 2 sudo ip link add dev vcan0 type vcansudo ip link set up vcan0
vcan卡连接、关闭
1 2 ifconfig vcan0 up ifconfig vcan0 down
发送随机数据用于开发、调试、测试
1 2 3 4 5 while true ; do cansend vcan0 123#1122334455667788; sleep 1; done ;while true ; do cansend vcan0 $(printf "%X#%02X%02X%02X%02X%02X%02X%02X%02X" $((RANDOM%0 x800)) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 )) $((RANDOM%256 ))); sleep 1; done ;
接收数据
can数据发送和接收情况统计
1 ip -details -statistics link show vcan0
数据分析:可以用wireshark工具抓取can帧
真实CAN 真实的CAN需要MCU/MPU/SOC端有CAN控制器(内部集成,引脚有RXD、TXD)、匹配的CAN收发器(CANH、CANL)、与之匹配的终端电阻(高速CAN为120Ω),常见的CAN收发器有:
TJA1043
MCP2515 / MCP2551
SN65HVD230
启用can设备:使用前先设置波特率,高速CAN为500k
1 2 sudo ip link set can0 type can bitrate 500000sudo ip link set up can0
本地测试直接设置数据回环模式,如果不需要记得关掉,否则数据出不去也进不来!
1 2 3 4 sudo ip link set can0 type can loopback onsudo ip link set can0 type can loopback off
编码 Linux C++实现CAN数据收发
、状态检测
、独立线程
处理
can_bus_interface.h CanWorker负责处理socket CAN连接状态和数据的收发,CanBusInterface则作为一个单例管理CAN通信(开辟线程)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #ifndef CANBUSINTERFACE_H #define CANBUSINTERFACE_H #include <QObject> #include <QSocketNotifier> #include <QThread> #include <QDebug> #include <linux/can.h> #include <string> class CanWorker : public QObject{ Q_OBJECT public : explicit CanWorker (int socket, QObject *parent = nullptr ) ; ~CanWorker (); signals: void frameReceived (const struct can_frame &frame) ; void errorOccurred (const QString &errorString) ; private slots: void onSocketActivated (int socket) ; private : int m_socket; QSocketNotifier *m_notifier; }; class CanBusInterface : public QObject{ Q_OBJECT public : static CanBusInterface *getInstance () ; protected : explicit CanBusInterface (QObject *parent = nullptr ) ; ~CanBusInterface (); public : bool open (const std::string &interfaceName) ; void close () ; bool isOpen () const ; bool sendFrame (const struct can_frame &frame) ; signals: void frameReceived (const struct can_frame &frame) ; void errorOccurred (const QString &errorString) ; private : int m_socket; bool m_isOpen; QThread *m_workerThread; CanWorker *m_worker; }; #endif
can_bus_interface.cpp CanWorker和CanBusInterface的实现部分,socketCAN的配置:socket(PF_CAN, SOCK_RAW, CAN_RAW)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 #include "can_bus_interface.h" #include <QDebug> #include <QSocketNotifier> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <unistd.h> CanWorker::CanWorker (int socket, QObject *parent) : QObject (parent), m_socket (socket), m_notifier (nullptr ) { m_notifier = new QSocketNotifier (m_socket, QSocketNotifier::Read, this ); connect (m_notifier, &QSocketNotifier::activated, this , &CanWorker::onSocketActivated); qRegisterMetaType <can_frame>("can_frame" ); } CanWorker::~CanWorker () { if (m_notifier) { m_notifier->setEnabled (false ); delete m_notifier; } } void CanWorker::onSocketActivated (int socket) { Q_UNUSED (socket); struct can_frame frame; int bytesRead = read (m_socket, &frame, sizeof (frame)); if (bytesRead == sizeof (frame)) { emit frameReceived (frame) ; } else if (bytesRead < 0 ) { emit errorOccurred ("Failed to read CAN frame" ); } } CanBusInterface *CanBusInterface::getInstance () { static CanBusInterface instance; return &instance; } CanBusInterface::CanBusInterface (QObject *parent) : QObject (parent), m_socket (-1 ), m_isOpen (false ), m_workerThread (nullptr ), m_worker (nullptr ) { } CanBusInterface::~CanBusInterface () { close (); } bool CanBusInterface::open (const std::string &interfaceName) { if (m_isOpen) { qWarning () << "CAN interface is already open" ; return true ; } m_socket = socket (PF_CAN, SOCK_RAW, CAN_RAW); if (m_socket < 0 ) { emit errorOccurred ("Failed to create socket" ) ; return false ; } struct ifreq ifr; strncpy (ifr.ifr_name, interfaceName.c_str (), IFNAMSIZ - 1 ); ifr.ifr_name[IFNAMSIZ - 1 ] = '\0' ; if (ioctl (m_socket, SIOCGIFINDEX, &ifr) < 0 ) { emit errorOccurred ("Failed to get interface index" ) ; ::close (m_socket); m_socket = -1 ; return false ; } struct sockaddr_can addr; addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind (m_socket, (struct sockaddr *)&addr, sizeof (addr)) < 0 ) { emit errorOccurred ("Failed to bind socket to interface" ) ; ::close (m_socket); m_socket = -1 ; return false ; } m_workerThread = new QThread (); m_worker = new CanWorker (m_socket); m_worker->moveToThread (m_workerThread); connect (m_workerThread, &QThread::finished, m_worker, &CanWorker::deleteLater); connect (m_workerThread, &QThread::finished, m_workerThread, &QThread::deleteLater); connect (m_worker, &CanWorker::frameReceived, this , &CanBusInterface::frameReceived); connect (m_worker, &CanWorker::errorOccurred, this , &CanBusInterface::errorOccurred); m_workerThread->start (); m_isOpen = true ; return true ; } void CanBusInterface::close () { if (!m_isOpen) return ; m_isOpen = false ; if (m_workerThread) { m_workerThread->quit (); m_workerThread->wait (); m_workerThread = nullptr ; m_worker = nullptr ; } if (m_socket >= 0 ) { ::close (m_socket); m_socket = -1 ; } } bool CanBusInterface::isOpen () const { return m_isOpen; } bool CanBusInterface::sendFrame (const struct can_frame &frame) { if (!m_isOpen) { emit errorOccurred ("CAN interface is not open" ) ; return false ; } int bytesWritten = write (m_socket, &frame, sizeof (frame)); if (bytesWritten != sizeof (frame)) { emit errorOccurred ("Failed to write CAN frame" ) ; return false ; } return true ; }
注意点1:这里需要把can_frame类型注册为Qt能够识别的元数据类型
1 qRegisterMetaType <can_frame>("can_frame" );
注意点2:只有独立的QObject才能使用moveToThread
1 2 3 4 5 m_workerThread = new QThread (); m_worker = new CanWorker (m_socket); m_worker->moveToThread (m_workerThread); m_workerThread->start ();