十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32H723ZG FDCAN初始化失败:CCCR INIT位无法清零的排查指南

STM32H723ZG FDCAN初始化失败:CCCR INIT位无法清零的排查指南 STM32H723ZG 的 FDCAN 是块好外设但有时候它也会让人血压升高。最近调试一块板子主控是 STM32H723ZGFDCAN1 做 CAN FD 通信结果卡在初始化上HAL_FDCAN_Init() 返回 HAL_ERROR追进去一看是等待 CC CR 寄存器 INIT 位清零超时。当时第一反应是“配置写错了”但反复检查 CubeMX 参数、对比例程发现问题没那么简单。这篇文章就围绕这个典型的“FDCAN_CCCR.Init bit is not getting cleared”问题把排查思路、底层机制和最终解决办法完整梳理一遍给后来者一个能直接上手的排查路径。先说结论范围这个问题本质上不是 INIT 位本身“卡住”而是 FDCAN 外设内部的启动握手条件没有被满足。所谓握手就是软件写 1 请求进入初始化模式硬件确认软件写 0 请求退出初始化模式硬件再次确认。如果硬件在某一环没有响应表现出来的就是 INIT 位不变化。H723 上 FDCAN1 和 FDCAN2 都受相同机制影响所以下面的排查方法两个实例通用。文章会从硬件机制开始讲再逐个分析时钟、调试冻结、位时序、模式配置、消息 RAM 这几大常见坑最后给出完整可复现的解决流程。读者可以把它当成一个排查手册来用不光是看完知道“哦原来是这样”而是拿着这篇文章在调试器里一步步操作就能把问题定位。1. 问题现象FDCAN 外设卡在初始化状态1.1 从哪一刻开始发现不对先说复现路径。我用 STM32CubeMX 生成工程FDCAN1 配置为 CAN FD BRS波特率 500K/2000K时钟源选了 PLL2QGPIO 用的是 PA11(RX) 和 PA12(TX)复用功能 AF9。生成后在 main 函数里执行if (HAL_FDCAN_Init(hfdcan1) ! HAL_OK) { Error_Handler(); }结果 Error_Handler 被触发。第一次碰到的朋友可能直接怀疑 HAL 库用错了或者 CubeMX 版本有问题。但调了几天之后我发现 HAL_FDCAN_Init 内部是有超时保护的超时点是两个循环第一个循环是向 CCR 写入 INIT1 后等待硬件把 INIT 位置为 1这是“进入初始化模式确认”过程。如果这里超时说明 FDCAN 外设根本接收不到任何寄存器写入或者根本没有时钟。第二个循环是配置完参数后向 CCR 写入 INIT0等待硬件把 INIT 位真正清零这是“退出初始化模式确认”过程。如果卡在这里说明硬件校验了你写入的配置认为配置非法拒绝进入正常模式。标题里说的 “Init bit is not getting cleared” 属于第二种也就是能进入初始化模式但退出时被拒。1.2 确认到底是哪种“清不掉”在调试器里观察 CCR 寄存器关键就看两点写入 INIT1 后寄存器是否在几微秒内变为 0x00000001写入 INIT0 后寄存器是否能及时回到 0x00000000我碰到的情况是INIT1 能正常确认但写入 INIT0 后寄存器一直保持 0x01HAL 等到超时。这说明外设内部逻辑活着但校验机制认为当前配置组合不可用。刚开始我会习惯性怀疑是不是总线末端电阻没接对但后来发现硬件层面的收发器问题一般不会阻碍 INIT 位清除INIT 位反映的是 CAN 内核状态不是总线收发状态。总线错误只会在启动发送后才体现不会卡在初始化阶段。所以先把硬件干扰的念头放一放重点看软件配置和时钟状态。2. 理解 CCR.INIT 位从硬件机制找突破口2.1 INIT 位的两次握手FDCAN 的 CCR 寄存器CC Control Register是控制整个外设状态机的核心INIT 位是第 0 位。这个位有意思的地方在于它既是请求位又是状态位软件写 1请求进入初始化模式。软件写 0请求退出初始化模式。硬件根据当前状态机决定是否“接受”这个请求。在进入初始化模式时软件写 1 后只要 FDCAN 内核时钟有效硬件通常会在几个时钟周期内把 INIT 位回读为 1。这个过程不校验参数所以基本只会因时钟失效而卡住。退出初始化模式时软件写 0 后硬件会做一轮配置有效性检查包括标称位时序NBTP是否配置了有效值。如果使能了数据相位FDOE1数据位时序DBTP是否合法。如果使能了 BRSBRSE1数据位时序是否进一步满足要求。各类使能组合是否有软件层面的冲突。如果这些检查没有通过硬件会干脆不响应“退出”请求INIT 位就一直保持 1。等待超时后 HAL 返回错误。这和 CPU 配置寄存器时“写入失败”完全不是一回事配置内容其实已经写到寄存器里了只是硬件不认。2.2 什么情况下硬件会拒绝退出初始化模式我按排查中遇到过的情况把“拒绝退出”的触发条件归纳成几类触发场景硬件层面的反馈典型表现FDCAN 内核时钟缺失INIT1 都等不到确认卡在 HAL_FDCAN_Init 第一个等待循环标称位时序含非法值配置校验失败INIT0 后无响应卡在第二个等待循环CAN FD 数据位时序非法同上卡在第二个等待循环FD/BRS 模式组合非法同上卡在第二个等待循环DBGMCU 冻结生效调试暂停时状态机停摆全速运行正常单步调试就超时外设处于复位状态寄存器无法正常访问读回值异常这种“先进入再拒绝退出”的设计是 CAN IP 核保护机制的一部分。设计者希望任何非法配置都不能让控制器带着致命错误进入总线。如果读回 INIT 位一直为 1最优先要考虑的就是配置参数是否越界。3. 六大原因逐个排查含代码与寄存器验证3.1 时钟源FDCAN 内核时钟是否真的活着这是所有 FDCAN 问题里最高频的一个。STM32H723 的 FDCAN1/2 挂载在 APB1 总线上但外设内核时钟可以独立选择来源有 PLL1Q、PLL2Q、HSE、CSI通过 RCC 的 CCIPR2 寄存器中 FDCAMSEL 字段选择。只调用__HAL_RCC_FDCAN1_CLK_ENABLE()只是打开了 APB1 上的外设时钟门控FDCAN 内核时钟源如果没有配置外设内部逻辑根本无法运行。尤其是当 CubeMX 里默认选择了一个没有激活的 PLL 输出时初始化必然失败。排查这个问题先做两件事第一读 RCC-CCIPR2确认 FDCAMSEL 字段。uint32_t fdcan_clk_sel (RCC-CCIPR2 12) 0x3UL;0 表示 PLL1Q1 表示 PLL2Q2 表示 HSE3 表示 CSI。第二确认对应时钟源在 RCC-CR 或 RCC-PLLCKSELR 等寄存器中确实使能。比如 FDCAMSEL2HSE就直接看if (RCC-CR RCC_CR_HSEON) { // HSE 使能 }如果选的是 PLL2Q就需要 RCC-CR 的 PLL2ON 位置 1并且 PLL2 的 Q 分频器被配置为有效输出。我遇到过一次典型情况CubeMX 里选了 PLL2Q但 PLL2 本身没有被使能。原因是 CubeMX 时钟树里 FDCAN 的时钟源显示为灰色实际没有真正连接到 PLL2Q。生成代码后 HAL_RCCEx_PeriphCLKConfig 里 FdcanClockSelection 确实写成了 RCC_FDCANCLKSOURCE_PLL2但 RCC_CR 里 PLL2ON 还是 0。这种情况不仔细看寄存器永远发现不了。解决方案是在 HAL_FDCAN_Init 之前强制确认时钟源RCC_PeriphCLKInitTypeDef PeriphClkInit {0}; PeriphClkInit.PeriphClockSelection RCC_PERIPHCLK_FDCAN; PeriphClkInit.FdcanClockSelection RCC_FDCANCLKSOURCE_PLL2; HAL_RCCEx_PeriphCLKConfig(PeriphClkInit);如果这里执行完读 RCC-CRPLL2ON 还是 0就回到 CubeMX 时钟树重新配置 PLL2。3.2 调试冻结断点一停FDCAN 就“死”了这个原因非常隐蔽也非常容易打击信心。现象是这样的程序全速运行FDCAN 一切正常收发没问题。但只要在 HAL_FDCAN_Init 或 HAL_FDCAN_Start 处设置断点然后单步执行初始化就会卡死INIT 位清不掉。根源在于调试器的 freeze 机制。STM32 的调试模块DBGMCU可以配置外设是否在 CPU 被调试器暂停时同步冻结。FDCAN1/2 的冻结控制位在 DBGMCU-APB1FZR2 中。如果这一位被置 1当调试器暂停 CPU 时FDCAN 的时钟也会被切断所有状态机都会停在原地。INIT 位的硬件确认逻辑当然也停了。于是软件写入 INIT0 后硬件根本不执行INIT 位永远无法清零。读回的值始终是 1但这不是配置错误而是外设被暂停了。CubeMX 或某些调试器初始化脚本可能默认使能了这个冻结位。排查方法是检查uint32_t apb1fzr2 DBGMCU-APB1FZR2;如果 FDCAN1 对应的位为 1调试时必然踩坑。临时规避方法去掉 HAL_FDCAN_Init 内部的断点把断点直接放到初始化完成之后或者干脆在初始化期间用全速运行。更彻底的做法是清除冻结位DBGMCU-APB1FZR2 ~(DBGMCU_APB1FZR2_DBG_FDCAN1_STOP);注意修改这个位只是影响调试期的冻结行为不影响正常运行。但这个位通常不建议永久关闭因为有些场景下开发者确实希望在断点暂停时 CAN 也暂停避免总线上出现异常帧。所以我的建议是确认问题后只在调试期间处理这个位程序发布时保持默认配置。3.3 位时序参数非法值让硬件拒收配置位时序是所有 CAN 控制器配置中容错率最低的部分。FDCAN 对配置值有一套硬性约束超界即拒绝退出初始化模式。可以粗分为标称位时序和数据位时序参数对应寄存器字段非法条件NominalPrescalerNBTP.NBRP0 或超出 10 位范围NominalTimeSeg1NBTP.NTSEG10 或大于 255取决于寄存器位宽NominalTimeSeg2NBTP.NTSEG20 或与 SJW 冲突NominalSyncJumpWidthNBTP.NSJW大于 TSeg2或者超出范围DataPrescalerDBTP.DBRP0 或超出范围DataTimeSeg1DBTP.DTSEG10 或超出范围DataTimeSeg2DBTP.DTSEG20 或与 DataSJW 冲突DataSyncJumpWidthDBTP.DSJW大于 DataTimeSeg2一个很容易忽略的问题在 CAN FD 模式下如果标准 CAN 位时序配置合法但数据位时序配置全为 0硬件会拒绝退出初始化。因为此时 FDOE1硬件认为必须提供有效的 FD 数据相位配置。我当时排查时把数据相位 TSeg1 误配置成了 0HAL 初始化直接失败。查了半天参数表才发现那个结构体成员在 CubeMX 生成的代码里被清零了。所以我的建议是在 HAL_FDCAN_Init 之前手动打印或观察 Init 结构体的每一个字段确认没有 0 值占位。如果是从旧工程拷贝过来的代码尤其要检查。3.4 FD 模式与 BRS 组合协议异常保护FDCAN 的 CCR 寄存器里FDOE 和 BRSE 这两个位的组合有严格要求。FDOE 是 FD 模式使能BRSE 是速率切换使能。合法组合FDOE0, BRSE0标准 CAN 2.0 模式。FDOE1, BRSE0CAN FD 模式不进行速率切换即整个报文都用标称位时序传输数据相位配置可能不被使用但硬件仍可能校验。FDOE1, BRSE1CAN FD 模式数据相位使用更快的数据位时序。非法组合FDOE0, BRSE1没有 FD 模式却启用速率切换无效。如果代码里不小心配置成这种非法组合INIT 位也可能会清不掉。HAL 的 FDCAN_Init 结构体里 FrameFormat 选项是枚举类型一般不会产生这种问题但如果你手动修改寄存器就可能踩中。还有一种边界情况使能了 BRSE 但没有正确配置数据位时序。硬件要求数据相位配置必须与内核时钟匹配如果 DBRP 为 0也可能导致退出失败。排查方式很直接把 FrameFormat 临时改成 FDCAN_FRAME_CLASSIC也就是标准 CAN 模式如果这样之后 INIT 位能正常清除基本可以确定问题在 FD 模式或 BRS 模式下。然后逐步改回 FDCAN_FRAME_FD_NO_BRS最后再改 FDCAN_FRAME_FD_BRS每改一步就在总线上发一帧测试数据快速定位是哪一级配置引发的问题。3.5 外设复位与时钟门控STM32H723 的 FDCAN1/2 都有独立的强制复位位和时钟门控位。如果在调用 HAL_FDCAN_Init 之前有人执行过__HAL_RCC_FDCAN1_FORCE_RESET();但没有配对的释放复位__HAL_RCC_FDCAN1_RELEASE_RESET();FDCAN 会一直处于复位锁定状态寄存器读写实际上无效。INIT1 都等不到确认更不用说清 0。还有一种情况是时钟门控被意外关闭。FDCAN1 时钟由 RCC 的 APB1ENR 或 APB1SMENR 控制。正常流程中__HAL_RCC_FDCAN1_CLK_ENABLE()会打开它但如果在低功耗处理代码里PWR 模块或 RCC 的低功耗模式关闭了 FDCAN 时钟之后回到正常运行模式时没有重新使能外设同样无法工作。排查时直接读门控寄存器uint32_t apb1enr RCC-APB1ENR; if ((apb1enr RCC_APB1ENR_FDCANEN) 0) { // FDCAN 时钟未使能 }如果 FDCAN1 时钟门控一直为 0HAL 内部虽然会调用__HAL_RCC_FDCAN_CLK_ENABLE()但有些移植版本里这行代码可能没有生效需要确认。3.6 消息 RAM 与过滤器配置FDCAN 的发送缓冲、接收 FIFO、事件 FIFO、过滤器全部存放在一块独立的 SRAM 中叫做消息 RAMMessage RAM。H723 上每个 FDCAN 实例有 10KB 的消息 RAM。如果 HAL_FDCAN_Init 里的标准过滤器数量、扩展过滤器数量配置得过多导致消息 RAM 分配溢出同样可能造成初始化异常。尤其某些 H7 型号支持的消息 RAM 大小不同代码从其他型号移植过来时容易踩坑。排查时手动计算消息 RAM 占用标准过滤器StdFiltersNbr每个 4 字节扩展过滤器ExtFiltersNbr每个 8 字节RX FIFO 0/1每个收到报文占用最大管理的内存块取决于 FD 模式下最大数据长度。通常按 72 或更多字节估算TX Event FIFO每个 8 字节TX Buffer按 72 字节左右估算如果估算值超过实例的消息 RAM 容量就必须减少数量。不过这里要说明一个容易混淆的点HAL_FDCAN_Init 里配置的 StdFiltersNbr 和 ExtFiltersNbr 只是数量字段并不会直接做 RAM 分配。真正的 RAM 布局是在后面调用 HAL_FDCAN_ConfigRxFifo、HAL_FDCAN_ConfigTxBuffer 等接口时设置的。但硬件在退出初始化模式时会对这些数量字段与 RAM 起始地址配置做一致性检查。如果数量超出硬件支持的最大值或者与后续配置的总空间冲突退出初始化模式也可能失败。我实际遇到过从 STM32F407 移植工程把原来 CAN 接收过滤器数量 14 个直接搬到 FDCAN又叠加了 4 个 RX FIFO消息 RAM 配置越界初始化时报错。把过滤器数量减少后问题消失。4. 实操解决流程一步步把 INIT 位“劝”下来4.1 第一步读写测试确认外设基本功能用调试器连接后先做一个最简单的读写测试判断外设是否处于可访问状态。在进入 HAL_FDCAN_Init 之前手动操作寄存器hfdcan1.Instance-CCCR | FDCAN_CCCR_INIT; uint32_t cccr_val hfdcan1.Instance-CCCR;如果读回值中 INIT 位为 1说明外设虽然可能时钟有问题但寄存器总线还能访问。如果读回值恒为 0且不管怎么写都不变化问题基本锁定在时钟或复位。这里有一个小技巧读回 CC CR 时不要只看 INIT 位要确认 INIT 所在的字节是否随写入变化。如果写入 1读回 1说明 APB 总线访问正常。之后再写 0观察读回是否变 0就能判断是配置校验失败还是状态机冻结。4.2 第二步验证时钟源并修正 RCC 配置正常情况下确认时序如下// 1. 使能 FDCAN APB 时钟 __HAL_RCC_FDCAN1_CLK_ENABLE(); // 2. 配置内核时钟源 RCC_PeriphCLKInitTypeDef PeriphClkInit {0}; PeriphClkInit.PeriphClockSelection RCC_PERIPHCLK_FDCAN; PeriphClkInit.FdcanClockSelection RCC_FDCANCLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(PeriphClkInit) ! HAL_OK) { // 时钟配置失败 }如果这里返回 HAL_ERROR直接检查 PLL2 是否使能PLL2Q 是否配置了非零分频系数。CubeMX 图形界面很容易让人忽略“使能 PLL2”这一项因为它可能在另一个下拉菜单里。还有一种做法是把 FDCAN 时钟源直接改成 HSE降低排查复杂度PeriphClkInit.FdcanClockSelection RCC_FDCANCLKSOURCE_HSE;如果 HSE 本身是系统时钟源那 FDCAN 内核时钟一定存在排除时钟问题后再改回 PLL。这是一个很好的二分定位思路。4.3 第三步简化参数逐项排除把 FDCAN 初始化参数简化为最保守的组合hfdcan1.Init.ClockDivider FDCAN_CLOCK_DIV1; hfdcan1.Init.FrameFormat FDCAN_FRAME_CLASSIC; hfdcan1.Init.Mode FDCAN_MODE_NORMAL; hfdcan1.Init.AutoRetransmission ENABLE; hfdcan1.Init.TransmitPause DISABLE; hfdcan1.Init.ProtocolException DISABLE; hfdcan1.Init.NominalPrescaler 4; hfdcan1.Init.NominalSyncJumpWidth 1; hfdcan1.Init.NominalTimeSeg1 13; hfdcan1.Init.NominalTimeSeg2 2; hfdcan1.Init.DataPrescaler 1; hfdcan1.Init.DataSyncJumpWidth 1; hfdcan1.Init.DataTimeSeg1 13; hfdcan1.Init.DataTimeSeg2 2; hfdcan1.Init.StdFiltersNbr 1; hfdcan1.Init.ExtFiltersNbr 0; hfdcan1.Init.TxFifoQueueMode FDCAN_TX_FIFO_OPERATION;这里把 FrameFormat 设为经典 CAN先确保能给外设点亮。如果经典模式下 INIT 位能清掉说明外设没坏问题在 FD 模式相关配置。然后再把 FrameFormat 改回 FDCAN_FRAME_FD_BRS并检查数据相位时序。实际验证时保持其他参数不变只改 FrameFormat很快就能确定问题范围。4.4 第四步调试模式下的特殊处理如果发现全速运行正常单步调试必现超时基本就是 DBGMCU 冻结位的问题。处理方式有几种取消调试器暂停时的冻结行为最简单的办法是上电后主动清位DBGMCU-APB1FZR2 ~DBGMCU_APB1FZR2_DBG_FDCAN1_STOP;或者在初始化过程中用全速运行不要在 HAL_FDCAN_Init 内部打断点把断点设在函数返回之后。等 FDCAN 进入正常模式再单步调试其他代码。很多人会忽略这一点以为 Init 内部卡死就是固件问题。实际上只是调试器把外设冻住了。4.5 参考一份能用的 FDCAN1 初始化配置下面给出我最终调试通过的 FDCAN1 配置帧格式为 CAN FD BRS内核时钟源为 PLL2
返回列表