![]()
CAN(Controller Area Network)總線最核心的精妙設計之一就是 非破壞性位仲裁。當多個節點同時開始發送消息時,總線不會像以太網那樣發生“碰撞”并丟棄數據,而是通過 ID 逐位比較,優先級高的節點繼續發送,優先級低的節點自動退出,整個過程不丟失任何數據,也無需重傳整個幀。
1. 仲裁原理
顯性位 (0) 和 **隱性位 (1)**:CAN 總線采用“線與”邏輯,顯性位(0)可以覆蓋隱性位(1)。
仲裁過程 :所有節點同時發送自己的消息 ID(從高位到低位)。每個節點在發送每一位的同時,也監聽總線狀態。
如果節點發送的是隱性位 (1),但總線上檢測到顯性位 (0),說明有其他節點正在發送優先級更高的 ID(0 比 1 優先級高)。
該節點立即 退出仲裁 ,停止發送,轉為接收模式。
仲裁獲勝的節點(ID 數值最小,即顯性位最多)繼續發送完整的數據幀。
失敗節點處理 :退出仲裁的節點會在總線空閑后 自動重發 ,無需軟件干預。
為什么更適合實時控制? 以太網發生沖突需要隨機退避重試,延遲不可預測;CAN 的仲裁機制保證了高優先級消息最多在一個幀時間內就能成功發送,延遲確定且極低。2. C++ 模擬代碼
下面用 C++ 模擬三個節點同時發送的場景,演示位仲裁過程。為了清晰,我們將 CAN 幀簡化為 11 位標準 ID(實際還有控制域、數據域等,但仲裁只依賴 ID)。
3. 運行結果示例#include
#include
#include
#include
#include
using namespace std;
// 模擬一個 CAN 節點
class CANNode {
public:
int nodeId; // 節點編號
int messageId; // 消息 ID(越小優先級越高)
bool sending; // 是否正在發送
bool arbitrationLost;// 仲裁是否失敗
CANNode(int id, int msgId) : nodeId(id), messageId(msgId), sending(false), arbitrationLost(false) {}
// 返回 11 位 ID 的二進制字符串(高位在前)
string getIdBits() const {
return bitset<11>(messageId).to_string();
}
};
// 模擬總線仲裁
// 參數:參與仲裁的節點列表
// 返回值:獲勝的節點指針,如果沒有節點返回 nullptr
CANNode* arbitrate(vector & nodes) {
if (nodes.empty()) return nullptr;
// 所有節點同時開始發送
for (auto node : nodes) {
node->sending = true;
node->arbitrationLost = false;
}
cout << "\n========== 仲裁開始 ==========" << endl;
cout << "參與節點: ";
for (auto node : nodes) {
cout << "Node" << node->nodeId << "(ID=" << node->messageId << ") ";
}
cout << endl;
// 獲取所有節點的 ID 位串
vector bitStrings;
for (auto node : nodes) {
bitStrings.push_back(node->getIdBits());
}
// 逐位仲裁(從高位到低位,即索引 0 到 10)
for (int bitPos = 0; bitPos < 11; ++bitPos) {
// 收集當前位所有節點發送的值(0 顯性,1 隱性)
vector bits;
for (auto node : nodes) {
if (node->sending) { // 只考慮尚未退出的節點
int bit = node->getIdBits()[bitPos] - '0';
bits.push_back(bit);
}
}
if (bits.empty()) break; // 沒有活躍節點了
// 總線狀態:線與邏輯,只要有一個 0(顯性),總線就是 0
int busState = 0;
for (int b : bits) {
if (b == 0) {
busState = 0;
break;
}
}
// 如果所有位都是 1,總線才是 1
bool allOne = true;
for (int b : bits) if (b != 1) { allOne = false; break; }
if (allOne) busState = 1;
// 輸出當前位的比較情況
cout << "位 " << bitPos << " (從高位起): ";
for (size_t i = 0; i < nodes.size(); ++i) {
if (nodes[i]->sending) {
cout << "Node" << nodes[i]->nodeId << "=" << nodes[i]->getIdBits()[bitPos];
cout << " ";
}
}
cout << "| 總線=" << busState << endl;
// 檢查哪些節點失敗:發送了 1 但總線是 0
for (auto node : nodes) {
if (node->sending) {
int sentBit = node->getIdBits()[bitPos] - '0';
if (sentBit == 1 && busState == 0) {
node->sending = false;
node->arbitrationLost = true;
cout << " -> Node" << node->nodeId << " 仲裁失敗,退出" << endl;
}
}
}
}
// 找出唯一獲勝者(仍然 sending 為 true 且未失敗)
CANNode* winner = nullptr;
for (auto node : nodes) {
if (node->sending && !node->arbitrationLost) {
winner = node;
break;
}
}
if (winner) {
cout << "\n★★★ 仲裁勝出: Node" << winner->nodeId
<< " (ID=" << winner->messageId << ") ★★★" << endl;
} else {
cout << "\n仲裁異常:無勝出節點" << endl;
}
cout << "========== 仲裁結束 ==========\n" << endl;
return winner;
}
int main() {
// 創建三個節點,消息 ID 分別為 0x123, 0x0FF, 0x100
// 注意:ID 是 11 位,范圍 0~0x7FF。數值越小優先級越高。
CANNode nodeA(1, 0x123); // 二進制: 001 0010 0011 -> 0x123
CANNode nodeB(2, 0x0FF); // 二進制: 000 1111 1111 -> 0x0FF (更小,優先級高)
CANNode nodeC(3, 0x100); // 二進制: 001 0000 0000 -> 0x100
vector nodes = { &nodeA, &nodeB, &nodeC };
// 模擬同時發送
CANNode* winner = arbitrate(nodes);
// 輸出仲裁后各節點的狀態
cout << "仲裁結果:" << endl;
for (auto node : nodes) {
cout << "Node" << node->nodeId << " (ID=" << node->messageId << ") : "
<< (node->arbitrationLost ? "仲裁失敗,等待重發" : "獲勝,繼續發送數據幀")
<< endl;
}
// 模擬失敗節點在總線空閑后自動重發(此處簡化:隨機退避后重試)
cout << "\n[模擬] 總線空閑后,失敗節點自動重發..." << endl;
vector failedNodes;
for (auto node : nodes) {
if (node->arbitrationLost) failedNodes.push_back(node);
}
if (!failedNodes.empty()) {
// 重新仲裁(實際會按優先級再次競爭)
arbitrate(failedNodes);
}return 0;
}
4. 代碼核心邏輯解釋========== 仲裁開始 ==========
參與節點: Node1(ID=291) Node2(ID=255) Node3(ID=256)
位 0 (從高位起): Node1=0 Node2=0 Node3=0 | 總線=0
位 1 (從高位起): Node1=0 Node2=0 Node3=0 | 總線=0
位 2 (從高位起): Node1=1 Node2=0 Node3=1 | 總線=0
-> Node1 仲裁失敗,退出
-> Node3 仲裁失敗,退出
位 3 (從高位起): Node2=1 | 總線=1
位 4 (從高位起): Node2=1 | 總線=1
位 5 (從高位起): Node2=1 | 總線=1
位 6 (從高位起): Node2=1 | 總線=1
位 7 (從高位起): Node2=1 | 總線=1
位 8 (從高位起): Node2=1 | 總線=1
位 9 (從高位起): Node2=1 | 總線=1
位 10 (從高位起): Node2=1 | 總線=1
★★★ 仲裁勝出: Node2 (ID=255) ★★★
========== 仲裁結束 ==========
仲裁結果:
Node1 (ID=291) : 仲裁失敗,等待重發
Node2 (ID=255) : 獲勝,繼續發送數據幀
Node3 (ID=256) : 仲裁失敗,等待重發
[模擬] 總線空閑后,失敗節點自動重發...
========== 仲裁開始 ==========
參與節點: Node1(ID=291) Node3(ID=256)
位 0: Node1=0 Node3=0 | 總線=0
位 1: Node1=0 Node3=0 | 總線=0
位 2: Node1=1 Node3=1 | 總線=1
位 3: Node1=0 Node3=0 | 總線=0
-> Node1 仲裁失敗,退出
位 4: Node3=0 | 總線=0
位 5: Node3=0 | 總線=0
位 6: Node3=0 | 總線=0
位 7: Node3=0 | 總線=0
位 8: Node3=0 | 總線=0
位 9: Node3=0 | 總線=0
位 10: Node3=0 | 總線=0★★★ 仲裁勝出: Node3 (ID=256) ★★★
========== 仲裁結束 ==========
**
getIdBits()**:將 11 位 ID 轉換為二進制字符串,高位在前,便于逐位比較。**
arbitrate()**:收集所有節點發送的當前位。
計算總線狀態(線與:任意 0 → 總線 0)。
檢查每個活躍節點:如果自己發的是 1 但總線是 0,則仲裁失敗,標記
sending=false。繼續下一位,直到所有位比較完畢或只剩一個節點。
失敗重試 :主函數中模擬了失敗節點在總線空閑后重新參與仲裁(實際 CAN 控制器硬件自動完成)。
非破壞性仲裁 :低 ID 勝出,高 ID 自動退讓, 無數據沖突 。
實時性保障 :高優先級消息最多等待一個最長幀時間(如 134 位時間)即可發送。
C++ 模擬 :清晰展示了逐位比較、顯性覆蓋隱性的過程,以及失敗節點的自動重試機制。
這個機制使得 CAN 總線在工業控制、汽車電子等領域成為事實標準,完美兼顧了實時性和可靠性。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.