ARTICLE DETAIL

资讯详情

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

STM32H7 FDCAN混合总线:兼容CAN FD与经典CAN的工程实践

STM32H7 FDCAN混合总线:兼容CAN FD与经典CAN的工程实践 简介面向STM32H7嵌入式开发者的FDCAN与CAN兼容通信完整工程聚焦Cortex-M7平台下CAN-FD高速通信的实际落地。工程包含从STM32CubeMX引脚初始化到FDCAN位速率设定、滤波器配置、中断处理以及音频卡场景中CAN总线控制与数据收发的测试代码。压缩包共344个文件、约25.95MB以h头文件、c源程序、uvprojx工程、ioc配置为主同时包含o/axf/hex等编译链接产物和map映射文件可直接打开查看、重新编译和烧录验证。已有4198人学习下载由Fairchild_1947整理发布。深入实践该工程可理解CAN-FD与传统CAN的速率差异、自动生成初始化代码的结构掌握错误帧诊断、状态机管理及针对总线通信的测试策略对嵌入式硬件与单片机项目的通信设计和排错具有直接参考价值。 前阵子整理了一套基于 STM32H7 的 CAN 总线工程模板核心就一句话用 FDCAN 外设同时兼容 CAN FD 和经典 CAN 2.0。这个需求在车载和工控里太常见了产线上老的 250kbps 经典 CAN 节点还没淘汰新的控制单元又想用 2Mbps 甚至 5Mbps 的 CAN FD 拉数据这时候换 H7 是现实的选择——它内置的 FDCAN 硬件支持两种帧格式但真正把工程排顺手还得处理一堆细节。这篇把完整思路捋一遍工程架构怎么设计、波特率和采样点怎么算、初始化代码怎么配、实测中踩了哪些坑。文字尽量落在可操作层面如果你正在从 F1/F4 往 H7 迁移或者刚接触 FDCAN 准备和经典 CAN 节点混跑应该能直接用上。1. 从需求出发为什么选 FDCAN 还要兼容经典 CAN1.1 这个工程要解决的实际问题先还原一下需求场景。手里的项目是一个网关控制板CAN1 总线上挂了好几个老传感器这些传感器的 MCU 只实现了经典 CAN 2.0速率固定在 250kbpsCAN2 总线准备接新的摄像头和控制单元数据量大想跑 CAN FD。板子用 STM32H7因为内部 FDCAN 外设既能发经典帧也能发 FD 帧理论上一个外设就能吃下两种总线需求。但实际工程里有两个问题一是 FDCAN 的 FIFO、滤波器、错误处理机制和 F1/F4 的 bxCAN 完全不同寄存器基本没法平移二是源码层如果写两套驱动经典一套、FD 一套维护成本太高而且发错帧格式会直接导致总线上错误帧一片。所以“兼容完整工程”的目标可以拆成三条。第一协议层统一上层应用只调一个发送接口内部根据参数自动选择帧格式第二配置层统一滤波、采样点、错误中断一次配好经典帧和 FD 帧共用第三调试层友好帧统计、总线错误状态能直接读出来。这套思路做完之后同样代码可以直接在下个项目的 FDCAN 实例上复制一份改改参数就能用。1.2 FDCAN 与 bxCAN 的硬件差异很多从 F1/F4 转过来的工程师第一反应是按照 bxCAN 的思维去填邮箱然后发现 H7 的 FDCAN 根本不是那个玩法。bxCAN 管的是 28 个邮箱逻辑简单FDCAN 则把所有消息对象集中放在一块 Message RAM 里滤波器、接收 FIFO0、接收 FIFO1、发送缓冲、发送事件 FIFO 都是通过寄存器指定基地址和长度来划分的。你看 CubeMX 里 FDCAN 配置页面有一排选项是 Message RAM 分区就是这个原因。帧格式上也有明显区别。经典 CAN 帧最长 8 字节FDCAN 支持最长 64 字节经典 CAN 的波特率是全帧固定速率CAN FD 则把仲裁段和数据段分开仲裁段用较低速率数据段可以切到更高速率这就是 BRS 位。还有 ESI 位用来标识发送节点是否处于错误被动状态这个在 bxCAN 里没有。这些差异决定了工程一定要做抽象层否则上层业务会被底层外设差异绑死。2. 工程架构设计一个接口收编两种帧格式2.1 抽象层上层不关心是经典帧还是 FD 帧我在这套工程里定义了一个统一的can_msg_t结构体包含 ID、ID 类型、DLC、数据指针、以及两个关键标志is_fd和brs。上层应用填充这个结构体然后调用can_send(channel, msg)完全不关心底层是 FDCAN1 还是 FDCAN2也不关心是哪种帧格式。接收端用回调机制can_register_rx_cb(channel, callback)注册回调函数。底层中断里收到帧之后把 FDCAN 的 RxHeader 转换成统一的can_msg_t再交给解包函数处理。这样做的直接收益是后面如果要把工程从 H7 换到别的带 FDCAN 或者同类控制器的平台只改驱动文件即可业务代码一行不用动。2.2 帧类型、DLC 与发送策略经典 CAN 的 DLC 最大 8CAN FD 的 DLC 映射不是简单的“值等于字节数”0~8 是直接对应8 以上被映射到 12、16、20、24、32、48、64 这些档位。所以代码里一定不要用裸数字写死 DataLength 字段HAL 库提供了FDCAN_DLC_BYTES_64这类宏能查表就查表。发送策略上有个很容易踩的坑总线上只要还有不支持 FD 帧的节点广播帧就必须发经典格式。因为经典 CAN 控制器识别到 FD 帧里的非标准位会把它判定成格式错误然后主动在总线上置错误帧整个总线通信都会被拖垮。这不是“大数据用 FD、小数据用经典”这么简单的事而是要看对端节点能力。我的处理方式是点对点的高速数据通道走 FD广播类消息和诊断类消息统一走经典格式。2.3 滤波器和 Message RAM 规划FDCAN 的滤波器分标准 ID 滤波器SIDFC和扩展 ID 滤波器XIDFC两种滤波器元素支持范围、位掩码、双 ID 等模式。一个总线同时收经典帧和 FD 帧时滤波器并不需要为帧格式额外做什么因为过滤只管 ID不管帧类型。Message RAM 规划上我建议 FIFO0 分配 8 条、FIFO1 分配 8 条、TX buffer 用 16 条左右够大多数场景。H7 每个 FDCAN 实例的 Message RAM 大约 10KB这部分空间既要给滤波器和 FIFO也要给发送缓冲。配太大后面代码里没有别的用途配太小高峰期容易丢帧。多实例共用时还要注意偏移量FDCAN1 和 FDCAN2 的 Message RAM 区域不能重叠否则会出现一个实例的帧被另一个实例覆盖的诡异问题。3. 核心配置波特率、采样点与时间量子计算3.1 位时序的几个基础概念FDCAN 的每一个位时间由同步段Sync Seg、传播段、相位缓冲段 1PS1、相位缓冲段 2PS2组成。采样点落在 PS1 和 PS2 之间。时间量子 tq 是基本单位每一位由若干个 tq 组成整体按预分频器从外设时钟分频得到。采样点的含义就是“在一个位周期内已经走过的 tq 数占总 tq 数的百分比”。采样点太靠前对线路传播延迟的容忍度差采样点太靠后重同步空间不够容易失步。一般取 70%~87.5%经典 CAN 常用 75%~80%CAN FD 数据段因为速率高常取 75% 左右。这里要多说一句接线距离长、终端电阻匹配不好采样点的影响会更明显。我曾经在一根 2 米长的测试线缆上看到连续错误帧最后排查下来根本不是速率问题而是采样点差了 3%调整之后错误帧立刻消失。所以不要随便抄例程里的采样点参数。3.2 一个 500kbps 2Mbps 的完整计算实例拿我的工程举例仲裁段 500kbps数据段 2MbpsFDCAN 外设时钟 40MHz。这里用的是 H7 内部 PLL 单独分配出来的时钟源不是简单等于 APB1。仲裁段预分频器 prescaler 4得到 10MHz 的 tq 频率。每个位 10MHz / 500kbps 20 tq。同步段 1 tqPS1 14 tqPS2 5 tq。采样点 (1 14) / 20 75%。SJW 取 4 tq。数据段预分频器 DBRP 1得到 40MHz 的 tq 频率。每个位 40MHz / 2Mbps 20 tq。同步段 1 tqPS1 14 tqPS2 5 tq采样点同样 75%。SJW 取 4 tq。CAN FD 仲裁段和数据段使用独立的位时序寄存器HAL 库配置项里 Nominal 开头的对应仲裁段Data 开头的对应数据段。如果数据段要跑 5Mbps40MHz 时钟下每个位只有 8 tqPS1 取 5、PS2 取 2采样点 (15)/8 75%可做但容错窗口很小SJW 只能取 1~2对晶振精度要求高。所以新项目如果器件允许我通常把 FDCAN 时钟喂到 80MHz这样数据段 5Mbps 时还能留 16 tq/位配置空间宽裕很多。提示每次改动 CAN 时钟源哪怕只是从一个 PLL 输出切到另一个都建议把采样点重新算一遍。采样点错了示波器上会看到连续错误帧控制器不会自动恢复。3.3 采样点与 SJW 怎么选给一个实际可参考的配置推荐表方便直接套用速率组合晶振精度采样点SJW备注仲裁 500kbps / 数据 2Mbps20ppm75%~80%4~5 tq常规车载场景仲裁 1Mbps / 数据 5Mbps50ppm75%2~3 tq数据段窗口较小注意收发器仲裁 250kbps / 数据 1Mbps50ppm80%4~5 tq适合老节点混跑SJW 的作用是实现重同步。总线空闲之后如果出现边沿偏移控制器允许每次重同步时把相位缓冲段扩展或者缩短的值就是 SJW。取值太小时钟偏差一大就追不上取值太大毛刺可能被当成有效边沿而误采样。一般取 1~4 tq并且不能超过 PS2 的值。4. 实操从零配置 H7 FDCAN 的关键代码与步骤4.1 时钟、引脚与 CubeMX 配置要点CubeMX 下先把 FDCAN 外设时钟配好。H7 的 FDCAN 时钟可以从 APB1 或 PLL2 来建议在 Clock Configuration 里给 FDCAN 单独安排一个 40MHz 或 80MHz 时钟然后在 NVIC 里打开 FDCAN1_IT0、FDCAN1_IT1 中断。引脚常用 PA11/PA12 或 PB8/PB9在芯片手册的 AF 复用表里查对应编号即可。FDCAN 的 HAL 初始化配置里有一个关键点MessageRAMOffset参数。单实例使用填 0多实例时要根据实际地址把偏移拉开。另外别忘了调低滤波器的数量如果 CubeMX 默认给了 128 个标准滤波器而 Message RAM 总共就那么大配置不当会导致初始化失败或者空间冲突。4.2 FDCAN 初始化代码关键配置如下注意 Nominal 和 Data 两组参数分别对应仲裁段和数据段FDCAN_HandleTypeDef hfdcan1; hfdcan1.Instance FDCAN1; hfdcan1.Init.ClockDivider FDCAN_CLOCK_DIV1; hfdcan1.Init.FrameFormat FDCAN_FRAME_FD_BRS; hfdcan1.Init.Mode FDCAN_MODE_NORMAL; // 仲裁段 500kbps hfdcan1.Init.NominalPrescaler 4; hfdcan1.Init.NominalTimeSeg1 14; hfdcan1.Init.NominalTimeSeg2 5; hfdcan1.Init.NominalSyncJumpWidth 4; // 数据段 2Mbps hfdcan1.Init.DataPrescaler 1; hfdcan1.Init.DataTimeSeg1 14; hfdcan1.Init.DataTimeSeg2 5; hfdcan1.Init.DataSyncJumpWidth 4; hfdcan1.Init.MessageRAMOffset 0; HAL_FDCAN_Init(hfdcan1);滤波器配置这里配了一个标准 ID 掩码模式所有经过掩码匹配的帧都进入 FIFO0FDCAN_FilterTypeDef sFilter; sFilter.IdType FDCAN_STANDARD_ID; sFilter.FilterType FDCAN_FILTER_MASK; sFilter.FilterConfig FDCAN_FILTER_TO_RXFIFO0; sFilter.FilterID1 0x100; sFilter.FilterID2 0x7FF; /* mask0x7FF 表示不掩码 */ HAL_FDCAN_ConfigFilter(hfdcan1, sFilter);帧过滤只按 ID 匹配经典帧和 FD 帧都会进 FIFO0。要同时接收扩展 ID还需要再配置扩展滤波器类型选FDCAN_EXTENDED_ID。4.3 发送与接收的完整逻辑发送接口的核心逻辑是根据is_fd标志决定 FDFormat 字段int can_send(CAN_Channel ch, const can_msg_t *msg) { FDCAN_TxHeaderTypeDef txh; memset(txh, 0, sizeof(txh)); txh.Identifier msg-id; txh.IdType (msg-id_type CAN_ID_EXT) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID; txh.TxFrameType FDCAN_DATA_FRAME; if (msg-is_fd) { txh.DataLength dlc_to_fdcan(msg-dlc); txh.FDFormat FDCAN_FD_CAN; txh.BitRateSwitch msg-brs ? FDCAN_BRS_ON : FDCAN_BRS_OFF; } else { txh.DataLength dlc_to_fdcan(msg-dlc); txh.FDFormat FDCAN_CLASSIC_CAN; txh.BitRateSwitch FDCAN_BRS_OFF; } return HAL_FDCAN_AddMessageToTxMailbox(hfdcan1, txh, msg-data) HAL_OK ? 0 : -1; }接收端在中断回调里取帧然后转换成长度信息交给上层void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t fifoIndex) { FDCAN_RxHeaderTypeDef rxh; uint8_t data[64]; HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, rxh, data); can_msg_t msg; msg.id rxh.Identifier; msg.is_fd (rxh.FDFormat FDCAN_FD_CAN); msg.dlc fdcan_to_dlc(rxh.DataLength); memcpy(msg.data, data, msg.dlc); notify_rx(0, msg); }接收时有一个细节需要留意rxh.DataLength返回的是一个编码值不能直接当字节个数来用要经过映射表转成真正的字节数。否则 64 字节的 FD 帧会被错误地截断。4.4 用 USB-CAN 工具做冒烟测试我调试这套工程时习惯用 USB-CAN 分析仪无论是周立功还是创芯这类工具都可以。先在 PC 端软件里建一个 500kbps 的经典通道确认发过去的经典帧 ID、数据都对再把工具切到 CAN FD 模式数据段速率选 2Mbps发一条 64 字节的 FD 帧验证双向通信。这一步能提前暴露不少问题比如收发器只支持经典 CAN在 FD 模式下数据段会直接采不到数据。板上再放一个周期任务每 100ms 交替发送经典帧和 FD 帧用分析软件的时间戳和帧类型列表确认两种格式在总线上是否和平共处。如果看到错误帧计数器在涨基本可以判定是某个节点不支持其中一种帧格式。5. 实测中踩过的坑与排查思路5.1 经典 CAN 节点被 FD 帧“冲”出错误帧第一次把 FD 帧发到带经典节点总线上总线上立刻冒出连续错误帧经典节点像被干扰了一样疯狂报错。原因不复杂经典 CAN 控制器不识别 FD 帧的连续位形式、BRS 位等把它判定成格式错误一旦检测到就会主动发错误帧。看起来像干扰其实是协议协商失败。解决方式要回到总线规划上把“这条总线是否能跑 FD 帧”作为硬约束。我当时的做法是广播帧永远发经典格式点对点的高速数据通道才切 FD。如果混跑节点特别多更稳妥的办法是把 FD 节点和经典节点放到两条物理总线网关板做桥接彻底隔离两种帧格式。5.2 数据段乱码最后发现是收发器不支持 FD把工程烧到另一块板子上仲裁段 500kbps 通信正常数据段一开 BRS 就乱码。查了一整天最后发现那块板子上的 CAN 收发器型号只支持经典 CAN数据段从 500kbps 切到 2Mbps 时收发器根本跟不上。现在市面上很多型号标着“CAN 收发器”实际上对 CAN FD 的支持程度差别很大。选型时一定要看数据手册有没有明确写支持 CAN FD 或 2Mbps/5Mbps 数据段。另外 PCB 层的处理也很关键FD 数据段速率高走线长度和终端匹配的影响比经典 CAN 明显得多120Ω 终端电阻的位置、是否有共模电感最好严格按参考设计来。5.3 CAN 矩阵里字节序和位序带来的信号错位CAN 矩阵DBC 文件里每个信号都会定义字节序Intel小端和 Motorola大端。很多人在代码里直接 memcpy结果发现信号值对不上。一个 16 位信号Intel 字节序就是低字节在前可以直接把小端结构体数据发过去Motorola 字节序则不同某些跨字节信号不但字节要交换位也要按矩阵指定的 bit 顺序重新排列。建议在工程里写一个通用的信号打包、解包层所有信号按 DBC 矩阵的起始字节、起始位、长度来映射不要在业务代码里裸拼数据。我自己的做法是写一个脚本把 DBC 矩阵导出成 C 结构定义和打包、解包函数。后来换 CAN ID 或者调整信号位置只改脚本生成的映射表业务逻辑一行不动。这个习惯帮我省了很多排查信号错位的精力。5.4 一帧时间的快速估算评估总线负载有时候要评估总线负载得先算一帧 CAN FD 帧占多长时间。这里给个快速估算方法。经典 CAN 标准数据帧固定开销约 44 位加上 8 倍 DLC 位除以波特率就是时间。CAN FD 帧要复杂一些因为仲裁段和数据段速率不同公式可以写成T_total 仲裁段位数 / 仲裁段速率 数据段位数 / 数据段速率。举一个实际例子64 字节 FD 帧仲裁 500kbps、数据 2Mbps仲裁段大约 70us 左右。数据段 64 字节就是 512 位加上协议开销大约 300us。一帧总时间不到 400us。算完会有一个直观感受CAN FD 的价值不只是单帧能放 64 字节而是同样的周期窗口里能塞进更多有效载荷总线负载压力小很多。做总线规划时我一般把负载控制在 40% 以下留足峰值余量。5.5 错误状态与总线恢复别把恢复当常态FDCAN 有错误主动、错误被动、总线关闭三个状态。错误计数器 TEC 和 REC 可以从寄存器里读出来总线关闭后 FDCAN 会执行恢复流程检测到 128 个连续空闲位后回到错误主动。这个恢复时间对某些实时控制场景来说可能太久了不能把总线关闭后的自动恢复当成正常流程。建议在工程里打开错误中断记录当前是哪个节点在发错误帧而不是等总线自己恢复。之前排查一个老问题时发现某个节点周期性搞挂总线就是靠 TEC/REC 的数值定位到软件里某条消息 ID 的 DLC 填错导致的。错误中断里加一个计数器和标志位比事后看波形高效得多。后面我在这个模板上又做了些扩展加了发送事件 FIFO用来确认帧确实在总线上发出去也接了 FreeMASTER 在线看变量。FDCAN 和经典 CAN 兼容这件事本身难度不大真正容易出问题的地方在于一开始就把帧格式策略定清楚。如果你也在做类似的混合总线工程建议先把总线拓扑和帧格式规划写进设计文档再碰代码。这比我当初直接上来改寄存器省事得多。本文还有配套的精品资源点击获取
返回列表