在Linux中配置和使用CAN通信的詳細(xì)指南
引言
CAN(Controller Area Network)是一種廣泛用于嵌入式系統(tǒng)、汽車和工業(yè)控制中的通信協(xié)議。Linux 支持 CAN 協(xié)議棧,并通過 SocketCAN 實(shí)現(xiàn)對 CAN 總線的訪問。在這篇博客中,我們將深入講解如何在 Linux 系統(tǒng)中配置和使用 CAN 通信,詳細(xì)介紹配置環(huán)境、測試案例、代碼實(shí)現(xiàn)以及如何使用 can-utils 工具和自定義代碼進(jìn)行測試。
本文內(nèi)容
- 環(huán)境配置:包括有外設(shè)和沒有外設(shè)兩種情況。
- 測試案例:如何使用
can-utils和自定義代碼測試 CAN 通信。 - 代碼實(shí)現(xiàn):編寫一個(gè)高效且線程安全的 CAN 通信代碼,并詳細(xì)注釋每一部分。
- 調(diào)試和測試:如何進(jìn)行調(diào)試以及常見問題的解決方法。
1. 環(huán)境配置
1.1 安裝和配置必備工具
在 Linux 系統(tǒng)上使用 CAN 通信,首先需要安裝一些必備的工具和庫:
- SocketCAN 驅(qū)動(dòng)程序:這是 Linux 內(nèi)核中實(shí)現(xiàn) CAN 協(xié)議棧的模塊,通常在大多數(shù) Linux 發(fā)行版中已經(jīng)默認(rèn)啟用。
- can-utils 工具:一個(gè)用于測試和調(diào)試 CAN 總線通信的工具集。
- 編譯器和開發(fā)工具:用于編譯 C++ 代碼的工具。
安裝依賴
首先,確保你安裝了所需的開發(fā)工具和庫:
sudo apt update sudo apt install build-essential sudo apt install can-utils # 安裝 can-utils 工具包 sudo apt install libsocketcan-dev # 如果需要安裝 SocketCAN 開發(fā)庫
can-utils 包含多個(gè)實(shí)用工具,例如 cansend 和 candump,可以用于測試 CAN 總線的發(fā)送和接收。
1.2 配置虛擬 CAN 接口(沒有外設(shè)的情況)
如果你沒有物理 CAN 接口設(shè)備(如 USB-to-CAN 適配器),你可以使用虛擬 CAN 接口 vcan0 來進(jìn)行測試。虛擬接口適用于不需要實(shí)際硬件的 CAN 總線仿真和開發(fā)。
啟用虛擬 CAN 接口
加載 vcan 驅(qū)動(dòng)模塊:
sudo modprobe vcan
創(chuàng)建虛擬 CAN 接口 vcan0:
sudo ip link add dev vcan0 type vcan sudo ip link set vcan0 up
測試虛擬接口:
使用 can-utils 工具測試虛擬 CAN 接口:
發(fā)送一個(gè) CAN 幀:
cansend vcan0 123#deadbeef
查看接收到的 CAN 數(shù)據(jù):
candump vcan0
這樣,你就可以在沒有實(shí)際硬件的情況下仿真 CAN 總線通信,進(jìn)行開發(fā)和測試。
1.3 配置物理 CAN 接口(有外設(shè)的情況)
如果你有物理 CAN 外設(shè)(如 USB-to-CAN 適配器),你需要配置物理接口。
檢查 CAN 適配器:首先,檢查系統(tǒng)是否識(shí)別到了 CAN 適配器,運(yùn)行以下命令:
ip link show
你應(yīng)該看到類似 can0 或 can1 的接口。如果沒有,請插入設(shè)備并確認(rèn)驅(qū)動(dòng)已加載。
啟用物理 CAN 接口:
假設(shè)你的物理接口為 can0,你可以通過以下命令啟用接口,并設(shè)置傳輸速率(例如 500 kbps):
sudo ip link set can0 up type can bitrate 500000
測試物理接口:同樣,使用 can-utils 發(fā)送和接收數(shù)據(jù):
發(fā)送數(shù)據(jù):
cansend can0 123#deadbeef
查看數(shù)據(jù):
candump can0
現(xiàn)在,你已經(jīng)成功配置了 CAN 環(huán)境,無論是通過虛擬接口進(jìn)行仿真,還是通過物理接口進(jìn)行實(shí)際通信。
2. 測試案例
2.1 使用 can-utils 工具測試
can-utils 提供了一些常用的命令行工具,可以快速地測試 CAN 總線的發(fā)送和接收。
cansend:用于向 CAN 總線發(fā)送數(shù)據(jù)。
發(fā)送一個(gè)數(shù)據(jù)幀:
cansend vcan0 123#deadbeef
這會(huì)向 vcan0 接口發(fā)送一個(gè)帶有 ID 為 0x123,數(shù)據(jù)為 deadbeef 的 CAN 幀。
candump:用于查看 CAN 總線上的數(shù)據(jù)。
查看所有 CAN 總線接口的數(shù)據(jù):
candump vcan0
你將看到類似下面的輸出,顯示收到的數(shù)據(jù)幀:
vcan0 123 [4] dead
canplayer:用于回放保存的 CAN 數(shù)據(jù)文件。
回放一個(gè) CAN 數(shù)據(jù)文件:
canplayer -I can_logfile.log
這個(gè)工具在處理實(shí)際的 CAN 數(shù)據(jù)日志時(shí)非常有用。
2.2 使用代碼測試
代碼測試:發(fā)送和接收 CAN 數(shù)據(jù)
我們將編寫一個(gè)簡單的代碼示例,用于發(fā)送和接收 CAN 幀。
創(chuàng)建線程池:我們將使用線程池來處理高并發(fā)的 CAN 數(shù)據(jù)接收。
CAN 通信類:負(fù)責(zé)與 CAN 總線進(jìn)行交互。
main 函數(shù):啟動(dòng)接收線程并發(fā)送數(shù)據(jù)。
#include <iostream> // 包含輸入輸出流,用于打印日志或調(diào)試信息
#include <string> // 包含 string 類的定義,用于字符串操作
#include <cstring> // 包含 C 風(fēng)格字符串操作函數(shù)的定義
#include <unistd.h> // 包含 POSIX 系統(tǒng)調(diào)用,例如 close, read, write
#include <net/if.h> // 包含網(wǎng)絡(luò)接口相關(guān)的定義
#include <sys/ioctl.h> // 包含 I/O 控制相關(guān)的函數(shù)定義,例如 SIOCGIFINDEX
#include <fcntl.h> // 包含文件控制相關(guān)的定義
#include <linux/can.h> // 包含 CAN 協(xié)議相關(guān)的定義
#include <linux/can/raw.h> // 包含原始 CAN 套接字定義
#include <sys/socket.h> // 包含 socket 套接字的相關(guān)定義
#include <thread> // 包含多線程支持的定義
#include <atomic> // 包含原子操作的定義,用于線程安全
#include <mutex> // 包含互斥量的定義,用于線程同步
#include <vector> // 包含 vector 容器的定義
#include <queue> // 包含隊(duì)列容器的定義
#include <functional> // 包含函數(shù)對象的定義,用于隊(duì)列任務(wù)
#include <condition_variable> // 包含條件變量定義,用于線程同步
#include <chrono> // 包含時(shí)間相關(guān)定義,用于控制線程等待時(shí)間
#include <iostream> // 包含 I/O 相關(guān)功能
// 線程池類,用于管理多個(gè)線程,執(zhí)行異步任務(wù)
class ThreadPool {
public:
// 構(gòu)造函數(shù),初始化線程池,啟動(dòng) numThreads 個(gè)線程
ThreadPool(size_t numThreads) : stop(false) {
// 創(chuàng)建并啟動(dòng)工作線程
for (size_t i = 0; i < numThreads; ++i) {
workers.push_back(std::thread([this]() { workerLoop(); }));
}
}
// 析構(gòu)函數(shù),停止線程池中的所有線程
~ThreadPool() {
stop = true; // 設(shè)置停止標(biāo)志
condVar.notify_all(); // 通知所有線程退出
for (std::thread &worker : workers) {
worker.join(); // 等待所有線程結(jié)束
}
}
// 向線程池隊(duì)列中添加一個(gè)任務(wù)
void enqueue(std::function<void()> task) {
{
std::lock_guard<std::mutex> lock(queueMutex); // 鎖住隊(duì)列,避免多線程訪問沖突
tasks.push(task); // 將任務(wù)放入隊(duì)列
}
condVar.notify_one(); // 喚醒一個(gè)等待的線程
}
private:
// 線程池中的工作線程函數(shù)
void workerLoop() {
while (!stop) { // 當(dāng) stop 為 false 時(shí),線程繼續(xù)工作
std::function<void()> task; // 定義一個(gè)任務(wù)對象
{
// 鎖住隊(duì)列,線程安全地訪問任務(wù)隊(duì)列
std::unique_lock<std::mutex> lock(queueMutex);
condVar.wait(lock, [this]() { return stop || !tasks.empty(); }); // 等待任務(wù)或停止信號(hào)
// 如果 stop 為 true 且隊(duì)列為空,退出循環(huán)
if (stop && tasks.empty()) {
return;
}
task = tasks.front(); // 獲取隊(duì)列中的第一個(gè)任務(wù)
tasks.pop(); // 從隊(duì)列中移除該任務(wù)
}
task(); // 執(zhí)行任務(wù)
}
}
std::vector<std::thread> workers; // 線程池中的所有線程
std::queue<std::function<void()>> tasks; // 任務(wù)隊(duì)列,存儲(chǔ)待處理的任務(wù)
std::mutex queueMutex; // 互斥鎖,用于保護(hù)任務(wù)隊(duì)列
std::condition_variable condVar; // 條件變量,用于通知線程執(zhí)行任務(wù)
std::atomic<bool> stop; // 原子變量,用于控制線程池的停止
};
// CAN 通信類,用于發(fā)送和接收 CAN 消息
class CanCommunication {
public:
// 構(gòu)造函數(shù),初始化 CAN 通信
CanCommunication(const std::string &interfaceName) : stopReceiving(false) {
sock = socket(PF_CAN, SOCK_RAW, CAN_RAW); // 創(chuàng)建原始 CAN 套接字
if (sock < 0) { // 如果套接字創(chuàng)建失敗,輸出錯(cuò)誤并退出
perror("Error while opening socket");
exit(EXIT_FAILURE);
}
struct ifreq ifr; // 網(wǎng)絡(luò)接口請求結(jié)構(gòu)體
strncpy(ifr.ifr_name, interfaceName.c_str(), sizeof(ifr.ifr_name) - 1); // 設(shè)置接口名
ioctl(sock, SIOCGIFINDEX, &ifr); // 獲取網(wǎng)絡(luò)接口的索引
struct sockaddr_can addr; // CAN 地址結(jié)構(gòu)體
addr.can_family = AF_CAN; // 設(shè)置地址族為 CAN
addr.can_ifindex = ifr.ifr_ifindex; // 設(shè)置接口索引
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { // 綁定套接字到指定的 CAN 接口
perror("Error while binding socket");
exit(EXIT_FAILURE);
}
}
// 析構(gòu)函數(shù),關(guān)閉 CAN 套接字
~CanCommunication() {
if (sock >= 0) {
close(sock); // 關(guān)閉套接字
}
}
// 發(fā)送 CAN 消息
void sendCanMessage(const can_frame &frame) {
if (write(sock, &frame, sizeof(frame)) != sizeof(frame)) { // 寫入套接字發(fā)送數(shù)據(jù)
perror("Error while sending CAN message");
}
}
// 接收 CAN 消息
void receiveCanMessages(ThreadPool &threadPool) {
while (!stopReceiving) { // 如果沒有接收停止信號(hào),繼續(xù)接收數(shù)據(jù)
can_frame frame; // 定義一個(gè) CAN 幀
int nbytes = read(sock, &frame, sizeof(frame)); // 從套接字中讀取數(shù)據(jù)
if (nbytes < 0) { // 如果讀取失敗,輸出錯(cuò)誤信息
perror("Error while receiving CAN message");
continue;
}
// 將解析任務(wù)提交到線程池
threadPool.enqueue([this, frame]() {
this->parseCanMessage(frame); // 解析 CAN 消息
});
}
}
// 停止接收數(shù)據(jù)
void stopReceivingData() {
stopReceiving = true; // 設(shè)置停止接收標(biāo)志
}
private:
int sock; // 套接字描述符
std::atomic<bool> stopReceiving; // 原子標(biāo)志,表示是否停止接收數(shù)據(jù)
std::mutex parseMutex; // 解析數(shù)據(jù)時(shí)的互斥鎖
// 解析 CAN 消息
void parseCanMessage(const can_frame &frame) {
std::lock_guard<std::mutex> lock(parseMutex); // 鎖住互斥量,確保解析數(shù)據(jù)時(shí)的線程安全
std::cout << "Received CAN ID: " << frame.can_id << std::endl; // 打印 CAN ID
std::cout << "Data: ";
for (int i = 0; i < frame.can_dlc; ++i) { // 遍歷 CAN 數(shù)據(jù)字節(jié)
std::cout << std::hex << (int)frame.data[i] << " "; // 打印每個(gè)字節(jié)的十六進(jìn)制表示
}
std::cout << std::endl;
}
};
// 主函數(shù)
int main() {
ThreadPool threadPool(4); // 創(chuàng)建一個(gè)有 4 個(gè)線程的線程池
CanCommunication canComm("vcan0"); // 創(chuàng)建一個(gè) CanCommunication 對象,使用虛擬 CAN 接口 "vcan0"
// 啟動(dòng)一個(gè)線程來接收 CAN 消息
std::thread receiverThread(&CanCommunication::receiveCanMessages, &canComm, std::ref(threadPool));
// 創(chuàng)建并發(fā)送一個(gè) CAN 消息
can_frame sendFrame;
sendFrame.can_id = 0x123; // 設(shè)置 CAN ID 為 0x123
sendFrame.can_dlc = 8; // 設(shè)置數(shù)據(jù)長度為 8 字節(jié)
for (int i = 0; i < 8; ++i) {
sendFrame.data[i] = i; // 填充數(shù)據(jù)
}
canComm.sendCanMessage(sendFrame); // 發(fā)送 CAN 消息
std::this_thread::sleep_for(std::chrono::seconds(5)); // 等待 5 秒,以便接收和處理消息
canComm.stopReceivingData(); // 停止接收數(shù)據(jù)
receiverThread.join(); // 等待接收線程結(jié)束
return 0; // 程序正常退出
}代碼注釋總結(jié):
線程池 (ThreadPool):
- 提供了一個(gè)用于并發(fā)執(zhí)行任務(wù)的線程池,通過
enqueue函數(shù)將任務(wù)放入隊(duì)列,工作線程從隊(duì)列中取出任務(wù)執(zhí)行。 - 使用
std::mutex保護(hù)任務(wù)隊(duì)列的訪問,并使用std::condition_variable實(shí)現(xiàn)線程間的同步。
CAN 通信 (CanCommunication):
- 提供了通過套接字進(jìn)行 CAN 消息的發(fā)送與接收功能。
- 使用
socket創(chuàng)建原始 CAN 套接字,bind綁定到指定的網(wǎng)絡(luò)接口。 - 發(fā)送和接收消息時(shí),通過多線程處理接收到的數(shù)據(jù),以提高并發(fā)性能。
主程序 (main):
- 創(chuàng)建線程池和 CAN 通信對象。
- 啟動(dòng)接收線程并發(fā)送測試消息。
- 主線程等待 5 秒以確保接收到的 CAN 消息被處理。
這種方式可以在 Linux 系統(tǒng)中使用 C++ 進(jìn)行高效的 CAN 通信,實(shí)現(xiàn)消息的發(fā)送與接收,并且利用線程池提高并發(fā)性能。
以上就是在Linux中配置和使用CAN通信的詳細(xì)指南的詳細(xì)內(nèi)容,更多關(guān)于Linux CAN通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
linux環(huán)境下安裝 openOffice 并啟動(dòng)服務(wù) 的方法
這篇文章主要介紹了linux環(huán)境下安裝 openOffice 并啟動(dòng)服務(wù) 的方法,需要的朋友可以參考下2018-06-06
CentOS設(shè)置精準(zhǔn)時(shí)間的方法
下面小編就為大家分享一篇CentOS設(shè)置精準(zhǔn)時(shí)間的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02
linux之SSH連接WSL2本地環(huán)境的操作方式
這篇文章主要介紹了linux之SSH連接WSL2本地環(huán)境的操作方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
解決linux未找到telnet命令問題(報(bào) -bash: telnet:未找到命令)
文章瀏覽閱讀2.9k次。在測試端口時(shí)遇到telent命令缺失的問題,通過運(yùn)行`rpm-qtelnet-server`檢查是否已安裝telnet-server服務(wù)。如果未安裝,可以使用`yuminstalltelnet-server`進(jìn)行安裝。安裝telnet服務(wù)后,問題得到解決,端口測試恢復(fù)正常。2025-07-07
系統(tǒng)講解Apache Kafka消息管理與異常處理的最佳實(shí)踐
Apache Kafka 作為分布式流處理平臺(tái)的核心組件,廣泛應(yīng)用于實(shí)時(shí)數(shù)據(jù)管道、日志聚合和事件驅(qū)動(dòng)架構(gòu),下面我們就來系統(tǒng)講解 Kafka 消息管理與異常處理的最佳實(shí)踐吧2025-04-04
解決CentOS7.6 yum報(bào)錯(cuò)GPG密鑰不匹配:GPG Keys are c
文章主要講述了在使用yum安裝`devtoolset-8-gcc*`時(shí)遇到GPG密鑰不匹配的問題,并提供了兩種解決方法:一是檢查并修正.repo文件中的GPG密鑰設(shè)置,二是同時(shí)保留阿里云和CentOS官方的GPG密鑰,最終成功安裝了gcc2025-03-03

