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

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.

相关推荐
热点推荐
差严子怡8米!日本奥运冠军痛哭:不甘心让她一枝独秀 明年要赢她

差严子怡8米!日本奥运冠军痛哭:不甘心让她一枝独秀 明年要赢她

风过乡
2026-09-28 09:06:08
洛夫顿离开上海得不偿失!发文开启勿扰模式,重返大鲨鱼障碍重重

洛夫顿离开上海得不偿失!发文开启勿扰模式,重返大鲨鱼障碍重重

体育见习官
2026-09-28 07:58:50
时隔两年半冲突视频曝光!斯图尔特挥拳尤班克斯 曾因此被禁赛3场

时隔两年半冲突视频曝光!斯图尔特挥拳尤班克斯 曾因此被禁赛3场

罗说NBA
2026-09-28 06:21:57
曼联无意让卡里克下课,重用拉什福德起作用!聘拉爵心仪主帅需1500万

曼联无意让卡里克下课,重用拉什福德起作用!聘拉爵心仪主帅需1500万

罗米的曼联博客
2026-09-28 10:14:03
CBA速递,上海大鲨鱼锁定麦基,六十万美元非常平价,三剑客浮现

CBA速递,上海大鲨鱼锁定麦基,六十万美元非常平价,三剑客浮现

体坛小快灵
2026-09-28 08:43:41
“升糖直接爆了”,家长晒儿子上学早餐,网友:吃完了倒头就睡

“升糖直接爆了”,家长晒儿子上学早餐,网友:吃完了倒头就睡

泽泽先生
2026-09-21 13:37:01
刘欢病逝2天,别墅、版权收入等资产曝光,遗产分配成焦点

刘欢病逝2天,别墅、版权收入等资产曝光,遗产分配成焦点

往史过眼云烟
2026-09-27 16:40:27
儿子作文里写:“我爸爸妈妈从来不吵架,但他们也不怎么说话”

儿子作文里写:“我爸爸妈妈从来不吵架,但他们也不怎么说话”

自由心向往之
2026-09-25 08:30:13
拒绝网红脸!央视秋晚四美火出圈,观众:这才是真审美

拒绝网红脸!央视秋晚四美火出圈,观众:这才是真审美

阿废冷眼观察所
2026-09-27 12:56:52
国家统计局:2026年1—8月份全国规模以上工业企业利润增长15.7%

国家统计局:2026年1—8月份全国规模以上工业企业利润增长15.7%

界面新闻
2026-09-28 09:31:49
台湾提出和统条件:一国三制,“外交权”和拥有军队?是痴心妄想

台湾提出和统条件:一国三制,“外交权”和拥有军队?是痴心妄想

老头的传奇色彩
2026-08-06 17:59:31
广州一工人误踩8层自建房楼顶绿色帆布坠入电梯井,连破两道安全网跌至一楼,调查报告披露:承包人无资质冒用他人名义,未设警示标志

广州一工人误踩8层自建房楼顶绿色帆布坠入电梯井,连破两道安全网跌至一楼,调查报告披露:承包人无资质冒用他人名义,未设警示标志

扬子晚报
2026-09-28 10:35:00
红薯正大量上市!研究发现:糖尿病吃一次红薯,等于给血糖添堵?

红薯正大量上市!研究发现:糖尿病吃一次红薯,等于给血糖添堵?

老李讲健康
2026-09-28 07:30:07
“磨膝大户”被揪出,是跑步的20倍,医生:不想软骨磨光,早扔掉

“磨膝大户”被揪出,是跑步的20倍,医生:不想软骨磨光,早扔掉

看世界的人
2026-09-20 22:04:47
王思聪坐香奈儿VIP贵宾室,懒懒旁边试包,消费降级传闻又被打脸

王思聪坐香奈儿VIP贵宾室,懒懒旁边试包,消费降级传闻又被打脸

梦醉为红颜一笑
2026-09-27 14:27:39
骁龙8EE6+11000mAh!荣耀新机官宣,9月28日发布

骁龙8EE6+11000mAh!荣耀新机官宣,9月28日发布

科技堡垒
2026-09-28 10:19:38
“抱歉,我们不喜欢肥头大耳的”,女孩公开追求体制内男,面相输了

“抱歉,我们不喜欢肥头大耳的”,女孩公开追求体制内男,面相输了

世界圈
2026-08-15 10:29:14
新央企首任董事长,未公布被调查就直接“双开”

新央企首任董事长,未公布被调查就直接“双开”

评论员岳连
2026-09-27 20:54:46
一个国家能蠢到什么程度?看看法国就明白了:法国黑人接近800万,巴黎新生儿里70%是黑人

一个国家能蠢到什么程度?看看法国就明白了:法国黑人接近800万,巴黎新生儿里70%是黑人

怪味历史连连看
2026-09-08 01:55:32
越老越妖,梅西30岁后罚进43粒直接任意球,比30岁前多10粒

越老越妖,梅西30岁后罚进43粒直接任意球,比30岁前多10粒

懂球帝
2026-09-28 10:05:05
2026-09-28 11:35:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

智能体惹了多少祸,OpenAI自己没查清!

头条要闻

牛弹琴:内塔尼亚胡有大麻烦了 不是命硬就能扛过去的

头条要闻

牛弹琴:内塔尼亚胡有大麻烦了 不是命硬就能扛过去的

体育要闻

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

娱乐要闻

好汉刘欢:一副好嗓子,一颗赤子心

财经要闻

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

汽车要闻

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

态度原创

艺术
数码
亲子
公开课
军事航空

艺术要闻

致敬,刘欢硬笔书法欣赏

数码要闻

D5 Single Boost实测!技嘉Z890 AORUS ELITE DUO X主板内存体验:单通道游戏性能干翻双通道

亲子要闻

江苏产妇产下5斤9两男宝,脐带刚剪断,宝宝突然刺耳尖叫

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

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

无障碍浏览 进入关怀版