网易首页 > 网易号 > 正文 申请入驻

CAN 总线仲裁机制详解 + C++ 模拟代码

0
分享至


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)。

#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;
}
3. 运行结果示例

========== 仲裁开始 ==========
参与节点: 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) ★★★
========== 仲裁结束 ==========
4. 代码核心逻辑解释
  • ** getIdBits() **:将 11 位 ID 转换为二进制字符串,高位在前,便于逐位比较。

  • ** arbitrate() **:

    1. 收集所有节点发送的当前位。

    2. 计算总线状态(线与:任意 0 → 总线 0)。

    3. 检查每个活跃节点:如果自己发的是 1 但总线是 0,则仲裁失败,标记 sending=false

    4. 继续下一位,直到所有位比较完毕或只剩一个节点。

  • 失败重试 :主函数中模拟了失败节点在总线空闲后重新参与仲裁(实际 CAN 控制器硬件自动完成)。

5. 总结
  • 非破坏性仲裁 :低 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.

相关推荐
热点推荐
杭州女子多次仅退款海鲜,被老板找上门,身份是律师,业主爆黑料

杭州女子多次仅退款海鲜,被老板找上门,身份是律师,业主爆黑料

观察鉴娱
2026-04-12 11:13:55
全程眼突鼓腮,看了观众对孙俪的评价,才知张艺谋这句话的含金量

全程眼突鼓腮,看了观众对孙俪的评价,才知张艺谋这句话的含金量

陈述影视
2026-04-04 17:53:34
愤怒的李想和遭到围攻的理想汽车

愤怒的李想和遭到围攻的理想汽车

界面新闻
2026-04-13 10:01:09
没想到吧?那辆“陪葬”的奔驰S级,后续比电视剧还离谱!

没想到吧?那辆“陪葬”的奔驰S级,后续比电视剧还离谱!

小娱乐悠悠
2026-04-13 09:51:49
可绕过弹劾直接罢免特朗普,万斯已经公开翻脸,将提前夺权?

可绕过弹劾直接罢免特朗普,万斯已经公开翻脸,将提前夺权?

清衣渡a
2026-04-13 12:02:37
烟草市场彻底变天!销量下滑非产能过剩,供需矛盾席卷全国烟酒店

烟草市场彻底变天!销量下滑非产能过剩,供需矛盾席卷全国烟酒店

老特有话说
2026-04-12 15:57:53
血战台儿庄:中国参战29万人、牺牲5万人,日军伤亡令人难以置信

血战台儿庄:中国参战29万人、牺牲5万人,日军伤亡令人难以置信

冰语历史
2026-04-11 17:05:58
上海植物园猥亵惯犯落网!被抓6次,仍嬉皮笑脸毫无羞耻心太可恶

上海植物园猥亵惯犯落网!被抓6次,仍嬉皮笑脸毫无羞耻心太可恶

行者聊官
2026-04-12 15:46:42
台湾到底何时统一?原来李敖早就预言了,说得很精准

台湾到底何时统一?原来李敖早就预言了,说得很精准

顾史
2026-03-03 15:23:07
官方通报从云南拉到焦作的11吨西瓜全坏

官方通报从云南拉到焦作的11吨西瓜全坏

黄河新闻网吕梁
2026-04-12 11:18:44
马斯克版“微信”来了,iPhone下载需谨慎!

马斯克版“微信”来了,iPhone下载需谨慎!

果粉之家
2026-04-12 12:57:47
身材是真绝了!令和时代的峰不二子!

身材是真绝了!令和时代的峰不二子!

贵圈真乱
2026-04-13 10:12:43
哈萨克斯坦2000万吨稀土转卖美日,签完协议发现,还是绕不开中国

哈萨克斯坦2000万吨稀土转卖美日,签完协议发现,还是绕不开中国

蔡蔡说史
2026-04-11 04:25:53
特朗普宣布将封锁霍尔木兹海峡,比伊朗的封锁更狠

特朗普宣布将封锁霍尔木兹海峡,比伊朗的封锁更狠

名人苟或
2026-04-12 23:03:38
郑丽文:绝不放弃武力保台,洪秀柱怒怼:两岸和平,岂能不统一?

郑丽文:绝不放弃武力保台,洪秀柱怒怼:两岸和平,岂能不统一?

安梦入天下
2026-04-12 17:01:16
从质疑到理解,原来73岁无儿无女的迟重瑞,早被陈丽华安排好退路

从质疑到理解,原来73岁无儿无女的迟重瑞,早被陈丽华安排好退路

皮皮电影
2026-04-13 09:58:59
22岁甜妹公开示爱,王楚钦回应让谁心碎?

22岁甜妹公开示爱,王楚钦回应让谁心碎?

运动探索
2026-04-13 09:29:35
访朝第二天,王毅在平壤提中方主张,话音刚落,朝鲜的回应很直接

访朝第二天,王毅在平壤提中方主张,话音刚落,朝鲜的回应很直接

井普椿的独白
2026-04-12 16:42:07
斯普利特:杨瀚森本赛季没有太多机会,这个夏天他需要变得更好

斯普利特:杨瀚森本赛季没有太多机会,这个夏天他需要变得更好

懂球帝
2026-04-13 10:40:10
严惩38号裁判!末节严重双标+4错漏判,公开帮广厦,真是看不下去

严惩38号裁判!末节严重双标+4错漏判,公开帮广厦,真是看不下去

南海浪花
2026-04-13 07:29:25
2026-04-13 14:12:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
955文章数 346关注度
往期回顾 全部

科技要闻

传荣耀与字节跳动接洽“豆包手机”合作

头条要闻

毛焦尔:匈牙利将再次成为欧盟和北约国家的坚定盟友

头条要闻

毛焦尔:匈牙利将再次成为欧盟和北约国家的坚定盟友

体育要闻

一支球队不够烂,也是一种悲哀

娱乐要闻

贾玲减重后现身冯巩生日宴 身材未反弹

财经要闻

封锁,还是收费站?

汽车要闻

不止命名更纯粹 领克10/10+要做纯电操控新王

态度原创

教育
时尚
旅游
游戏
军事航空

教育要闻

发音不准或引发的自信危机与应对措施

这些才是普通人借鉴的穿搭!上短下长、上窄下宽,显瘦又舒适

旅游要闻

“追光经济”点亮眉山青神振兴新路径

杰洛特来东瀛!《巫师》新衍生作官宣 和服叶奈法超美

军事要闻

美国副总统万斯:美伊谈判未能达成协议

无障碍浏览 进入关怀版