STM32G474 FDCAN配置详解:位时序、滤波器与Flash存储实战
2026/9/12 18:23:58 网站建设 项目流程

简介:面向 STM32 开发者,这是一份基于 STM32CubeMX 的 CAN-FD 实战工程合集,涵盖 STM32G474 的 FDCAN 外设驱动与 STM32F405RGT6 的经典 CAN 接口配置,也包含闪存编程相关内容。压缩包共 164 个文件,以 h 头文件与 c 源码为主,另有 ioc、uvprojx、uvoptx 等 STM32CubeMX 与 Keil 工程配置文件,以及启动汇编 s 文件和 mxproject 工程说明,整体仅 1.32MB,结构清晰,适合需要快速上手 STM32 CAN-FD 通信的嵌入式工程师参考。资源来自 CSDN 作者 weixin_42696271,已有 1061 人学习下载。通过示例代码可掌握 HAL 库下 FDCAN/CAN 的初始化、滤波器设置、报文收发与中断处理,并了解如何在 STM32G474 上结合闪存读写实现参数保存;两个系列工程的对照便于迁移和复用,对做实时控制、车载通信或工业总线开发都有直接参考价值。

1. FDCAN 在 stm32g474 上为什么值得单独研究

STM32G474 的 FDCAN 外设是 Bosch M_CAN 核心的实例,它同时兼容经典 CAN 协议和 CAN FD 协议。很多从 stm32f103 时代过来的工程师,习惯把 CAN 收发当成一个改改波特率就能跑的中断外设,但换到 stm32g474 的 fdcan,事情开始不一样:数据场最长 64 字节,仲裁段和数据段可以跑两套波特率,外设自带 128 个过滤元素,收发路径上有 RX FIFO、TX FIFO、TX Queue、TX Buffer 四种缓冲组合。用 stm32cubemx 生成工程只是起点,位时序怎么算、滤波器怎么分、上电后收到的标定数据怎么安全写进 G4 的 Flash,这些决定了板子挂在真实 can 总线上能不能稳定跑。这篇文章按 stm32cubemx 配置 FDCAN、收发送数据、Flash 存储这条链路往下走,把参数和坑一次说清楚。

2. FDCAN 位时序与外设架构:先把 stm32g474 的 can/fdcan 资源理清

2.1 经典 CAN 到 FDCAN:速率、帧格式和总线仲裁的变化

FDCAN 与经典 CAN 的差异不是「更快」两个字能带过的。经典 can 总线数据场固定 8 字节,波特率全帧统一,比如 500kbps,整帧都是 500k。FDCAN 在帧头的仲裁段仍用较低速率保证与 CAN 2.0 节点共存,而从控制场 BRS 位之后的数据段可以切到更高速率,常见配置是仲裁段 1Mbps、数据段 5Mbps。这意味着总线上的老节点能识别帧头,但读到 BRS 位之后就可能出错,所以 FD 帧不能混进只支持 CAN 2.0 的网络,这是设计整车或工业网络拓扑时第一件要确认的事。

帧格式上,FD 帧数据场从 8 字节跳到 12、16、20、24、32、48 直到 64 字节,同时 CRC 段也换成 17 位或 21 位多项式,数据段速率越高,对晶振精度和位时序的余量要求越苛刻。stm32g474 的 fdcan 还在仲裁机制上和经典 CAN 保持一致:多个节点同时发送时,ID 越小优先级越高,隐性位被显性位覆盖的裁决过程发生在仲裁段。由于仲裁段是低速段,数据段高速切换后不再存在总线仲裁,这也正是 FDCAN 能提升有效带宽的原因之一。

2.2 FDCAN1/FDCAN2 实例与消息 RAM 分配

STM32G474 集成两个 FDCAN 实例,FDCAN1 和 FDCAN2,它们共用一个消息 RAM,但各自可以通过寄存器配置滤波器、FIFO 和 TX Buffer。消息 RAM 不是简单的一片数据缓冲区,它被划分成几个可配置区域:标准/扩展过滤元素列表、RX FIFO 0、RX FIFO 1、TX Event FIFO、TX Buffer / TX Queue / TX FIFO。用 stm32cubemx 配置时,界面上会出现这些区域的大小选项,比如 RX FIFO 0 深度、TX Buffer 个数,每个选项改动都会改变整个消息 RAM 的地址布局。

G4 系列 FDCAN 的过滤资源是它的强项,每个 FDCAN 实例可以配置最多 128 个过滤元素,标准 ID 用 28 位过滤元素,扩展 ID 用 32 位过滤元素。所谓元素,就是一条条过滤规则,每一条有两种工作方式:Mask 方式和 List 方式。Mask 方式拿一个 ID 加一个掩码去匹配一段 ID 区间,List 方式直接精确匹配连续列表里的 ID。实际做汽车网关的时候,我一般把关键报文用 List 精确收,把一组同类型报文用 Mask 归并收,两种方式按需混在同一张过滤表里。

2.3 FDCAN 位时间与采样点计算

FDCAN 的位时间由四段组成:同步段 SYNC_SEG、传播时间段、相位缓冲段 BS1、相位缓冲段 BS2。一个位时间对应一个或多个 Tq(Time Quantum),Tq 由外设时钟和波特率预分频器决定。位时间配置有几个参数需要关注:预分频器设有总线时钟分频系数,此外还有 SJW、采样点偏移、数据段位时间。数据段位时间的相位缓冲段与仲裁段是分开配置的,因为数据段跑 5Mbps 时,一个位时间只有 200ns,任何一段配多了,采样点误差都会被放大。

参数仲裁段典型配置数据段典型配置说明
预分频器21分频后得到 Tq 时钟
传播时间段11G4 的 BS1 配置里通常合并传播段
BS1134决定位宽前半段
BS223决定位宽后半段
采样点约 80%约 75%高速率下采样点不宜过高
SJW42同步补偿宽度

以 FDCAN 时钟 80MHz、仲裁段 1Mbps 为例,一个位时间需要 80 个 Tq,预分频器取 1,Tq 为 12.5ns。SYNC_SEG 固定占 1 个 Tq,BS1 加传播时间段共 13 个 Tq,BS2 取 2 个 Tq,采样点约在 87.5% 处,对 1Mbps 足够。数据段 5Mbps 时一个位时间 16 个 Tq,预分频器保持 1,BS1 取 10、BS2 取 5,采样点约 68.8%。这里不用手算到小数点后两位,stm32cubemx 界面里输入期望波特率和采样点,自动反推出位时序参数,但手算能力能帮你在总线报错时判断是参数给过头还是时钟源不对。

计算 Tq 的公式是:Tq = (Prescaler + 1) / FDCAN_kernel_clock,位时间 = (1 + BS1 + BS2) 个 Tq,采样点 = (1 + BS1) / (1 + BS1 + BS2)。在 stm32 的 hal 库FDCAN_InitTypeDef里对应NominalPrescalerNominalSyncJumpWidthNominalTimeSeg1NominalTimeSeg2DataPrescalerDataSyncJumpWidthDataTimeSeg1DataTimeSeg2两组字段,配置时容易漏的是 Data 组,如果只填仲裁段不填数据段,FD 帧根本发不出去。

3. 用 stm32cubemx 配置 stm32g474 FDCAN 与生成工程

3.1 时钟树:FDCAN 内核时钟到底走哪条路

不少人在 stm32cubemx 里配完 FDCAN 外设却发现波特率差一倍,问题出在时钟树上。FDCAN 外设的时钟不是直接挂 PCLK1,而是有独立的内核时钟源,可以在 RCC 配置页里选择。常见的做法是把 PLL1Q 输出接到 FDCAN kernel clock,让 FDCAN 时钟与串口、定时器各自独立,避免 PCLK1 被其他外设的分频影响。

进入 Clock Configuration 页面后,找到 FDCAN1 kernel clock 的选项,候选一般是 PCLK1 或 PLL1Q。PCLK1 走 APB1 分频链,如果总线外设多、分频系数改来改去,FDCAN 时钟会被牵动。PLL1Q 是独立的 PLL 输出,配置为 80MHz 或 100MHz 之后长期不变,位时序分析起来更可控。stm32g474 的 FDCAN 总线时钟范围在数据手册里有上限,比如 100MHz,配置时建议在 CubeMX 里把 PLL1Q 调到 80MHz,留出裕量。

3.2 Connectivity 页面的 FDCAN 参数配置

在 Pinout & Configuration 页面选中 FDCAN1,打开 Parameter Settings,重点看这几个分组:

配置分组关键字段推荐值作用
ProtocolFrame FormatFD 还是 Classic决定只发经典帧还是支持 FD 帧
ProtocolBit TimingsNominal 1M, Data 5M仲裁段与数据段波特率
ProtocolAutomatic RetransmissionDisable调试期建议关闭,避免总线错误风暴
Message RAMStandard Filters28标准 ID 过滤元素个数
Message RAMExtended Filters12扩展 ID 过滤元素个数
Message RAMRx FIFO0 Elements32接收 FIFO 深度
Message RAMTx Buffers16发送缓冲个数
Message RAMTx Event FIFO16发送完成事件记录
InterruptNotification SelectionRX FIFO0 Message使能接收中断

Automatic Retransmission这个选项建议在调试初期设为 Disable。如果网络上有节点没回 ACK,硬件默认会不停重发,总线一直被占住,其他报文全被拖死。关闭重发后发送失败会进FDCAN_TX_MAILBOX状态错误回调,日志里能直接看到哪一帧没发出去,定位问题快得多。

Filter 数量按实际报文规划,标准 ID 报文多就多分标准过滤元素,扩展 ID 报文是少数就少分几个。消息 RAM 总量有限,Filter 分多了 FIFO 就浅,报文高频率进来时容易溢出。

3.3 生成代码后的 FDCAN 初始化骨架

CubeMX 生成工程后,MX_FDCAN1_Init()函数会给出外设初始化,但在调用HAL_FDCAN_Start()之前,还缺滤波器和回调注册。先看生成的初始化函数:

static void MX_FDCAN1_Init(void) { hfdcan1.Instance = FDCAN1; hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; hfdcan1.Init.AutoRetransmission = DISABLE; hfdcan1.Init.TransmitPause = DISABLE; hfdcan1.Init.ProtocolException = DISABLE; hfdcan1.Init.NominalPrescaler = 1; hfdcan1.Init.NominalSyncJumpWidth = 4; hfdcan1.Init.NominalTimeSeg1 = 13; hfdcan1.Init.NominalTimeSeg2 = 2; hfdcan1.Init.DataPrescaler = 1; hfdcan1.Init.DataSyncJumpWidth = 2; hfdcan1.Init.DataTimeSeg1 = 10; hfdcan1.Init.DataTimeSeg2 = 5; hfdcan1.Init.StdFiltersNbr = 28; hfdcan1.Init.ExtFiltersNbr = 12; hfdcan1.Init.RxFifo0ElmtsNbr = 32; hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_64; hfdcan1.Init.RxBuffersNbr = 0; hfdcan1.Init.TxEventsNbr = 16; hfdcan1.Init.TxBuffersNbr = 16; hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK) { Error_Handler(); } }

时钟分频系数ClockDivider只有FDCAN_CLOCK_DIV1FDCAN_CLOCK_DIV2两种选择,如果内核时钟选太高,靠这一个分频可以降到可用的 Tq 范围。FrameFormat设为FDCAN_FRAME_FD_BRS表示支持 FD 帧且带位速率切换,如果对端是经典 CAN 节点,需要另起一个配置FDCAN_FRAME_CLASSIC的实例。RxFifo0ElmtSize决定 FIFO 里每个元素的最大字节数,设成 64 字节更保险,哪怕当前只收发 8 字节的经典帧,也不会浪费太多。

初始化函数只是前半段,main()里要调用它,紧接着做过滤配置和启动:

MX_FDCAN1_Init(); if (FDCAN_ConfigFilter() != HAL_OK) { Error_Handler(); } HAL_FDCAN_Start(&hfdcan1); HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);

HAL_FDCAN_ActivateNotification的第三个参数是写指针,非 DMA 模式下不关心,填 0 即可。使能中断通知要在启动之后,否则消息可能在使能前进来丢了,导致回调永远不触发。

4. FDCAN 收发驱动与滤波器:从初始化到中断回调

4.1 发送路径:TX FIFO、TX Buffer 与 DLC 字节映射

stm32g474 的 FDCAN 发送有三种路径:TX Buffer、TX FIFO、TX Queue。CubeMX 配置时如果打开FDCAN_TX_FIFO_OPERATION模式,发送走 FIFO,可以连续压入多帧,硬件自动按顺序发送;如果配成 Queue 模式,它会按消息 ID 优先级排队。TX Buffer 适合做周期发送,比如每 10ms 发一帧状态报文,可以把帧预存进固定 Buffer,下次只改数据场。TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION时用 HAL 的HAL_FDCAN_AddMessageToTxFifoQ()入队。

FDCAN_TxHeaderTypeDef pTxHeader; uint8_t txData[16]; pTxHeader.Identifier = 0x123; pTxHeader.IdType = FDCAN_STANDARD_ID; pTxHeader.TxFrameType = FDCAN_DATA_FRAME; pTxHeader.DataLength = FDCAN_DLC_BYTES_16; pTxHeader.FDFormat = FDCAN_FD_CAN; pTxHeader.BitRateSwitch = FDCAN_BRS_ON; pTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &pTxHeader, txData) != HAL_OK) { printf("tx fifo full\r\n"); }

DataLength字段用的是 DLC 编码,不是字节数直接填进去,16 字节对应FDCAN_DLC_BYTES_16,24、32、48、64 各自有宏。很多调试问题出在DataLength填了 16、但txData缓冲区只有 8 字节,硬件读走 16 字节,后面 8 字节是缓冲区的脏数据。BitRateSwitchFDCAN_BRS_ON才走数据段高速率,设 OFF 时整个帧都按仲裁段速率发,这是 FD 帧兼容性的重要开关。

4.2 接收中断与数据解析

接收侧配置 FIFO0 后,中断回调里取帧即可:

void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { FDCAN_RxHeaderTypeDef pRxHeader; uint8_t rxData[64]; if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0) { HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &pRxHeader, rxData); uint8_t dlc = pRxHeader.DataLength; uint8_t len = 1 << ((dlc >> 4) - 1); // 解析 pRxHeader.Identifier, pRxHeader.FDFormat, pRxHeader.BitRateSwitch } }

HAL 的HAL_FDCAN_RxFifo0Callback是弱定义回调,重叠定义后在文件里会覆盖 HAL 默认函数,链路保证FDCAN_IT_RX_FIFO0_NEW_MESSAGE被即时消费。数据长度转换1 << ((dlc >> 4) - 1)在 HAL 里是常见解析做法,把 DLC 位域还原成字节数,FD 帧 64 字节时 DLC 编码为 0x0F,右移再减 1 得到 7,1 左移 7 是 128?不对,64 字节对应的 FDCAN_DLC_BYTES_64 的值是 0x0F,dlc >> 4是 0,减 1 成负数,移位出错。实际这行转换写错的概率不低,建议直接查表:

static const uint8_t fdcan_dlc_to_len[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 }; uint8_t len = fdcan_dlc_to_len[pRxHeader.DataLength >> 4];

FDCAN 的数据长度在 DLC 位里用 0x9 到 0xF 表示大于 8 字节的帧,这个映射表比移位公式可靠得多。

4.3 过滤器:Mask、List 与接收 Buffer 的绑定

配置过滤器的结构体要把过滤类型和接收 FIFO 绑定,常见配置如下:

FDCAN_FilterTypeDef sFilterConfig; sFilterConfig.IdType = FDCAN_STANDARD_ID; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterType = FDCAN_FILTER_MASK; sFilterConfig.FilterID1 = 0x120; sFilterConfig.FilterID2 = 0x7FF; sFilterConfig.FilterIndex = 0; sFilterConfig.IsCalibrationMsg = DISABLE; HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);

FilterType有三个取值:FDCAN_FILTER_MASK模式下FilterID1是关注 ID,FilterID2是掩码,掩码对应位为 1 时必须匹配,为 0 时不关心;FDCAN_FILTER_RANGE模式下FilterID1FilterID2组成闭区间;FDCAN_FILTER_DUAL精确匹配两个独立 ID。同类过滤元素从低索引开始分,标准 ID 和扩展 ID 的过滤索引段是分开的,两者互不挤占。

接收 FIFO 的对应关系写在FDCAN_FILTER_TO_RXFIFO0里,这条过滤器把所有符合 Mask 规则的帧送进 FIFO0。中断回调里读 FIFO0,就只会拿到被过滤过的报文。如果不配过滤器,FDCAN 默认丢弃所有报文,这是新手最容易卡住的地方:外设能初始化、能进发送回调,但接收永远没数据,因为过滤表是空的。

5. 把 FDCAN 收到的标定数据写进 STM32G474 Flash 与验证技巧

FDCAN 最常见的落地场景是远程标定:上位机通过 can 总线把 PID 参数或出厂配置发过来,设备掉电后要保存下来。STM32G474 没有外部 EEPROM 时,可以直接写内部 Flash 的一个页,但要遵守两条规则:写入前必须按页擦除,擦除动作会把整页清成 0xFF;写入方向只能从 1 变 0,所以修改一页里的几个字节也必须先擦整页。G474 的 Flash 按双字(64 位)编程,写入地址必须 8 字节对齐,数据也要凑成 8 字节的整数倍。

#define PARAM_FLASH_ADDR 0x08060000 /* 选在 Bank2 的用户区 */ void Flash_WriteParams(uint8_t *buf, uint32_t len) { FLASH_EraseInitTypeDef erase; uint32_t page_err = 0; HAL_FLASH_Unlock(); erase.TypeErase = FLASH_TYPEERASE_PAGES; erase.Banks = FLASH_BANK_2; erase.Page = 32; erase.NbPages = 1; HAL_FLASHEx_Erase(&erase, &page_err); for (uint32_t i = 0; i < len; i += 8) { uint64_t data = 0; for (uint32_t j = 0; j < 8; j++) { if (i + j < len) data |= ((uint64_t)buf[i + j]) << (8 * j); } HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, PARAM_FLASH_ADDR + i, data); } HAL_FLASH_Lock(); }

擦除期间 CPU 从 Flash 取指会停顿,如果系统里还有 FDCAN 在收发,这几十毫秒内收到的帧会丢,标定场景要在写 Flash 前通知对方暂停发送,或者先把数据搬到中断里收完。HAL_FLASHEx_Erasepage_err在失败时记录出错页,正常流程里返回值加上对page_err == 0xFFFFFFFF的判断。Flash 写入地址要避开程序区,G474B 的 Flash 分两 Bank,把参数页放在 Bank2 尾部最稳妥,程序在 Bank1,即使擦写失误也不会把代码清掉。

验证写入结果时用内存比对:

for (uint32_t i = 0; i < len; i++) { if (*(uint8_t *)(PARAM_FLASH_ADDR + i) != buf[i]) return FLASH_READBACK_ERR; } return FLASH_READBACK_OK;

读取 Flash 不需要解锁,直接按地址取值即可。我在实际板上验证 FDCAN 链路时会先做回环测试:把 FDCAN 配成 Loopback 模式,自发自收,再把收到的参数写入上面这段 Flash 逻辑,然后复位重启,从 Flash 读回参数初始化 FDCAN 位时序。如果在回环里收到 64 字节 FD 帧且读回参数一致,说明收发链路、过滤器、Flash 存储三段都通了;此时再换 Normal 模式接真实 can 总线,用另一个 stm32g474 节点对发,同时抓取错误帧计数寄存器,错误计数持续上涨就先查数据段采样点,稳定为零再确认双方位时序一致。这种验证顺序能隔离问题,避免总线错误和 Flash 写入问题混在一起难排查。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询