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

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-09-27 11:33:42
张本智和:阿拉米扬以往发球就算不上完全合规,现在又肆无忌惮

张本智和:阿拉米扬以往发球就算不上完全合规,现在又肆无忌惮

懂球帝
2026-09-27 15:40:00
9月28日正式开播!中央八台好剧来袭!看到演员我兴趣来了

9月28日正式开播!中央八台好剧来袭!看到演员我兴趣来了

阿废冷眼观察所
2026-09-26 11:30:42
随着爱尔兰3-0,葡萄牙2-1,德国0-1,荷兰2-1,丹麦2-0,欧国联最新积分榜出炉

随着爱尔兰3-0,葡萄牙2-1,德国0-1,荷兰2-1,丹麦2-0,欧国联最新积分榜出炉

侧身凌空斩
2026-09-28 05:00:43
新首相决定对中国赔钱那一刻,全世界都看懂了:不能对华继续天真

新首相决定对中国赔钱那一刻,全世界都看懂了:不能对华继续天真

比利
2026-08-06 11:11:07
《兰香如故》导演只是请喜剧演员孔连顺来演个配角,可守城那场戏,竟将一代明君的气魄演到了骨子里,网友:谁还记得他当年是“孔女神”

《兰香如故》导演只是请喜剧演员孔连顺来演个配角,可守城那场戏,竟将一代明君的气魄演到了骨子里,网友:谁还记得他当年是“孔女神”

喵喵的追剧笔记
2026-09-27 22:58:27
一座1亿瓦的核反应堆,只有洗衣机大小,运行63年却没发一度电,专门生产一些造出来就快速消失的东西,已帮助了数亿生命

一座1亿瓦的核反应堆,只有洗衣机大小,运行63年却没发一度电,专门生产一些造出来就快速消失的东西,已帮助了数亿生命

徐德文科学频道
2026-09-27 14:20:48
9-5领先被翻盘!王曼昱打碎日本32年梦想,张本美和:我真是完败

9-5领先被翻盘!王曼昱打碎日本32年梦想,张本美和:我真是完败

而长终
2026-09-28 04:22:06
教授夫妇双双从25楼跳下,账户查出700万流水,遗言:没有活着的希望

教授夫妇双双从25楼跳下,账户查出700万流水,遗言:没有活着的希望

悬案解密档案
2025-10-31 15:05:24
中国人祖祖辈辈都用“粪水浇菜”,欧洲科学家却才发现,这是为什么?

中国人祖祖辈辈都用“粪水浇菜”,欧洲科学家却才发现,这是为什么?

磊子讲史
2026-08-17 18:31:51
你听过最炸裂的三观是什么?网友:我承认老一辈吃盐比我吃的饭多

你听过最炸裂的三观是什么?网友:我承认老一辈吃盐比我吃的饭多

另子维爱读史
2026-09-03 20:47:51
原来他就是孙颖莎的亲舅舅,低调但不简单,难怪孙颖莎能这么优秀

原来他就是孙颖莎的亲舅舅,低调但不简单,难怪孙颖莎能这么优秀

小欣欣聊体育
2026-08-04 16:08:11
央视怒批、戏子误国!这三位明星恶贯满盈,没有一个人值得同情!

央视怒批、戏子误国!这三位明星恶贯满盈,没有一个人值得同情!

秋姐居
2026-08-03 11:54:12
九月还没过完,4 位名人相继离世,最大93最小63,一代人回忆落幕

九月还没过完,4 位名人相继离世,最大93最小63,一代人回忆落幕

乡野小珥
2026-09-28 02:46:09
人不会无缘无故患糖尿病!研究证实:得糖尿病的人,离不开这8点

人不会无缘无故患糖尿病!研究证实:得糖尿病的人,离不开这8点

叙说医疗健康
2026-08-25 08:00:47
生物学有个残忍事实:雄性从不为任何一只雌性停留,但它会为一只即将消失的雌性放弃整片领地!这是两性依恋的原始密码

生物学有个残忍事实:雄性从不为任何一只雌性停留,但它会为一只即将消失的雌性放弃整片领地!这是两性依恋的原始密码

心理观察局
2026-08-02 07:16:04
《兰香如故》林绣茹戏台一笑!袁绍辉至死不知,他丢的不是妾是妻

《兰香如故》林绣茹戏台一笑!袁绍辉至死不知,他丢的不是妾是妻

东方不败然多多
2026-09-20 08:29:54
明晚开播,央八次黄金档又一好剧来袭!看到阵容我很放心,要爆!

明晚开播,央八次黄金档又一好剧来袭!看到阵容我很放心,要爆!

情感大头说说
2026-09-28 03:27:14
发现一个规则,顶层的家庭里,绝对不会让孩子去碰这三件事……

发现一个规则,顶层的家庭里,绝对不会让孩子去碰这三件事……

新东方家庭教育
2026-09-20 10:19:36
曾国藩警告:和强势的人打交道,顺从是险,对抗是蠢,应该这样做

曾国藩警告:和强势的人打交道,顺从是险,对抗是蠢,应该这样做

纪史行者
2026-09-14 07:00:22
2026-09-28 05:36:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

星舰第14飞,为什么准备绕地球六圈?

头条要闻

教师称成绩排名靠后被强制培训 广西南丹通报调查情况

头条要闻

教师称成绩排名靠后被强制培训 广西南丹通报调查情况

体育要闻

114项指控成立,曼城已经完蛋了吗?

娱乐要闻

刘欢去世后谣言四起,韩磊被翻旧账

财经要闻

“为了看短剧,我妈两年花了22万 ”

汽车要闻

MAZDA EZ-60激光版上市 焕新全系11.39万元起

态度原创

亲子
教育
游戏
家居
时尚

亲子要闻

工程车小故事:洗澡澡

教育要闻

成绩靠后的老师,就该“强制”自费培训20周吗?

Halo工作室曾规划《光环:初次反击》改编战役

家居要闻

2026建博会(广州) 公装联探展交流活动

兰香如故:谁为打工人的情绪工伤买单?

无障碍浏览 进入关怀版