ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32G0B1 FDCAN实战:从CubeMX配置到总线调试全流程

STM32G0B1 FDCAN实战:从CubeMX配置到总线调试全流程 手头有个小项目需要用CANFD做ECU诊断刷写我对比了一圈芯片最后敲定STM32G0B1。这颗料在M0内核里算异类两路FDCAN、主频64MHz、价格也压得够低非常适合做车载从站、BMS采集板这类对成本敏感又要CANFD的活儿。这篇就围绕“STM32G0B1 FDCAN实战”这一个主题从CubeMX参数配置、代码生成到实际接周立功USB转CANFD接口卡调试把整个链路完整走一遍。看完你至少能少踩一半的坑尤其是那些CubeMX默认配置带出来的隐性雷区。需要提前说明本文面向的是有一定单片机基础、但第一次接触CANFD的工程师。如果你以前只玩过STM32F103的bxCAN那正好FDCAN和经典CAN虽然都叫CAN但寄存器和编程模型完全是两回事。我会把两者差异、位时序计算、报文结构、调试工具使用都揉碎了讲照着做就能把CANFD跑起来。1. 为什么选STM32G0B1做CANFD硬件资源与场景定位1.1 STM32G0B1的FDCAN资源够不够用STM32G0B1属于STM32G0系列的高配型号Cortex-M0内核最高工作频率64MHz内置Flash最高512KBSRAM 144KB?。这里很多人会问G0系列不是主打低成本嘛做CANFD行不行实际看下来完全没问题STM32G0B1集成了两路FDCAN控制器底层用的是Bosch M_CAN IP核和G4、H7上那套FDCAN是同源的。这意味着它的编程模型和H7基本一致你在这个平台上调通的FDCAN驱动将来移植到G4或者H7上也很快。具体到FDCAN内部资源G0B1每一路FDCAN都有3个发送邮箱、2组接收FIFO每组深度3个报文以及若干过滤元素。虽然不如H7上那样动辄十几个发送缓冲但对大多数网关、诊断从站、电机控制器来说已经绰绰有余。3个发送邮箱意味着你可以把3帧不同报文排进队里硬件会自动按优先级或者FIFO顺序发出去。2组接收FIFO可以配合过滤器分别承接不同类型报文比如一个FIFO放诊断报文另一个FIFO放控制报文程序处理起来逻辑非常清晰。相比STM32F103的bxCAN那种固定邮箱模式M_CAN的编程模型更容易理解也灵活得多。bxCAN的邮箱既要当发送用又要当接收用配置起来容易绕晕。FDCAN把发送缓冲、接收FIFO、过滤元素这几个概念彻底分开了你只需在CubeMX里勾选好参数生成代码后按HAL API调用即可。对于从F103迁移过来的工程师学习成本并不高主要是个别寄存器名称和初始化结构体字段变了。1.2 CANFD比经典CAN强在哪数据量与波特率双升级做CANFD项目之前得先把CANFD相比经典CAN的优势吃透。经典CAN 2.0一帧最多带8字节数据波特率最高1Mbps。这对现代汽车诊断、Bootloader刷写、多节点大数据交互来说真的不够用刷一次固件动辄几十KB甚至几MB8字节一帧跑1Mbps中间还有大量协议开销用户体验就是等着急人。CANFD把单帧数据长度直接拉到64字节并且引入BRS位控制可变波特率。一个CANFD报文分两个阶段仲裁段和数据段。仲裁段负责总线仲裁和ID传输考虑到总线上可能有多个节点同时竞争仍然保持较保守的波特率数据段在成功获得总线后可以切到高速传输常见的有2Mbps、4Mbps甚至5Mbps。这样一来单帧64字节的数据段乘以高波特率有效吞吐量直接比经典CAN高出好几倍。诊断仪刷ECU的时候这种提升非常明显。另外CANFD还加强了数据完整性。经典CAN用的是15位CRCCANFD帧长变长了如果还用15位CRC出错的概率会上升所以FD帧在数据长度超过一定字节后会采用17位或21位CRC。这一点在工程上是实打实的可靠性提升尤其是走UDS诊断刷写时每一帧数据都关系到固件能不能写对。1.3 典型应用场景诊断刷写、BMS与工业总线回到项目定位STM32G0B1 FDCAN最常见的应用场景有三类。第一类是车载诊断也就是UDS on CANFD。现在很多ECU诊断还是基于CANFD做的因为诊断SeedKey、DTC读取、Bootloader刷写都需要大数据交互CANFD的高吞吐很有优势。G0B1的性价比正好匹配这类独立ECU或T-Box从站。第二类是电池管理系统BMS电芯电压、温度数据动辄几十上百个经典CAN 8字节一帧根本装不下CANFD的64字节能把一整串电芯数据打包发出去既能减少总线上报文数量也能缩短数据刷新周期。第三类是工业伺服和机器人关节多个关节控制器之间要同步传输位置环、电流环数据数据量大且要求时延低CANFD的64字节加高速数据段能很好地满足。如果你只是做一个小型CAN网络数据量不大用经典CAN也完全够。但产品迭代的时候想升级就得上CANFD把FDCAN控制器选进去硬件布局时注意引脚分配后续软件升级就能平滑过渡。从方案选型角度讲G0B1带FDCAN且内部资源足够不会让你做到一半发现邮箱不够用。2. CubeMX配置FDCAN的完整步骤与参数计算2.1 时钟树配置FDCAN内核时钟的优先级最高很多人拿到CubeMX后先配引脚和FDCAN参数最后才看时钟树结果波特率各种不对。我建议先把时钟树理清楚因为FDCAN的位时序完全依赖内核时钟时钟选错了后面全是白搞。在STM32G0B1上FDCAN的内核时钟可以通过RCC配置选择来自PCLK还是其他时钟源。CubeMX的Clock Configuration界面里能看到FDCAN Kernel Clock这一项。我的习惯是把系统主频用PLL拉到64MHz然后让FDCAN内核时钟也取64MHz。不过要注意不是所有波特率组合都能用64MHz精确分出来后面计算的时候我会专门讲这个问题。一个比较容易踩的坑是CubeMX里Pinout配置页选中FDCAN后会自动帮你把相关时钟配好但有时候它默认选的时钟源不是你想要的那个。比如HSE外部晶振和PLL输出的精度不同如果你用了外部晶振作为系统时钟却让FDCAN走了内部HSI 16MHz波特率虽然也能算出整数但时序稳定性就差一些。我倾向于让FDCAN时钟和系统主频同源这样内部PLL输出的抖动最小。FDCAN1和FDCAN2的时钟是同一路所以如果你同时使用两路FDCAN必须保证这一路时钟对两个控制器都合适。我曾经为了把波特率凑成5Mbps专门改了HSE晶振频率结果FDCAN2的仲裁段波特率又分不整了来回改了好几轮。所以建议项目一开始就固定系统时钟方案再根据波特率反推FDCAN参数。2.2 FDCAN参数配置详解位时序和采样点计算CubeMX的FDCAN配置界面里主要关注以下参数Operation Mode、Frame Format、Nominal Bit Timing、Data Bit Timing、Synchronization Jump Width。其中Nominal时间参数对应仲裁段波特率Data时间参数对应数据段波特率。先讲位时序的原理。FDCAN控制器以CAN时钟为基础产生最小时间单元tq一个位的总时长由同步段、传播段/相位缓冲段1、相位缓冲段2三部分构成其中同步段固定为1个tq。所以总共需要的tq数等于(1 Time Quanta in Bit Segment 1 Time Quanta in Bit Segment 2)。采样点在位周期的末端附近通常在80%左右计算公式为(1 TS1) / (1 TS1 TS2)。以我常用的一组参数为例系统时钟64MHzFDCAN内核时钟64MHz仲裁段波特率1Mbps数据段波特率4Mbps。仲裁段一个位周期是1/1M1us换算成tq数量就是64MHz × 1us 64个tq。如果把Prescaler设为1那么TS1取50TS2取13这样1501364采样点为(150)/64≈79.7%属于经典CAN比较常用的采样点。同步跳转宽度SJW取4用于补偿总线节点之间的时钟偏差。数据段波特率4Mbps一个位周期是250ns64MHz下正好拆成16个tqPrescaler保持1TS1取13TS2取2这样113216采样点(113)/1687.5%。SJW取1即可因为数据段是连续字节流SJW太大反而容易误采样。这里必须特别提醒64MHz时钟无法精确产生5Mbps的数据段波特率因为1/5M200ns200ns里的64MHz时钟周期数是12.8不是整数。很多新手上来就填Data Baudrate5M结果CubeMX会提示无法找到匹配参数或者自动生成一个误差很大的分频组合导致总线错误率飙升。我有一次用4Mbps和5Mbps分别做了对比测试5Mbps在长线传输时误码率明显高于4Mbps。工程上如果非要5Mbps建议换80MHz或者更高频率的MCU或者改用G0B1的HSE直连FDCAN来凑整数分频但这会牺牲一部分时钟灵活性不是首选。在CubeMX里设置这些参数时界面上可以直接选择Prescaler、TS1、TS2的单位是tq你只需要把上面算好的数字填进去就行。注意不同HAL版本里Bit Segment 1的命名可能有细微差别有的叫Time Quanta in Bit Segment 1有的直接叫BS1含义一致。填完之后CubeMX会在左下角或者悬浮提示里显示计算出的最终波特率和采样点一定要核对一下。2.3 使能中断与生成代码后的工程结构CubeMX里FDCAN的中断配置点和普通外设一样在NVIC Settings标签页勾选FDCAN1 interrupt。这里要看你准备用什么方式接收数据如果只靠轮询查询接收FIFO那中断可以不勾。但CANFD报文64字节轮询处理在高负载下很容易丢帧所以实战中我至少会开启FDCAN1 interrupt和RX FIFO0 message pending中断。NVIC优先级这里也提醒一句G0B1是M0内核只有4个优先级不像F103有抢占优先级和子优先级的概念。M0的NVIC直接通过优先级数值控制数字越小优先级越高。CANFD通信涉及实时性如果还有其他外设中断建议把FDCAN中断优先级设得比串口高一点避免串口打印日志时把CANFD报文憋在FIFO里。配置完成后点击Generate CodeCubeMX会生成一个基础工程。main.c里外设初始化顺序是HAL_Init、SystemClock_Config、MX_GPIO_Init、MX_FDCAN1_Init等FDCAN初始化函数在fdcan.c里。打开fdcan.c可以看到HAL_FDCAN_Init和HAL_FDCAN_MspInit后者负责打开FDCAN时钟、配置中断优先级和引脚复用。如果你发现收发不正常先检查这两处的引脚和时钟有没有配对。MspInit里有一个容易忽略的地方如果用了中断CubeMX会在HAL_FDCAN_MspInit里调用HAL_NVIC_EnableIRQ(FDCAN1_IRQn)。但如果你的代码里还有别的外设使用相同的引脚复用CubeMX会在GPIO_Init里重新配置引脚可能把FDCAN的复用覆盖掉。所以建议生成代码后把Pinout部分不要大改或者每次改完重新生成后检查一下。3. FDCAN收发代码实现与中断回调写法3.1 理解FDCAN_TxHeaderTypeDef与FDCAN_RxHeaderTypeDefFDCAN发送函数并不像经典CAN库那样直接传ID和Data数组而是先填充一个发送头结构体FDCAN_TxHeaderTypeDef再把数据数组指针传进去。第一次用的人可能不太适应但其实这种设计更接近CANFD的灵活帧格式。关键字段包括Identifier帧ID、IdType标准ID还是扩展ID、TxFrameType数据帧还是远程帧、DataLength数据长度对应的DLC宏、FDFormat经典CAN还是CANFD、BitRateSwitch是否启用BRS高速数据段等。特别要注意DataLength的值它不是直接填字节数而是要填HAL定义好的宏比如FDCAN_DLC_BYTES_8、FDCAN_DLC_BYTES_12、FDCAN_DLC_BYTES_16一直到FDCAN_DLC_BYTES_64。接收头结构体FDCAN_RxHeaderTypeDef也类似读出一帧报文后里面会告诉你这个帧是经典CAN还是CANFD、有没有启用BRS、接收时错误状态指示等。这些信息在调试时很有用。比如你想确认收到的帧是不是真的以4Mbps数据段过来的查BitRateSwitch字段即可。3.2 发送端的标准写法发送一帧CANFD数据代码逻辑通常是这样的填充TxHeader填充数据Buffer调用HAL_FDCAN_AddMessageToTxBuffer。下面的例子发送一个标准ID为0x123的CANFD帧64字节数据启用BRSFDCAN_TxHeaderTypeDef hTxHeader; uint8_t txData[64]; uint8_t txBufferIndex FDCAN_TX_BUFFER0; hTxHeader.Identifier 0x123; hTxHeader.IdType FDCAN_STANDARD_ID; hTxHeader.TxFrameType FDCAN_DATA_FRAME; hTxHeader.DataLength FDCAN_DLC_BYTES_64; hTxHeader.FDFormat FDCAN_FD_CAN; hTxHeader.BitRateSwitch FDCAN_BRS_ON; hTxHeader.ErrorStateIndicator FDCAN_ESI_ACTIVE; hTxHeader.BitRateSwitch FDCAN_BRS_ON; hTxHeader.TxEventFifoControl FDCAN_NO_TX_EVENTS; for (uint8_t i 0; i 64; i) { txData[i] i; } if (HAL_FDCAN_AddMessageToTxBuffer(hfdcan1, hTxHeader, txData, txBufferIndex) ! HAL_OK) { Error_Handler(); }HAL_FDCAN_AddMessageToTxBuffer函数有4个参数句柄、发送头指针、数据指针、缓冲区索引。很多资料里会看到fifo0/fifo1之类的常量那是接收用的发送时要么指定某个发送邮箱比如FDCAN_TX_BUFFER0要么用FDCAN_TX_BUFFER0取3个邮箱里的一个。G0B1支持发送缓冲在调用前可以检查该缓冲是否空闲返回值是HAL_BUSY就说明上一帧还没发完。如果在你的HAL库版本里找不到AddMessageToTxBuffer而只有AddMessageToTxMailbox这样的函数说明你的HAL包版本比较老函数名不同但参数结构类似。这种情况下我建议升级STM32CubeG0的固件包新版本API更规范也修了一些边角Bug。3.3 接收过滤器与FIFO的回调处理接收端先要配置过滤器否则总线上的所有报文都会涌进FIFO。配置过滤器时需要注意FDCAN_FilterTypeDef结构体里的FilterID1和FilterID2的含义会随FilterType不同而不同。我最常用的方式是Mask模式即FilterID1是期望接收的报文IDFilterID2是掩码。下面的例子接收标准ID为0x123的帧掩码用0x7FF表示完全匹配只让ID等于0x123的报文进入RXFIFO0FDCAN_FilterTypeDef sFilterConfig; sFilterConfig.IdType FDCAN_STANDARD_ID; sFilterConfig.FilterIndex 0; sFilterConfig.FilterType FDCAN_FILTER_MASK; sFilterConfig.FilterConfig FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 0x123; sFilterConfig.FilterID2 0x7FF; HAL_FDCAN_ConfigFilter(hfdcan1, sFilterConfig);这里有两个细节要注意。第一FilterIndex的范围是0到某个上限G0B1的FDCAN过滤元素数量够用但如果你配置了多个过滤器Index不能冲突。第二如果只想接收某一个ID用Mask模式时FilterID2填0x7FF是对的如果你想接收连续一段ID比如0x100到0x103那FilterType改成Range模式会更方便。接收中断使能后在回调函数里取数据。FDCAN的中断回调不像串口那样直接在中断服务函数里传数据而是在stm32g0xx_hal_fdcan.c的默认回调函数里留了钩子你需要在自己代码里重写这个弱函数。读取报文用HAL_FDCAN_GetRxMessage传入FIFO编号FDCAN_RX_FIFO0或FDCAN_RX_FIFO1和接收头结构体指针、数据缓冲区指针void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { FDCAN_RxHeaderTypeDef hRxHeader; uint8_t rxData[64]; if ((RxFifo0ITs FDCAN_IT_RX_FIFO0_NEW_MESSAGE) 0) { return; } if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, hRxHeader, rxData) HAL_OK) { rxProcessFlag 1; rxId hRxHeader.Identifier; rxLen (hRxHeader.DataLength 16) 1; // 根据DLC解出实际字节数 memcpy(rxBuffer, rxData, 64); } }接收数据长度在实际使用中经常要换算因为FDCAN_DLC_BYTES_64这类宏编码后的值不是直接的字节数。最简单的方式是用HAL提供的数据长度换算宏或者直接查表。如果你的应用只固定接收64字节那直接拷贝64字节也没问题但最好还是按照实际长度处理避免把未定义的数据误用。3.4 main函数里的启动顺序FDCAN初始化后并不会立刻进入正常收发状态必须显式调用HAL_FDCAN_Start并且通过HAL_FDCAN_ActivateNotification使能想要的中断源。很多新手在CubeMX里勾了FDCAN中断但在main里忘掉ActivateNotification导致回调函数一直不执行。HAL_FDCAN_Start(hfdcan1); HAL_FDCAN_ActivateNotification(hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_TX_BUFFER_COMPLETE, FDCAN_RX_FIFO0 | FDCAN_TX_BUFFER0);启动后主循环里就可以做周期发送。对于不需要非常精确节拍的任务用HAL_Delay(1)做1ms循环也行但注意不要在中断回调里做耗时操作。我在回调里只设置标志位真正解析和处理放到主循环这样即使报文突发进来也不会卡中断。4. CANFD调试实录从回环测试到工具排查4.1 先做内部回环别急着接总线第一次调CANFD强烈建议先在CubeMX把FDCAN1的Operation Mode配成Internal Loopback模式生成代码跑一下自发自收。内部回环模式下发送的数据直接在芯片内部回到接收FIFO不需要外部连接CAN收发器也不用担心总线上有其他节点捣乱。回环模式下代码基本不用改发送一帧数据然后看接收回调是否触发。如果回环都收不到那肯定不是硬件问题而是初始化、时钟或者滤波器配置不对。我在这个阶段排查过好几次最后发现都是滤波器没生效FilterConfig配错了FIFO导致数据根本没进RXFIFO0。等回环测试通过后再改回Normal模式接上外部CAN收发器和真实总线。这一步一定要按步骤来先测板子对外发送拿CAN分析仪看报文再让分析仪主动发送看板子能不能收到。不要一上来就双向同时跑否则出了问题根本分不清是发的问题还是收的问题。4.2 用周立功USB转CANFD接口卡做总线验证调试CANFD最方便的工具就是周立功的USBCANFD系列比如USBCANFD-200U配合它的上位机软件CANTest或者ZCANPro可以配置CANFD的仲裁段波特率、数据段波特率、是否允许FD帧格式。这些配置必须和MCU端严格一致尤其是BRS模式。连接的时候记住三根线CAN_H、CAN_L、GND确认板子的CAN收发器供电正常。周立功接口卡一般内置120欧终端电阻板端也常常会加终端电阻如果两边都开了总线上就变成60欧左右信号反射会增大距离远的话容易出误码。我一般测试时只在分析仪端开终端电阻板端关掉或者反过来。用CANTest软件接收时重点看两个地方一是仲裁段波特率是否匹配二是能不能正确解析出64字节的FD帧。如果软件显示的报文是Classic CAN说明MCU发出去的还是经典CAN帧不是FD帧这时候检查FDFormat和DataLength是否设置正确。如果软件里能看到FD帧但BRS一直是off那说明BitRateSwitch没置位数据段还是跑的仲裁段低波特率。发送方向的验证也很有价值。CANTest软件里可以手动输入一帧64字节FD报文发到总线上板子的接收回调收到后用串口打印出ID和长度。我通常会在ZCANPro的报文列表里同时开几个窗口一帧MCU发给PC、一帧PC发给MCU这样双向状态一目了然。4.3 示波器和逻辑分析仪确认波形质量接口卡能解出报文不代表物理层没有问题。遇到偶发错误帧或者总线关闭最好用示波器看看CAN_H和CAN_L之间的差分波形。显性电平对应逻辑0典型幅值2V左右隐性电平对应逻辑1差分接近0V。波形要干净边沿不要太缓。如果只有逻辑分析仪很多型号也能解码CANFD不过对BRS可变波特率的支持程度不一样。有些逻辑分析仪标称支持CANFD但实际对数据段波特率高于2Mbps时解码算不稳这时候可以在上位机里手动设置BRS切换后的波特率。如果逻辑分析仪实在解不了FD帧可以先用经典CAN模式跑通链路确认物理连接没问题再切回FD。在调试过程中我经常用示波器量FDCAN_TX引脚和CAN收发器的TXD引脚对比波形是否一致。如果TX引脚有信号但总线差分波形不对那问题基本在CAN收发器或者终端电阻上和MCU配置无关了。反过来如果TX引脚根本没有波形那就要回头查FDCAN是否进入正常模式或者是不是还在初始化失败状态。4.4 常见问题排查技巧速查我把实际项目中踩过或者见过的问题整理成一张速查表遇到现象直接对号入座问题现象可能原因排查方向发送API返回HAL_BUSY发送邮箱未释放或发送缓冲被占用检查上一帧是否发送完成加大发送间隔能收到经典CAN帧但收不到FD帧滤波器配置或FDFormat不匹配确认对端发的确实是FD帧检查DLC和BRS总线一上负载就进BUS OFF波特率误差过大或终端电阻不对用示波器测位时间核对采样点接收中断不触发NVIC优先级没使能或ActivateNotification漏调检查CubeMX中断勾选和main里的启动代码滤波器配了但所有ID都能进FilterID2掩码用错Mask模式下FilterID2表示掩码不要填0发出的帧是经典CAN格式FDFormat变量设置错误确认FDFormat FDCAN_FD_CAN且DataLength用了FD DLC宏两路FDCAN同时跑一路正常一路异常内核时钟配置冲突检查RCC里FDCAN时钟源确保两路共用同一时钟且能满足波特率排查逻辑上我建议遵循“先回环、再外部先经典、再FD先单帧、再突发”的原则。从最容易确认的单一环节开始验证不要一上来就怀疑HAL库有Bug。事实上绝大多数问题都出在自己配置上。4.5 一点优化建议用串口日志辅助调试CANFD调通之后如果还想继续优化可以在每个关键API调用后打印返回值和HAL_FDCAN_GetError的归零结果。I2C、UART那种外设可能还能靠示波器盲猜FDCAN协议复杂盲猜效率太低。我一般会在板子上预留一个UART调试串口波特率115200把FDCAN的初始化状态、发送返回值、接收到的报文ID和长度都打印出来。这样不用每次接CAN分析仪光看串口日志就能知道程序跑到了哪一步。注意UART中断优先级最好比FDCAN低否则高频打印可能干扰FDCAN中断的实时性。如果做的是长时间稳定性测试建议把HAL_FDCAN_GetError的返回值定期记录下来。CANFD总线在高温、强干扰环境下容易出现位错误和CRC错误这些错误计数能帮你判断通信余量。G0B1内部有错误计数器配合HAL库读取很方便别浪费这个信息。最后再分享一个个人习惯CubeMX生成代码后我会在fdcan.c的HAL_FDCAN_MspInit里手动加一句GPIO引脚状态的初始化前检查确保FDCAN引脚没有被其他外设复用。这个操作不做的话偶尔会出现明明CubeMX配好了下载代码后却发现波形完全没输出的情况排查到最后往往是引脚冲突。养成这个习惯之后很多奇怪问题都能从根源上避免。
返回列表