4. 深度配置、触发阈值、溢出标志寄存器
FIFO 这东西,说白了就是一个环形缓冲。你往里写,我往外读,谁快谁慢全靠现场。但 CAN FD 控制器里的 FIFO 没那么简单——你得告诉它:“这个 FIFO 多大?”、“写到多深该告诉我一声?”、“溢出了我怎么知道?”。
我个人习惯在设计之初就把这三件事想清楚,否则板子调起来就是一团乱麻。先放一张整体关系图,方便你理解这几个参数怎么配合工作。
4.1 FIFO 深度配置
深度指的是这个 FIFO 可以同时存放多少个 CAN FD 报文。硬件上通常是一个 可配置的 2 的幂次:比如 4、8、16、32、64。为什么要 2 次幂?因为地址指针 wrap 的逻辑可以用简单的与门实现,省门电路。
我记得有一个项目,同事为了省 RAM,把深度设成了 8。结果主频降了一点后,中断响应变得不稳定——每次处理完 FIFO,读入读出一段操作,中间新帧又填进来了,根本来不及。
深度配置的具体操作一般在 CAN_FIFOx_CTRL 这类寄存器里,举例:
// 配置 FIFO0 深度为 16 帧
CAN_FIFO0_CTRL |= (0x02 << FIFO_DEPTH_POS); // 假设 00=4,01=8,10=16,11=32
配置后,硬件会自动管理写指针和读指针。你只需要保证读的速度 > 写的平均速度——峰值另算。
4.2 触发阈值(Watermark / Trigger Level)
阈值解决的是“何时去读”的问题。没有阈值,你得靠中断每次来一帧就去读一次。那样 CPU 负担太重。
阈值是什么呢?FIFO 里已经填了多少个报文。一旦达到你设定的数字,硬件就触发一个中断或 DMA 请求。
举个栗子:深度 16,阈值设 4。意味着每一批至少有 4 帧我才叫你。你一次性读走,效率很高。但如果你设成 1,那就相当于每帧都中断——退化了。
这里有个坑。我曾经设置阈值等于深度,结果一直没中断。为什么?因为 FIFO 根本没满到那个值。阈值 必须小于等于深度,少数控制器甚至会限制必须小于深度,需要看手册确认。
阈值寄存器的形式通常如下:
// 设定 FIFO0 触发阈值为 6 帧
CAN_FIFO0_WATERMARK = 6;
如果是 DMA 配合,阈值可以设得更大(比如 10、12),一次性搬运,CPU 几乎不参与。
4.3 溢出标志寄存器
溢出是菜鸟最常见的错误——总线来了一个报文,FIFO 却告诉我“满了”,于是报文直接丢掉。
这通常是因为软件读得太慢。但有些场景下,比如诊断服务疯狂发流控帧,瞬间就能把 FIFO 塞爆。硬件为了不让你蒙在鼓里,会设置一个 溢出标志。你要是定期去看那一位,马上就知道出事了。
典型的寄存器叫 CAN_STATUS 或 CAN_ERR,里面会有类似 FIFO0_OVF 的位:
// 检查 FIFO0 是否溢出
if (CAN_STATUS & FIFO0_OVF_MASK) {
// 有溢出发生,手动清标志
CAN_STATUS = FIFO0_OVF_MASK; // 写 1 清零
}
但注意:溢出发生时,最老的数据还在,新来的数据直接丢弃。如果你希望记录丢失了多少帧,那需要配合一个计数器来累加。我习惯做一个溢出计数器变量,每检测到一次标志就加 1,方便定位问题。
4.4 三者之间的关系
来一个表格,总结一下这三个东西怎么配合:
| 参数 | 作用 | 如何配置 |
| 深度 | 一次最多存多少帧 | 越大抗突发能力越强,但 RAM 开销也大 |
| 触发阈值 | 何时通知软件取数 | 低于深度,否则可能无法触发 |
| 溢出标志 | 告知软件有帧被丢弃 | 定期检查或中断处理 |
你想想:深度是缓冲容量,阈值是报警点,溢出是最后一根稻草。三者都是 FIFO 控制器的核心,但分工明确。我见过一些初级工程师只配深度,阈值不用,用轮询一帧帧读,结果 CPU 负载飙升。浪费啊。
4.5 配置流程建议
我个人项目里习惯按以下顺序做:
- 评估总线负载和最大突发长度,确定深度。
- 根据 CPU 任务调度粒度,确定阈值(通常 = 深度 / 2)。
- 使能溢出中断或开启 DMA 传输。
- 在代码里加上溢出计数器,方便排查。
对了,有些控制器支持多个 FIFO(比如 FIFO0 和 FIFO1),每个都有独立的深度和阈值。CAN FD 的标准帧和数据帧频率可以不同,你甚至可以给不同优先级配不同 FIFO,但那是后话了。
这一块东西不算多,但细节异常重要。调试时 95% 的通信异常都源自这三者之一的配置失误。记住: