一、CAN FD扩展帧是什么?
1.1 标准帧 vs 扩展帧
| 特性 | 标准帧 | 扩展帧 |
|---|---|---|
| ID位数 | 11位 | 29位 |
| ID范围 | 0x000 ~ 0x7FF | 0x00000000 ~ 0x1FFFFFFF |
| 节点容量 | 最多2048个 | 最多5.36亿个 |
| 应用场景 | 小型网络 | 大型复杂系统 |
扩展帧的29位ID为大型网络提供了海量的地址空间,是CAN FD的强大特性之一。
1.2 CAN FD vs 传统CAN
传统CAN 2.0:
┌──────┬──────────┬──────┬─────┐
│ ID │ DLC(≤8) │ Data │ CRC │
│ 11/29│ 4bit │ 0-8B │15bit│
└──────┴──────────┴──────┴─────┘
CAN FD:
┌──────┬──────────┬──────┬───────┬─────┐
│ ID │ DLC(≤64) │ Data │ Stuff │ CRC │
│ 11/29│ 4bit │0-64B │ Count │17/21│
└──────┴──────────┴──────┴───────┴─────┘
↑
可指示最多64字节数据!
核心优势:
-
数据段速率可达8Mbps(传统CAN仅1Mbps)
-
单帧最大64字节(传统CAN仅8字节)
-
硬件CRC校验,无需软件组帧
二、扩展帧ID的位域设计
2.1 为什么需要自定义ID结构?
CAN FD的29位ID不仅仅是"地址",更是协议的载体。通过位域划分,可以在ID中编码丰富的语义信息。
2.2 实际案例:机器人关节控制协议
以下是一个真实项目的ID编码方案:
/* 29位扩展ID的位域分配 */
#define CAN_DEV_ID_NUM (7u) // 设备ID:7位,0-127
#define CAN_DEV_ID_SHIFT (0u)
#define CAN_PRODUCT_ID_NUM (7u) // 产品ID:7位
#define CAN_PRODUCT_ID_SHIFT (7u) // 注意:从bit7开始!
#define CAN_RW_NUM (1u) // 读写标志:1位
#define CAN_RW_SHIFT (14u) // 0=读,1=写
#define CAN_REG_NUM (8u) // 寄存器号:8位
#define CAN_REG_SHIFT (15u)
#define CAN_REG_ADD_NUM (5u) // 子地址:5位
#define CAN_REG_ADD_SHIFT (23u)
// 广播地址
#define CAN_BROADCAST_DEV_ID (0u)
ID布局可视化:
Bit: 28 ...... 24 | 23 ...... 15 | 14 | 13 ...... 7 | 6 ...... 0
[子地址 5位] [寄存器 8位] [R/W] [产品ID 7位] [设备ID 7位]
三、关键认知:ID里存的是源地址!
3.1 CAN总线的寻址哲学
这是初学者最容易混淆的地方:
| 总线类型 | 寻址方式 | ID含义 |
|---|---|---|
| I2C/SPI | 目标寻址 | 我要跟谁通信 |
| CAN | 源地址广播 | 我是谁 |
3.2 为什么这样设计?
CAN总线是广播式通信,所有节点同时监听总线。发送方报出自己身份,接收方根据"谁发的"来决定是否处理。
// 发送时:填写自己的ID
uint32_t u32_id = pack_id(MY_PRODUCT_ID, MY_DEV_ID, rw, reg, reg_add);
// 接收时:判断发送方是谁
if(u8_dev_id == MASTER_ID || u8_dev_id == BROADCAST_ID) {
// 来自主控或广播,处理
}
3.3 目标地址放哪里?
如果需要点对点通信,目标地址放在数据区:
// 发送给设备5
uint8_t au8_data[64];
au8_data[0] = 0x05; // 目标设备ID
// ... 其他数据
四、硬件过滤器的正确配置
4.1 过滤的是什么?
过滤的是"发送方ID",不是"目标地址"!
// 错误理解:过滤"发给我的帧"
// 正确理解:过滤"我关心的发送方发的帧"
// 示例:只接收来自主控(0x01)和广播(0x00)的帧
uint32_t u32_master_id = pack_id(PRODUCT_ID, 0x01, 0, 0, 0);
uint32_t u32_broadcast_id = pack_id(PRODUCT_ID, 0x00, 0, 0, 0);
st_filter.FilterID1 = u32_master_id;
st_filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
4.2 多FDCAN外设的过滤器配置陷阱
问题现象:FDCAN1收不到数据,FDCAN2正常。
根本原因 :多个FDCAN外设共享同一个过滤器RAM,FilterIndex冲突导致覆盖。
// ❌ 错误:两个外设使用相同的FilterIndex
// bsp_fdcan1_init()
st_filter.FilterIndex = 0; // 被覆盖!
st_filter.FilterIndex = 1; // 被覆盖!
// bsp_fdcan2_init()
st_filter.FilterIndex = 0; // 覆盖了CAN1的配置
st_filter.FilterIndex = 1; // 覆盖了CAN1的配置
✅ 正确方案1:错开FilterIndex
c
// FDCAN1使用0~1
st_filter.FilterIndex = 0;
st_filter.FilterIndex = 1;
// FDCAN2使用2~3
st_filter.FilterIndex = 2;
st_filter.FilterIndex = 3;
✅ 正确方案2:使用全局过滤器(推荐)
// FDCAN1:所有扩展帧进FIFO0
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,
FDCAN_REJECT, FDCAN_REJECT,
FDCAN_FILTER_TO_RXFIFO0, // 扩展数据帧
FDCAN_FILTER_TO_RXFIFO0); // 扩展远程帧
// FDCAN2:所有扩展帧进FIFO1
HAL_FDCAN_ConfigGlobalFilter(&hfdcan2,
FDCAN_REJECT, FDCAN_REJECT,
FDCAN_FILTER_TO_RXFIFO1,
FDCAN_FILTER_TO_RXFIFO1);