ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TC387 MCMCAN Message RAM配置全解析:从RAM结构到Tx/Rx FIFO实战

TC387 MCMCAN Message RAM配置全解析:从RAM结构到Tx/Rx FIFO实战 做 TC387 嵌入式开发的朋友第一次碰 MCMCAN 大概率会卡在同一个地方Message RAM。波特率配了、滤波器配了、中断也开了但发出去的报文要么一直挂在发送队列里要么接收中断来得莫名其妙。问题往往不在 CAN 协议本身而是那 32KB 的 Message RAM 没配明白。MCMCAN 的 Message RAM 和普通单片机的 SRAM 不一样它不是给 CPU 随便堆变量的而是 M_CAN 内核专门用来存放滤波器、Rx FIFO、Tx Buffer、Tx Event FIFO 的一块“私有地盘”。硬件不会帮你规划地址每个区域从哪个偏移开始、放多少个元素、总共占多大全部要软件在初始化时写进对应寄存器。这块 RAM 用得好CAN 收发就是顺手的事用不好轻则丢帧重则整节点静默。这篇文章就围绕这个核心问题展开先从 MCMCAN 的 RAM 结构讲清楚再手把手演示 Tx Buffer、Tx FIFO、Rx FIFO 的配置流程最后给一套完整的 RAM 预算模板和排障方法。适合正在用 TC387以及 TC3xx 系列做整车控制器、BMS、网关的朋友参考也适合从 TC2xx 的 MultiCAN 切换到 MCMCAN 的老工程师快速上手。1. Message RAM 的本质与布局规则1.1 从 MultiCAN 到 MCMCAN变化的不仅是名字TC2xx 时代用的是 MultiCAN里面的报文对象是“Message Object”那一套256 个 MO 由软件分配每个 MO 能同时管接收和发送用起来有点像内存池。到了 TC3xxCAN 模块换成了基于 Bosch M_CAN 内核的 MCMCAN整体模型彻底变了报文的存放不再是“对象池”而是按照“区域”来划分每个区域有固定格式的元素比如标准 ID 滤波器、扩展 ID 滤波器、Rx FIFO 0/1、独占式 Rx Buffer、Tx Buffer、Tx FIFO/Queue、Tx Event FIFO。TC387 上有多个 MCMCAN 模块每个模块里的 CAN 节点都有自己独立的 Message RAM。规划 Message RAM 时先打开芯片用户手册UM的 Memory Map 和 MCMCAN 章节确认你手上这颗芯片每个节点分到的是 32KB 还是其他容量。后面我统一按单节点 32KB、也就是 8192 个 32 位字来举例。千万不要把这里说的 Message RAM 和 TC387 的 LMU、PSPR、DSPR 搞混。CPU 上电启动、跑 RTOS、堆栈、全局变量都用不到这块 RAM它只属于 CAN 模块CPU 访问它也要通过 MCMCAN 的映射窗口。调试时可以打开内存视图直接看但业务代码里不要把它当普通数组乱写。1.2 一个元素多大决定了你的 RAM 预算M_CAN 的各个区域都有固定的元素大小这是整个 RAM 规划的基础先记死这几个数区域单元素大小32 位字字节数起始地址/数量配置寄存器典型上限标准 ID 滤波器列表14 字节SIDFC.FLSSA / LSS128 个扩展 ID 滤波器列表28 字节XIDFC.FLESA / LSE64 个Rx FIFO 01872 字节RXF0C.F0SA / F0S64 个Rx FIFO 11872 字节RXF1C.F1SA / F1S64 个独占式 Rx Buffer1872 字节RXBC.RBSA64 个专用 Tx Buffer1872 字节TXBC.TBSA / NDTB与 Tx FIFO 合计 ≤32 个Tx FIFO / Tx Queue1872 字节TXBC.TBSA / TFQS与专用 Buffer 合计 ≤32 个Tx Event FIFO416 字节TXEFC.EFSA / EFS32 个为什么接收和发送元素都是 18 个字因为 M_CAN 要为 CAN FD 做准备2 个字是报文头ID、标志位、DLC、时间戳等16 个字是 64 字节数据场。注意这里的关键点不管你这帧只发 8 字节还是 0 字节只要占了一个 Tx Buffer 或 Rx FIFO 元素硬件就固定给你预留 64 字节的数据空间。所以“我报文短应该很省 RAM”这个想法在 M_CAN 里不成立元素大小是刚性的。1.3 地址是 word 偏移不是字节偏移M_CAN 手册里所有起始地址字段比如 SIDFC.FLSSA、RXF0C.F0SA、TXBC.TBSA默认都是以 32 位字为单位的偏移量从 Message RAM 的起始地址算起。这是新手最容易踩的坑从网上复制一段 STM32 或者其他芯片的 M_CAN 代码里面地址写的是字节偏移直接搬到 TC387 上所有区域全错位。轻则滤波器把数据过滤没了重则 Tx Buffer 和 Rx FIFO 区域重叠报文内容互相覆盖。举个例子如果 Rx FIFO 0 从 offset 0x100 开始那么实际内存地址是实际地址 Message RAM 基地址 0x100 * 4在 TC3xx 的寄存器操作里你写的是 0x100 这个 word 偏移而不是 0x400。如果要给两个区域做内存检查务必先把 word 偏移换算成字节再去看调试器里的内存视图否则会以为数据放错地方。2. Tx 方向Tx Buffer / Tx FIFO / Tx Queue 三兄弟怎么配2.1 三种发送通道的选型逻辑M_CAN 的发送侧不是只有一个“发送缓冲区”而是可以同时存在三种通道专用 Tx Buffer、Tx FIFO、Tx Queue。它们共用同一块 Message RAM 区域通过 TXBC 寄存器里的配置来切分。专用 Tx Buffer 的特点是“一个萝卜一个坑”每个消息有自己固定的槽位软件可以精确控制哪个槽发送哪条消息也能单独取消某一条还不舍得发的消息。适合放周期性的关键帧、故障帧这类需要“确定能被发出去”的报文。Tx FIFO 是严格的先入先出软件把报文按顺序塞进去硬件按顺序发。适合突发性的大批量数据比如升级时的刷写流。缺点是不好单独撤回某条消息一旦进了 FIFO就只能等它自然发出或被冲掉。Tx Queue 和前两个都不同它虽然也占用 FIFO 的存储空间但是发送顺序不按入队先后而是按报文的仲裁优先级CAN ID 越小优先级越高来排。适合同时存在多种优先级报文的节点保证高优先级报文不用排队等低优先级报文。TXBC 里的 TFQM 位就是用来切换后两种模式的写 0 是 FIFO 模式写 1 是 Queue 模式。类型发送顺序单条取消适用场景专用 Tx Buffer软件指定槽位发送可以周期帧、关键事件帧Tx FIFO严格按入队顺序困难批量刷写、大数据流Tx Queue按仲裁优先级困难多优先级报文混合发送实际工程里最常见的组合是留几个专用 Tx Buffer 给最重要的报文再开一条有一定深度的 Tx FIFO 或 Queue 给其余报文。注意总和不能超过 32 个元素这是 M_CAN 硬件对 Tx 缓冲区的硬上限。2.2 TXBC 寄存器怎么填配置 Tx 侧的寄存器主要是 TXBCTransmit Buffer Configuration核心字段有四个TBSA 是 Tx 缓冲区的起始 word 偏移NDTB 是专用 Tx Buffer 的个数TFQS 是 Tx FIFO/Queue 的深度TFQM 决定 FIFO 还是 Queue 模式。举个实际例子。假设 Message RAM 规划里Rx FIFO 等接收区域占到了 0x0378 这个 word 偏移之前那么 Tx 区域从 0x0378 开始我要配 8 个专用 Tx Buffer外加 16 个 Tx FIFO 元素。那这段内存的占用是8 个专用 Tx Buffer 8 * 18 144 words 16 个 Tx FIFO 元素 16 * 18 288 words 合计 144 288 432 words即 1728 字节寄存器写法大致是这样#define TX_MSG_RAM_OFFSET 0x0378u #define NUM_DEDICATED_TX 8u #define NUM_TX_FIFO 16u CAN0_TXBC.U (TX_MSG_RAM_OFFSET IFX_CAN_TXBC_TBSA_OFF) | (NUM_DEDICATED_TX IFX_CAN_TXBC_NDTB_OFF) | (NUM_TX_FIFO IFX_CAN_TXBC_TFQS_OFF) | (0u IFX_CAN_TXBC_TFQM_OFF);这里的宏名以你工程里 iLLD 生成的 IfxCan_reg.h 为准不同版本的 M_CAN 在对 NDTB、TFQS 的编码上可能有细微差别有的地方写的是实际个数有的地方写的是个数减一。配置前翻 UM 的 TXBC 寄存器说明页核对一下这是老手也会反复确认的细节。2.3 发送流程和状态寄存器配置好 TXBC 只是划好了“停车位”真正发送时走的是这么一条链路软件往目标 Tx Buffer 元素里写报文内容ID、DLC、数据。置位 TXBAR 里对应的位触发发送请求。硬件收到请求后把 TXBRP 对应位置 1表示发送挂起。发送完成TXBTO 对应位置 1如果需要取消写 TXBCF 对应位。对于 Tx FIFO/Queue还要理解 TXFQS 寄存器里的几个指针TFPI 是硬件当前写入的 put indexTFGI 是硬件已经取走的 get indexTFFL 是 FIFO 里还等着发送的元素个数。软件往 FIFO 塞数据时应该写到 TFPI 指向的那个元素然后置位 TXBAR 中 TFPI 对应的位。如果直接把消息写到固定的 0 号缓冲区而 0 号其实是专用 Tx Buffer 区域那语义就全乱了。iLLD 环境下IfxCan_Can_sendMessage 这类接口把上述步骤封装好了但理解这套状态寄存器仍然很重要。我排查“报文发不出去”的问题时第一个动作永远是读 TXBRP 和 TXBTO如果 TXBRP 一直为 1、TXBTO 一直为 0说明报文根本没上总线问题一般在波特率、总线错误或 Message RAM 配置如果 TXBRP 和 TXBTO 都正常但对方收不到那才要考虑线束和终端电阻。3. Rx 方向Rx FIFO 的配置与读取流程3.1 Rx FIFO 0、Rx FIFO 1、独占 Rx Buffer 怎么分工接收侧同样不是只有一种选择。Rx FIFO 0 是主接收通道绝大多数报文默认进这里Rx FIFO 1 可以作为第二条接收队列比如把高优先级报文单独引到 FIFO 1应用层只处理 FIFO 1 就能快速响应关键消息独占式 Rx Buffer 则适合固定 ID 的报文每个 ID 对应一个固定槽位读的时候直接按 ID 索引不需要关心 FIFO 的顺序。它们的存储格式完全一样都是 18 个 word 一个元素。到底进哪个区域由 ID 滤波器决定。后面我会讲滤波器配置这里先记住滤波器元素的 SFECStandard Filter Element Configuration字段里有“receive to FIFO 0”和“receive to FIFO 1”之类的选项对应着不同的接收目标。3.2 RXF0C 配置和 Rx FIFO 的内存占用配置 Rx FIFO 0 主要看 RXF0C 寄存器F0SA 是 FIFO 0 的起始 word 偏移F0S 是元素个数F0WM 是水线watermark——当 FIFO 里的消息数达到这个值时可以触发一个水线中断方便软件提前搬数据而不是等 FIFO 满了才处理。F0DM 则决定 FIFO 满时的行为允许覆盖的话新消息会覆盖最老的消息不允许覆盖的话新消息丢弃并置溢出标志。假设我要在 word 偏移 0x0018 处放 32 个 Rx FIFO 0 元素#define RXF0_MSG_RAM_OFFSET 0x0018u #define NUM_RXF0 32u CAN0_RXF0C.U (RXF0_MSG_RAM_OFFSET IFX_CAN_RXF0C_F0SA_OFF) | (NUM_RXF0 IFX_CAN_RXF0C_F0S_OFF) | (8u IFX_CAN_RXF0C_F0WM_OFF); /* 水线: 8 帧 */内存占用怎么算32 个 Rx FIFO 0 元素 32 * 18 576 words 2304 字节这里有个容易被忽视的点F0WM 水线不要设得太靠近 F0S。比如 FIFO 深度 32水线设成 31那中断触发时基本已经快满了软件稍微调度慢一点就会溢出。建议水线设在深度的四分之一到一半之间比如 8 或 16给自己留出调度余量。3.3 读懂 Put Index 和 Get Index接收才不会乱Rx FIFO 的读取逻辑和串口 FIFO 很像硬件维护两个指针F0PIput index是硬件写入新消息的位置F0GIget index是软件应该读取的最老消息的位置。F0FL 是当前 FIFO 里的有效消息数。正确的读取流程是读 RXF0S检查 F0FL 是否大于 0。取 F0GI 作为本次要读的元素位置。从 F0SA F0GI * 18 这个 word 偏移处读出 18 个 word 的报文元素。解析报文头里的 ID、标志位、DLC 和数据。写 RXF0A 寄存器把 F0AI 设为刚才读走的 F0GI。这一步叫“消费确认”硬件收到确认后才把该元素释放F0FL 减一。第 5 步最容易漏。如果只读 FIFO 不写 ACK硬件会认为消息没有被消费F0FL 永远不会降F0GI 也不往前走。表现出来就是接收中断频繁触发读到的却永远是同一帧老数据。很多“CAN 接收数据不变”的诡异 bug 都是这么产生的。iLLD 里 IfxCan_Can_readMessage 会帮你处理 ACK但你还是得理解机制因为一旦出现问题寄存器排查永远是最快的路径。4. 完整 RAM 预算实例与空间优化4.1 假设一套真实需求规划 Message RAM 不能拍脑袋要拿着具体需求算。假设某个 TC387 节点要承担这样的业务标准 ID 滤波器 16 个扩展 ID 滤波器 4 个。主接收通道 Rx FIFO 0 配 32 个元素备用通道 Rx FIFO 1 配 16 个元素。发送侧要 8 个专用 Tx Buffer给故障帧和心跳帧用再开 16 个 Tx FIFO 元素给普通报文用。开 8 个 Tx Event FIFO 元素用于记录每条消息实际发送完成的时间戳和结果。这就是文章开头提过的布局方案现在把它落成一张表。4.2 逐段计算地址和容量区域数量元素大小word起始偏移结束偏移占用 words占用字节标准 ID 滤波器1610x00000x000F1664扩展 ID 滤波器420x00100x0017832Rx FIFO 032180x00180x02575762304Rx FIFO 116180x02580x03772881152专用 Tx Buffer8180x03780x0407144576Tx FIFO16180x04080x05272881152Tx Event FIFO840x05280x054732128合计0x0548135254080x548 是结束偏移加一的数值也就是 1352 个 word换算成字节是 5408 字节。如果按单节点 32KB 算只用了 5408 / 32768 ≈ 16.5%。余量非常充足后续要加滤波器、加 FIFO 深度都有空间。注意各段之间的衔接上一段的结束偏移加 1就是下一段的起始偏移。我见过不少工程把地址随便填段与段之间要么重叠要么留一个大空洞白白浪费 Message RAM。最稳妥的做法就是像我这样先算好一张完整表格再把起始偏移填进对应的寄存器。4.3 三个真正有用的 RAM 优化建议第一Rx FIFO 深度按峰值流量算不要为了“保险”直接拉满 64。一个 64 深的 Rx FIFO 0 就是 64 × 72 4608 字节两个 FIFO 直接吃走近 9KB。如果业务上总线报文率不高32 深足够省下来的空间完全可以不加但至少心里有数。第二记住 M_CAN 元素大小固定DLC 小不代表省 RAM。8 字节的经典 CAN 帧和 64 字节的 CAN FD 帧在 Message RAM 里占用的元素空间一模一样。所以不要用“我报文很短”来推断 RAM 够用要用元素数量乘 18 word 去算。第三Tx Event FIFO 别轻易砍掉。它一个元素才 4 个 word却能让你精确知道每条消息是否发送成功、在什么时间点发送的对排查偶发性丢帧特别有用。我见过有人为了省几十字节把 TEF 关了后来查“某条报文偶尔没发出去”查了整整两天最后打开 TEF 一看是发送时序冲突导致的取消。这个成本远大于省下的那点 RAM。4.4 把布局写进工程加一道编译期检查预算表不能只存在文档里最好直接固化成代码。一个比较实用的做法是把所有数量定义成宏再用预编译判断检查总占用#define CFG_SIDF_CNT 16u #define CFG_XIDF_CNT 4u #define CFG_RXF0_CNT 32u #define CFG_RXF1_CNT 16u #define CFG_TXD_CNT 8u #define CFG_TXFIFO_CNT 16u #define CFG_TEF_CNT 8u #define CFG_RAM_TOTAL_WORDS (CFG_SIDF_CNT * 1u CFG_XIDF_CNT * 2u \ CFG_RXF0_CNT * 18u CFG_RXF1_CNT * 18u \ CFG_TXD_CNT * 18u CFG_TXFIFO_CNT * 18u \ CFG_TEF_CNT * 4u) #if (CFG_RAM_TOTAL_WORDS 8192u) #error MCMCAN Message RAM 超预算请压缩滤波器或 FIFO 深度 #endif之后不管谁改大了 FIFO 或者滤波器数量编译期就能爆出错误而不是等到上总线之后出现灵异现象。5. 常见问题与排查技巧实录5.1 TXBRP 一直挂着报文发不出去现象是软件已经往 Tx Buffer 写了数据也置了发送请求但总线上测不到波形。此时先读 TXBRP如果对应位一直为 1说明硬件一直在尝试发但发不出去重点检查三件事。一是波特率是否准确特别是用内部时钟分频时注意 TC387 的 CAN 时钟来源和分频系数示波器量一下实际位时间最直接二是总线有没有错误读 PSR 寄存器看错误状态如果大量错误帧说明总线负载或终端电阻有问题三是确认没有误开 Busoff 状态Busoff 后模块会自动停止发送必须按恢复流程重新恢复。另外还有一种隐蔽情况TBSA 地址写错导致 Tx Buffer 区域和其他区域重叠。你写的报文内容被 Rx FIFO 的新数据覆盖硬件读到的是一串随机数据ID 校验不过自然发不出去。这种问题排查起来最费时间最好一开始就把 Message RAM 布局表写好。5.2 接收中断疯狂触发读到的却总是同一帧这个现象十有八九是没回写 RXF0A。软件读了 Rx FIFO 元素但硬件认为你还没消费F0GI 不动下一轮又给你同一个 get index。每次新报文到达都会触发一次中断但软件每次读的都是最老那帧。处理方式很简单读完元素后立即把 RXF0A 写成当前的 F0GI。如果你用 iLLD 的读接口确认它内部是否帮你做了 ACK如果自己写了接收逻辑这一步绝对不能省。5.3 报文能收能发但数据偶尔对不上如果报文收发都正常只是某些帧的数据字段偶尔错乱优先怀疑两个地方。一个是 DLC 解析问题M_CAN 元素里的数据拷贝长度要和 DLC 一致不能只看 ID 就复制全部 64 字节。另一个是 Message RAM 重叠比如 Rx Buffer 区域和 Tx Event FIFO 区域都指向了相同偏移发送完成事件不断覆盖你正在读的接收数据。用调试器内存窗口看一眼两个区域的起始地址对照布局表重叠问题一眼就能看出来。5.4 给 Message RAM 做一次“体检”如果怀疑是硬件问题或者地址线问题可以用类似 memtest 的思路对 Message RAM 做一次检查。传统 memtest 里有一项是测试地址总线有没有 stuck bits也就是某根地址线恒为 0 或恒为 1导致不同地址访问到同一块存储。放到 CAN 场景里可以在调试器里把 Message RAM 的一段区域交替写入 0x5A5A5A5A 和 0xA5A5A5A5再读回来比对看看是否有位被卡死或地址线被短路。更贴近实际的做法是配置 CAN 模块进入回环模式Loopback发送一帧 64 字节、内容为已知 pattern 的 CAN FD 报文再从 Rx FIFO 读出来逐字节比对。如果回环测试能稳定通过说明 Message RAM 的读写链路没有问题如果偶发字节翻转才需要进一步怀疑硬件或电源干扰。5.5 排查速查表症状优先级高的排查方向备注发不出去TXBRP 一直挂起波特率、总线错误、Busoff示波器量位时间最直接发送完成但对方收不到采样点、终端电阻、错误帧干扰看 PSR 错误计数接收中断频繁读同帧RXF0A 未回写检查 get/put 逻辑数据偶发错乱Message RAM 区域重叠对照布局表检查地址FIFO 总是溢出深度不足或水线太高按峰值流量重新算深度帧进错 FIFO滤波器 SFEC 配置错误核对滤波器的目标区域排查 CAN 问题我的经验是先分“协议层”和“内存层”。协议层看波特率、采样点、错误帧内存层看 Message RAM 布局、滤波器和 FIFO 指针。很多看起来像协议层的问题最后都出在内存层。最后分享一个小技巧我会把 Message RAM 的布局表以注释形式直接贴在 CAN 初始化文件的最顶部每段区域的起始偏移、元素数量、用途写成一目了然的表格。换人维护时不用翻文档就能看懂地址安排加新报文时照着注释计算新缓冲区位置也不会把别人的区域盖掉。这套布局方法不只适用于 TC387凡是基于 Bosch M_CAN 内核的芯片比如 NXP、瑞萨、ST 的部分型号都是同一个套路换平台时只需要把寄存器和 RAM 基地址换成对应芯片的就行。
返回列表