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

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.

相关推荐
热点推荐
芒果V基金:刘欢留下的原创音乐公益金一切都不变,他已授权,其妻卢璐将继续参与监督

芒果V基金:刘欢留下的原创音乐公益金一切都不变,他已授权,其妻卢璐将继续参与监督

每日经济新闻
2026-09-26 16:59:21
周末消息大反转,周一开盘,行情要变天

周末消息大反转,周一开盘,行情要变天

魏家东
2026-09-27 09:08:55
霸气“拒绝”领取铜牌?女篮丢冠后不甘一幕:王思雨杨舒予拒带奖牌

霸气“拒绝”领取铜牌?女篮丢冠后不甘一幕:王思雨杨舒予拒带奖牌

篮球快餐车
2026-09-27 03:45:30
第一集就上尺度,网飞成人新剧太生猛了

第一集就上尺度,网飞成人新剧太生猛了

来看美剧
2026-09-04 18:29:48
别克听劝大幅升级配置,至境E7焕新发布14.79万元起

别克听劝大幅升级配置,至境E7焕新发布14.79万元起

深度车评
2026-09-27 15:12:26
国庆节放假最新通知!不光放假7天,而且还有4个好消息告诉大家

国庆节放假最新通知!不光放假7天,而且还有4个好消息告诉大家

爱下厨的阿酾
2026-09-27 11:26:24
周总理只是让他去经商,为党赚取经费,他却给组织赚回个商业帝国

周总理只是让他去经商,为党赚取经费,他却给组织赚回个商业帝国

文史达观
2026-04-18 09:29:56
舍不得放下远东,是俄罗斯最大的弊病,如果放下远东,俄罗斯或许能满血复活

舍不得放下远东,是俄罗斯最大的弊病,如果放下远东,俄罗斯或许能满血复活

怪味历史连连看
2026-09-11 01:23:21
细思极恐!糖尿病确诊10年后,55%的人或走向肾病、心衰或死亡!

细思极恐!糖尿病确诊10年后,55%的人或走向肾病、心衰或死亡!

徐德文科学频道
2026-09-27 15:01:06
孙继海建议赵松源别读书:亚马尔周薪30万还有必要上学?名记:可笑

孙继海建议赵松源别读书:亚马尔周薪30万还有必要上学?名记:可笑

林子说事
2026-09-27 18:30:22
没想到,在跳水界叱咤风云的全红婵,如今借着33岁男星发了笔大财

没想到,在跳水界叱咤风云的全红婵,如今借着33岁男星发了笔大财

做一个合格的吃瓜群众
2026-08-11 14:52:38
失业的人越来越多,社会为啥静悄悄?

失业的人越来越多,社会为啥静悄悄?

奇思妙想生活家
2026-09-28 00:11:59
汪小菲中秋带女儿游玩,小玥儿正脸曝光,眉眼不像S,下巴神似

汪小菲中秋带女儿游玩,小玥儿正脸曝光,眉眼不像S,下巴神似

胡一舸南游y
2026-09-27 13:49:54
画面不堪入目,地下人奶交易乱象,成年人躺进怀里吃奶的灰色黑产

画面不堪入目,地下人奶交易乱象,成年人躺进怀里吃奶的灰色黑产

易玄
2026-09-12 10:24:46
有些反常!今年国庆跌价最猛的商品,白酒排第3,第一名让人有些意外

有些反常!今年国庆跌价最猛的商品,白酒排第3,第一名让人有些意外

小谈食刻美食
2026-09-28 08:02:40
张本美和:王曼昱方方面面都强过我!早田直言没有被中国观众干扰

张本美和:王曼昱方方面面都强过我!早田直言没有被中国观众干扰

排球黄金眼
2026-09-28 06:59:08
巴祖努:杀害无辜民众是错误的,我决定不参加对以色列比赛

巴祖努:杀害无辜民众是错误的,我决定不参加对以色列比赛

懂球帝
2026-09-27 12:38:39
莉莉公主正脸,红发淡眉冷白皮跟夏洛特轮廓相似,梅根基因落败

莉莉公主正脸,红发淡眉冷白皮跟夏洛特轮廓相似,梅根基因落败

情感大头说说
2026-09-28 03:18:33
反华妖女许可馨,回国后狂妄叫嚣无人敢动她,如今下场大快人心

反华妖女许可馨,回国后狂妄叫嚣无人敢动她,如今下场大快人心

街上的行人很刺眼
2026-07-27 14:58:45
弟弟被割头邀功,哥哥将仇人剥皮,做成马鞍,骑了半生!

弟弟被割头邀功,哥哥将仇人剥皮,做成马鞍,骑了半生!

黑句本
2026-09-26 21:21:18
2026-09-28 09:08:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

特斯拉人形机器人被曝周产量已达数百台

头条要闻

13岁少年和老爸"手搓"涡喷发动机 建家庭实验室花20万

头条要闻

13岁少年和老爸"手搓"涡喷发动机 建家庭实验室花20万

体育要闻

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

娱乐要闻

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

财经要闻

标价298元核桃油, 实为大豆油冒充

汽车要闻

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

态度原创

房产
健康
游戏
艺术
军事航空

房产要闻

全年霸榜TOP1!南海・叁號院周年官宣:新作即将登场

鼻子三角区的痘,千万别乱挤!

《最终幻想7:启示》总监解释为何这么早确认推出DLC

艺术要闻

致敬,刘欢硬笔书法欣赏

军事要闻

特朗普"罕见"提二战中美同盟 日本慌了

无障碍浏览 进入关怀版