RCC与时钟树深度解析)
摘要本文从为什么用外设前要先开时钟这一疑问出发系统讲解 STM32F103 的时钟体系。先厘清时钟的本质——它是催促 CPU 和所有片上外设工作的节拍器再拆解时钟树的四个原始时钟源HSI/HSE/LSI/LSE与三类基本元件时钟源、倍频/分频器、选择器/使能开关随后梳理主干六级时钟PLLCLK、SYSCLK、FCLK、HCLK、PCLK1、PCLK2的作用与频率并介绍 CSS 时钟安全系统、低速时钟RTC、独立看门狗与 MCO 时钟输出。最后结合 RCC 的十个寄存器逐行拆解 SystemClock_Config() 源码讲清配置时钟源 写寄存器、配置分频 改 CFGR的底层逻辑帮助读者真正打通时钟树、总线与外设之间的关系。开发板STM32F103ZET6工具链Keil MDK VSCode STM32CubeMX HAL 库上一篇嵌入式入门二GPIO 深度解析上一篇点灯时我们照抄了这么一句__HAL_RCC_GPIOF_CLK_ENABLE(); // 使能 GPIOF 时钟当时只知道用外设前要先开时钟但为什么必须开时钟到底是什么72MHz 到底是怎么来的这些问题悬在那里导致后面的 USART、I2C、SPI 全都会知其然不知其所以然。这一篇把整层地基挖开从时钟为什么存在一路讲到时钟树结构最后落到SystemClock_Config()的源码逐行拆解。1时钟所有设备都是被驱动的1.1 CPU也要被催促你有没有想过CPU 能从 Flash 读指令并执行可谁让它去读的又是谁让它一直读下去的警察能抓坏人CPU 能执行指令但是谁让警察去抓坏人的CPU 很聪明——能取指令、分析指令、执行指令CPU 也很傻——它必须在别人的催促下才能完成这些工作。这个催促的硬件机制就叫时钟。时钟像一台节拍器以固定频率发出电信号催促目标设备工作。1.2 片上外设也是如此MCU 内部除了 CPU还有各种片上外设GPIO、USART、I2C……它们自己能主动工作吗不能。核心结论硬件层面上所有设备CPU 和片上外设都要在时钟推动下统一步调地工作。1.3 时钟信号与时钟源产生高低电平时序的源头硬件叫时钟源这串高低电平的时序就叫时钟信号画出来就是方波。有的设备在上升沿工作有的在下降沿工作。易混概念时钟源是产生或提供时钟的硬件实体时钟信号是这个实体输出到电路中的电信号波形。一个是东西一个是波形。1.4 时钟周期与频率时钟周期一次完整波动所需的时间起始状态 → 变化 → 回到起始状态。注意——周期是时间单位。一个周期必然有且只有一个上升沿 一个下降沿。时钟频率每秒输出的完整周期数单位赫兹Hz。一个周期耗时 1ms → 1 秒输出 1s/1ms 1000 个周期 → 频率 1000Hz1.5 使能开关的由来一段设计者的心理活动我是单片机的设计者。我知道芯片上有很多设备但它们不一定被同时使用。如果开发者用不到的设备还在时钟催促下做无效功不就是白白耗电功耗高 → 使用成本高 → 我的芯片就没竞争力了。可我又要满足用户能自由选择用哪些外设——既要又要怎么办答案给每个设备受理时钟的能力加一个使能开关。于是使用任何外设前先使能它的时钟——本质是开启该外设对时钟的受理CPU 例外它要一直执行代码所以一般不设使能。核心结论这就是为什么用任何一个片上外设之前都要先开启它的时钟。1.6 倍频和分频一个源头驱动不同的频率不同外设的工作频率并不相同受硬件工艺、功耗散热、通信协议、精度要求影响。为什么不能给每个外设配一个时钟源——不现实功耗高设备间同步还是大问题。于是几乎所有单片机都用同一个做法——倍频 分频操作定义典型电路倍频输入 1 个周期输出 n 个周期升频PLL 锁相环Phase-Locked Loop分频输入 n 个周期输出 1 个周期降频分频器Frequency Divider时钟的本质时钟是决策片上外设什么时候工作的设备是发起者其他外设是执行者。CPU 在要被时钟驱动这一点上和片上外设并无本质区别。扩展PLL 锁相环为什么能倍频PLL 的核心是一个压控振荡器VCO——它的输出频率能被输入电压控制。PLL 把 VCO 的输出用分频器降频后与输入参考时钟做相位比较根据误差反馈去调整 VCO 频率最终锁定在输入频率 × 倍频系数上。就像一个闭环的自动追频系统你让它输出 9 倍频率它就自己调节直到稳定在 9 倍。这也是为什么 PLL 上电后需要等待就绪——锁定是需要时间的。1.7 时钟源是真实存在的硬件不是抽象概念开发板上就焊着类型实物典型型号8MHz 晶振外部高速晶振X50328MSB2GI32.768kHz 晶振外部低速晶振Q13FC135000042时钟树把时钟功能独立成一套硬件2.1 像硬件工程师一样思考继续站在设计者角度推理问题一如果只有一个时钟源万一它因极端情况高温、线路老化停摆整个系统不就瘫痪了→ 所以不能只有一个要冗余备份。问题二既然可以有多个时钟源那给每个外设都配一个行不行→ 不行。每个时钟都要分频、倍频、使能MCU 内部会出现大量重复电路设计重复意味着成本增加。MCU 是成本敏感的设备此路不通。所以答案把时钟功能单独设计成一套硬件电路黑盒式地对外提供各种频率的时钟——这就是时钟树。把扇出图逆置过来正好是一棵树时钟源就是树根。扩展为什么世界的组织结构主流是树人要协作就得先把人组织起来而主流组织结构就是树形。因为人做事只有两件事——决策和执行决策要集中只有一个根执行要分散叶子多。树形结构通过调整分支个数和层高让从根到每个叶子的通知路径最短。时钟树长成树也是同一个道理一个根时钟源靠分频倍频不断分叉把不同频率的时钟高效分发到每一个外设。2.2 四个原始时钟来源时钟树的树根不是一个而是四个命名是一套二维矩阵——高速/低速 × 内部/外部简称全称来源典型频率HSIHigh-Speed Internal内部 RC 振荡器8 MHzHSEHigh-Speed External外部晶振4~16 MHz板子用 8MHzLSILow-Speed Internal内部 RC 振荡器40 kHzLSELow-Speed External外部 32.768kHz 晶振32.768 kHz内部外部高速HSIHSE低速LSILSE记住这个二维表四个时钟源就永远不会记混。扩展RC 振荡器 vs 晶振精度差在哪HSI/LSI 是片内RC 振荡器靠电阻电容充放电定时受温度、工艺、电压影响大精度只有1% 左右所以 HSI 还带一个HSICalibrationValue校准值可微调而 HSE/LSE 是石英晶振靠晶体的压电效应谐振精度可到百万分之几十ppm 级。这就是为什么最佳实践是拿 HSE 当系统主时钟源——精度高通信波特率、定时才准。2.3 时钟树只有三类基本元件那张极其复杂的时钟树图拆开后其实只有三种东西时钟源——树根四个原始来源倍频/分频器——改频率。有的系数固定如/2有的支持软件动态配置如/1,2,4,5,16倍频器主要是 PLL选择器复用器 使能开关——圆圈画的是选择器从多个输入信号里选一个方框画的是使能开关。整棵树的核心主干就是这样一条链路HSE (8MHz) ┐ ┌──÷1──→ HCLK 72MHz → AHB内核/内存/DMA ├→[PLL ×9]→ SYSCLK ──┤ HSI (8MHz) ┘ 72MHz 72MHz ├──÷1──→ PCLK2 72MHz → APB2GPIO/USART1/TIM1 └──÷2──→ PCLK1 36MHz → APB1I2C/USART2/TIM23主干解析六级时钟3.1 先解决时钟源的称呼问题这个词在软硬件两个视角下含义不同狭义硬件工程师只有 HSE、HSI、LSE、LSI 这四个硬件实体才叫时钟源广义软件工程师由它们分频倍频形成的PLLCLK、SYSCLK、FCLK、HCLK、PCLK1、PCLK2也常被叫时钟源——因为从下游设备看它们就是时钟从哪里来。结合语境理解即可。3.2 六级时钟的作用与频率时钟全称核心作用最高频率PLLCLKPhase-Locked Loop Clock倍频输出让系统可达最高速72 MHzSYSCLKSystem Clock芯片根时钟所有总线的源头72 MHzFCLKFree-running ClockCPU 内核自由运行时钟72 MHzHCLKAHB ClockAHB 总线、内存、DMA 时钟72 MHzPCLK1APB1 ClockAPB1 外设USART2/3、I²C、SPI2、TIM2~436 MHzPCLK2APB2 ClockAPB2 外设GPIO、USART1、SPI1、TIM1、ADC72 MHz最容易记错的一点APB1 最高只能 36MHzAPB2 才能跑满 72MHz。以后发现某个外设最高频率算不对十有八九是把 APB1 当成 72MHz 了。3.3 CSS时钟安全系统回到冗余的推测主干部分的原始时钟来源其实有两个——HSI 和 HSE一内一外互为备份。STM32F103默认用 HSI 起步不依赖外部器件上电就能跑最佳实践用 HSE精度高HSE 异常时CSSClock Security System时钟安全系统接管。CSS 的两条行为一条重要、一条常被误解情形硬件行为HSE 故障立即关闭 HSE 和 PLL把 SYSCLK强制切到 HSI8MHz——全过程硬件完成软件来不及干预但产生NMI 中断供补救HSI 故障不会自动切到 HSE。HSI 是内部 RC规格书没有HSI 失效检测 自动切 HSE的机制一旦停振只能死机或复位核心结论只有 HSE 故障会自动降级到 HSI反向不行想反向保护得自己写软件监控。在工业、汽车等高可靠场景HSE 停振会让 PLL/SYSCLK 立刻丢失、CPU/外设/通信全停CSS 能在毫秒级切到内部 HSI至少让 MCU 有机会上报故障或安全停机。4低速时钟与时钟输出4.1 RTC与VBATRTCReal-Time Clock实时时钟核心功能是在系统断电、重启或休眠时仍持续准确记录年月日时分秒。典型场景是高速公路 ETC 门架计费——过车瞬间要记录毫秒级时间戳断电丢了时间就会漏扣、对账失败。它靠VBAT 引脚Voltage Battery电池电压引脚外接一颗纽扣电池实现正常供电时电池不工作一旦 VDD 掉电电池顶上维持计时。RTC 的本质就是一个递增计数器记录 MCU 启动到现在过了多少秒。4.2 独立看门狗与喂狗独立看门狗IWDGMCU 里的自动重启按钮。程序跑飞死机时自动把板子重启。本质是一个递减计数器减到 0 就重启你在循环里不断重置它这个动作叫喂狗——一直喂就永远减不到 0一旦跑飞不再喂它就减到 0 替你重启。两者都是秒/毫秒级更新的计数器kHz 级时钟就够所以走 LSE/LSI 低速线。最佳实践RTC首选LSE32.768kHz掉电也能走备选 LSI≈40kHz或 HSE/128IWDG只能用 LSI独立运行不受外部晶振影响。扩展还有一个窗口看门狗WWDG和 IWDG 的区别是——它不仅有下限不能减到 0还有上限喂狗不能太早喂太早或太晚都会复位。IWDG 管程序彻底卡死WWDG 管程序时序跑偏。这些留到看门狗专题再展开。4.3 为什么是32.768kHz因为 32768 2¹⁵。RTC 内部有默认分频系数 32768一除正好得到1Hz稳定的 1 秒。这就是电子世界里这个奇怪数字的由来。如果你硬要用 8MHz 的 HSE 当 RTC 时钟源就得自己算预分频值8MHz 8,000,000 Hz 分频系数总数 8,000,000 ÷ 128 ÷ 1 62,5004.4 MCO时钟输出MCOMicrocontroller Clock Output把 MCU 内部选中的时钟信号HSI/HSE/PLLCLK 等引到某个 GPIO 引脚对外输出。两个用途给外围器件直接供时钟同步设备、省一颗晶振的成本当测试接口用——外接示波器看波形验证配置是否正确。5时钟树总线外设如何打通5.1 总线不是一根线总线是一簇线的集合内部一般分三类总线回答什么问题地址总线去哪里读从哪读写写到哪数据总线具体数据是什么控制总线什么时候读写、方向、是否完毕等控制逻辑注意时钟线CLK和读写方向线都属于控制总线内部。不管 ICode、DCode、System、AHB、APB硬件本质都一样只是挂的设备不同。5.2 外设也有寄存器寄存器不只 CPU 有——外设里也有上一章 GPIO 的 CRL/CRH/IDR/ODR 就是。大体分三类数据寄存器、控制/配置寄存器、状态寄存器。5.3 时钟线怎么接入外设把总线结构图和时钟树合并起来看每个片上外设前面都有一个开关这个开关就来自时钟树的使能开关。你未来用代码使能的就是这个开关。5.4 寄存器如何存住1个bit一个存储单元要成立必须有三种功能写入读取读完数据还在相当于拷贝保持下次写入到来之前值不变即锁存。保持靠一个内部开关写完就断开直到下次闭合才接受新数据。开关由电平/边沿控制上升沿触发、下降沿触发、高电平触发、低电平触发。所以写入成立的两个条件① 写端有数据②使能端来一个上升沿闭合开关。把 8 个存储单元的开关接到同一个使能位上——恭喜你亲手做出了一个 8 位寄存器32 位同理。5.5 时钟与数据读写的同步CPU 操作片上外设的完整过程一个时钟到来 → CPU 把数据放到数据总线同时把目标地址、读写方向放到对应总线位置 → 与此同时不考虑分频时外设寄存器的开关也被使能打开→ 数据从 CPU → 总线 → 外设寄存器。核心结论时钟线上的时钟让把数据放到总线和开启目标设备寄存器这两件事同步进行。而这里的同步不一定是同时——因为有分频这里的同步是步调有规律。6RCC时钟树的配置面板6.1 RCC是什么前面讲的全是时钟树本身可它的选择器、倍频/分频系数、使能开关都需要被配置。配置在哪做RCC。RCC Reset and Clock Controller复位与时钟控制器。配置的本质就是配置寄存器。6.2 与门使能开关的硬件本质关键在与门输入 A输入 B输出111100010000看出来了吗——A1 时输出等于 BA0 时B 完全无效。这不就是开关吗再把一个存储单元接到 A 端存 1 → 持续输出 1 → 与门开关闭合存 0 → 持续输出 0 → 与门开关断开。寄存器的输出连线接到各外设的 enable 输入。所以配置寄存器 使能外设本质就这么简单。6.3 RCC的十个寄存器序号寄存器一句话记忆1RCC_CR开/关全部时钟源HSI/HSE/PLL/LSI/LSE及就绪标志2RCC_CFGR选时钟源、设分频倍频、MCO 输出3RCC_CIR时钟就绪/失钟中断使能与标志4RCC_APB2RSTR一键复位挂在 APB2 上的高速外设5RCC_APB1RSTR一键复位挂在 APB1 上的低速外设6RCC_AHBENR开/关 DMA、SRAM、CRC、FSMC、SDIO 等 AHB 外设时钟7RCC_APB2ENR开/关 GPIO、TIM1、ADC1/2、USART1、SPI1 等 APB2 外设时钟8RCC_APB1ENR开/关 TIM2~7、USART2/3、I²C1/2、USB、CAN 等 APB1 外设时钟9RCC_BDCR控制 RTC、LSE 及备份域复位10RCC_CSR读谁复位了我、清标志、控制 LSI 开关现在回头看就通了__HAL_RCC_GPIOF_CLK_ENABLE()就是往RCC-APB2ENR对应位写 1——GPIO 挂在 APB2 上所以用 APB2ENR。这也解释了为什么 GPIO 的时钟是 PCLK272MHz。7实战时钟配置与源码解析7.1测试方案用CPU死循环称重时钟怎么直观感受时钟频率的影响这次不用HAL_Delay()改用 CPU 空转void CPU_Delay(volatile uint32_t delay) { while(delay--); // 让 CPU 疯狂死循环 } while (1) { HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_8); CPU_Delay(XXXXX); // 空转次数留给你自己算 }原理CPU 执行速度由FCLK决定。时钟调高 → 同样的空转代码跑完更快 → 灯闪得更快调低 → 更慢。用肉眼看到时钟配置对系统的影响。在 72MHz 下估算while(delay--);一行需要多少个时钟周期把从 Flash 取指令也算进去以及要让 CPU 空转约 200ms 需要执行多少次。这个留给你推——提示注意 Flash 等待周期见 7.4 的 FLASH_LATENCY会拖慢取指速度。7.2 RCC的物理地址和结构体RCC 本质上也是一个片上外设同样遵循统一编址。它的地址空间是0x4002 1000 ~ 0x4002 13FF大小 1KB#define RCC ((RCC_TypeDef *)RCC_BASE) #define RCC_BASE (AHBPERIPH_BASE 0x00001000UL) #define AHBPERIPH_BASE (PERIPH_BASE 0x00020000UL) #define PERIPH_BASE 0x40000000UL /*! 外设基地址 */ typedef struct { __IO uint32_t CR; __IO uint32_t CFGR; __IO uint32_t CIR; __IO uint32_t APB2RSTR; __IO uint32_t APB1RSTR; __IO uint32_t AHBENR; __IO uint32_t APB2ENR; __IO uint32_t APB1ENR; __IO uint32_t BDCR; __IO uint32_t CSR; } RCC_TypeDef;结构体成员的排布和寄存器偏移一一对应——这和上一章GPIO_TypeDef是同一个套路。7.3__HAL_RCC_GPIOF_CLK_ENABLE拆解#define __HAL_RCC_GPIOF_CLK_ENABLE() do { \ __IO uint32_t tmpreg; \ SET_BIT(RCC-APB2ENR, RCC_APB2ENR_IOPFEN); \ tmpreg READ_BIT(RCC-APB2ENR, RCC_APB2ENR_IOPFEN); \ UNUSED(tmpreg); \ } while(0U)SET_BIT(RCC-APB2ENR, RCC_APB2ENR_IOPFEN)给 APB2ENR 的 GPIOF 位写 1开启 GPIOF 的时钟后面那句READ_BIT(...)UNUSED(tmpreg)是一次哑读——给外设一点时间让时钟稳定是 HAL 库的惯例写法。SET_BIT的本质#define SET_BIT(REG, BIT) ((REG) | (BIT))而RCC_APB2ENR_IOPFEN最终指向某个具体的 bit 位对应 APB2ENR 里的 GPIOF 使能位。7.4 SystemColck_Config选择时钟源CubeMX 里点几下时钟树最终都会翻译成SystemClock_Config()。它分两段——先配振荡器再配分频void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; // 振荡器配置结构体 RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; // 时钟配置结构体 /* ---------- 第一段配置振荡器时钟源 ---------- */ RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; // 配置 HSE RCC_OscInitStruct.HSEState RCC_HSE_ON; // 使能 HSE RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; // HSE 不分频 RCC_OscInitStruct.HSIState RCC_HSI_ON; // HSI 作备用 RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; // 使能 PLL RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; // PLL 输入源 HSE RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; // 9 倍频8MHz × 9 72MHz 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; // 系统时钟 PLL 输出 RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; // AHB 不分频HCLK 72MHz RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; // APB1 2 分频PCLK1 36MHz RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; // APB2 不分频PCLK2 72MHz if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2) ! HAL_OK) { Error_Handler(); } }扩展FLASH_LATENCY_2 是什么当 SYSCLK 超过一定频率STM32F103 约 48MHz 以上Flash 的读取速度跟不上 CPU 取指速度需要在访问 Flash 时插入等待周期wait state。72MHz 时通常需要2 个等待周期FLASH_LATENCY_2。这就是 7.1 思考题里取指令要考虑 Flash 等待的来源——它会实实在在拖慢 CPU 的执行速度。调整时钟让灯变慢——把 AHB 分频系数改大即可RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV8; // AHB 8 分频HCLK 72/8 9MHz RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; // PCLK1 9/2 4.5MHz RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; // PCLK2 9MHzHCLK 从 72MHz 降到 9MHzCPU 慢下来同样的CPU_Delay跑完花的时间变长灯自然就变慢了。7.5 HAL 库底层配振荡器 写寄存器HAL_RCC_OscConfig底层就是一堆写寄存器的宏。以 HSE 为例#define __HAL_RCC_HSE_CONFIG(__STATE__) \ do { \ if ((__STATE__) RCC_HSE_ON) \ { \ SET_BIT(RCC-CR, RCC_CR_HSEON); \ } \ else if ((__STATE__) RCC_HSE_OFF) \ { \ CLEAR_BIT(RCC-CR, RCC_CR_HSEON); \ CLEAR_BIT(RCC-CR, RCC_CR_HSEBYP); \ } \ /* ...旁路模式等其他分支略 */ \ } while(0U) #define RCC_HSE_ON RCC_CR_HSEON #define RCC_CR_HSEON_Pos (16U) #define RCC_CR_HSEON_Msk (0x1UL RCC_CR_HSEON_Pos) /*! 0x00010000 */ #define RCC_CR_HSEON RCC_CR_HSEON_Msk注意RCC_CR_HSEON是bit160x00010000——HSE 的使能开关就在 RCC_CR 的第 16 位。SET_BIT(RCC-CR, RCC_CR_HSEON)就是把这一位置 1HSE 开始起振。LSE 同理只是操作的是 RCC_BDCR#define __HAL_RCC_LSE_CONFIG(__STATE__) \ do { \ if ((__STATE__) RCC_LSE_ON) \ { \ SET_BIT(RCC-BDCR, RCC_BDCR_LSEON); \ } \ /* ... */ \ } while(0U) #define RCC_BDCR_LSEON_Pos (0U) #define RCC_BDCR_LSEON_Msk (0x1UL RCC_BDCR_LSEON_Pos) /*! 0x00000001 */底层逻辑完全一致配置时钟源 往对应寄存器的对应位写 1 或写 0。HSE 归 CR 管LSE 归 BDCR 管——这和 6.3 节RCC_CR 开/关全部时钟源、RCC_BDCR 管 LSE完全对上了。7.6 配置分频修改CFGR分频系数也落在 RCC_CFGR。以 APB1 分频为例#define RCC_HCLK_DIV2 RCC_CFGR_PPRE1_DIV2 #define RCC_CFGR_PPRE1_DIV2 0x00000400U /*! HCLK divided by 2 */ HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency) { /* PCLK1 配置 */ if (((RCC_ClkInitStruct-ClockType) RCC_CLOCKTYPE_PCLK1) RCC_CLOCKTYPE_PCLK1) { MODIFY_REG(RCC-CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct-APB1CLKDivider); } /* PCLK2 配置 */ if (((RCC_ClkInitStruct-ClockType) RCC_CLOCKTYPE_PCLK2) RCC_CLOCKTYPE_PCLK2) { MODIFY_REG(RCC-CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct-APB2CLKDivider) 3)); } }几个关键宏#define RCC_CFGR_PPRE1_Pos (8U) #define RCC_CFGR_PPRE1_Msk (0x7UL RCC_CFGR_PPRE1_Pos) /*! 0x00000700 */ #define RCC_CFGR_PPRE1 RCC_CFGR_PPRE1_Msk /*! PRE1[2:0] APB1 预分频 */RCC_CFGR_PPRE1是 CFGR 的 bit8~10 三位0x7 8管 APB1 分频MODIFY_REG是读-改-写先把目标三位清零再写入新值不影响其他位——和上一章 GPIO 的MODIFY_REG是同一个宏APB2 的分频位在 PPRE2bit11~13所以代码里 3把它挪到正确位置。7.7 关键结构体与宏(附录整理)SystemClock_Config用到的两个结构体typedef struct { uint32_t OscillatorType; // 要配置哪些振荡器HSE/HSI/LSE/LSI uint32_t HSEState; // HSE 状态ON/OFF/BYPASS uint32_t HSEPredivValue; // HSE 预分频PREDIV1 / PLLXTPRE uint32_t LSEState; // LSE 状态 uint32_t HSIState; // HSI 状态 uint32_t HSICalibrationValue; // HSI 校准值0x00~0x1F uint32_t LSIState; // LSI 状态 RCC_PLLInitTypeDef PLL; // PLL 参数 } RCC_OscInitTypeDef; typedef struct { uint32_t PLLState; // PLL 开关 uint32_t PLLSource; // PLL 输入源HSI/2 或 HSE uint32_t PLLMUL; // 倍频系数2~16 } RCC_PLLInitTypeDef; typedef struct { uint32_t ClockType; // 配哪些时钟SYSCLK/HCLK/PCLK1/PCLK2 uint32_t SYSCLKSource; // 系统时钟源HSI/HSE/PLLCLK uint32_t AHBCLKDivider; // AHB 分频 uint32_t APB1CLKDivider; // APB1 分频 uint32_t APB2CLKDivider; // APB2 分频 } RCC_ClkInitTypeDef;PLL 倍频系数的可选值2~16#define RCC_PLL_MUL2 RCC_CFGR_PLLMULL2 #define RCC_PLL_MUL3 RCC_CFGR_PLLMULL3 /* ... 一直到 ... */ #define RCC_PLL_MUL16 RCC_CFGR_PLLMULL16再补一个附录里的完整示例48MHz MCO 输出它和标准配置的区别就两处——PLLMUL 改成 6 倍频8×648MHz并加了 MCO 输出RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL6; // 8 × 6 48MHz RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; // 不分频48MHz RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; /* ... */ HAL_RCC_MCOConfig(RCC_MCO, RCC_MCO1SOURCE_SYSCLK, RCC_MCODIV_1); // MCO 输出 SYSCLK8总结8.1 知识体系回顾时钟 ├── 本质节拍器催促 CPU 和所有片上外设工作 ├── 周期 一个上升沿 一个下降沿时间单位频率 每秒周期数Hz ├── 使能开关设计者为降功耗给每个外设加时钟受理开关用外设先开时钟 └── 倍频/分频一个源头驱动不同频率设备PLL 锁相环 / 分频器 时钟树 ├── 为什么要树多时钟源冗余 避免重复电路 ├── 四个原始来源HSI / HSE高速、LSI / LSE低速内外 × 高低二维矩阵 ├── 三类元件时钟源 倍频分频器 选择器/使能开关 └── 三部分主时钟树重点/ 低速时钟RTC·看门狗/ 时钟输出MCO 主干六级时钟 ├── PLLCLK(72) → SYSCLK(72) → FCLK(72) / HCLK(72) → PCLK1(36) / PCLK2(72) ├── 广义 vs 狭义时钟源 └── CSSHSE 故障自动切 HSI单向产生 NMI 时钟树 × 总线 × 外设 ├── 总线三类地址 / 数据 / 控制时钟线在控制总线内 ├── 寄存器存 bit写入 读取 保持锁存靠使能上升沿 └── 同步时钟让放数据到总线和开外设寄存器步调一致有规律不一定是同时 RCC ├── Reset and Clock Controller配置 写寄存器 ├── 使能开关本质 与门A 端接寄存器输出 └── 十个寄存器CR / CFGR / CIR / APB2RSTR / APB1RSTR / AHBENR / APB2ENR / APB1ENR / BDCR / CSR 源码 ├── RCC 地址0x4002 10001KB结构体映射 ├── 开 GPIOF 时钟 SET_BIT(RCC-APB2ENR, IOPFEN) ├── SystemClock_Config先配振荡器CR/BDCR再配分频CFGR └── FLASH_LATENCY_272MHz 需 2 个 Flash 等待周期8.2 核心要点时钟是发起者CPU 和外设都不会主动干活全靠时钟节拍驱动——这就是用外设前先开时钟的根本原因。四个时钟源是二维矩阵高速/低速 × 内部/外部 → HSI/HSE/LSI/LSE一次记住。时钟树只有三类元件时钟源、倍频/分频器、选择器/使能开关再复杂的图都是这三样的组合。六级时钟要背频率PLLCLK/SYSCLK/FCLK/HCLK 最高 72MHzPCLK1 最高 36MHz、PCLK2 才 72MHz。CSS 是单向保护HSE 挂了自动切 HSI毫秒级 NMI反向不行。使能开关 与门寄存器存 1 输出 1 闭合开关存 0 断开——配置寄存器就能使能外设的硬件真相。同步是步调有规律不是同时分频会让不同设备错开节拍工作。配置本质是写寄存器开时钟写 APB2ENR/APB1ENR/AHBENR选时钟源写 CR/BDCR配分频写 CFGR。Flash 等待周期不可忽视72MHz 要 FLASH_LATENCY_2否则 Flash 读取跟不上 CPU 取指。HSE 精度远高于 HSI晶振 ppm 级 vs 内部 RC 约 1%所以系统主时钟选 HSE。8.3 后续展望USART 串口第 7 章第一次和电脑聊天printf 重定向调试大法中断与 EXTI让 CPU 从轮询走向事件驱动理解中断优先级定时器TIM基于 PCLK 时钟的精确定时、PWM 输出I2C / SPI第 8、9 章外设时钟的使能与复用功能实战RTC / 看门狗专题把本篇一笔带过的第二部分时钟落地