深度解析 CAN 总线:从底层物理层到 SocketCAN 编程实战

从 CAN 差分物理层、非破坏性仲裁、2.0A/2.0B 帧与错误状态机讲起,到 Linux SocketCAN/vcan 环境与 python-can 异步收发实战及 DBC/负载率最佳实践。



一、 引言:为什么工业界离不开 CAN?

在自动驾驶、轨道交通和工业自动化领域,CAN(Controller Area Network)是不折不扣的通信基石。不同于以太网或串口,CAN 总线天生为实时性高可靠性而设计。其独特的非破坏性仲裁机制和极强的抗干扰能力,使其在极端电磁环境下依然能稳定传输关键控制指令。


二、 核心技术深度剖析

1. 物理层:差分信号与逻辑电平

CAN 采用**差分信号(Differential Signaling)**传输,通过两条线(CAN_H, CAN_L)的电压差来表示逻辑状态:

  • 显性电平(Dominant, 逻辑 0): CAN_H ≈ 3.5V,CAN_L ≈ 1.5V。此时总线被“压制”,即使有节点想发送隐性电平,总线也会呈现显性。

  • 隐性电平(Recessive, 逻辑 1): CAN_H ≈ CAN_L ≈ 2.5V。

  • 终端电阻: 总线两端必须各接一个 120Ω 电阻,用以匹配阻抗,防止信号反射。

    在这里插入图片描述

2. 数据链路层:非破坏性逐位仲裁

这是 CAN 最具魅力的部分。CAN 采用 CSMA/CD + AMP(载波侦听多路访问/冲突检测+仲裁优先级)

  • 原理: 当多个节点同时发送时,它们会一边发送一边监听总线。由于显性位(0)会覆盖隐性位(1),发送高 ID(低优先级)的节点会发现总线电平与自己发出的不符,从而立即停止发送,退出竞争。

  • 结果: 高优先级消息无延迟通过,低优先级消息自动重发,不会产生类似以太网的“碰撞”。

    在这里插入图片描述

是的,你观察得非常敏锐!你之前列出的确实是 CAN 2.0A(标准帧) 的结构。

在实际应用和博客介绍中,通常需要对比 CAN 2.0A (Standard)CAN 2.0B (Extended)。两者的核心区别在于 ID 的长度 以及为了兼容这两种长度而引入的控制位

以下是为你整理的 A 和 B 两个版本的详细对比介绍,你可以直接补充到博客中:


3. 帧结构:标准帧 (2.0A) vs 扩展帧 (2.0B)

CAN 协议有两个主要版本,它们在同一条总线上可以共存。它们最显著的区别在于“身份标签(ID)”的容量。

A. CAN 2.0A (标准帧)

这是最基础的格式,适用于大多数中小型系统。

  • Identifier (ID): 11 位长度。最多支持 211=20482^{11} = 2048211=2048 个不同的报文 ID。
  • 控制位 - IDE (Identifier Extension): 位于控制段,此时为显性 (0),表示这是一个标准帧。
  • 特点: 结构紧凑,开销更小,传输效率略高于扩展帧。

B. CAN 2.0B (扩展帧)

为了满足复杂系统(如重型机械、商用车 J1939 协议)对大量节点的需求,扩展帧应运而生。

  • Identifier (ID): 29 位长度。由 11 位基本 ID + 18 位扩展 ID 组成。支持超过 5 亿个 ID。
  • 控制位 - SRR (Substitute Remote Request): 代替了标准帧中的 RTR 位,保持占位。
  • 控制位 - IDE (Identifier Extension): 此时为隐性 (1),告诉接收节点:“后面还有 18 位 ID,请继续接收”。
  • 特点: 能够承载更复杂的协议信息(如将优先级、源地址、目标地址都编码进 ID 中)。

在这里插入图片描述

技术细节对比表

字段名称标准帧 (CAN 2.0A)扩展帧 (CAN 2.0B)作用说明
SOF1 bit1 bit帧起始
ID 长度11 bit29 bit (11+18)决定优先级和消息含义
IDE 位显性 (0)隐性 (1)区分标准帧与扩展帧的关键
RTR / SRRRTR (远程请求)SRR (替代远程请求)区分数据帧与远程帧
Control 段6 bit6 bit包含 DLC (数据长度)
Data 段0 - 8 Byte0 - 8 Byte实际有效载荷
CRC 段15 bit15 bit循环冗余校验
ACK 段2 bit2 bit应答位

兼容性:

CAN 总线硬件在读取 ID 的过程中,一旦读到第 12 位(即 IDE 位),如果它是 0,硬件就知道 ID 结束了,开始读 DLC;如果它是 1,硬件就知道后面还有 18 位 ID。这种设计允许标准帧和扩展帧在同一条物理总线上混跑而不会出错。

在 Python 例子中如何体现?

# 发送标准帧 (2.0A)
msg_standard = can.Message(
    arbitration_id=0x123, 
    is_extended_id=False, # 对应 IDE = 0
    data=[1, 2, 3]
)

# 发送扩展帧 (2.0B)
msg_extended = can.Message(
    arbitration_id=0x12345678, 
    is_extended_id=True,  # 对应 IDE = 1
    data=[4, 5, 6]
)

CAN 总线之所以被称为“永不死机的总线”,核心就在于其极其严密的错误处理与隔离机制。它不仅能发现错误,还能判断是“偶尔的手抖”还是“硬件损坏”,并能自动断开故障节点。

以下是针对这部分的详细深度解析:


4. 错误处理机制:CAN 的“自我修复”艺术

CAN 协议定义了 5 种错误检测方法,并在硬件层面通过两个计数器(TEC 和 REC)来管理节点的健康状态。

(1) 五大错误检测逻辑
  1. 位错误 (Bit Error):
    • 原理: 节点在发送位信息的同时,也会读取总线上的电平。如果发送的是“1”却读到“0”(或反之),则报出位错误。
    • 例外: 在“仲裁段”发送隐性读到显性是正常的(输掉了仲裁),或在“应答位”发送隐性读到显性也是正常的(收到了 ACK),这些情况不会报错。
  2. 填充错误 (Stuff Error):
    • 原理: 为了防止总线长时间没有电平变化导致时钟不同步,CAN 规定连续发送 5 个相同位后,必须自动插入一个相反位(位填充)。
    • 触发: 如果接收端发现总线上出现了连续 6 个相同位,说明同步逻辑失效,报出填充错误。
  3. CRC 错误 (CRC Error):
    • 原理: 发送方计算 15 位校验码,接收方根据接收数据重新计算。
    • 触发: 如果计算结果不一致,报出 CRC 错误。
  4. 格式错误 (Form Error):
    • 原理: CAN 帧中有一些固定格式的位(如 CRC 界定符、ACK 界定符、EOF 等),它们必须是隐性(1)。
    • 触发: 如果这些位置读到了显性(0),说明帧结构损坏。
  5. 应答错误 (ACK Error):
    • 原理: 发送方在 ACK Slot 发送一个隐性位。
    • 触发: 如果没有任何接收者将该位拉低(显性),发送方就知道这封信“石沉大海”了,报出应答错误。

(2) 节点的健康管理:TEC 与 REC

每个 CAN 控制器内部有两个神秘的计数器,它们决定了节点的“生死”:

  • TEC (Transmit Error Counter): 发送错误计数器。
  • REC (Receive Error Counter): 接收错误计数器。

奖惩机制:

  • 如果检测到一次错误,计数器会大幅增加(如 +8)。
  • 如果成功完成一次收/发,计数器会小幅减少(如 -1)。
  • 这种“重罚轻赏”的机制能快速定位那些持续出错的故障节点。

(3) 节点的三种错误状态

根据 TEC/REC 的值,节点会在三种状态间切换:

状态触发条件行为特征影响
主动错误 (Error Active)TEC < 127 且 REC < 127发现错误后发送 显性错误标志(6位连续0)。全局干扰: 会强制打断全总线的传输,让大家都重发。这是正常的“纠错”状态。
被动错误 (Error Passive)TEC > 127 或 REC > 127发现错误后发送 隐性错误标志(6位连续1)。局部抗争: 发出的错误标志不会影响别人,且发完报文后必须等待 8bit 时间才能发下一帧。
总线关闭 (Bus Off)TEC > 255节点直接从物理层面断开与总线的连接。自我隔离: 节点不再收发任何数据。防止一个损坏的硬件(如短路)持续发出错误帧,拖垮整个车辆的通信。
在这里插入图片描述

5. 进阶:如何从 Bus Off 中恢复?

  • 在汽车电子中,这通常对应于 Network Management (网络管理) 逻辑。
  • 如果一个节点频繁进入 Bus Off,工程师就需要检查物理链路(是否有干扰、终端电阻是否脱落)或硬件收发器是否损坏。

三、 进阶演进:CAN FD (Flexible Data-rate)

随着传感器数据量剧增,传统 CAN(8字节负载,1Mbps带宽)已力不从心。CAN FD 引入了:

  1. 更长的负载: 单帧支持高达 64 字节。
  2. 双速率: 仲裁段维持低速,数据段切换至高速(可达 5Mbps 或更高)。

四、 实战:在 PC 上实现专业级 CAN 通信模拟

在 Linux 环境下,CAN 设备被抽象为网络接口(SocketCAN),这使得我们可以像操作 TCP/IP 套接字一样操作 CAN。

1. 环境构建 (Ubuntu/Debian)

如果没有硬件,我们使用内核模块 vcan (Virtual CAN) 来模拟真实总线。

# 加载虚拟 CAN 内核模块
sudo modprobe vcan

# 创建虚拟接口 vcan0
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# 安装 can-utils 工具集(专业调试必用)
sudo apt-get install can-utils

2. Python 高级编程实例

我们将使用 python-can 库,采用 异步监听 + 事件循环 的专业写法。

import can
import threading
import time

def print_message(msg):
    """消息回调函数"""
    print(f"[{time.strftime('%H:%M:%S')}] ID: {msg.arbitration_id:03x} | "
          f"DLC: {msg.dlc} | Data: {msg.data.hex().upper()}")

class CanNode:
    def __init__(self, channel='vcan0'):
        # 初始化 SocketCAN 接口
        self.bus = can.interface.Bus(channel=channel, bustype='socketcan')
        self.notifier = None

    def start_receive(self):
        # 使用 Notifier 实现非阻塞监听
        self.notifier = can.Notifier(self.bus, [print_message])
        print("Listening on vcan0...")

    def send_periodic_data(self):
        """模拟周期性发送控制指令"""
        msg = can.Message(
            arbitration_id=0x101, 
            data=[0xAA, 0xBB, 0xCC, 0x00, 0x01, 0x02, 0x03, 0x04],
            is_extended_id=False
        )
        try:
            while True:
                self.bus.send(msg)
                print(f"Sent: {msg.arbitration_id:03x}")
                time.sleep(1) # 1Hz 频率
        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        if self.notifier:
            self.notifier.stop()
        self.bus.shutdown()

if __name__ == "__main__":
    node = CanNode('vcan0')
    node.start_receive()
    node.send_periodic_data()

3. 使用专业工具链进行验证

打开另一个终端,使用 candump(相当于 CAN 界的 Wireshark)监控总线:

candump vcan0

你会看到类似如下的实时报文:

  vcan0  101   [8]  AA BB CC 00 01 02 03 04

还可以使用 cangen 模拟高负载流量,测试你的 Python 代码在高并发下的表现:

cangen vcan0 -g 10 -I 7FF -L 8  # 每10ms生成一帧随机报文

五、 总结与最佳实践

  1. ID 规划: 在系统设计初期就要严格定义 ID 分配表(DBC文件),确保关键控制指令(如制动、转向)拥有最小 ID。
  2. 负载率控制: 建议总线负载率保持在 30%-50% 以下,峰值不超过 70%,以确保实时性。
  3. 应用层协议: 裸 CAN 只解决了“怎么传”,实际开发中应结合 CANopen(工业)、J1939(商用车)或 UDS(诊断)等应用层协议。

CAN 通信不仅是底层硬件的连接,更是一门关于确定性与可靠性的艺术。希望这篇介绍能帮你从应用层深入到协议核心,开启汽车电子/工业控制的大门。

深度解析 CAN 匯流排:從底層物理層到 SocketCAN 程式設計實戰

從 CAN 差分物理層、非破壞性仲裁、2.0A/2.0B 幀與錯誤狀態機講起,到 Linux SocketCAN/vcan 環境與 python-can 異步收發實戰及 DBC/負載率最佳實踐。

來源:https://blog.csdn.net/2403_87969572/article/details/157186001

抓取時間(ISO本地):2026-05-18 05:17:24



一、 引言:為什麼工業界離不開 CAN?

在自動駕駛、軌道交通和工業自動化領域,CAN(Controller Area Network)是不折不扣的通訊基石。不同於乙太網或串列埠,CAN 匯流排天生為實時性高可靠性而設計。其獨特的非破壞性仲裁機制和極強的抗干擾能力,使其在極端電磁環境下依然能穩定傳輸關鍵控制指令。


二、 核心技術深度剖析

1. 物理層:差分訊號與邏輯電平

CAN 採用**差分訊號(Differential Signaling)**傳輸,透過兩條線(CAN_H, CAN_L)的電壓差來表示邏輯狀態:

  • 顯性電平(Dominant, 邏輯 0): CAN_H ≈ 3.5V,CAN_L ≈ 1.5V。此時匯流排被“壓制”,即使有節點想傳送隱性電平,匯流排也會呈現顯性。

  • 隱性電平(Recessive, 邏輯 1): CAN_H ≈ CAN_L ≈ 2.5V。

  • 終端電阻: 匯流排兩端必須各接一個 120Ω 電阻,用以匹配阻抗,防止訊號反射。

    在這裡插入圖片描述

2. 資料鏈路層:非破壞性逐位仲裁

這是 CAN 最具魅力的部分。CAN 採用 CSMA/CD + AMP(載波偵聽多路訪問/衝突檢測+仲裁優先順序)

  • 原理: 當多個節點同時傳送時,它們會一邊傳送一邊監聽匯流排。由於顯性位(0)會覆蓋隱性位(1),傳送高 ID(低優先順序)的節點會發現匯流排電平與自己發出的不符,從而立即停止傳送,退出競爭。

  • 結果: 高優先順序訊息無延遲透過,低優先順序訊息自動重發,不會產生類似乙太網的“碰撞”。

    在這裡插入圖片描述

是的,你觀察得非常敏銳!你之前列出的確實是 CAN 2.0A(標準幀) 的結構。

在實際應用和部落格介紹中,通常需要對比 CAN 2.0A (Standard)CAN 2.0B (Extended)。兩者的核心區別在於 ID 的長度 以及為了相容這兩種長度而引入的控制位

以下是為你整理的 A 和 B 兩個版本的詳細對比介紹,你可以直接補充到部落格中:


3. 幀結構:標準幀 (2.0A) vs 擴充套件幀 (2.0B)

CAN 協議有兩個主要版本,它們在同一條匯流排上可以共存。它們最顯著的區別在於“身份標籤(ID)”的容量。

A. CAN 2.0A (標準幀)

這是最基礎的格式,適用於大多數中小型系統。

  • Identifier (ID): 11 位長度。最多支援 211=20482^{11} = 2048211=2048 個不同的報文 ID。
  • 控制位 - IDE (Identifier Extension): 位於控制段,此時為顯性 (0),表示這是一個標準幀。
  • 特點: 結構緊湊,開銷更小,傳輸效率略高於擴充套件幀。

B. CAN 2.0B (擴充套件幀)

為了滿足複雜系統(如重型機械、商用車 J1939 協議)對大量節點的需求,擴充套件幀應運而生。

  • Identifier (ID): 29 位長度。由 11 位基本 ID + 18 位擴充套件 ID 組成。支援超過 5 億個 ID。
  • 控制位 - SRR (Substitute Remote Request): 代替了標準幀中的 RTR 位,保持佔位。
  • 控制位 - IDE (Identifier Extension): 此時為隱性 (1),告訴接收節點:“後面還有 18 位 ID,請繼續接收”。
  • 特點: 能夠承載更復雜的協議資訊(如將優先順序、源地址、目標地址都編碼進 ID 中)。

在這裡插入圖片描述

技術細節對比表

欄位名稱標準幀 (CAN 2.0A)擴充套件幀 (CAN 2.0B)作用說明
SOF1 bit1 bit幀起始
ID 長度11 bit29 bit (11+18)決定優先順序和訊息含義
IDE 位顯性 (0)隱性 (1)區分標準幀與擴充套件幀的關鍵
RTR / SRRRTR (遠端請求)SRR (替代遠端請求)區分資料幀與遠端幀
Control 段6 bit6 bit包含 DLC (資料長度)
Data 段0 - 8 Byte0 - 8 Byte實際有效載荷
CRC 段15 bit15 bit迴圈冗餘校驗
ACK 段2 bit2 bit應答位

相容性:

CAN 匯流排硬體在讀取 ID 的過程中,一旦讀到第 12 位(即 IDE 位),如果它是 0,硬體就知道 ID 結束了,開始讀 DLC;如果它是 1,硬體就知道後面還有 18 位 ID。這種設計允許標準幀和擴充套件幀在同一條物理匯流排上混跑而不會出錯。

在 Python 例子中如何體現?

# 傳送標準幀 (2.0A)
msg_standard = can.Message(
    arbitration_id=0x123, 
    is_extended_id=False, # 對應 IDE = 0
    data=[1, 2, 3]
)

# 傳送擴充套件幀 (2.0B)
msg_extended = can.Message(
    arbitration_id=0x12345678, 
    is_extended_id=True,  # 對應 IDE = 1
    data=[4, 5, 6]
)

CAN 匯流排之所以被稱為“永不宕機的匯流排”,核心就在於其極其嚴密的錯誤處理與隔離機制。它不僅能發現錯誤,還能判斷是“偶爾的手抖”還是“硬體損壞”,並能自動斷開故障節點。

以下是針對這部分的詳細深度解析:


4. 錯誤處理機制:CAN 的“自我修復”藝術

CAN 協議定義了 5 種錯誤檢測方法,並在硬體層面透過兩個計數器(TEC 和 REC)來管理節點的健康狀態。

(1) 五大錯誤檢測邏輯
  1. 位錯誤 (Bit Error):
    • 原理: 節點在傳送位資訊的同時,也會讀取匯流排上的電平。如果傳送的是“1”卻讀到“0”(或反之),則報出位錯誤。
    • 例外: 在“仲裁段”傳送隱性讀到顯性是正常的(輸掉了仲裁),或在“應答位”傳送隱性讀到顯性也是正常的(收到了 ACK),這些情況不會報錯。
  2. 填充錯誤 (Stuff Error):
    • 原理: 為了防止匯流排長時間沒有電平變化導致時鐘不同步,CAN 規定連續傳送 5 個相同位後,必須自動插入一個相反位(位填充)。
    • 觸發: 如果接收端發現匯流排上出現了連續 6 個相同位,說明同步邏輯失效,報出填充錯誤。
  3. CRC 錯誤 (CRC Error):
    • 原理: 傳送方計算 15 位校驗碼,接收方根據接收資料重新計算。
    • 觸發: 如果計算結果不一致,報出 CRC 錯誤。
  4. 格式錯誤 (Form Error):
    • 原理: CAN 幀中有一些固定格式的位(如 CRC 界定符、ACK 界定符、EOF 等),它們必須是隱性(1)。
    • 觸發: 如果這些位置讀到了顯性(0),說明幀結構損壞。
  5. 應答錯誤 (ACK Error):
    • 原理: 傳送方在 ACK Slot 傳送一個隱性位。
    • 觸發: 如果沒有任何接收者將該位拉低(顯性),傳送方就知道這封信“石沉大海”了,報出應答錯誤。

(2) 節點的健康管理:TEC 與 REC

每個 CAN 控制器內部有兩個神秘的計數器,它們決定了節點的“生死”:

  • TEC (Transmit Error Counter): 傳送錯誤計數器。
  • REC (Receive Error Counter): 接收錯誤計數器。

獎懲機制:

  • 如果檢測到一次錯誤,計數器會大幅增加(如 +8)。
  • 如果成功完成一次收/發,計數器會小幅減少(如 -1)。
  • 這種“重罰輕賞”的機制能快速定位那些持續出錯的故障節點。

(3) 節點的三種錯誤狀態

根據 TEC/REC 的值,節點會在三種狀態間切換:

狀態觸發條件行為特徵影響
主動錯誤 (Error Active)TEC < 127 且 REC < 127發現錯誤後傳送 顯性錯誤標誌(6位連續0)。全域性干擾: 會強制打斷全匯流排的傳輸,讓大家都重發。這是正常的“糾錯”狀態。
被動錯誤 (Error Passive)TEC > 127 或 REC > 127發現錯誤後傳送 隱性錯誤標誌(6位連續1)。區域性抗爭: 發出的錯誤標誌不會影響別人,且發完報文後必須等待 8bit 時間才能發下一幀。
匯流排關閉 (Bus Off)TEC > 255節點直接從物理層面斷開與匯流排的連線。自我隔離: 節點不再收發任何資料。防止一個損壞的硬體(如短路)持續發出錯誤幀,拖垮整個車輛的通訊。
在這裡插入圖片描述

5. 進階:如何從 Bus Off 中恢復?

  • 在汽車電子中,這通常對應於 Network Management (網路管理) 邏輯。
  • 如果一個節點頻繁進入 Bus Off,工程師就需要檢查物理鏈路(是否有干擾、終端電阻是否脫落)或硬體收發器是否損壞。

三、 進階演進:CAN FD (Flexible Data-rate)

隨著感測器資料量劇增,傳統 CAN(8位元組負載,1Mbps頻寬)已力不從心。CAN FD 引入了:

  1. 更長的負載: 單幀支援高達 64 位元組。
  2. 雙速率: 仲裁段維持低速,資料段切換至高速(可達 5Mbps 或更高)。

四、 實戰:在 PC 上實現專業級 CAN 通訊模擬

在 Linux 環境下,CAN 裝置被抽象為網路介面(SocketCAN),這使得我們可以像操作 TCP/IP 套接字一樣操作 CAN。

1. 環境構建 (Ubuntu/Debian)

如果沒有硬體,我們使用核心模組 vcan (Virtual CAN) 來模擬真實匯流排。

# 載入虛擬 CAN 核心模組
sudo modprobe vcan

# 建立虛擬介面 vcan0
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# 安裝 can-utils 工具集(專業除錯必用)
sudo apt-get install can-utils

2. Python 高階程式設計例項

我們將使用 python-can 庫,採用 非同步監聽 + 事件迴圈 的專業寫法。

import can
import threading
import time

def print_message(msg):
    """訊息回撥函式"""
    print(f"[{time.strftime('%H:%M:%S')}] ID: {msg.arbitration_id:03x} | "
          f"DLC: {msg.dlc} | Data: {msg.data.hex().upper()}")

class CanNode:
    def __init__(self, channel='vcan0'):
        # 初始化 SocketCAN 介面
        self.bus = can.interface.Bus(channel=channel, bustype='socketcan')
        self.notifier = None

    def start_receive(self):
        # 使用 Notifier 實現非阻塞監聽
        self.notifier = can.Notifier(self.bus, [print_message])
        print("Listening on vcan0...")

    def send_periodic_data(self):
        """模擬週期性傳送控制指令"""
        msg = can.Message(
            arbitration_id=0x101, 
            data=[0xAA, 0xBB, 0xCC, 0x00, 0x01, 0x02, 0x03, 0x04],
            is_extended_id=False
        )
        try:
            while True:
                self.bus.send(msg)
                print(f"Sent: {msg.arbitration_id:03x}")
                time.sleep(1) # 1Hz 頻率
        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        if self.notifier:
            self.notifier.stop()
        self.bus.shutdown()

if __name__ == "__main__":
    node = CanNode('vcan0')
    node.start_receive()
    node.send_periodic_data()

3. 使用專業工具鏈進行驗證

開啟另一個終端,使用 candump(相當於 CAN 界的 Wireshark)監控匯流排:

candump vcan0

你會看到類似如下的實時報文:

  vcan0  101   [8]  AA BB CC 00 01 02 03 04

還可以使用 cangen 模擬高負載流量,測試你的 Python 程式碼在高併發下的表現:

cangen vcan0 -g 10 -I 7FF -L 8  # 每10ms生成一幀隨機報文

五、 總結與最佳實踐

  1. ID 規劃: 在系統設計初期就要嚴格定義 ID 分配表(DBC檔案),確保關鍵控制指令(如制動、轉向)擁有最小 ID。
  2. 負載率控制: 建議匯流排負載率保持在 30%-50% 以下,峰值不超過 70%,以確保實時性。
  3. 應用層協議: 裸 CAN 只解決了“怎麼傳”,實際開發中應結合 CANopen(工業)、J1939(商用車)或 UDS(診斷)等應用層協議。

CAN 通訊不僅是底層硬體的連線,更是一門關於確定性與可靠性的藝術。希望這篇介紹能幫你從應用層深入到協議核心,開啟汽車電子/工業控制的大門。

In-Depth CAN Bus Analysis: From the Physical Layer to SocketCAN Hands-On Programming

In autonomous driving, rail transit, and industrial automation, CAN (Controller Area Network) is the undisputed cornerstone of communications.Unlike Ethernet or serial interfaces, CAN is inherently designed for real-time behavior and high reliability.Its unique non-destructive arbitration mechanism and strong immunity to electromagnetic interference

Captured at (ISO local): 2026-05-18 05:17:24



I. Introduction: Why Industry Cannot Do Without CAN

In autonomous driving, rail transit, and industrial automation, CAN (Controller Area Network) is the undisputed cornerstone of communications. Unlike Ethernet or serial interfaces, CAN is inherently designed for real-time behavior and high reliability. Its unique non-destructive arbitration mechanism and strong immunity to electromagnetic interference allow critical control commands to be transmitted reliably even in harsh EMI environments.


II. Core Technology in Depth

1. Physical Layer: Differential Signaling and Logic Levels

CAN uses differential signaling to transmit data. The logical state is represented by the voltage difference between two wires (CAN_H, CAN_L):

  • Dominant level (logical 0): CAN_H ≈ 3.5 V, CAN_L ≈ 1.5 V. The bus is “pulled down” in this state—even if a node wishes to transmit a recessive bit, the bus will still appear dominant while any node transmits dominant.

  • Recessive level (logical 1): CAN_H ≈ CAN_L ≈ 2.5 V.

  • Termination resistors: Both ends of the bus must have a 120 Ω terminator to match impedance and prevent reflections.

    Figure: CAN differential levels

This is CAN’s defining feature. CAN uses CSMA/CD + AMP (Carrier Sense Multiple Access / Collision Detection + Arbitration Priority Message):

  • Principle: When multiple nodes transmit at the same time, they transmit while continually monitoring the bus. Because dominant bits (0) override recessive bits (1), nodes sending a numerically larger ID (lower priority) will detect a discrepancy between what they transmitted and what they read on the bus, and therefore stop transmitting immediately, exiting the contention.

  • Outcome: Higher-priority frames pass through without arbitration delay. Lower-priority frames are automatically rescheduled. This does not produce Ethernet-like “collisions”.

    Figure: CAN arbitration

You are absolutely right—it is indeed the structure of CAN 2.0A (standard frame) that was listed earlier.

In real projects and introductory articles, CAN 2.0A (Standard) versus CAN 2.0B (Extended) is usually discussed side by side. The core distinction is ID length plus the resulting control bits needed so both widths can coexist.

Below is a structured contrast you can paste into technical notes:


3. Frame structure: Standard frame (2.0A) vs. extended frame (2.0B)

CAN defines two dominant formats that can coexist on the same bus. The clearest distinction is capacity of the “identity tag (ID)”:

A. CAN 2.0A (standard frame)

Most common for smaller and mid-sized systems:

  • Identifier (ID): 11-bit encoding. Supports up to 2¹¹ = 2,048 distinct message identifiers.
  • Control bit IDE (Identifier Extension): In the control field it is dominant (0), declaring a standard frame.
  • Characteristics: Compact format, somewhat lower framing overhead compared with extended IDs.

B. CAN 2.0B (extended frame)

For complex systems needing many logical addresses (heavy machinery, automotive J1939, etc.), extended frames widen the identifier:

  • Identifier (ID): 29 bits composed of 11-bit base ID + 18-bit extension. Supports hundreds of millions of distinct IDs from a bitwise-combinatorics perspective.
  • Control bit SRR (Substitute Remote Request): Replaces RTR positioning as found in pure standard frames—keeps the bit-timing layout aligned.
  • Control bit IDE (Identifier Extension): Recessive (1) tells receivers “there are eighteen more identifier bits ahead—keep receiving.”
  • Characteristics: Can pack richer semantics into IDs (priority, sources, destinations, etc.) according to OEM protocols.

(Illustration placeholder)

Technical comparison matrix

FieldStandard frame (CAN 2.0A)Extended frame (CAN 2.0B)Role
SOF1 bit1 bitStart of frame
ID width11 bit29 bit (11+18)Priority plus semantic routing
IDE bitDominant (0)Recessive (1)Decoder switch between widths
RTR / SRRRTR (remote request)SRR (substitute remote request)Distinguishes data vs. remote concepts
Control field6 bits6 bitsIncludes DLC (payload length code)
Data field0 – 8 bytes0 – 8 bytesActual payload
CRC field15 bits15 bitsCyclic redundancy check
ACK field2 bits2 bitsAcknowledgement bits

Compatibility mechanics

Hardware reading the serialized ID observes bit twelve (IDE). If IDE is dominant, receivers know exactly eleven identity bits preceded the DLC. If IDE is recessive, hardware knows another eighteen-bit extension trails before DLC. Mixed traffic therefore coexists cleanly—no ambiguity about where one frame ends conceptually relative to DLC.

How Python reflects this distinction

# Standard frame (CAN 2.0A)
msg_standard = can.Message(
    arbitration_id=0x123, 
    is_extended_id=False, # corresponds to IDE = 0
    data=[1, 2, 3]
)

# Extended frame (CAN 2.0B)
msg_extended = can.Message(
    arbitration_id=0x12345678, 
    is_extended_id=True,  # corresponds to IDE = 1
    data=[4, 5, 6]
)

CAN is often called “the non-freezing fieldbus.” Achieving zero lockups is not coincidence—it owes to exceptionally strict fault handling plus isolation. The protocol not only discovers errors—it differentiates fleeting noise from catastrophic hardware faults and removes persistently offending nodes automatically.

Detailed drill-down:


4. Error handling—the “self-recovery” playbook

CAN mandates five orthogonal error checks coordinated at the silicon level via TEC and REC counters (“transmit” and “receive” error budgets).

(1) Five detection mechanisms
  1. Bit error
    • Principle: While transmitting bits, controllers read the bus concurrently. Transmitting recessive yet observing dominant—or the opposite—is a bit error unless a defined exception applies.
    • Exemptions: Losing arbitration (reading dominant while transmitting recessive) or receiving ACK dominance while recessive locally are deliberate and ignored.
  2. Stuff error
    • Principle: To avoid long runs without edges (clock-recovery killers), transmitters inject a complementary bit whenever five identical bits would otherwise appear consecutively (bit stuffing).
    • Trigger: Witnessing six identical bits violates the rule—raise stuff error.
  3. CRC error
    • Principle: Transmitter attaches a fifteen-bit residue; receivers recompute polynomial division.
    • Trigger: Mismatch ⇒ CRC fault.
  4. Form error
    • Principle: Dedicated recessive separators (CRC delimiter, ACK delimiter, EOF, etc.).
    • Trigger: Unexpected dominant positions corrupt frame grammar.
  5. ACK error
    • Principle: Transmitters recessive during ACK-slot expect at least one listener to dominate.
    • Trigger: Completely recessive slot means “nobody heard me.”
(2) Per-node bookkeeping: TEC versus REC

Each controller tracks two escalating counters guiding fault progression:

  • TEC (Transmit Error Counter)
  • REC (Receive Error Counter)

Penalty / reward heuristic:

  • Any detected fault ⇒ jump (typically +8 for local contributors).
  • Successful exchange ⇒ graceful decay (−1), ensuring chronic offenders escalate quickly yet healed nodes rehabilitate steadily.
(3) Three node states governed by counters

Depending on thresholds, controllers rotate among:

StateEntry conditionObservable behaviorSystem impact
Error activeTEC < 127 AND REC < 127On fault, transmits six dominant bits signaling error aggressivelyGlobal notification—forces everyone to reschedule carefully
Error passiveTEC > 127 or REC > 127Errors announce via six recessive bitsLocalized warning; must insert recessive intermission (~8 recessive bits) before next dominant frame startup
Bus offTEC > 255Disconnects logically—no transmissionsPrevents perpetual babble (“short CAN transceiver frying the backbone”)

(Illustration placeholder; see ./3.png if bundled with original article.)


5. Advanced—recovering intentionally from Bus-Off?

  • Vehicles typically rely on NM (Network Management) policies to resurrect modules after deterministic cool-down sequencing.
  • Frequent recurring Bus Off indicates physical-layer investigation (reflections due to absent termination; damaged transceiver; harness shielding ruptures).

III. Evolution milestone: CAN FD (Flexible Data-Rate)

As sensor payloads grew, classical CAN (≤8 B per frame, ≈ 1 Mbit/s) became cramped. FD adds:

  1. Up to sixty-four payload bytes
  2. Dual bit-rates: arbitration-phase remains conservative; accelerates strictly inside BRS/data phases (≈ 5 Mbit/s and beyond silicon-dependent).

IV. Hands-on desktop simulation with production-grade toolchain

Linux exposes controllers as networked interfaces (SocketCAN), giving socket semantics identical-ish to TCP/IP abstractions:

1. Environment scaffolding (Ubuntu/Debian)

Without hardware leverage Kernel vcan virtual adapters:

# load virtual CAN module
sudo modprobe vcan

# bring up pseudo interface `vcan0`
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# install candump / candump-compatible utilities bundle
sudo apt-get install can-utils

2. Python idiomatic notifier pattern

Leveraging python-can with non-blocking notifier callback:

import can
import threading
import time

def print_message(msg):
    """Callback printing arriving frames."""
    print(f"[{time.strftime('%H:%M:%S')}] ID: {msg.arbitration_id:03x} | "
          f"DLC: {msg.dlc} | Data: {msg.data.hex().upper()}")

class CanNode:
    def __init__(self, channel='vcan0'):
        # initialise SocketCAN
        self.bus = can.interface.Bus(channel=channel, bustype='socketcan')
        self.notifier = None

    def start_receive(self):
        # notifier pattern => thread-friendly receive path
        self.notifier = can.Notifier(self.bus, [print_message])
        print("Listening on vcan0...")

    def send_periodic_data(self):
        """simulate periodic actuator telegrams"""
        msg = can.Message(
            arbitration_id=0x101, 
            data=[0xAA, 0xBB, 0xCC, 0x00, 0x01, 0x02, 0x03, 0x04],
            is_extended_id=False
        )
        try:
            while True:
                self.bus.send(msg)
                print(f"Sent: {msg.arbitration_id:03x}")
                time.sleep(1) # emit at 1 Hz
        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        if self.notifier:
            self.notifier.stop()
        self.bus.shutdown()

if __name__ == "__main__":
    node = CanNode('vcan0')
    node.start_receive()
    node.send_periodic_data()

3. Observing canonical traffic snapshots

Separate shell—candump is CAN’s analogue to Wireshark line-mode:

candump vcan0

Typical streamed row:

  vcan0  101   [8]  AA BB CC 00 01 02 03 04

Synthetic stress:

cangen vcan0 -g 10 -I 7FF -L 8  # random frame burst every ten milliseconds

V. Takeaways plus practical guidance

  1. ID choreography: Ship DBC artefacts early—safety-critical telegrams (brakes, steer-by-wire proxies) merit numerically dominating IDs (smaller eleven-bit identifiers ⇒ higher dominance).
  2. Bus-loading discipline: Aim ≤50 % average utilization; instantaneous peaks seldom exceed ~70 % if latency budgets matter—measure with analyzers routinely.
  3. Application strata: Bare CAN transports bits; semantics ride CANopen, J1939, UDS, etc.—never reinvent wheels without ISO alignment.

Beyond copper and bits, mastering CAN blends physics, probability, and choreography of deterministic collaboration. Hopefully this teardown elevates abstraction from sporadic tooling usage toward confident architecture—opening automotive / industrial vistas alike.