4. 深度配置、触发阈值、溢出标志寄存器

FIFO 这东西,说白了就是一个环形缓冲。你往里写,我往外读,谁快谁慢全靠现场。但 CAN FD 控制器里的 FIFO 没那么简单——你得告诉它:“这个 FIFO 多大?”、“写到多深该告诉我一声?”、“溢出了我怎么知道?”。

我个人习惯在设计之初就把这三件事想清楚,否则板子调起来就是一团乱麻。先放一张整体关系图,方便你理解这几个参数怎么配合工作。

核心思路: FIFO 的深度决定你能攒多少帧,触发阈值决定你何时取走。溢出标志?那是最后一道防线。

4.1 FIFO 深度配置

深度指的是这个 FIFO 可以同时存放多少个 CAN FD 报文。硬件上通常是一个 可配置的 2 的幂次:比如 4、8、16、32、64。为什么要 2 次幂?因为地址指针 wrap 的逻辑可以用简单的与门实现,省门电路。

我记得有一个项目,同事为了省 RAM,把深度设成了 8。结果主频降了一点后,中断响应变得不稳定——每次处理完 FIFO,读入读出一段操作,中间新帧又填进来了,根本来不及。

经验: 我建议深度至少配到 16。如果系统有可能长时间关中断(比如 Flash 擦写),那直接上 64 或更大。

深度配置的具体操作一般在 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 根本没满到那个值。阈值 必须小于等于深度,少数控制器甚至会限制必须小于深度,需要看手册确认。

避坑: 我遇到过阈值设置为深度减 1 的情况,结果某次突发数据刚好停在了深度 - 1,中断没来,数据也一直没读走。多好的排查机会。建议配为深度的一半,或者根据总线负载和 CPU 负载动态调整。

阈值寄存器的形式通常如下:

// 设定 FIFO0 触发阈值为 6 帧
CAN_FIFO0_WATERMARK = 6;

如果是 DMA 配合,阈值可以设得更大(比如 10、12),一次性搬运,CPU 几乎不参与。

4.3 溢出标志寄存器

溢出是菜鸟最常见的错误——总线来了一个报文,FIFO 却告诉我“满了”,于是报文直接丢掉。

这通常是因为软件读得太慢。但有些场景下,比如诊断服务疯狂发流控帧,瞬间就能把 FIFO 塞爆。硬件为了不让你蒙在鼓里,会设置一个 溢出标志。你要是定期去看那一位,马上就知道出事了。

关键: 溢出标志通常是可读可写的。写 1 清零,写 0 无操作。这叫“读保持,写清除”。

典型的寄存器叫 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% 的通信异常都源自这三者之一的配置失误。记住:

三不原则: 深度不要太小,阈值不要等于深度,溢出标志不能只看一次。