Skip to content

CAN 通信原理与实践

CAN(Controller Area Network,控制器局域网)是 RoboMaster 机器人中最核心的通信协议。所有电机、电调、超级电容等设备均通过 CAN 总线与主控通信。理解 CAN 的工作原理对于调试电机通信问题至关重要。

上一篇:I2C 通信原理与实践


1. 概念——为什么需要 CAN

1.1 UART/SPI/I2C 的局限性

在机器人场景中,用传统协议连接多个执行器存在明显问题:

UART 方案:
  MCU ──TX──→ 电机1
   │  ←─RX──
   │
   └──TX──→ 电机2
      ←─RX──
  需要 N 个 UART 外设,只支持点对点

SPI 方案:
  MCU ──SCLK/MOSI/MISO──┬── 电机1 (CS1)
                         ├── 电机2 (CS2)
                         ├── 电机3 (CS3)
                         └── 电机4 (CS4)
  需要 N 根 CS 线,高速但抗干扰差,且只能一主多从

CAN 总线解决了这些问题:

  • 两根线连接所有设备(差分信号 CAN_H + CAN_L)
  • 多主架构:任何节点都可以主动发送
  • 仲裁机制:ID 小的帧优先发送
  • 硬件错误检测:CRC、位填充、帧格式检查
  • 抗干扰能力强:差分传输,适用于电机等强电磁干扰环境

为什么机器人/车辆领域必须用 CAN? 最关键的原因在于 CAN 的硬件级错误处理:当电机产生的电磁干扰(EMI)导致某一帧数据损坏时,CAN 控制器会在硬件层自动检测出 CRC 错误或位错误,并自动重发该帧——整个过程不需要软件介入。UART、SPI、I2C 则完全不同:它们没有硬件重传机制,数据损坏就是永久丢失,软件层必须自己实现校验和重传。在机器人中,8 个大功率电机同时运转产生的 EMI 足以轻易破坏单端信号——只有 CAN 的差分信号 + 硬件重传组合能保证可靠通信。

1.2 CAN 总线的物理拓扑

┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐
│  MCU    │      │ 电机 1  │      │ 电机 2  │      │ 电机 3  │
│ (主控)  │      │(GM6020) │      │(M3508)  │      │(DM4310) │
└────┬────┘      └────┬────┘      └────┬────┘      └────┬────┘
     │                │                │                │
     ├────────────────┼────────────────┼────────────────┤── CAN_H
     │                │                │                │
     ├────────────────┼────────────────┼────────────────┤── CAN_L
     │                │                │                │
    [120Ω]                               [120Ω]       ← 终端电阻
     │                                                │
    GND                                              GND

两个关键物理要素

  1. 差分信号:CAN_H 和 CAN_L 电压差表示逻辑值。共模干扰同时影响两根线,差模值不变。
  2. 终端电阻(120Ω):在总线两端并联,防止信号反射。没有它高速通信会失败。

为什么差分信号能抗干扰? 电机运转时产生的电磁噪声会同时耦合到 CAN_H 和 CAN_L 两根线上——这叫共模干扰。差分信号的精妙之处在于:接收端只关心 CAN_H 减去 CAN_L 的差值,而共模噪声在两根线上产生的电压偏移完全相同,做减法后恰好抵消。换句话说,即便每根线上的噪声高达数伏特,只要两根线上的噪声幅度一致,差模信号就丝毫不受影响。单端信号(UART/SPI/I2C)没有这种保护机制——一根信号线上的噪声直接就是噪声。

为什么必须有终端电阻? 从传输线理论来看,电信号在导线末端会发生反射——就像水波碰到墙壁会反弹一样。对于 CAN 的 1Mbps 速率,1-bit 宽度约 1μs,而一条 2 米长的总线上信号反射返回时间约 10ns——恰好落在同一位的时间内,会叠加在原始信号上造成畸变。总线的 120Ω 终端电阻相当于"吸收器",将到达末端的信号能量吸收掉而不是反射回去。没有终端电阻,信号完整性会完全崩溃——这就是为什么测 CAN 通信问题时第一件事就是拿万用表量 CAN_H 和 CAN_L 之间的电阻(两个 120Ω 并联应为 60Ω)。

为什么终端电阻必须在两端而不是一端? 一条总线的两个端点都会产生反射,所以两端各需要一个电阻来吸收能量。如果只装一端,另一端的反射仍然会破坏信号。

差分信号示意:

CAN_H: ───────────┐         ┌───────────
                  │         │
                  └─────────┘
CAN_L: ───────────┐         ┌───────────
                 ┌┘         └┐
                 └───────────┘
差值:  ────────────┐       ┌─────────────
                   │       │
                   └───────┘
          显性(0)   隐性(1)  显性(0)

显性电平 (Dominant): CAN_H - CAN_L ≈ 2V  → 逻辑 0
隐性电平 (Recessive): CAN_H - CAN_L ≈ 0V  → 逻辑 1
重要: 显性覆盖隐性 → ID 小的帧能"赢"仲裁

2. CAN 数据帧格式

标准 CAN 2.0A 使用 11-bit ID。GSRL 中我们主要使用这种格式:

CAN 标准数据帧 (CAN 2.0A):

┌─────┬─────┬──────┬────┬────┬─────┬──────┬────┬────┬──────┬─────┬──────┬───┐
│ SOF │ ID  │ RTR  │IDE │ r0 │ DLC │ Data │ CRC│ACK │ EOF  │ IFS │ 间隔 │...│
│  1  │ 11  │  1   │ 1  │ 1  │  4  │0~64  │ 15 │ 2  │  7   │  3  │      │   │
│ bit │ bits│ bit  │bit │bit │ bits│ bits │bits│bits│ bits │ bits│      │   │
└─────┴─────┴──────┴────┴────┴─────┴──────┴────┴────┴──────┴─────┴──────┴───┘
字段 含义 说明
SOF Start of Frame 1 bit 显性位,表示帧开始
ID Identifier 11-bit(标准帧)或 29-bit(扩展帧),决定优先级和数据含义
RTR Remote Transmission Request 0=数据帧,1=远程帧(请求对方发送数据)
IDE Identifier Extension 0=标准帧(11-bit ID),1=扩展帧(29-bit ID)
DLC Data Length Code 数据长度,0~8 字节
Data Data Field 实际数据,0~8 字节
CRC Cyclic Redundancy Check 15-bit CRC 校验
ACK Acknowledgment 接收方应答,至少一个节点收到就确认
EOF End of Frame 7 bit 隐性位

2.1 ID 的双重职能

CAN 的 ID 既是优先级也是标识符

  • 仲裁:多个节点同时发送时,ID 小的帧"赢"。显性位(0)覆盖隐性位(1)。
  • 过滤:接收方根据 ID 决定是否接收该帧。

为什么 CAN ID 同时承担优先级功能? 这是 CAN 协议最巧妙的设计之一,专为实时系统而生。当多个节点同时发送时,它们各自在总线上输出自己的 ID 位,同时监控总线电平。由于显性位(逻辑 0)会覆盖隐性位(逻辑 1),ID 数值越小的帧自动赢得仲裁——整个过程不需要任何中央调度器,零延迟完成优先级判决。这意味着紧急消息(如电机急停,ID=0x001)可以在任何时刻抢占总线,而低优先级消息(如温度上报,ID=0x500)会自动退让。相比之下,I2C 等基于地址的协议无法在硬件层实现这种优先级机制——如果两个设备同时想说话,它们需要复杂的软件仲裁协议。

仲裁示例(两个节点同时发送):

节点A ID: 0 0 1 1 0 0 1 0 1 0 0  (0x194)
节点B ID: 0 0 1 1 0 0 1 0 1 0 1  (0x195)
总线:     0 0 1 1 0 0 1 0 1 0 0 (节点A获胜!)

前10位完全相同,第11位:
- 节点A发送 0 (显性) → 覆盖节点B的 1 (隐性)
- 节点B检测到自己发的1被拉成了0 → 知道自己输了 → 停止发送

3. RoboMaster 中的 CAN 通信规范

3.1 大疆电机 CAN 协议

以 GM6020 为例,大疆电机使用标准 CAN 帧(11-bit ID),1Mbps 波特率

控制帧(MCU → 电机,ID = 0x1FF + 控制组):

数据字节 含义
Data[0:1] 电机1 控制电压 (-30000 ~ +30000)
Data[2:3] 电机2 控制电压
Data[4:5] 电机3 控制电压
Data[6:7] 电机4 控制电压

一个控制帧可以携带最多 4 个电机的电压指令("一控多"模式)。

反馈帧(电机 → MCU,ID = 0x204 + 电机序号):

数据字段 含义 范围
Data[0:1] 机械角度(编码器值) 0 ~ 8191 (13-bit)
Data[2:3] 转速 (RPM) -10000 ~ +10000
Data[4:5] 实际转矩电流 -16384 ~ +16384
Data[6] 温度 0 ~ 127 ℃

3.2 达妙电机 MIT 协议

达妙 DM4310 使用 MIT 模式(力矩控制):

控制帧(ID = masterID << 8 | motorID):

Data[0:1] = 期望位置 (float → uint16, PMAX 归一化)
Data[2:3] = 期望速度 (float → uint16, VMAX 归一化)
Data[4:5] = 前馈力矩 (float → uint16, TMAX 归一化)
Data[6:7] = KP + KD

反馈帧(ID = 0x300 + motorID):

Data[0:1] = 当前位置
Data[2:3] = 当前速度
Data[4:5] = 当前力矩
Data[6]   = 温度 (MOSFET)
Data[7]   = 温度 (线圈) + 错误状态

4. HAL 库的 CAN 操作

4.1 基本配置

在 CubeMX 中配置 CAN:

参数 含义 RoboMaster 典型值
Bit Rate 波特率 1 Mbps
Prescaler 时钟分频 7 (APB1=42MHz → 42/(1+5+1)/7=1Mbps)
Time Quanta Bit 时间分段 BS1=5, BS2=2, SJW=1
Mode 工作模式 Normal

4.2 发送和接收

// 发送 CAN 消息
CAN_TxHeaderTypeDef txHeader;
txHeader.StdId = 0x1FF;       // 标准帧 ID
txHeader.ExtId = 0;           // 不用扩展帧
txHeader.IDE = CAN_ID_STD;    // 标准帧
txHeader.RTR = CAN_RTR_DATA;  // 数据帧
txHeader.DLC = 8;             // 数据长度 8 字节

uint8_t txData[8] = {0};
uint32_t txMailbox;
HAL_CAN_AddTxMessage(&hcan1, &txHeader, txData, &txMailbox);

// 接收 CAN 消息(在 HAL 回调中)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rxHeader;
    uint8_t rxData[8];
    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);
    // rxHeader.StdId → 判断是哪个电机的反馈
    // rxData → 解析电机状态
}

5. GSRL 的 CAN 驱动设计

5.1 全通过滤器 + 软件过滤

GSRL 将 CAN 滤波器配置为"全通"模式(接收所有 ID 的帧),过滤工作放在软件层做:

// drv_can.c: 全通过滤器初始化
static void can_all_pass_filter_init(CAN_HandleTypeDef *hcan)
{
    CAN_FilterTypeDef can_filter_st = {0};
    can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK;
    can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT;
    can_filter_st.FilterIdHigh = 0x0000;
    can_filter_st.FilterIdLow = 0x0000;
    can_filter_st.FilterMaskIdHigh = 0x0000;
    can_filter_st.FilterMaskIdLow = 0x0000;
    // Mask 全为 0 → 不关心 ID → 接收所有帧
    can_filter_st.FilterFIFOAssignment = CAN_FilterFIFO0;
    can_filter_st.FilterActivation = CAN_FILTER_ENABLE;
    HAL_CAN_ConfigFilter(hcan, &can_filter_st);
}

为什么这样做?因为 RoboMaster 比赛中有多种不同 ID 的设备(电机、裁判系统、超级电容等),硬件滤波器数量有限,用软件过滤更灵活。

5.2 双路径数据分发

CAN 驱动同时提供两条数据处理路径:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    can_rx_message_t s_rx_msg;
    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &s_rx_msg.header, s_rx_msg.data);

    // 路径 1: FreeRTOS 队列(任务线程延迟处理)
    xQueueSendToBackFromISR(canRxQueueHandle, &s_rx_msg, &woken);

    // 路径 2: 用户注册的回调(ISR 中直接处理)
    CAN_Manage_Object_t *can_obj = CAN_Get_Object(hcan);
    if (can_obj->rxCallbackFunction != NULL) {
        can_obj->rxCallbackFunction(&s_rx_msg);
    }

    portYIELD_FROM_ISR(woken);
}

5.3 离线检测

GSRL 的 Motor 类通过反馈序列号判断电机是否离线:

// 每次收到反馈帧,序列号递增
// 如果连续 N 次未收到新数据,判定电机离线
bool decodeCanRxMessageFromISR(const can_rx_message_t *rxMessage) {
    m_motorLastFeedbackSequence = m_motorFeedbackSequence;
    m_motorFeedbackSequence = rxMessage->header.StdId & 0x07;  // 或其他序列号字段
    // 判断: 连续多帧未更新 → 离线
}

6. CAN 对比其他协议

特性 CAN UART SPI I2C
信号类型 差分 单端 单端 单端
最大节点数 理论无限(实际受驱动能力限制) 点对点 一主多从 127
多主 支持 不支持 不支持 支持
硬件错误检测 CRC、位填充、帧检查 可选校验位
自动重发 有(硬件)
典型速率 1Mbps 115200bps 10Mbps+ 400kbps
数据帧长 0~8 字节 无固定帧 无固定帧 无固定帧
抗干扰

7. 常见问题与调试

问题 原因 排查方法
电机无反馈 终端电阻缺失 用万用表测 CAN_H-CAN_L 电阻,应为 60Ω(两个 120Ω 并联)
数据乱码 波特率不匹配 确认所有节点波特率一致(RoboMaster 为 1Mbps)
间歇通信失败 信号反射 检查终端电阻,缩短分支线长度
电机 ID 冲突 两个电机同 ID 每个电机 ID 必须唯一,通过电调软件修改
CAN 中断风暴 滤波器未配置 检查滤波器设置,限制接收范围
HAL_CAN 错误回调频繁触发 总线连接不良 检查接线,确保 CAN_H/CAN_L 未接反

8. 总结

要点 说明
差分信号 CAN_H 和 CAN_L 差值表示数据,抗干扰强
显性/隐性 显性(0)覆盖隐性(1),实现非破坏性仲裁
ID 仲裁 同时发送时,ID 小的帧优先
数据帧 0~8 字节有效载荷,大疆一控多模式同时发 4 电机指令
终端电阻 总线两端各 120Ω,消除信号反射
GSRL CAN 驱动 全通过滤 + 双路径分发 + 离线检测

与 GSRL 的关系

CAN 是 GSRL 中最重要的通信协议。drv_can.c 封装了 HAL CAN 的初始化、过滤、收发和中断处理。所有 Motor 派生类(GM6020、M3508、DM4310 等)通过 CAN 接收反馈和发送控制指令。CAN1 通常挂载底盘电机,CAN2 挂载云台电机——两个 CAN 外设独立工作,避免了总线负载过高。

下一篇:IMU 姿态传感器

作者: Qing | 修改日期: 2026-07-18