4. CANFD底层驱动开发:FlexCAN初始化流程、报文发送实现、报文接收实现、中断处理机制、CANFD与经典CAN兼容性处理
好,咱们进入正题。这一章是CANFD底层驱动的核心,说白了就是让FlexCAN模块真正跑起来。我当年第一次调FlexCAN时,光初始化就折腾了两天,后来发现是时钟配置搞错了。嗯,咱们一步步来,把坑都填上。
4.1 FlexCAN初始化流程
初始化FlexCAN,我习惯按这个顺序来:时钟使能、模块复位、配置时序参数、设置滤波器、最后使能中断。别急着上来就发数据,先让模块“醒过来”。
核心步骤:
- 使能FlexCAN模块时钟(在PCC寄存器里)
- 等待模块进入冻结模式(FRZ位+HALT位)
- 配置CAN时钟源(选总线时钟还是外部晶振)
- 设置位时序(波特率、采样点)
- 配置消息缓冲区(MB)或FIFO
- 设置接收掩码和滤波器
- 退出冻结模式,进入正常模式
这里有个关键点——位时序配置。CANFD的波特率分两段:仲裁段和数据段。仲裁段通常用500kbps,数据段可以到2Mbps甚至5Mbps。我建议仲裁段采样点设在75%~80%之间,数据段可以放宽到70%左右。
// FlexCAN初始化示例(S32K144)
void FLEXCAN_Init(void)
{
// 1. 使能时钟
PCC->PCCn[PCC_FLEXCAN0_INDEX] |= PCC_PCCn_CGC_MASK;
// 2. 进入冻结模式
CAN0->MCR |= CAN_MCR_FRZ_MASK | CAN_MCR_HALT_MASK;
while(!(CAN0->MCR & CAN_MCR_FRZ_ACK_MASK));
// 3. 配置位时序(仲裁段500kbps,数据段2Mbps)
CAN0->CTRL1 = CAN_CTRL1_PRESDIV(39) | // 预分频
CAN_CTRL1_PROPSEG(0) |
CAN_CTRL1_PSEG1(7) |
CAN_CTRL1_PSEG2(4) |
CAN_CTRL1_RJW(2);
// 4. 使能CANFD模式
CAN0->MCR |= CAN_MCR_FDEN_MASK;
// 5. 配置数据段时序
CAN0->FDCTRL = CAN_FDCTRL_FPRESC(0) |
CAN_FDCTRL_TDCEN_MASK; // 使能收发延迟补偿
// 6. 退出冻结模式
CAN0->MCR &= ~(CAN_MCR_FRZ_MASK | CAN_MCR_HALT_MASK);
while(CAN0->MCR & CAN_MCR_FRZ_ACK_MASK);
}
我的经验:配置完时序后,一定要读一下错误计数器(ECR寄存器)。如果看到大量错误,八成是位时序没算对。我曾经因为预分频算错一位,导致整条CAN总线瘫痪,排查了整整一个下午。
4.2 报文发送实现
发送报文,说白了就是把数据塞进消息缓冲区(MB),然后告诉硬件“发出去”。FlexCAN有32个MB,每个MB可以配置成发送或接收。
我个人习惯用轮询发送做调试,用中断发送做产品。为什么?轮询简单,但会阻塞CPU;中断效率高,但代码复杂些。
// 发送CANFD报文(中断方式)
uint8_t FLEXCAN_SendMsg(uint32_t id, uint8_t *data, uint8_t len, uint8_t isFD)
{
uint8_t mb_idx = 0;
// 找空闲MB
for(mb_idx = 0; mb_idx < 32; mb_idx++)
{
if((CAN0->RAMn[mb_idx].CS & CAN_CS_CODE_MASK) == CAN_CS_CODE_INACTIVE)
{
break;
}
}
if(mb_idx >= 32) return 0xFF; // 没有空闲MB
// 配置MB
CAN0->RAMn[mb_idx].CS = CAN_CS_CODE_TX_DATA | // 发送数据帧
CAN_CS_IDE_MASK | // 扩展帧
(isFD ? CAN_CS_EDL_MASK : 0) | // CANFD标志
CAN_CS_DLC(len); // 数据长度
CAN0->RAMn[mb_idx].ID = id;
// 复制数据
for(uint8_t i = 0; i < len; i++)
{
CAN0->RAMn[mb_idx].DATA[i] = data[i];
}
// 触发发送
CAN0->IFLAG1 |= (1 << mb_idx); // 清除中断标志
CAN0->RAMn[mb_idx].CS |= CAN_CS_CODE_TX_DATA; // 激活发送
return 0;
}
注意:CANFD报文的数据长度编码(DLC)和经典CAN不一样。经典CAN的DLC最大是8字节,CANFD可以到64字节。但DLC编码不是线性的——比如DLC=9代表12字节,DLC=12代表20字节。这个坑我踩过,发出去的数据对不上。
4.3 报文接收实现
接收报文,我推荐用FIFO+中断的方式。FlexCAN有一个8层深的FIFO,配合中断,基本不会丢帧。你想想看,如果每个报文都去轮询MB,CPU得累死。
// 配置FIFO接收
void FLEXCAN_ConfigFIFO(void)
{
// 使能FIFO
CAN0->MCR |= CAN_MCR_FEN_MASK;
// 配置FIFO滤波器(接收所有ID)
CAN0->RXIMR[0] = 0; // 掩码全0,接收所有
// 设置FIFO深度为8
CAN0->CTRL2 &= ~CAN_CTRL2_RFFN_MASK;
CAN0->CTRL2 |= CAN_CTRL2_RFFN(0); // 8个FIFO缓冲区
// 使能FIFO中断
CAN0->IMASK1 |= CAN_IMASK1_BUF5M_MASK; // FIFO中断在MB5
}
// FIFO中断服务函数
void FLEXCAN0_IRQHandler(void)
{
if(CAN0->IFLAG1 & CAN_IFLAG1_BUF5I_MASK)
{
// 读取FIFO中的数据
uint32_t id = CAN0->RAMn[5].ID;
uint8_t dlc = (CAN0->RAMn[5].CS & CAN_CS_DLC_MASK) >> 16;
uint8_t data[64];
for(uint8_t i = 0; i < dlc; i++)
{
data[i] = CAN0->RAMn[5].DATA[i];
}
// 处理接收到的数据(回调函数)
if(rx_callback) rx_callback(id, data, dlc);
// 清除中断标志
CAN0->IFLAG1 = CAN_IFLAG1_BUF5I_MASK;
}
}
避坑指南:我曾经在FIFO中断里做太多处理,导致中断嵌套,丢了好几帧。后来我把数据处理放到任务队列里,中断里只做标志位设置。记住:中断服务函数要短小精悍。
4.4 中断处理机制
FlexCAN的中断系统其实挺清晰的。每个MB都有自己的中断标志位,FIFO也有独立的中断。我建议把中断优先级设高一点,毕竟CAN报文实时性要求高。
| 中断源 | 标志位 | 说明 |
|---|---|---|
| MB0~MB4 | IFLAG1[0:4] | 独立MB中断 |
| MB5(FIFO) | IFLAG1[5] | FIFO满或溢出 |
| 错误中断 | ESR寄存器 | 总线错误、仲裁丢失等 |
| 唤醒中断 | MCR中的WAKMSK | 总线活动唤醒 |
嗯,这里有个细节:中断标志位需要软件清除。而且清除时机很重要——必须在读取完数据后再清除,否则可能丢失数据。我习惯在中断函数最后统一清除标志。
4.5 CANFD与经典CAN兼容性处理
这是实际项目中绕不开的问题。你的节点可能同时连接CANFD设备和经典CAN设备。怎么处理?说白了就是自动识别。
FlexCAN有个好功能——CANFD帧检测。当收到一个报文时,硬件会自动判断它是经典CAN还是CANFD帧。你只需要在接收中断里检查EDL位就行。
// 兼容性处理:自动识别CANFD和经典CAN
void FLEXCAN_RxHandler(uint32_t mb_idx)
{
uint32_t cs = CAN0->RAMn[mb_idx].CS;
if(cs & CAN_CS_EDL_MASK)
{
// CANFD帧,数据段波特率可能更高
// 注意:数据段长度可能超过8字节
uint8_t dlc = (cs & CAN_CS_DLC_MASK) >> 16;
uint8_t len = FLEXCAN_DLC2Len(dlc); // DLC转实际长度
// 处理CANFD数据
ProcessCANFDMsg(mb_idx, len);
}
else
{
// 经典CAN帧,数据长度最大8字节
uint8_t len = (cs & CAN_CS_DLC_MASK) >> 16;
if(len > 8) len = 8; // 经典CAN最多8字节
// 处理经典CAN数据
ProcessClassicCANMsg(mb_idx, len);
}
}
重要提醒:如果总线上同时有CANFD和经典CAN设备,数据段波特率必须一致。否则CANFD设备发高速数据时,经典CAN设备会报错。我见过一个项目,就因为没注意这个,导致经典CAN节点疯狂报错,总线利用率飙到90%。
另外,收发延迟补偿(TDC)在高速CANFD中特别重要。当数据段波特率超过2Mbps时,信号在总线上的传输延迟会明显影响采样。FlexCAN的TDC功能可以自动补偿这个延迟。我建议在初始化时就使能它。
// 使能收发延迟补偿
CAN0->FDCTRL |= CAN_FDCTRL_TDCEN_MASK;
// 配置补偿偏移量(根据实际布线长度调整)
CAN0->FDCTRL |= CAN_FDCTRL_TDCOFF(5); // 5个时间量子
好了,这一章的内容就这些。FlexCAN的驱动开发,说白了就是初始化配好、发送别堵、接收别丢、中断别长、兼容别乱。下一章咱们会讲UDS诊断栈的移植,到时候这些底层驱动就是基础了。
本章要点回顾:
- 初始化一定要先进入冻结模式再配置
- 发送用中断方式,接收用FIFO+中断
- 中断服务函数要短,数据处理放外面
- CANFD和经典CAN兼容靠EDL位识别
- 高速CANFD必须使能TDC