在Linux中配置和使用CAN通信的詳細(xì)指南
引言
CAN(Controller Area Network)是一種廣泛用于嵌入式系統(tǒng)、汽車和工業(yè)控制中的通信協(xié)議。Linux 支持 CAN 協(xié)議棧,并通過 SocketCAN 實(shí)現(xiàn)對(duì) 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
的接口。如果沒有,請(qǐng)插入設(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ì)象的定義,用于隊(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ì)象 { // 鎖住隊(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ò)接口請(qǐng)求結(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 對(duì)象,使用虛擬 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 通信對(duì)象。
- 啟動(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通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
函數(shù)sync、fsync與fdatasync的總結(jié)整理(必看篇)
下面小編就為大家?guī)硪黄瘮?shù)sync、fsync與fdatasync的總結(jié)整理(必看篇)。小編覺得挺不錯(cuò)的?,F(xiàn)在就分享給大家。也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12Linux系統(tǒng)防CC攻擊自動(dòng)拉黑IP增強(qiáng)版(Shell腳本)
這篇文章主要介紹了Linux系統(tǒng)防CC攻擊自動(dòng)拉黑IP增強(qiáng)版(Shell腳本),需要的朋友可以參考下2015-04-04Apache啟動(dòng)提示錯(cuò)誤undefined symbol: libiconv_open解決方法
這篇文章主要介紹了Apache啟動(dòng)提示錯(cuò)誤undefined symbol: libiconv_open解決方法,需要的朋友可以參考下2015-06-06如何使用Linux文本操作命令ed進(jìn)行提權(quán)nov5詳解
這篇文章主要給大家介紹了關(guān)于如何使用Linux文本操作命令ed進(jìn)行提權(quán)nov5的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Linux系統(tǒng)具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08關(guān)于crontab定時(shí)任務(wù)命令解讀
這篇文章主要介紹了關(guān)于crontab定時(shí)任務(wù)命令的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07