简介面向STM32嵌入式开发者与车载、工业通信方向工程师的FDCAN应用笔记聚焦STM32G4等系列如何落地CAN-FD协议。内容从CAN-FD相对传统CAN 2.0的带宽与帧结构改进讲起梳理EDL、BRS、ESI等新增控制位、最长64字节有效负载与17/21位CRC校验机制并延伸到STM32G0、G4、H7、L5及MP1器件中FDCAN外设的模式与特性说明。资源为1个PDF文件压缩包约554KB篇幅精炼、便于随查随用适合作为数据手册之外的协议入门与配置参考。已有698人学习下载。读者可据此建立从协议原理到外设实现的完整认知理解仲裁阶段与数据阶段的双比特率切换、帧格式差异及错误处理方式为后续CAN-FD收发调试、波特率配置与网络升级提供依据也可作为团队内部培训的轻量资料。1. 10 KB 消息 RAMstm32 CANFD 真正的天花板不在波特率调 stm32 CANFD 的人第一反应几乎都是去调位时序把数据段从 2 Mbit/s 拉到 5 Mbit/s示波器上看波形漂亮以为成功了。真正翻车的地方往往不在这里——而是那 10 KB、共 2560 个 32 位字的消息 RAM滤波器区、Rx FIFO、专用 Rx 缓冲区、Tx 缓冲区全部挤在同一块内存里起始偏移由你手填硬件不做重叠检查元素尺寸配小了一档64 字节的报文会被悄悄截断一个错误都不报。FDCAN 把“配置”做成了“内存布局”理解不了这套布局波特率调得再准也只是表面功夫。这一篇就把帧结构、RAM 分区、滤波器和收发索引这四层拆开看清 FDCAN 到底怎么把总线上的电平变成内存里的一段字。2. CAN-FD 帧结构BRS 位凭什么让同一帧跑出两种速率传统 CAN 一帧里所有位都跑在同一个速率上帧内没有快慢之分。CAN-FD 把这个前提打破了同一帧里前段慢、中段快、后段又慢回来。这不是为了炫技而是因为仲裁必须慢——仲裁靠的是多个节点同时驱动总线、逐位比较 ID速率越高线缆延迟和收发器延迟占位时间的比例越大仲裁就越容易失效。CAN-FD 的做法是把“必须慢”的部分和“可以快”的部分切开只在纯数据传输的那一段提速最多可以提到仲裁速率的 8 倍。2.1 一帧被切成三段采样点也跟着换两次按 ISO 11898-1:2015 的定义一条 CAN-FD 帧被划分为三个阶段第一仲裁阶段、数据阶段、第二仲裁阶段。第一仲裁阶段从 SOF 开始包含 11 位标识符、RRS、IDE、EDL、r0、BRS、ESI 和 4 位 DLC数据阶段是真正的载荷加 CRC 序列第二仲裁阶段从 CRC 界定符之后开始包含 ACK、7 位 EOF 和 3 位 IFS。分界点很关键速率切换发生在 BRS 位采样点之后切回标称速率发生在 CRC 界定符处。也就是说CRC 序列本身是在高速下发送的而 ACK 必须回到低速——因为 ACK 是总线上所有接收节点共同驱动的任何一个节点都必须能在同一个位时间里采样到它。把 ACK 留在高速段等于要求全网每个节点的收发器延迟都精确匹配这在多节点拓扑下不现实。实现上FDCAN 内核里有两套完全独立的位时序参数仲裁段一套数据段一套共用同一个内核时钟但分频和段长度互不相干。常见的实现思路是这样组织/* 同一帧里两套位时序仲裁段用 NBTP数据段用 DBTP */ typedef struct { uint16_t prescaler; /* 分频系数 */ uint8_t sync_seg; /* 同步段固定 1 个时间份额 */ uint8_t prop_seg; /* 传播段补偿收发器与线缆往返延迟 */ uint8_t phase_seg1; /* 相位缓冲段 1位于采样点之前 */ uint8_t phase_seg2; /* 相位缓冲段 2位于采样点之后 */ uint8_t sjw; /* 同步跳转宽度用于重同步 */ } fdcan_timing_t; /* 内核时钟 80 MHz 下的一组示例值实际必须按板级时钟重算 */ static const fdcan_timing_t nom_500k { 10, 1, 6, 3, 6, 4 }; /* 16 tq采样点 62.5% */ static const fdcan_timing_t dat_2m { 4, 1, 4, 3, 2, 2 }; /* 10 tq采样点 80% */这段结构的含义是位时间被拆成 sync_seg prop_seg phase_seg1 phase_seg2 个时间份额tq采样点落在 phase_seg1 结束处比例等于 (1 prop_seg phase_seg1) / 总 tq 数。仲裁段 80 MHz ÷ 10 ÷ 16 500 kbit/s采样点 62.5%数据段 80 MHz ÷ 4 ÷ 10 2 Mbit/s采样点 80%。数据段采样点普遍要比仲裁段更靠后因为高速下传播延迟占比更大采样点太靠前会把延迟当成位错误。提示prop_seg 是唯一用来吸收物理延迟的段收发器环路延迟大的场合必须把它留够光靠调相位缓冲段是补不回来的。2.2 EDL、BRS、ESI三个新增控制位的分工CAN 2.0 帧在标识符之后紧跟的是 RTR 位用来区分数据帧和远程帧。CAN-FD 取消了远程帧这个位置改叫 RRS永远发显性。真正的扩展信息塞进了控制场新增的三个位EDL扩展数据长度位是整帧的“身份开关”。它发隐性时这一帧被解释为 CAN-FD 帧发显性时这个位置在 CAN 2.0 帧里叫 R0整帧按传统格式解析。这就是 FD 节点能兼容 CAN 2.0 报文的硬件基础——不需要软件预先约定靠一位就能区分。BRS比特率切换位决定数据段是否提速。发隐性表示数据段使用第二套位时序发显性则全程走仲裁速率。这一位让同一套硬件可以按报文重要性分级实时性要求高的报文开 BRS调试日志类的报文关掉 BRS避免长时间占用高速段。ESI错误状态指示位把发送节点自身的错误状态广播出去。发隐性说明该节点处于错误被动状态发显性说明是错误主动。接收方据此可以判断“这条报文的来源是否可靠”这是 CAN 2.0 做不到的——传统 CAN 只有全局的错误计数器没有帧级的健康标记。三个位合在一起把“帧格式”“速率”“发送方健康度”这三件事压缩进了控制场的前四位代价是控制场变长好处是接收方在收到数据之前就拿到了全部决策信息。2.3 DLC 的跳跃编码与 CRC 从 15 位变 21 位DLC 依然是 4 位位置和长度跟 CAN 2.0 一模一样但语义被扩展了。CAN 2.0 里 DLC 大于 8 时统一按 8 处理等于浪费了 7 个码点CAN-FD 把这 7 个码点利用起来编码成 12、16、20、24、32、48、64DLC十进制0123456789101112131415CAN 2.0 字节数0123456788888888CAN-FD 字节数01234567812162024324864这种跳跃式编码不是随意选的12/16/20/24/32/48/64 都是 4 的倍数正好对应整数个 32 位字内存里不需要做字节对齐填充。写代码时如果按线性关系去算长度一定会在 DLC9 的地方错位。载荷变长带来的直接问题是校验强度。CAN 2.0 用 15 位 CRC在 8 字节载荷下已经够用64 字节载荷下同样的 15 位 CRC 漏检概率会上升一个数量级。CAN-FD 的做法是分段载荷不超过 16 字节用 17 阶多项式超过 16 字节用 21 阶多项式。另一个更隐蔽的改动是 CRC 计算范围包含了位填充位——CAN 2.0 的 CRC 不算填充位CAN-FD 算这样填充位被干扰也能被检出。这些结构差异折算到时间上有多明显用一段估算代码说明比堆文字更清楚def frame_time_us(n_bytes, nom500_000, dat2_000_000, fdTrue): 粗略估算一帧占用总线的时间位填充按经验比例放大不追求精确位置 if not fd: # 标准帧SOFIDRTRIDEr0DLC数据CRC15DELACKEOFIFS bits 1 11 1 1 1 4 n_bytes * 8 15 1 2 7 3 return bits * 1.25 / nom * 1e6 crc 17 if n_bytes 16 else 21 arb (1 11 1 1 1 1 1 1 4) * 1.25 # 仲裁段填充位比例高 data (n_bytes * 8 crc) * 1.10 # 数据段填充位比例低 trailer 1 2 7 3 # CRC 界定符 ACK EOF IFS return arb / nom * 1e6 data / dat * 1e6 trailer / nom * 1e6 print(frame_time_us(8, fdFalse)) # 传统 CAN 8 字节 print(frame_time_us(64)) # CAN-FD 64 字节按这组参数算下来传统 CAN 8 字节帧在总线上约占 280 µsCAN-FD 64 字节帧约占 370 µs。时间只多花了三成载荷却翻了 8 倍有效吞吐提升接近 6 倍。这就是为什么 CAN-FD 的价值不在“更快”而在“同样多的时间里塞进更多报文于是总线上报文数量下降多包拆分的软件逻辑跟着消失”。3. 消息 RAM 布局2560 个 32 位字怎么切才不打架FDCAN 把收发报文全部放在一块独立的 10 KB RAM 里CPU 通过配置寄存器告诉外设“每一类东西放在哪个偏移、放多少个”。这块 RAM 不占 CPU 的地址空间带宽也不参与 Cache代价是用户必须自己管账起始偏移是 0 到 2560 之间的任意数硬件不会阻止你把 Rx FIFO 写在 Tx 缓冲区的范围里。绝大多数“发着发着就收不到了”的玄学源头都在这里。3.1 一个元素 两个字头 数据公式决定全部CAN 消息 RAM 里存的东西叫“元素”。一个元素不存整帧只存软件真正需要的那部分DLC、控制位ESI、XTD、RTR、BRS、FDF、数据场以及用于控制的位字段——接收侧是滤波器索引、接受的不匹配帧和 Rx 时间戳发送侧是消息标志和事件 FIFO 控制位。帧起始、CRC、ACK 这些由硬件自动处理不落内存。元素大小的计算公式只有一行元素大小32 位字数 2头 数据场字节数 / 4分子里那个常数 2 是固定的两个字头装标识符、DLC 和控制位。数据部分按 4 字节一个字向上取整。注意一个特例数据场在 0 到 8 字节之间时数据部分一律按 2 个字算而不是 1 个或 2 个。这个规则让 8 字节的传统 CAN 报文和空载荷报文占用同样的空间数据场字节0~812162024324864元素大小字45678101418这张表是全部 RAM 预算的基础。64 字节元素占 18 个字8 字节元素占 4 个字差 4.5 倍。同一个应用里如果混着两种尺寸的报文把它们放进同一个 FIFO 就意味着按最大值统一尺寸浪费相当可观——这也是为什么 FDCAN 给 Rx FIFO 0、Rx FIFO 1 和专用 Rx 缓冲区各自配了独立尺寸寄存器。3.2 四个区按固定顺序连续分配但边界要你自己守CAN 消息 RAM 被划分为四类区域滤波区11 位滤波器、29 位滤波器、接收区Rx FIFO 0、Rx FIFO 1、专用 Rx 缓冲区、发送区Tx 事件 FIFO、Tx 缓冲区、触发存储器区。文档给出的各分区容量上限是区域元素数上限占用字数上限11 位滤波器12812829 位滤波器64128Rx FIFO 0 / FIFO 1各 64各 1152专用 Rx 缓冲区641152Tx 事件 FIFO3264Tx 缓冲区32576触发存储器64128这些上限加起来远超 2560 字所以必须做取舍。分配方式是“动态且连续”每个区的元素在 RAM 中连续排布区与区之间按配置的顺序衔接但硬件不给每个区分配固定的起止地址——起始偏移由对应寄存器的 SA 字段给出比如 11 位滤波器用 FDCAN_SIDFC 的 FLSSA[13:0]29 位滤波器用 FDCAN_XIDFC 的 FLESA[13:0]元素个数由另一个字段给出。用户的责任是让所有区的总和不超过 2560 字且互不重叠。文档里那个把 2560 字用满的例子很典型128 个 11 位滤波器占 128 字Rx FIFO 0 收 64 条 64 字节报文占 1152 字专用 Rx 缓冲区 64 条 64 字节报文占 1152 字Tx 缓冲区 32 条 8 字节报文占 128 字加起来正好 2560。注意这里 Tx 缓冲区只用了 8 字节元素——如果发送侧也要 64 字节128 字只够装 7 条。靠人脑记这些偏移迟早出事我一般会在初始化前先跑一遍静态检查#define FDCAN_RAM_WORDS 2560u typedef struct { const char *name; /* 区名出错时打印用 */ uint16_t offset; /* 起始偏移单位32 位字 */ uint16_t elems; /* 元素个数0 表示该区不使用 */ uint8_t words; /* 单个元素占用字数 2 数据字节/4 */ } ram_region_t; /* 返回 0 合法-1 越界-2 与前一个区交叠 */ int ram_layout_check(const ram_region_t *r, int n) { uint32_t cursor 0; for (int i 0; i n; i) { if (r[i].elems 0) continue; /* 未使用的区不占空间 */ if (r[i].offset cursor) return -2; /* 起始偏移落在上一区内部 */ cursor (uint32_t)r[i].offset (uint32_t)r[i].elems * r[i].words; if (cursor FDCAN_RAM_WORDS) return -1; /* 超出 10 KB */ } return 0; }这个函数只做两件事算出每个区的结束位置检查下一个区的起点是否在它之后。看起来简陋但它能挡住九成的布局错误。参数 words 必须按上一节的表填不能用2 bytes/4直接算 0~8 字节的情况——那样会得到 3 和 4 两个不同的值而硬件只认 4。还有一点容易忽略配置完成后分配的地址范围会被硬件初始化为零。这意味着如果两个区重叠重叠部分的内容会被后配置的那个区覆盖现象是“先配的区数据莫名其妙变了”而不是报错。3.3 多实例共享同一块 RAM偏移是唯一的分界多数 STM32 器件带不止一个 FDCAN 实例这些实例共享同一块消息 RAM。划分方式很直接第一个实例从某个偏移开始、占用若干字第二个实例从“第一个实例的起始偏移 第一个实例的大小”开始。文档里的例子是按 2:1 切分第一个实例占三分之二第二个占三分之一。这里有个隐含约束每个实例内部的分区顺序不变仍然是滤波器、接收区、发送区、触发存储器。也就是说第二个实例的滤波器区紧跟在第一个实例的触发存储器区之后两者之间没有任何保护带。跨实例踩内存的现象比单实例更隐蔽——你在实例 0 上加大 Rx FIFO实例 1 的滤波器表就悄悄被覆盖了表现是实例 1 突然开始接收本该被拒绝的报文。注意给每个实例预留一点余量别把 2560 字卡到整数边界后续加两条滤波器就会连锁改动全部偏移。4. 验收滤波与收发路径一条报文从总线到内存要过几道门报文进了内核之后要经过三道决策才能被 CPU 读到过不过滤、放到哪个区、什么时候能读。这三步全部由硬件完成软件只在最后一步挪数据。理解这三步的先后关系才能解释为什么“滤波器顺序”这种看似无关紧要的细节会直接决定报文去向。4.1 三种滤波器类型范围、双 ID、位屏蔽FDCAN 给标准 ID 配了最多 128 个滤波器元素给扩展 ID 配了最多 64 个。每个标准滤波器元素由四个字段拼成SFT[31:30]标准滤波器类型、SFEC[29:27]标准滤波器元素配置、SFID1[26:16]ID1、SFID2[15:0]ID2。SFT 决定匹配规则SFEC 决定匹配之后干什么。三种类型各有明确用途。范围滤波器SFT00把 SFID1 和 SFID2 当作 ID 区间的上下界落在区间内的全部命中适合“某一段 ID 一律拒绝”这种粗粒度策略。双 ID 滤波器SFT01让 SFID1 和 SFID2 各表示一个具体 ID一条规则同时匹配两个不相邻的 ID省元素。传统位屏蔽滤波器SFT10把 SFID1 当基准 ID、SFID2 当掩码掩码某位为 0 表示该位不关心。位屏蔽滤波器的用法值得展开。文档里的例子要求匹配形如0b11100XX0000的 ID其中 XX 任意。基准 ID 取 0x7000b111 0000 0000掩码取 0x7CF0b111 1100 1111——掩码里为 1 的位表示“这一位必须和基准 ID 一致”为 0 的位表示“不关心”。所以基准 ID 也可以取 0x710、0x720、0x730 中的任意一个结果完全一样因为它们在被关心的那些位上取值相同。这个细节经常被误解成“基准 ID 必须取最小那个”。滤波器SFTSFECSFID1SFID2效果第一个00 范围011 拒绝0x160x20拒绝 ID 在 [0x16, 0x20] 的报文第二个01 双 ID010 存 FIFO10x150x120命中 0x15 或 0x120 存入 FIFO1第三个xx 无关111 存 Rx 缓冲区0x1300x04缓冲区索引命中 0x130 存入缓冲区 4第四个10 位屏蔽001 存 FIFO00x7000x7CF命中 0x700/0x710/0x720/0x730 存入 FIFO0第三个元素那行藏着一个坑SFEC 配成“存到 Rx 缓冲区”时SFT 的配置会被硬件忽略SFID2 不再表示 ID 而是表示缓冲区索引。同一组字段在不同 SFEC 下语义完全不同读代码时看到 SFID2 填了个 4别以为是滤波器 ID。4.2 匹配即停滤波器顺序本身就是优先级硬件执行验收滤波时永远从元素 #0 开始顺序遍历在第一个匹配的元素处停止不再评估后面的元素。这条规则决定了滤波器表的顺序具有语义——它不是一个集合是一个有序规则链。由此推出的实践原则很直接拒绝规则必须放在接受规则前面。如果你想“除了 0x16~0x20 之外全部接受”就得先放一条范围拒绝再放一条全匹配接受顺序反过来全匹配先命中拒绝规则永远没有机会执行。另一个隐蔽点在高优先级消息上。滤波器元素除了决定“存哪儿”还能决定“要不要单独通知”。配置成“匹配则设优先级并存进 FIFO 0/1”时硬件会额外置位中断标志配置成“匹配则设优先级但不存储”时只通知不落数据。后一种配置在调试阶段特别容易误用——中断进来了去 FIFO 里翻什么也没有因为报文压根没存。提示改滤波器表时按“先拒绝、后接受、最后兜底”排比按 ID 大小排更不容易出错。硬件过滤的价值在于把 CPU 从软件筛选里解放出来。代价是规则一旦配错报文就是彻底收不到没有任何日志——软件过滤至少还能打印一行“丢弃了 ID 0xXXX”。所以滤波器表的每一项都值得在初始化时用静态断言或单元测试校验一遍。4.3 Rx FIFO 的 get/put 索引与应答机制两个 Rx FIFO 各自最多 64 个元素元素大小由 FDCAN_RXESC 寄存器按 FIFO 单独配置F0DS、F1DS、RBDS 三个字段分别对应 FIFO0、FIFO1 和专用缓冲区。元素个数和起始地址由 FDCAN_RXF1C 的 F1S[6:0] 和 F1SA[13:0] 给出。硬件用一对循环递增的索引管理 FIFO放入索引由硬件维护获取索引由软件读取。软件要取最早的元素得自己算地址元素地址 RAM 基地址 F1SA F1GI × 元素大小F1GI 就是 FDCAN_RXF1S 里的获取索引。读完数据之后还有一步绝对不能漏把刚读的最后一个元素的缓冲区索引写回 FDCAN_RXF1A。硬件看到应答才会推进获取索引把这个缓冲区腾出来复用。不写应答的后果不是报错而是 FIFO 看起来永远是满的新报文全被丢弃。/* 读一个 Rx FIFO1 元素算地址、读数据、必须应答 */ uint32_t f1sa FLD_GET(FDCAN-RXF1C, F1SA_MASK, F1SA_POS); /* 起始偏移单位字 */ uint32_t f1gi FLD_GET(FDCAN-RXF1S, F1GI_MASK, F1GI_POS); /* 获取索引 */ /* 元素大小按配置换算成字2 字头 数据字节/4 */ uint32_t words 2u (elem_bytes / 4u); volatile uint32_t *p (volatile uint32_t *)(FDCAN_RAM_BASE 4u * (f1sa f1gi * words)); uint32_t h0 p[0]; /* 头字 0标识符与部分控制位 */ uint32_t h1 p[1]; /* 头字 1DLC、BRS、ESI、滤波器索引等 */ for (uint32_t i 0; i elem_bytes / 4u; i) { rx_data[i] p[2 i]; /* 数据场按 32 位字读出 */ } /* 应答写回刚读的元素索引硬件才推进 F1GI 并释放缓冲区 */ FLD_WRITE(FDCAN-RXF1A, F1AI_MASK, F1AI_POS, f1gi);代码里的 FLD_GET/FLD_WRITE 是两个读写位字段的宏字段位置要按具体型号的寄存器手册填这里只表达结构。真正要盯住的是最后一行应答写的是“刚读的最后一个元素的索引”一次读多个元素时写最后一个不要每读一个写一次否则索引会推进过头。FIFO 满了之后的处理方式有两种。默认是拦截模式FIFO 满时新到达的元素直接丢弃直到有元素被读出。另一种是覆盖模式新元素覆盖最早的元素放入索引和获取索引同时加一等于软件永远读到最新的那批。覆盖模式适合“只关心当前状态”的场景拦截模式适合“一条都不能丢”的场景选错方向会丢数据且不报警。4.4 专用 Rx 缓冲区与 NDAT 锁除了 FIFOFDCAN 还支持最多 64 个专用 Rx 缓冲区每个缓冲区固定存一个元素起始地址由 FDCAN_RXBC 的 RBSA[13:0] 给出。和 FIFO 的动态分配不同专用缓冲区的索引是写死的滤波器元素里直接把 SFID2 填成缓冲区索引命中就往那个位置写没被引用的位置永远不会被写。这条机制带来一个必须注意的行为缓冲区里的数据被读走之前是锁定的。硬件在 FDCAN_NDAT1/NDAT2 里把对应位置 1 表示“有新数据且已锁”此时新到达的匹配报文会被丢弃不会覆盖旧数据。软件读完之后必须主动把 NDAT1/NDAT2 的对应位清零缓冲区才解锁。这个设计和 FIFO 的拦截模式目的相同——防止读到一半被覆盖——但责任方不同。FIFO 靠应答寄存器缓冲区靠清标志位。两种都忘现象一样数据一直不变。区别是 FIFO 那边表现成“满”缓冲区这边表现成“旧值”后者更难察觉因为读出来的是合法数据只是它不新。特性专用 Rx 缓冲区Rx FIFO数量最多 64 个索引由滤波器指定2 个每个最多 64 元素每区元素数每个缓冲区恒为 1 个元素一个 FIFO 内可放多个元素内存位置由滤波器索引决定静态由获取/放入索引动态推进满时行为缓冲区被锁时丢弃新元素需清 NDAT拦截模式丢弃覆盖模式覆盖最早元素选缓冲区还是选 FIFO本质是“要不要给某条报文一个固定槽位”。周期性的控制报文用专用缓冲区最好软件直接按索引取跳过地址计算突发的、数量不定的报文用 FIFO空间利用率高。4.5 Tx 三套机制与 Tx 事件 FIFO发送侧比接收侧多一个维度顺序。FDCAN 支持三种发送机制——专用 Tx 缓冲区、Tx FIFO、Tx 队列但它们不是自由组合的。专用 Tx 缓冲区可以和另外两者之一共存Tx FIFO 和 Tx 队列则互斥由 FDCAN_TXBC 里的 TFQM 位选择写 0 是 FIFO写 1 是队列。三种机制的行为差异体现在出队顺序上。专用 Tx 缓冲区在请求发送后按报文 ID 参与总线仲裁ID 小的先发如果多个专用缓冲区配了相同 ID先请求的那个先发。Tx FIFO 完全按写入顺序发与 ID 优先级无关。Tx 队列则按 ID 优先级出队相当于在发送前先排一次序。RAM 上的排布顺序也是固定的如果用了专用 Tx 缓冲区它排在 Tx FIFO/Tx 队列之前起始地址由 TBSA[13:0] 给出。写 Tx FIFO 的流程是读 FDCAN_TXFQS 拿到放入索引和空闲水平TFFL[5:0]按基地址 TBSA TFQPI × 元素大小算出下一个空槽写入元素然后置位 TXBAR 请求发送。空闲水平是获取索引与放入索引的差值等于 0 时写入会覆盖还没发出去的数据——这是发送路径上最常见的静默故障。Tx 事件 FIFO 是另一条独立的路用来记录“哪条报文什么时候真正发出去了”。它最多 32 个元素起始地址由 FDCAN_TXEFC 的 EFSA[13:0] 给出元素大小固定为 2 个字。触发条件是 Tx 缓冲区元素里的 EFC 位为 1——不是所有发出的报文都会被记录只有显式标记的才进事件 FIFO。事件里存的是该报文的消息标志和时间戳软件靠消息标志把事件和原始发送请求对应起来。事件 FIFO 满了之后新事件直接丢弃不会阻塞发送。这个行为合理但危险追踪日志缺了几条很容易被误判成“报文没发出去”。文档给的对策是用事件 FIFO 的水位线做预警CPU 在读到一定数量时批量清空。和 Rx FIFO 一样读完要应答写入 FDCAN_TXEFA 的 EFAI[4:0] 字段。5. 排查清单FDCAN 收不到、发不出、长度不对的五种典型成因FDCAN 的故障大多不报错。外设不会告诉你“你把 FIFO 写满了”它只是安静地不再接收。下面五条是实际调试中最常撞上的每条按现象、原因、解决三段拆开。5.1 发送路径上的两个静默失败第一条报文发不出去TXBAR 置位了总线上什么都没有。原因通常是 Tx 缓冲区的起始偏移和 Tx FIFO 的起始偏移算重了或者 Tx FIFO 的空闲水平已经是 0软件还在往里写。判断方法很直接——读 FDCAN_TXFQS 的 TFFL 字段如果空闲水平长期为 0说明元素发不出去而不是写不进去。解决是把 TFQM、TBSA、元素大小三个配置对着 RAM 布局表重新核一遍特别是用了专用 Tx 缓冲区之后FIFO 的起始偏移必须加上专用缓冲区的占用空间。第二条部分报文丢失且丢的都是 ID 较小的。原因是把 Tx FIFO 当成优先级队列用了。FIFO 严格按写入顺序发送早写入的低优先级报文会先占住总线如果应用依赖 ID 仲裁决定先后就必须切到 Tx 队列TFQM1让硬件按 ID 重排。这个切换会改变出队行为切换后要重新确认发送超时逻辑。5.2 接收路径上的三种错位第三条滤波器配了报文也确认在总线上就是收不到。最常见的原因是滤波器顺序错了——某条更靠前的接受规则先命中报文被送去了别的 FIFO 或者别的缓冲区索引而软件在错误的区里找。排查方式是临时把表压到只剩一条规则逐个加回去看在哪一条加上之后行为变了。另一个可能是滤波器元素个数配得比实际使用少后面的规则压根没被加载。第四条FIFO 收到几十条之后彻底停止。这是没写应答的典型表现。硬件把元素放进 FIFO 后不会自动释放必须由软件写入 FDCAN_RXF1A不写获取索引就不动FIFO 一直显示本文还有配套的精品资源点击获取