STM32F1 CAN标准帧与扩展帧实战:6种过滤器配置与双机通信全解析
在工业控制、汽车电子和物联网设备中,CAN总线因其高可靠性和实时性成为首选通信协议。STM32F1系列微控制器内置CAN控制器,但实际开发中,工程师常面临标准帧与扩展帧混用场景下的过滤器配置难题。本文将深入解析6种过滤器模式,并提供可直接移植的代码实现。
1. CAN帧类型与STM32过滤器核心机制
CAN协议定义了两种帧格式:标准帧(11位ID)和扩展帧(29位ID)。STM32F1的CAN控制器通过硬件过滤器实现高效帧过滤,其核心机制包含三个关键点:
- 双FIFO结构:STM32F1提供FIFO0和FIFO1两个接收缓冲区,每个可存储3个完整报文
- 14个共享过滤器:所有过滤器编号0-13,可灵活分配给FIFO0或FIFO1
- 两种过滤模式:
- 标识符屏蔽模式(CAN_FilterMode_IdMask)
- 标识符列表模式(CAN_FilterMode_IdList)
标准帧与扩展帧的ID结构差异如下表所示:
| 帧类型 | ID长度 | 结构组成 | 典型应用场景 |
|---|---|---|---|
| 标准帧 | 11位 | 11位基础ID | 简单控制指令、传感器数据 |
| 扩展帧 | 29位 | 11位基础ID + 18位扩展ID | 复杂系统、大容量数据传输 |
关键提示:扩展帧的IDE位恒为1(隐性电平),标准帧IDE位恒为0(显性电平)。这一特性是硬件过滤器区分帧类型的基础。
2. 6种过滤器配置模式详解
2.1 标准数据帧过滤(模式1)
// 只接收指定ID的标准数据帧 void CAN_Filter_StdDataFrame(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; // 高16位:标准ID左移5位(对齐到bit15-bit5) filter.FilterIdHigh = (id << 5) & 0xFFFF; // 低16位:IDE=0(标准帧),RTR=0(数据帧) filter.FilterIdLow = (id << 5) | CAN_ID_STD | CAN_RTR_DATA; filter.FilterMaskIdHigh = 0xFFFF; // 必须完全匹配ID filter.FilterMaskIdLow = 0xFFFF; // 必须匹配IDE和RTR位 filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }配置要点:
- 标准帧ID左移5位对齐到bit15-bit5
- IDE位显性(0)表示标准帧
- RTR位显性(0)表示数据帧
2.2 标准遥控帧过滤(模式2)
// 只接收指定ID的标准遥控帧 void CAN_Filter_StdRemoteFrame(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; filter.FilterIdHigh = (id << 5) & 0xFFFF; filter.FilterIdLow = (id << 5) | CAN_ID_STD | CAN_RTR_REMOTE; filter.FilterMaskIdHigh = 0xFFFF; filter.FilterMaskIdLow = 0xFFFF; filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }特殊处理:
- 遥控帧的RTR位置1(隐性电平)
- 接收方需在中断处理中回复数据帧
2.3 标准帧通用过滤(模式3)
// 接收指定ID的所有标准帧(数据帧+遥控帧) void CAN_Filter_StdAll(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; filter.FilterIdHigh = (id << 5) & 0xFFFF; filter.FilterIdLow = (id << 5) | CAN_ID_STD; filter.FilterMaskIdHigh = 0xFFFF; filter.FilterMaskIdLow = 0xFFFC; // 不检查RTR和保留位 filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }应用场景:
- 需要同时处理数据帧和遥控帧
- 屏蔽位设置为0xFFFC忽略RTR位差异
2.4 扩展数据帧过滤(模式4)
// 只接收指定ID的扩展数据帧 void CAN_Filter_ExtDataFrame(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; // 高16位:扩展ID高16位(ID28-ID13) filter.FilterIdHigh = (id << 3) >> 16; // 低16位:扩展ID低13位(ID12-ID0)+ IDE=1 + RTR=0 filter.FilterIdLow = ((id << 3) | CAN_ID_EXT | CAN_RTR_DATA) & 0xFFFF; filter.FilterMaskIdHigh = 0xFFFF; filter.FilterMaskIdLow = 0xFFFF; filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }关键点:
- 29位ID左移3位后拆分到高低16位寄存器
- IDE位置1(隐性电平)表示扩展帧
2.5 扩展遥控帧过滤(模式5)
// 只接收指定ID的扩展遥控帧 void CAN_Filter_ExtRemoteFrame(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; filter.FilterIdHigh = (id << 3) >> 16; filter.FilterIdLow = ((id << 3) | CAN_ID_EXT | CAN_RTR_REMOTE) & 0xFFFF; filter.FilterMaskIdHigh = 0xFFFF; filter.FilterMaskIdLow = 0xFFFF; filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }注意事项:
- 接收到遥控帧后需在1ms内回复数据帧
- 总线负载较高时需考虑响应实时性
2.6 扩展帧通用过滤(模式6)
// 接收指定ID的所有扩展帧(数据帧+遥控帧) void CAN_Filter_ExtAll(uint8_t fifo, uint8_t filter_num, uint32_t id) { CAN_FilterInitTypeDef filter; filter.FilterNumber = filter_num; filter.FilterMode = CAN_FilterMode_IdMask; filter.FilterScale = CAN_FilterScale_32bit; filter.FilterIdHigh = (id << 3) >> 16; filter.FilterIdLow = ((id << 3) | CAN_ID_EXT) & 0xFFFF; filter.FilterMaskIdHigh = 0xFFFF; filter.FilterMaskIdLow = 0xFFFC; // 不检查RTR和保留位 filter.FilterFIFOAssignment = fifo ? CAN_FIFO1 : CAN_FIFO0; filter.FilterActivation = ENABLE; CAN_FilterInit(&filter); }典型应用:
- 需要处理同一ID的数据帧和遥控帧
- 工业设备双向通信场景
3. 双机通信实战:环回模式与正常模式
3.1 硬件连接方案
双机通信硬件连接需注意:
- CAN_H和CAN_L需使用双绞线
- 总线两端各接120Ω终端电阻
- 推荐使用TI的SN65HVD23x系列CAN收发器
# STM32F103C8T6最小系统连接示例 +-----------------+ +-----------------+ | STM32 | | STM32 | | | | | | PA11 --------+---------->| PA12 (CAN_RX) | | PA12 <-------+-----------| PA11 (CAN_TX) | | | | | +--------+--------+ +--------+--------+ | | \|/ \|/ +-------+-------+ +-------+-------+ | CAN_H | | CAN_H | | CAN_L | | CAN_L | +-------+-------+ +-------+-------+ | | \|/ \|/ +-----------------+ +-----------------+ | 120Ω 终端电阻 | | 120Ω 终端电阻 | +-----------------+ +-----------------+3.2 环回模式测试代码
环回模式用于单节点自测试,无需实际硬件连接:
void CAN_Mode_Config(uint8_t mode, uint32_t baudrate) { CAN_InitTypeDef CAN_InitStruct; CAN_StructInit(&CAN_InitStruct); // 模式选择 if(mode == 0) { CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack; // 环回模式 } else { CAN_InitStruct.CAN_Mode = CAN_Mode_Normal; // 正常模式 } // 波特率配置(以1MHz为例) CAN_InitStruct.CAN_SJW = CAN_SJW_1tq; CAN_InitStruct.CAN_BS1 = CAN_BS1_3tq; CAN_InitStruct.CAN_BS2 = CAN_BS2_2tq; CAN_InitStruct.CAN_Prescaler = 6; // 36MHz/(1+3+2)/6 = 1MHz CAN_Init(CAN1, &CAN_InitStruct); // 配置过滤器(示例:FIFO0接收标准数据帧ID=0x123) CAN_Filter_StdDataFrame(0, 0, 0x123); }3.3 中断处理与数据收发
完整的通信流程需要配合中断处理:
// 发送标准数据帧 int CAN_SendStdData(uint32_t id, uint8_t* data, uint8_t len) { CanTxMsg msg; msg.StdId = id; msg.IDE = CAN_ID_STD; msg.RTR = CAN_RTR_DATA; msg.DLC = len > 8 ? 8 : len; memcpy(msg.Data, data, msg.DLC); uint8_t mailbox = CAN_Transmit(CAN1, &msg); return (mailbox != CAN_NO_MB) ? 0 : -1; } // 接收中断处理 void USB_LP_CAN1_RX0_IRQHandler(void) { if(CAN_GetITStatus(CAN1, CAN_IT_FMP0)) { CanRxMsg rx_msg; CAN_Receive(CAN1, CAN_FIFO0, &rx_msg); if(rx_msg.IDE == CAN_ID_STD) { printf("StdID:0x%03X Data:", rx_msg.StdId); } else { printf("ExtID:0x%08lX Data:", rx_msg.ExtId); } for(int i=0; i<rx_msg.DLC; i++) { printf("%02X ", rx_msg.Data[i]); } printf("\n"); } CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0); }4. 常见问题与调试技巧
4.1 过滤器配置失败排查
- 检查过滤器编号:确保不重复使用同一过滤器编号
- 验证ID对齐方式:
- 标准帧:ID左移5位
- 扩展帧:ID左移3位
- 确认FIFO分配:过滤器必须明确指定到FIFO0或FIFO1
4.2 通信异常处理
当通信异常时,可通过以下寄存器状态诊断:
| 寄存器 | 关键位 | 含义 |
|---|---|---|
| CAN_ESR | LEC[2:0] | 最后错误代码(0=无错误) |
| CAN_MSR | SLAKI | 睡眠模式确认中断 |
| CAN_TSR | TME[2:0] | 发送邮箱空状态 |
错误处理参考代码:
void CAN_ErrorHandler(void) { uint32_t esr = CAN->ESR; if(esr & CAN_ESR_BOFF) { printf("Bus-off状态,需重新初始化CAN\n"); } else { switch((esr & CAN_ESR_LEC) >> CAN_ESR_LEC_Pos) { case 1: printf("填充错误\n"); break; case 2: printf("格式错误\n"); break; case 3: printf("ACK错误\n"); break; case 4: printf("隐性位错误\n"); break; case 5: printf("显性位错误\n"); break; case 6: printf("CRC错误\n"); break; } } // 清除错误状态 CAN->MSR |= CAN_MSR_ERRI; }4.3 性能优化建议
- 过滤器分组策略:
- 高优先级消息分配到FIFO0
- 低优先级消息分配到FIFO1
- 中断优先级设置:
NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 高于其他通信接口 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); - 总线负载监控:
float CAN_GetBusLoad(void) { uint32_t esr = CAN->ESR; uint32_t rec = (esr & CAN_ESR_REC) >> CAN_ESR_REC_Pos; uint32_t tec = (esr & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos; return (rec + tec) / 255.0f * 100; // 返回百分比 }
5. 进阶应用:混合帧网络设计
在实际项目中,常需同时处理标准帧和扩展帧。推荐两种设计方案:
方案A:双过滤器组策略
// FIFO0处理标准帧,FIFO1处理扩展帧 void CAN_MixedFilter_Init(void) { // FIFO0过滤器组:标准帧ID 0x100-0x103 CAN_Filter_StdDataFrame(0, 0, 0x100); CAN_Filter_StdDataFrame(0, 1, 0x101); // FIFO1过滤器组:扩展帧ID 0x1000000-0x1000003 CAN_Filter_ExtDataFrame(1, 2, 0x1000000); CAN_Filter_ExtDataFrame(1, 3, 0x1000001); }方案B:优先级分流策略
// 高优先级消息(标准帧)用FIFO0,普通消息(扩展帧)用FIFO1 void CAN_PriorityFilter_Init(void) { // FIFO0:关键控制指令(标准帧) CAN_Filter_StdDataFrame(0, 0, 0x01); // 紧急停止 CAN_Filter_StdDataFrame(0, 1, 0x02); // 状态查询 // FIFO1:普通数据(扩展帧) CAN_Filter_ExtDataFrame(1, 2, 0x10000001); // 传感器数据1 CAN_Filter_ExtDataFrame(1, 3, 0x10000002); // 传感器数据2 }在汽车电子控制系统中,通常会采用混合帧设计:关键控制指令使用高优先级的标准帧,诊断和配置信息使用扩展帧。这种设计既保证了实时性,又满足了大数据量传输需求。