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

资讯详情

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

STM32H7 FDCAN的INIT位清不掉?完整排查指南

STM32H7 FDCAN的INIT位清不掉?完整排查指南 1. 问题现象FDCAN的INIT位为什么赖着不走如果你手里的STM32H723ZG在跑FDCAN外设时发现HAL_FDCAN_Start()一直返回HAL_TIMEOUT或者在调试器里盯着FDCAN1-CCCR寄存器怎么往INIT位写0它都顽固地保持为1那恭喜你你撞上了FDCAN调试里最经典的一道坎。这个现象在不少嵌入式论坛上都被反复问过标题往往就是一句话FDCAN_CCCR.Init bit is not getting cleared。先说结论这个现象90%不是芯片坏了而是初始化时序、时钟配置、或者外围电路的问题。FDCAN的CCCR寄存器里有一个INIT位它相当于FDCAN外设的“总开关”。置1进入初始化模式这时候整个CAN控制器停下来等你配置清0请求退出初始化模式进入正常通信模式。很多人第一次接触时以为这个位跟普通寄存器位一样写0立刻生效结果发现它根本不听使唤于是开始怀疑人生。这篇文章会把这个问题从原理到实操完整拆一遍。适合谁看正在用STM32H7系列做CAN/CAN FD通信的嵌入式工程师尤其是刚把H723ZG板子焊好、第一次跑FDCAN就卡住的人。我会先讲清楚为什么INIT位会清不掉再给一份按出现频率排序的排查清单最后用寄存器级别的代码带你把初始化流程走通。1.1 卡住时的典型现场先描述一下最常见的现场看看你是不是也这样。用STM32CubeMX生成工程选好FDCAN1配置了引脚和时钟然后写代码// 初始化FDCAN HAL_FDCAN_Init(hfdcan1); // 配置过滤器 HAL_FDCAN_ConfigFilter(hfdcan1, sFilterConfig); // 启动FDCAN if (HAL_FDCAN_Start(hfdcan1) ! HAL_OK) { Error_Handler(); }结果程序就死在Error_Handler()里了。进调试器一看HAL_FDCAN_Start()的返回值是HAL_TIMEOUT。再点开寄存器窗口FDCAN1的CCCR寄存器里INIT位清清楚楚地显示1。这时候如果你去翻HAL库源码会发现HAL_FDCAN_Start()内部其实做了一件很关键的事往CCCR寄存器的INIT位写0然后不停回读这个位等它真正变成0。等待有超时限制超时就返回HAL_TIMEOUT。换句话说HAL库已经替你做了“写0并等待确认”这个动作但它等不到INIT位自己变0。问题就出在这个“等不到”上。1.2 INIT位不是普通寄存器位退出初始化是有条件的要理解为什么INIT位写0不生效得先搞清楚FDCAN内部的机制。这个位不是简单的一位锁存器它背后连着一个协议内核状态机。CCCR寄存器的INIT位在M_CAN内核设计里的行为是这样的软件写1请求进入初始化模式内核会在完成当前动作后把INIT位置1表示“我已经进入初始化模式了”软件写0请求退出初始化模式但内核首先要确认总线处于空闲状态、位时序配置有效、协议状态机可以正常启动然后才会真正把INIT位清0。打个比方INIT位就像一扇门的安全锁舌。你按下开门按钮写0只是发出请求锁舌得先确认门外没人卡着、门闩位置正确才会缩回去。如果门外正好有人顶着门总线被持续显性电平占住你按再多次按钮也没用。这个设计有什么好处它保证了配置期间不会跟总线上的实时事件打架。你正在改位时序参数总线忽然来一个报文如果控制器还在正常工作状态状态机就乱套了强制先进入初始化模式让内核停稳再让你改配置最后统一退出这样就能保证配置的一致性。所以当你发现INIT位清不掉时本质上是在说一件事FDCAN协议内核认为“现在不具备退出初始化的条件”。那到底是哪些条件不满足接下来逐一排查。2. 按出现频率排的排查清单先把大概率原因扫一遍我自己排过一批这类问题也看过同事和社区里其他人踩的坑按出现频率从高到低大概是这样的时钟没配好、引脚复用冲突、总线硬件问题、单节点无ACK、配置参数非法、API调用顺序错误。下面逐个拆。2.1 FDCAN内核时钟没配好最隐蔽的坑这是最隐蔽的一个坑因为CubeMX生成代码时如果你没有主动去配置FDCAN的时钟源时钟树页面会给你一个默认值但这个默认值往往不是FDCAN外设想要的频率甚至FDCAN的时钟源可能根本没使能。H723ZG的FDCAN1和FDCAN2挂在FDCAN内核时钟上这个时钟源可以在RCC里选择通常来自PLL1Q或PLL2Q。CubeMX生成的时钟配置宏可能是这样的__HAL_RCC_FDCANCLK_CONFIG(RCC_FDCANCLKSOURCE_PLL2Q);如果你的PLL2Q没有正确配置或者这个宏根本就没执行FDCAN内核时钟就可能为0或者一个乱七八糟的频率。更麻烦的是时钟没配好时HAL_FDCAN_Init()不一定报错因为初始化阶段只做寄存器写入不一定立刻触发问题等到HAL_FDCAN_Start()要退出初始化模式时协议内核手里没有正常的时钟来跑位时序状态机INIT位就完全不理你。排查方法不复杂进调试器看DBGMCU或者直接在RCC寄存器里确认FDCAN内核时钟是否使能并选择了正确的源。再狠一点的做法是先不管配置多少把FDCAN内核时钟调到一个已知频率比如40MHz然后去算波特率相关参数如果算出来的值和预期对不上十有八九时钟链路有问题。2.2 引脚复用和原理图冲突第二个高频坑是引脚复用配置错误。H723ZG的FDCAN引脚有多个可选映射比如FDCAN1_RX可以是PA11、PB8、PD0等FDCAN1_TX可以是PA12、PB9、PD1等。CubeMX里看着选对了实际板子上的丝印或原理图可能跟你以为的不一样。常见错误包括选用了PA11/PA12但这两个引脚在板子上同时被USB的DM/DP占用信号互相干扰GPIO配置成了输出模式而不是复用模式Alternate Function选错了比如FDCAN1的AF是AF9你配置成别的AF编号引脚上根本出不来CAN信号在同一组GPIO上初始化了两个外设后者把前者的复用配置覆盖了。正常的GPIO复用配置应该是这样GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_FDCAN1_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_11 | GPIO_PIN_12; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);如果你确认GPIO没问题再用万用表量一下PA11和PA12到CAN收发器之间的走线是否通畅有些板子的FDCAN引脚和收发器之间还有跳线或0Ω电阻没焊就断开了。2.3 总线硬件终端电阻、收发器、线序第三个高频坑在芯片外面不在芯片里面。FDCAN的INIT位退不退出某种程度上取决于它能不能在总线上检测到“空闲状态”。如果总线上一直有显性电平协议内核会认为总线忙从而拒绝退出初始化模式。CAN总线的物理层是差分信号隐性状态时CANH和CANL都约为2.5V显性状态时CANH上升到约3.5VCANL下降到约1.5V。正常空闲时总线应该是隐性的。如果CANH和CANL被短路在一起或者收发器故障把总线钳位在显性状态FDCAN就永远等不到总线空闲INIT位就永远清不掉。终端电阻也是经典问题。标准CAN总线要求在总线两端各接1个120Ω电阻。有人可能觉得“我就一个节点接1个120Ω就行了”这个说法在极短距离、低速率的实验场景下可能能跑但不是可靠的做法。如果你的板子上已经有一个120Ω终端电阻然后你又接了一个带120Ω终端的分析仪那总线上就有两个120Ω并联等效60Ω这也是正常的。但如果你用万用表量CANH和CANL之间电阻为0说明短路了如果远大于120Ω说明终端电阻没接或者接触不良如果只有60Ω左右说明两头都有终端这是标准状态。还有个容易被忽视的点CAN收发器不能少。有人做实验时会直接把FDCAN的TX/RX引脚飞线接到另一个板子的TX/RX上没有收发器芯片这是不行的。CAN是差分信号MCU的引脚是单端逻辑电平必须经过收发器转换成差分信号才能在总线上通信。没有收发器RX引脚可能一直处于不确定电平总线空闲检测根本没法做。2.4 单节点调试总线没有第二个节点也可能卡住这是新手最容易忽略的问题。CAN协议里发送节点发出的帧需要至少一个其他节点在总线上确认ACK否则发送节点会报ACK错误。如果你只拿一块板子没有接分析仪、没有接第二块板子直接用正常模式跑FDCAN发送帧时就会反复报ACK错误错误计数器一路飙升。有些情况下这会导致FDCAN的协议内核持续处理错误状态表现为INIT位清不掉或者清掉了但程序马上又进入错误状态。你可能会觉得“我自发自收总行吧”但默认配置下FDCAN并不支持自发自收那是回环模式Loopback才干的事。所以单节点调试时最省事的办法是先切到回环模式验证FDCAN内核本身是否工作正常。回环模式下FDCAN内部把TX回环到RX不依赖外部总线不依赖终端电阻也不需要另一个节点。如果回环模式下能正常收发说明芯片和外设配置没问题问题百分之百出在总线物理层或外部节点上。2.5 CCCR.CCE没有先置位配置根本没写进去还有一个很隐蔽的坑出在寄存器操作顺序上。FDCAN的CCCR寄存器里除了INIT位还有一个CCE位Configuration Change Enable。它的作用是只有在INIT1且CCE1的情况下才允许修改FDCAN的配置寄存器如NBTP、DBTP、TXBC等。如果你只置了INIT没置CCE就去写那些配置寄存器写入会被忽略。这个坑的阴险之处在于HAL_FDCAN_Init()内部其实会同时处理INIT和CCE正常情况下不会出问题。但如果你是自己操作寄存器或者在某些库的早期版本里可能会漏掉CCE。漏掉的后果就是配置寄存器写了个寂寞全是默认值或者乱值退出初始化时时序参数无效INIT位自然清不掉。正确顺序永远是// 1. 请求进入初始化模式同时使能配置更改 FDCAN1-CCCR | (FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); // 2. 等待INIT位置1确认进入初始化模式 while ((FDCAN1-CCCR FDCAN_CCCR_INIT) 0) {} // 3. 此时才能写NBTP、DBTP、TXBC等寄存器 // 4. 配置完成后请求退出初始化模式 FDCAN1-CCCR ~FDCAN_CCCR_INIT; // 5. 等待INIT位自动清0 while ((FDCAN1-CCCR FDCAN_CCCR_INIT) ! 0) {}很多人卡在第5步的等待循环里回头检查时发现第3步的配置寄存器全是错的根源就是第1步没把CCE置到位。3. 寄存器级实操手把手把初始化流程调通排查完上面那些大概率原因后如果问题还在那就得回到代码本身把初始化流程一步步走一遍。这一章我会给出HAL库和裸寄存器两种方式的完整代码并解释每一步背后在干什么。3.1 HAL库方式的初始化与启动代码先看一个标准、可用的HAL库初始化配置。假设FDCAN内核时钟为40MHz目标是CAN FD模式仲裁段波特率625kbps数据段1.25Mbps。FDCAN_HandleTypeDef hfdcan1; 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 ENABLE; hfdcan1.Init.TransmitPause DISABLE; hfdcan1.Init.ProtocolException DISABLE; // 仲裁段位时序预分频4SyncSeg固定为1TimeSeg113TimeSeg22 // 总time quanta 1 13 2 16 // 波特率 40MHz / (4 * 16) 625kbps hfdcan1.Init.NominalPrescaler 4; hfdcan1.Init.NominalSyncJumpWidth 1; hfdcan1.Init.NominalTimeSeg1 13; hfdcan1.Init.NominalTimeSeg2 2; // 数据段位时序预分频2DataTimeSeg113DataTimeSeg22 // 总time quanta 1 13 2 16 // 数据段波特率 40MHz / (2 * 16) 1.25Mbps hfdcan1.Init.DataPrescaler 2; hfdcan1.Init.DataSyncJumpWidth 1; hfdcan1.Init.DataTimeSeg1 13; hfdcan1.Init.DataTimeSeg2 2; // Message RAM配置跟INIT位没关系但会影响后续收发 hfdcan1.Init.StdFiltersNbr 1; hfdcan1.Init.ExtFiltersNbr 0; hfdcan1.Init.RxFifo0ElmtsNbr 32; hfdcan1.Init.RxFifo0ElmtsSize FDCAN_DATA_BYTES_8; hfdcan1.Init.RxFifo1ElmtsNbr 0; hfdcan1.Init.RxFifo1ElmtsSize FDCAN_DATA_BYTES_8; hfdcan1.Init.TxEventsNbr 0; hfdcan1.Init.TxBuffersNbr 0; hfdcan1.Init.TxBuffersSize FDCAN_DATA_BYTES_8; hfdcan1.Init.TxFifoQueueMode FDCAN_TX_FIFO_OPERATION; if (HAL_FDCAN_Init(hfdcan1) ! HAL_OK) { Error_Handler(); } }这里给一个理解位时序的公式波特率 FDCAN内核时钟 / (预分频 × (1 TimeSeg1 TimeSeg2))。那个“1”是固定的同步段CAN协议规定同步段就是1个time quanta不用配置。你只要调整预分频和时间段的比例就能得到想要的波特率。HAL_FDCAN_Init()内部做了一件事先把CCCR的INIT位置1然后配置用户给的参数但注意它不会自动修改FDCAN时钟源时钟源要你自己在RCC里配好这也是为什么前面说时钟坑最隐蔽。启动时// 配置过滤器至少要配一个否则收不到任何报文 FDCAN_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; // mask模式下表示全部匹配 HAL_FDCAN_ConfigFilter(hfdcan1, sFilterConfig); // 启动FDCAN if (HAL_FDCAN_Start(hfdcan1) ! HAL_OK) { // 在这里打断点看返回值和寄存器 Error_Handler(); }如果HAL_FDCAN_Start()真的超时了先别急着改代码在Error_Handler()里打开寄存器窗口看这几个值hfdcan1.Instance-CCCR的INIT位、hfdcan1.Instance-PSR的LEC和BO位、hfdcan1.Instance-ECR的REC和TEC。这些寄存器会告诉你FDCAN内部到底在经历什么。3.2 裸寄存器方式的关键顺序如果你用的是裸机风格不想依赖HAL库那更要理解INIT和CCE的配合顺序。下面这段代码展示标准流程// 使能FDCAN1时钟 __HAL_RCC_FDCAN1_CLK_ENABLE(); // 配置GPIO复用PA11RX, PA12TX, AF9 // ... GPIO_InitStruct 配置 ... // 要求进入初始化模式 禁止配置更改先清CCE FDCAN1-CCCR ~FDCAN_CCCR_CCE; FDCAN1-CCCR | FDCAN_CCCR_INIT; // 等待INIT位置1确认进入初始化模式 while ((FDCAN1-CCCR FDCAN_CCCR_INIT) 0) {} // 使能配置更改 FDCAN1-CCCR | FDCAN_CCCR_CCE; // 配置标准/扩展ID过滤、FIFO等示例省略 // 配置位时序寄存器NBTP // 例如40MHz内核时钟625kbps预分频4TimeSeg113TimeSeg22 FDCAN1-NBTP (4 - 1) 16 | (2 - 1) 12 | (13 - 1) 8 | (4 - 1) 0; // 注意NBTP各字段的宽度和偏移以参考手册为准这里只示意 // 配置完成后请求退出初始化模式 // 注意不要动CCE让硬件在退出后自动清CCE FDCAN1-CCCR ~FDCAN_CCCR_INIT; // 等待INIT位自动清0 uint32_t timeout 10000; while ((FDCAN1-CCCR FDCAN_CCCR_INIT) ! 0) { if (--timeout 0) { // 超时进入错误处理 break; } }这段代码里最关键的就是每一行的顺序。很多人图省事直接一行FDCAN1-CCCR ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE);想把两个位都清掉这会导致CCE在INIT还没退出的过程中被提前清零配置更改被锁定后面再想补救就难了。3.3 一次真实调试记录调试器里看到的寄存器变化我调过一块H723ZG的板子现象跟标题一模一样HAL_FDCAN_Start()返回超时CCCR.INIT位一直是1。当时我用调试器做了几件事第一步看hfdcan1.Instance-CCCR的值。发现INIT1CCE0。INIT1说明外设还在初始化模式CCE0说明配置更改没使能。理论上HAL库返回超时后这个状态是有可能的但如果有其他代码在启动后又把CCCE改了就会出问题。第二步看hfdcan1.Instance-ECR。TEC和REC都是0说明没有错误帧计数芯片也没在发错误帧。第三步看hfdcan1.Instance-PSR。BO位是0不在BusOff状态LEC字段是0没有记录到最近错误类型。这一系列寄存器状态告诉我内部没有错误、没有BusOff、没有错误帧那问题大概率出在外部总线状态。我拿万用表量了CANH和CANL之间电阻显示120Ω正常再量电压发现CANH和CANL都是0V。问题找到了CAN收发器的供电没接。收发器芯片是5V供电板子上那个5V电源域的LDO没焊导致收发器完全不工作CANH和CANL都被拉到了地电平总线一直处于非正常的显性状态FDCAN等不到空闲INIT位自然清不掉。补焊LDO后程序一次跑通。这个案例想说明的是当内部寄存器状态看似正常没有错误计数、没有BusOff时你的排查方向就该从芯片内部转向芯片外部了。4. 常见问题速查表与排障工具箱这一章整理一份速查表把上面提到的大部分现象、原因、解法浓缩在一起再讲一下排查工具怎么用。调试时我都习惯把寄存器窗口、示波器、逻辑分析仪同时准备好因为光靠代码层面的猜测效率太低。4.1 一张表搞定80%的INIT卡住问题现象可能原因检查方法解决办法HAL_FDCAN_Start返回超时INIT1ECR无计数外部总线上持续显性电平万用表量CANH-CANL电压和电阻检查收发器供电、终端电阻、总线是否短路INIT1PSR.BO1总线处于BusOff状态读PSR寄存器检查总线错误帧来源降低总线负载INIT1CCE0配置更改使能未置位读CCCR寄存器按标准顺序INIT置1后再置CCE配置寄存器写不进去CCE没置位就写配置读NBTP/DBTP确认写入值先置INIT1和CCE1再写配置每次上电都卡有时又正常时钟源不稳定或未使能检查RCC时钟树配置确认FDCAN内核时钟源和分频单节点自发自收不行没有启用回环模式查看Mode配置单节点调试先切回环模式接了分析仪后正常不接就卡总线上只有单节点无ACK接分析仪或另一个节点调试期保持至少两个节点PA11/PA12引脚异常引脚被其他外设占用看原理图、量波形换其他可用引脚或解决占用这张表覆盖了我见过的大部分情况。如果你的问题不在表里那多半是更偏门的寄存器配置或硬件设计问题继续往下看。4.2 常用排障工具和判断思路工具不在多关键是用对。我自己调试FDCAN这类问题时固定会用到这几样万用表是所有排查的第一步。量电源、量终端电阻、量CANH和CANL之间的阻抗这些能在30秒内排除一大半硬件问题。量的时机也有讲究板子断电时量终端电阻板子上电时量差分电压。示波器的作用是看总线上的实际波形。正常空闲时CANH和CANL应该都在2.5V附近有报文传输时能看到明显的差分跳变。如果示波器显示CANH和CANL被拉到了0V或者5V那就是物理层不正常。示波器还有个好用的点用单次触发抓总线上的错误帧能看出是哪一段位时序不对。逻辑分析仪主要用来抓MCU引脚上的数字信号比如PA11RX和PA12TX上有没有数据。如果有数据但总线波形不对问题在收发器如果MCU引脚上就没数据问题往上追溯到配置或状态机。CAN分析仪是调试CAN总线最直接的工具。PCAN、SocketCAN配合USB转CAN模块、或者周立功的USBCAN都可以。分析仪不仅能看到总线上的报文还能看错误帧、测量总线负载。单节点调试时用一个CAN分析仪挂在总线上等于给总线加了一个真实的第二节点很多INIT位清不掉的问题会直接消失。调试器SWD这边除了看CCCR、PSR、ECR这几个寄存器外我还习惯在HAL库的等待循环里打断点观察INIT位在等待过程中的变化趋势。如果INIT位偶尔抖动一下又变回1说明有外部事件在打断退出流程如果纹丝不动说明条件根本没满足过。5. 个人经验与建议最后聊几点我踩过几次坑之后的个人习惯不一定写在参考手册里但实践中很有用。5.1 遇到INIT卡住时的调试顺序建议我的固定顺序是先看寄存器 → 再量硬件 → 最后改代码。具体来说先看CCCR、PSR、ECR这三个寄存器把芯片内部状态摸清楚。INIT1是必然的关键看CCE、BO、LEC。如果CCE是0优先怀疑有人动了CCE或者初始化顺序不对如果BO是1说明总线刚经历过BusOff得先等恢复或者手动清除如果LEC记录的是ACK错误那大概率是单节点没接第二个节点。然后量硬件。万用表量终端电阻、收发器供电、CANH/CANL电压。示波器看空闲时总线是不是在2.5V。如果硬件一切正常再回头审视代码。最后才是改代码。改代码时优先做两个验证一是把模式切成内部回环确认FDCAN内核本身没毛病二是把波特率相关参数简化成最经典的配置——比如1Mbps、预分频和时段取整排除参数非法导致的状态机不启动。5.2 一个值得养成的调试习惯我自己后来养成的习惯是在FDCAN相关的每个关键寄存器写入后加一个短暂的读回确认。虽然看着啰嗦但在排查这类问题时能省大量时间。例如在HAL库的HAL_FDCAN_Start()返回超时后我会立刻在Error_Handler()里补一行代码把当时的寄存器值dump出来void Error_Handler(void) { volatile uint32_t cccr_val hfdcan1.Instance-CCCR; volatile uint32_t psr_val hfdcan1.Instance-PSR; volatile uint32_t ecr_val hfdcan1.Instance-ECR; __BKPT(0); while (1) {} }然后把这三个值记下来再去对照速查表。很多时候解决问题的关键信息就在这几个数字里只是你平时没花时间去读它们。最后再分享一个适用于任何FDCAN调试场景的小技巧如果你手头有示波器把探头夹在CAN_RX引脚MCU侧不是收发器侧上然后上电跑程序。正常工作时CAN_RX引脚应该有一条稳定的高电平线因为隐性状态对应RX引脚的高电平。如果你看到这条线被拉低或者乱七八糟地跳说明总线上有东西在捣乱先从物理层入手排查别急着改寄存器配置。
返回列表