ARTICLE DETAIL

资讯详情

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

STM32F407改主频后串口乱码:时钟配置与PLL参数排查全指南

STM32F407改主频后串口乱码:时钟配置与PLL参数排查全指南 搞了半天串口吐出来的还是乱码。板子上一秒还在跑 168MHz我把主频降了一档之后重新上电一排看不懂的符号就铺满了整个终端窗口。长按复位没用拔掉重新上电也没用把波特率从这个值切到那个值乱码还是一样的顽固。我当时心里冒出来的第一个念头是这芯片是不是被我写挂了先别急着怀疑芯片也别急着翻手册查“串口乱码怎么解决”。遇到这种问题九成以上都是时钟链路改完之后外设时钟和你以为的不一致了。这篇文章我就以 STM32F407 加 HAL 库为例把时钟频率修改的来龙去脉、改完之后的乱码排查思路、以及我踩过的一堆坑全部摊开讲。无论你是刚用 HAL 库写点灯程序的新手还是准备把工程从别的板子迁移到 F407 的老手只要遇到“改主频之后串口打印乱码”这种问题按这篇文章的思路走一遍基本都能定位到根因。1. 乱码的真相串口波特率背后的时钟从哪来1.1 乱码不是“坏了”是双方没对齐串口通信这件事本质上就是两个设备之间按照约定的节奏一格一格地把电平高低读出来。这个节奏就是波特率单位是 bps也就是每一秒传多少个 bit。发送端按照自己内部时钟的节奏把电平拉高拉低接收端按照自己内部时钟的节奏去采样这些电平变化。如果两边的节奏不一致接收端就会在错误的时刻采到错误的值最终表现出来就是一团乱码。所以串口乱码绝大多数情况下都指向一个问题收发双方的波特率不一致。这里说的不一致并不是说你在串口助手里选的是 115200实际就是 115200因为串口助手的波特率是固定的问题往往出在单片机这一侧——单片机认为自己发的还是 115200但实际上因为时钟变了它真正发出去的波特率已经完全偏离了。串口助手用 115200 去解自然解出一堆垃圾。在 HAL 库的工程里波特率不是由你手动算好再写进寄存器的而是 HAL_UART_Init() 这个函数会自动计算。它计算的时候读的是串口所在的 APB 总线时钟频率也就是 PCLK1 或者 PCLK2。问题就出在这里如果你改了系统时钟改了 APB 分频但是没有让 HAL 库在正确的时机重新计算波特率或者改的配置本身就让 PCLK 到了一个你意想不到的值那串口就废了。1.2 STM32F407 时钟树上的三个关键节点要把这个问题讲清楚得先看一下 STM32F407 这个芯片的时钟分配逻辑。它的时钟源头有好几个我们最常用的就是外部高速晶振也就是 HSE一般板子上贴的就是 8MHz 的石英晶体。HSE 进来之后经过 PLL 锁相环倍频产生系统主时钟 SYSCLK。SYSCLK 再往下走先经过 AHB 预分频器得到 HCLK也就是给 CPU 内核和大部分总线用的时钟HCLK 随后又分出两条路经过 APB1 预分频器和 APB2 预分频器得到 PCLK1 和 PCLK2。F407 默认的配置是这样的外部 8MHz 晶振PLL 倍频到 168MHzAHB 不分频所以 HCLK 也是 168MHzAPB1 是 4 分频得到 42MHzAPB2 是 2 分频得到 84MHz。这三组数字要刻在脑子里因为后面所有外设的时钟上限都是从它们来的。APB1 这条总线上的外设最大只能跑 42MHzAPB2 上的外设最大可以跑到 84MHz。USART1 和 USART6 挂在 APB2 总线上USART2、USART3、UART4、UART5 挂在 APB1 总线上。你用的串口是几号它的波特率就和对应那根总线的 PCLK 直接相关。修改系统主频的时候如果没有同步调整 APB 分频系数让 PCLK1 超出 42MHz 上限或者 PCLK2 超出 84MHz 上限外设工作就会不稳定表现出来就有可能是乱码。1.3 HAL 库初始化顺序里的隐藏规则HAL 库工程里main() 函数开头一般长这样int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); while (1) { } }这个顺序是有讲究的。SystemClock_Config() 必须先于所有外设初始化执行因为 HAL_UART_Init() 这类外设初始化函数内部会去调用 HAL_RCC_GetPCLK1Freq() 或 HAL_RCC_GetPCLK2Freq() 来读取当前的 PCLK 频率然后基于这个频率计算波特率分频系数。如果时钟还没配置好或者时钟配置代码本身有问题外设初始化拿到的 PCLK 就是错的后面怎么调都不可能对。所以排查乱码问题第一步不是去怀疑串口助手而是去确认一件事SystemClock_Config() 执行完之后PCLK1 和 PCLK2 到底是多少这个值必须和你预期的一致而且串口初始化必须排在时钟配置之后。2. 时钟修改的三种姿势和它们暗藏的坑2.1 用 CubeMX 重新配置时钟树最省心但有两个前置条件如果你的工程一开始就是用 STM32CubeMX 创建的那么修改主频最直观的方式就是打开 .ioc 文件进入 Clock Configuration 这个选项卡。界面上画了一棵完整的时钟树你可以在对应的输入框里修改 PLL 参数、AHB 分频、APB1 分频、APB2 分频。CubeMX 在你修改的时候会实时判断当前配置是否超出芯片手册给出的范围如果超了相关输入框会变红这个设计很贴心可以避免很多低级错误。改完之后重新生成代码工程里的 SystemClock_Config() 会自动更新串口初始化也会在生成时拿到正确的参数。但用 CubeMX 改时钟有两个前置条件必须注意。第一个是你要保证 CubeMX 认为的外部晶振频率和你的板子实际贴的晶振频率一致。CubeMX 新建工程的时候默认 HSE 的值是 8MHz。如果你的板子实际用的是 25MHz 晶振而你没有在 Clock Configuration 界面的 HSE 输入框里改成 25那么生成出来的 PLL 参数是按 8MHz 设计的实际跑起来你就会发现系统时钟完全不对串口乱码只是其中一个表现。第二个是CubeMX 重新生成代码之后可能覆盖你手动改过的其他文件所以如果你在外设初始化之后加过自己的打印代码改完时钟要检查一遍。2.2 手动修改 PLL 参数必须会算的一个公式没有用 CubeMX或者想自己掌控一切的人通常会直接改 SystemClock_Config() 函数。那就绕不开 PLL 的计算公式。F407 的 PLL 工作方式是这样的外部时钟 HSE 先经过一个分频器 M 分频得到 PLL 的输入参考时钟然后再经过一个倍频器 N 倍频得到 VCO 输出VCO 输出再经过一个输出分频器 P 分频最后得到的就是 SYSCLK。用公式表示就是SYSCLK HSE / M * N / P拿最常见的 8MHz 外部晶振、168MHz 系统主频举例CubeMX 生成的参数是 M8、N336、P2代入公式就是 8 / 8 * 336 / 2 168MHz。M 的作用是把 8MHz 降到 1MHzN 负责拉升频率P 把它降回合理范围最终精度可以做到很高。手动改参数的时候要时刻盯住几个约束条件。VCO 的输出频率有一个允许范围F407 的参考手册建议保持在 100MHz 到 432MHz 之间。最大系统时钟是 168MHz不要再往上硬拉了虽然有人超频到 180MHz 甚至更高还能跑但稳定性没有保障而且 FLASH 等待周期等一系列参数都要跟着调不建议在产品上这么干。P 只能是 2、4、6、8 这几个偶数。改了 N 之后还要顺手检查一下 PLL_Q因为 Q 是用来给 USB OTG FS 提供 48MHz 的时钟的公式是 VCO / Q 48MHz。很多人改了主频之后 USB 识别不了设备原因往往就是这里。2.3 更换外部晶振老手也容易漏掉的宏定义第三种情况比较隐蔽就是你的板子换了外部晶振。比如以前用的 8MHz现在换成了 25MHz或者反过来。这种情况下即使你重新算了 PLL 参数把 SYSCLK 配回了 168MHz依然可能遇到串口乱码因为 HAL 库里面很多地方依赖一个叫 HSE_VALUE 的宏它定义在 stm32f4xx_hal_conf.h 文件里默认值是 8000000。如果你把板子上的晶振从 8MHz 换成了 25MHz但 HSE_VALUE 还是 8000000那么 HAL 库在所有需要用到晶振频率的地方都会算错。这个宏必须手动改改成你实际使用的晶振频率。我见过不止一个人在 CubeMX 的时钟树界面里把 HSE 改成了 25生成的代码里 PLL 参数也是对的但就是不注意 HSE_VALUE 这个宏结果程序跑起来全是问题。CubeMX 在较新版本里会自动同步这个宏但手动改代码的工程里它不会自己变务必检查。3. 实操记录把主频从 168MHz 降下来再恢复3.1 这次要完成的目标纸上谈兵讲了半天现在实际动手走一遍。我手上这块 F407 板子外部晶振是 8MHz原来的系统主频是 168MHzUSART1 使用的是 115200 波特率串口打印一切正常。现在的需求是把系统主频降到 84MHz看看会发生什么。为什么要主动降频有些场合用不到这么高的性能降下来之后功耗可以低不少发热也会小很多。正好可以用这个过程来演示一下改主频之后串口会发生什么变化。拆开 SystemClock_Config()原来的核心代码是这样的static void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM 8; RCC_OscInitStruct.PLL.PLLN 336; RCC_OscInitStruct.PLL.PLLP RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ 7; if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_5) ! HAL_OK) { Error_Handler(); } }这个配置读起来很直观PLLM8PLLN336PLLP2所以 SYSCLK 是 168MHzAHB 不分频所以 HCLK 也是 168MHzAPB1 4 分频得 42MHzAPB2 2 分频得 84MHzFLASH 等待周期是 5。3.2 降频到 84MHz还要不要 USB 是两套方案要把主频降到 84MHz我第一反应是把 PLLN 从 336 改成 168这样 8 / 8 * 168 / 2 84MHzPLLP 仍然是 2公式上完全没问题。但这时候如果我还需要 USB 功能PLLQ 就尴尬了因为 VCO 输出是 168MHz168 / 7 24MHz不是 48MHz。USB 直接罢工。所以这里有个分岔路口。如果不需要 USB只是为了跑串口、跑 GPIO、跑定时器那用 PLLN168、PLLP2 这个方案最简单VCO168MHz也在手册允许范围内。如果还要保留 USB那就得换一种思路PLLN 还是 336PLLP 从 DIV2 改成 DIV4这样 SYSCLK 336 / 4 84MHzVCO 仍然是 336MHzPLLQ 保持 7 不变USB 的 48MHz 就能正常出来。我这次工程里恰好用了 USB 虚拟串口所以我选择第二种方案把所有参数都摆出来static void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM 8; RCC_OscInitStruct.PLL.PLLN 336; RCC_OscInitStruct.PLL.PLLP RCC_PLLP_DIV4; RCC_OscInitStruct.PLL.PLLQ 7; if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2) ! HAL_OK) { Error_Handler(); } }注意看 APB1 分频从原来的 DIV4 改成了 DIV2。为什么因为 SYSCLK 是 84MHzAHB 不分频HCLK 就是 84MHzAPB1 如果还是 DIV4那 PCLK1 就是 21MHz虽然不超过 42MHz 的上限但我的板子上挂了一个跑在 36MHz 的外设它要求 PCLK1 尽量高一些所以我把 APB1 改成 DIV2PCLK1 回到 42MHz和原来 168MHz 配置时一致。APB2 我改成了 DIV1这样 PCLK2 就是 84MHz和原来也保持一致。这样做的好处是APB 总线上所有外设的时钟都和原来一样对它们来说系统主频的变化几乎是透明的。这里还要专门说一句 FLASH_LATENCY_2。84MHz 的系统主频对应的 FLASH 等待周期是 2不能再沿用原来的 FLASH_LATENCY_5。如果等待周期配大了Flash 读取速度跟不上 CPU程序执行会出问题配小了又会因为 Flash 还没准备好就取指直接 HardFault。这个值在《STM32F407 参考手册》里有表格可以查不同主频区间对应不同等待周期。3.3 验证手段把时钟参数全打印出来改完代码烧录之前我建议先把验证代码写好。强烈建议在串口初始化完成之后立刻打印一行当前时钟信息不要只打印一个“Hello”就完事。打印的内容至少包括 SYSCLK、HCLK、PCLK1、PCLK2 这四个值这样一旦出问题你能第一时间知道是哪个环节错了。用 printf 重定向到 USART1写法很简单int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }然后在 main 函数里加一行打印printf(SYSCLK: %lu Hz\n, HAL_RCC_GetSysClockFreq()); printf(HCLK : %lu Hz\n, HAL_RCC_GetHCLKFreq()); printf(PCLK1: %lu Hz\n, HAL_RCC_GetPCLK1Freq()); printf(PCLK2: %lu Hz\n, HAL_RCC_GetPCLK2Freq());HAL_RCC_GetPCLK1Freq() 和 HAL_RCC_GetPCLK2Freq() 这两个函数内部会读取 RCC 寄存器里的 APB 分频配置再结合 SYSCLK 的值算出当前 PCLK。注意这两个函数返回的值是实时从寄存器里读出来的不是你写进某个变量里的预期值所以用它来判断时钟是否真的切换成功非常可靠。我把代码编译下载到板子上打开串口助手波特率仍然是 115200这次终端里干干净净地出现了期望的打印内容。因为 APB 总线的分频被我刻意保持和原来一致所以 PCLK1 和 PCLK2 都是熟悉的 42MHz 和 84MHzUSART1 的波特率计算逻辑因此没有受到任何影响。3.4 换个改法立刻复现乱码为了演示乱码是怎么来的我又试了另一种改法PLLN168、PLLP2、APB1 分频维持 DIV4、APB2 分频维持 DIV2也就是只把主频降到 84MHzAPB 分频一点不动。这样一来SYSCLK84MHzHCLK84MHzPCLK121MHzPCLK242MHz。USART1 挂在 APB2 总线上PCLK2 从 84MHz 降到了 42MHz而 HAL_UART_Init() 在系统启动时早就按 42MHz 算完波特率了。看起来波特率应该能对上但实际情况并非如此。原因在 USART 的时钟倍频逻辑。STM32F4 的 USART 有一个特殊机制当 APB 分频系数为 1 时USART 的时钟源就是 PCLK当 APB 分频系数大于 1 时USART 的时钟源是 PCLK 的两倍。原来 APB2 分频是 DIV2所以 USART1 实际时钟是 PCLK2 的两倍也就是 168MHzHAL 库按这个值计算了波特率分频。现在改成 APB2 分频还是 DIV2但 PCLK2 是 42MHzUSART1 实际时钟就变成了 84MHz。这还没完因为 HAL 库初始化的时候按当时的寄存器配置算出的是 115200 对应的分频值那个值在 84MHz 的 USART 时钟下实际产生的波特率和 115200 已经差了一倍左右。我重新烧录了这个版本串口助手立刻开始吐乱码。乱码形态很典型看起来像是每个字符都变了但又不是完全随机。这时候我故意把串口助手的波特率从 115200 改成 57600乱码立刻恢复了正常。这一步操作基本可以实锤问题出在单片机发出来的实际波特率已经不是 115200 了。所以核心结论很简单修改系统主频时如果 APB 分频系数变了串口实际波特率一定会变哪怕 APB 分频系数没变但 PCLK 因为 SYSCLK 的变化而变化串口波特率同样会变。唯一安全的做法就是保持串口所在 APB 总线的 PCLK 频率在你修改前后完全一致。4. 乱码问题排查速查表与调试三板斧4.1 从乱码现象反推原因串口乱码这件事不是所有乱码都是同一个原因。我整理了一个速查表遇到问题先对照一下现象现象可能原因排查方向一上电就全是乱码一个字符都不对时钟配置错误PCLK 和初始化时不符打印时钟参数检查 PLL 和 APB 分频前几个字符正常后面全乱串口发送和外部中断抢占了 CPU或者芯片频繁复位查中断优先级查看复位原因用十六进制看每个字节都比预期大/小有规律波特率偏移通常是实际波特率是预期的一半或两倍尝试切换串口助手波特率验证只有中文乱码英文和数字正常编码问题不是时钟问题串口助手换成 UTF-8 或者 ASCII改完主频后 USB 也不识别了PLL_Q 没有配置出 48MHz检查 PLL_Q确保 VCO / Q 48MHz打印出现大量重复字符夹杂 0x00HAL_UART_Transmit 超时时间太短把超时参数调大确认 TX 完成4.2 三板斧复位、MCO 测量、寄存器对比遇到乱码我最常用的调试手法就是三板斧。第一板斧把时钟配置里可能卡死的地方绕过去。SystemClock_Config() 里 HAL_RCC_OscConfig() 如果返回错误程序会停在 Error_Handler() 的死循环里表现出来就是芯片完全不跑。你可以在 Error_Handler() 里放一个 GPIO 翻转或者干脆先把 Error_Handler() 里的 while(1) 注释掉改成让程序继续往下跑看看串口能不能吐出东西来。如果绕过错误处理之后能打印说明 PLL 配置有问题函数返回了错误只是你没看到。第二板斧用示波器量 MCO 引脚的输出。F407 的 PA8 可以复用为 MCO1PC9 可以复用为 MCO2它们能把 SYSCLK、HSE、HSI、PLL 这些时钟信号引到外部。用 HAL 库配置一下就能量出来例如要量 PLL 时钟HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_1);把示波器探头点在 PA8 上直接就能看到实际时钟频率。这个方法特别适合怀疑晶振没起振或者 PLL 没锁定的场景。注意如果 PA8 已经被你的板子用于 USB 检测之类功能那就用 PC9 输出 MCO2避免硬件冲突。另外这里提一句PA8 接 USB 的 VBUS 检测是很多 Type-C 方案的常见做法和 MCO 复用是冲突的用之前一定要看原理图。第三板斧在调试器里直接看 RCC 寄存器。我用的是 ST-Link 加 Keil停在断点处打开 Watch 窗口输入 RCC-CFGR 和 RCC-PLLCFGR对照参考手册里的位定义检查一下 SWS、SW、HPRE、PPRE1、PPRE2 这些字段的值确认实际生效的时钟源是否是你配置的那个。注意 CFGR 里有配置位和状态位之分SWS 读出来是 10 才代表 PLL 已经真正被选成了系统时钟源。4.3 串口助手本身的几个坑不要忽略工具层面的问题。很多时候时钟配置一点毛病没有但换到某个串口助手就乱码是因为工具设置不对。常见问题有波特率误选比如代码是 115200串口助手选的却是 9600文本模式编码不对工程里 printf 输出 UTF-8 中文串口助手却按 GBK 解十六进制显示和字符显示混淆让人误以为数据不对。我用过的串口工具里有些在打开串口时会自动控制 DTR/RTS 引脚导致单片机被拉进复位状态表现就是一点连接板子重启然后输出一段残的启动信息看起来也像乱码实际上是复位了。遇到这种情况在串口助手里把 DTR/RTS 相关的自动选项关掉即可。5. 高频踩坑实录这些错误我犯过不止一次5.1 改了主频却忘了 Flash 等待周期这个错误太典型了。刚开始学 HAL 库的时候我以为 FLASH_LATENCY 这个参数随便填一个大的就行了结果实际跑起来96MHz 左右的配置填 FLASH_LATENCY_5 反而问题不大但反过来主频降到 84MHz 的时候如果还保留 FLASH_LATENCY_5也会有问题。后来才明白等待周期多和少都不行必须严格按主频区间来查表。F407 在 2.7V 到 3.6V 的供电范围内90MHz 以上需要 4 个等待周期150MHz 以上需要 5 个。如果你跑到 168MHz 却只填了 4程序大概率会随机 HardFault表现出来就是跑着跑着串口突然吐乱码或者完全卡死特别像外界干扰其实就是 Flash 读取时序不对。5.2 时钟改完I2C、定时器、OLED 全是混沌状态串口乱码只是主频修改后最常见的一个表象。同一个工程里如果用了 I2C、SPI、定时器它们一样会乱。I2C 的时序参数由系统时钟分频而来主频变了如果软件里没有重新计算I2C 的通信速率就不在协议允许范围内OLED 屏显示花屏DHT11 读不到数据SD 卡读写概率性失败。定时器的 PWM 频率同样会漂移。所以修改主频不是只配一个 SystemClock_Config() 就结束了要把所有外设的时钟源重新过一遍。最有效的方法就是我在前面强调过的APB1、APB2 分频尽量保持和原来一致让挂在总线上的外设感觉不到变化。5.3 从 F103 迁到 F407时钟配置不能照搬经常有人问F103 的代码拿到 F407 上为什么乱码。这两个芯片的 PLL 结构完全不同。F103 的 PLL 是 HSE 直接乘一个倍数没有 M、N、P、Q 这么多参数最大主频也只有 72MHz。你要是把 F103 的 SystemCoreClock 变量值编进 F407 的工程里或者照搬 F103 的 RCC 配置思路结果必然不对。F407 的 PLL 参数必须重新按照公式计算不能用老代码里的那套。另外F407 的 FLASH 等待周期最大要到 5这也是 F103 工程里没有的概念。5.4 使用 letter shell 等交互组件时乱码还会掩盖软件问题这两年不少人喜欢在 STM32 上跑 letter shell 这类命令行交互组件调试确实方便。但它依赖串口的正确性。如果主频改完串口波特率全乱了命令行界面就会出现大量看似指令解析错误的现象比如敲命令没反应、历史记录是乱码、自动补全位置错乱。这些并不是 letter shell 本身的问题而是底层串口数据全是错的解析器拿到的是垃圾数据自然无法工作。遇到这种情况先解决时钟和波特率再去排查上层组件会省很多力气。5.5 长按复位、拔掉重插这些操作为什么没用很多人乱码后的第一反应是复位或者重新上电。如果是时钟配置错误导致的乱码复位多少次都一样因为程序每次启动都会先按错误的参数配时钟。唯一的例外是如果你在调试模式下发现芯片其实卡死在某个错误处理循环里复位确实有可能让它短暂跑起来但随即又会卡死。这种时候不要依赖复位老老实实用调试器看程序停在哪一行比反复按复位键有效得多。我的经验先打印时钟信息再谈其他折腾了这么多次乱码问题我现在养成了一个习惯新工程或者改主频之后第一件事就是在串口初始化后面打一行时钟信息。这一行信息能让所有时钟相关的问题无处遁形。如果打印出来的 SYSCLK、HCLK、PCLK 数值和自己预期一致那串口乱码十有八九是别的原因如果不一致问题就锁定在时钟配置这一层解决起来方向感会强很多。最后还有一个小技巧遇到乱码时先把目标波特率减半试试。比如原来用 115200改成 57600 之后乱码奇迹般恢复了那基本可以断定是单片机实际输出的波特率变成了原来的一半或者两倍问题就在 PCLK 和 APB 分频上。这个小小的动作我已经用过很多次每次都帮我快速锁定方向。时钟频率这件事看着很底层其实弄明白一次之后以后遇到各种外设异常都能举一反三。
返回列表