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

资讯详情

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

深入浅出 STM32(十九):从寄存器原理到 volatile 避坑实战

深入浅出 STM32(十九):从寄存器原理到 volatile 避坑实战 目录前言一、RCC 的核心概念与时钟树配置原理1.1 RCC 的定义与核心作用1.2 RCC 的功能划分与 CPU 访问机制1.3 时钟使能的硬件本质1.3.1 与门的工作原理1.4 RCC 的寻址与访问机制1.4.1 统一编址1.4.2 寄存器封装二、关键 RCC 寄存器详解2.1 时钟控制寄存器 (RCC_CR)2.1.1 内部高速时钟使能 (HSION)2.1.2 外部高速时钟使能 (HSEON)2.1.3 其他关键位2.2 时钟配置寄存器 (RCC_CFGR)2.2.1 PLL 倍频系数 (PLLMUL)2.2.2 高速 APB 预分频器 (PPRE2)2.2.3 低速 APB 预分频器 (PPRE1)2.3 外设时钟使能寄存器2.3.1 APB2 外设时钟使能寄存器 (RCC_APB2ENR)2.3.2 APB1 外设时钟使能寄存器 (RCC_APB1ENR)三、RCC 时钟系统实验设计与原理3.1 RCC 的核心定位与实验思路3.2 硬件连接与控制逻辑解析四、项目配置与代码实现细节4.1 CubeMX 关键配置项4.2 自定义延时函数的陷阱与修正编译器优化的“坑”volatile 关键字的救赎4.3 时钟树配置策略结语 云泽Q个人主页 专栏传送入口: 《C语言》《数据结构》《C》《Linux》《蓝桥杯系列》《笔试算法》《AI赋能》《STM32》《Python》⛺️遇见安然遇见你不负代码不负卿~前言大家好啊我是云泽Q欢迎阅读我的文章一名热爱计算机技术的在校大学生喜欢在课余时间做一些计算机技术的总结性文章希望我的文章能为你解答困惑~一、RCC 的核心概念与时钟树配置原理1.1 RCC 的定义与核心作用RCC 的全称是 Reset and Clock Control即复位和时钟控制。在本篇文章中我们主要聚焦于其 Clock Control时钟控制的功能。RCC 在 MCU 内部扮演着至关重要的角色它相当于整个时钟系统的“总管家”。我们需要明确 RCC 与时钟树的关系RCC 是支持我们进行配置和管理时钟树的硬件模块。时钟信号在 MCU 内部通过总线进行布线连接着 CPU、内存以及各类片上外设。无论是系统核心的运行还是外设的驱动都依赖于这套精密的时钟网络。然而时钟树本身是一个复杂的硬件结构其中的关键节点——包括时钟源的选择、倍频、分频以及各个外设的使能开关——都需要被精确配置。我们在 STM32CubeMX 等工具中点击设置倍频、分频参数本质上就是在生成这些配置信息。而要真正理解这些配置是如何生效的我们就必须找到一个硬件层面的突破口这个突破口就是“寄存器”。所谓的配置归根结底就是对 RCC 内部寄存器的读写操作。1.2 RCC 的功能划分与 CPU 访问机制RCC 作为 MCU 内部的一个片上外设其内部并非只有一个功能模块。从整体架构来看RCC 内部主要划分为三大功能区域RTC实时时钟用于维持系统的时间计数本次暂不展开。复位功能负责 MCU 的系统复位管理。比如我们把板子烧完程序后按一下复位按键程序就能重新运行这就是复位功能在起作用。关于复位的具体机制后续在讲解程序编译烧写流程时再详细说明。配置时钟树这是我们本篇文章的核心关注点也是 RCC 最重要的功能。1.3 时钟使能的硬件本质当我们想要开启某个外设例如 GPIO时我们在代码中调用的开启函数其底层物理动作究竟是什么本质上开启外设就是把该设备接入到对应的时钟线上。我们可以把时钟线想象成一条输送动力的管道而每个外设前面都有一个“开关”。只有当这个开关闭合时时钟信号才能流向该外设外设才能工作。这个“开关”在硬件电路中实际上是一个逻辑门电路。在参考手册的电路图中它被明确画为一个**与门AND Gate**结构。1.3.1 与门的工作原理这个与门有两个输入端时钟输入端Input连接着总线上的时钟信号如 PCLK1 或 PCLK2。使能端Enable连接着 RCC 寄存器中的某一位Bit。根据与门的逻辑特性只有当两个输入都为高电平1时输出端Output才会有时钟信号输出。如果寄存器位为 0无论时钟信号如何跳动输出永远为 0外设处于“断电”状态。如果寄存器位为 1时钟信号就能无损地通过与门驱动外设工作。因此我们在软件层面写的“开启 GPIO 时钟”在硬件层面就是向 RCC 的特定寄存器写入一个1将与门的使能端拉高从而闭合开关。1.4 RCC 的寻址与访问机制RCC 作为 MCU 内部的一个片上外设它的访问方式与我们熟悉的 GPIO 是完全一致的。1.4.1 统一编址在 ARM Cortex-M 架构中内存、片上外设包括 RCC、GPIO、USART 等都是统一编址的。这意味着 RCC 在内存映射中拥有一个固定的起始物理地址。CPU 可以通过系统总线直接访问这个地址。1.4.2 寄存器封装RCC 内部包含大量的寄存器这些寄存器在物理上是连续排列的。在软件驱动开发中我们会将这些寄存器封装成一个结构体Struct。结构体的基地址对应 RCC 的起始物理地址。结构体内的成员变量对应具体的寄存器它们之间的偏移量Offset对应硬件上的地址偏移如 0x00, 0x04, 0x08 等。通过这种方式CPU 就可以像访问普通内存变量一样精准地修改 RCC 内部的每一个配置位从而完成对整个时钟树的控制。在阅读源码时大家会看到 RCC 一定有一个定义的基地址所有的配置操作都是基于这个基地址加上偏移量来进行的。二、关键 RCC 寄存器详解要掌握 RCC 的配置就必须深入理解几个核心寄存器。这些寄存器直接对应了我们在 CubeMX 中看到的各种选项。2.1 时钟控制寄存器 (RCC_CR)RCC_CR(Clock Control Register) 是 RCC 中最基础的寄存器主要用于控制时钟源的开启与状态监测。2.1.1 内部高速时钟使能 (HSION)位置位 0 (Bit 0)功能Internal high-speed clock enable。操作由软件置 ‘1’ 开启内部高速时钟HSI清零则关闭。HSI 是芯片内部自带的 RC 振荡器通常频率为 8MHz不需要外部元件即可起振是系统启动时的默认时钟源。2.1.2 外部高速时钟使能 (HSEON)位置位 16 (Bit 16)功能External high-speed clock enable。操作由软件置 ‘1’ 开启外部高速时钟HSE清零则关闭。HSE 通常接外部晶振如 8MHz精度比 HSI 高。2.1.3 其他关键位除了上述两个使能位RCC_CR还包含 PLL 的使能位PLLON、时钟就绪标志位如 HSIRDY, HSERDY, PLLRDY等。这些就绪标志位是只读的用于告诉 CPU 时钟源是否已经稳定起振只有在稳定后才能进行切换操作。2.2 时钟配置寄存器 (RCC_CFGR)如果说RCC_CR负责“点火”那么RCC_CFGR(Clock Configuration Register) 就负责“调速”。它决定了系统的最终运行频率以及各总线的分频系数。2.2.1 PLL 倍频系数 (PLLMUL)位置位 21:18功能PLL multiplication factor。详解锁相环PLL的作用是将输入的时钟频率进行倍频以获得更高的系统主频。该字段由软件设置来确定倍频系数。000x: 保留0010: PLL 4 倍频输出0011: PLL 5 倍频输出…以此类推…0111: PLL 9 倍频输出1101: PLL 6.5 倍频输出注意只有在 PLL 关闭的情况下才可被写入。警告PLL 的输出频率绝对不能超过 72MHz针对 STM32F1 系列。2.2.2 高速 APB 预分频器 (PPRE2)位置位 13:11功能APB high-speed prescaler (APB2)。详解用于控制高速 APB2 时钟PCLK2的预分频系数。0xx: HCLK 不分频100: HCLK 2 分频101: HCLK 4 分频110: HCLK 8 分频111: HCLK 16 分频2.2.3 低速 APB 预分频器 (PPRE1)位置位 10:8功能APB low-speed prescaler (APB1)。详解用于控制低速 APB1 时钟PCLK1的预分频系数。0xx: HCLK 不分频100: HCLK 2 分频…111: HCLK 16 分频注意软件必须保证 APB1 时钟频率不超过 36MHz。这是由挂载在 APB1 上的外设性能决定的。2.3 外设时钟使能寄存器这是我们在开发中最常打交道的寄存器组。MCU 的外设分布在不同的总线上因此使能寄存器也按总线分类。2.3.1 APB2 外设时钟使能寄存器 (RCC_APB2ENR)APB2 是高速总线挂载着对速度要求较高的外设。位 2 (IOPAEN)IO port A clock enable。置 1 开启 GPIOA 时钟置 0 关闭。这是点亮 LED 或读取按键前必须做的第一步。位 3 (IOPBEN)IO port B clock enable。位 4 (IOPCEN)IO port C clock enable。其他外设还包括 USART1 (位 14)、SPI1 (位 12)、TIM1 (位 11)、ADC1/2 (位 9/10) 等。2.3.2 APB1 外设时钟使能寄存器 (RCC_APB1ENR)APB1 是低速总线最大频率通常限制在 36MHz。位 0 (TIM2EN)TIM2 定时器时钟使能。位 1 (TIM3EN)TIM3 定时器时钟使能。位 14 (SPI2EN)SPI2 时钟使能。位 17 (USART2EN)USART2 时钟使能。其他外设还包括 I2C1/2、CAN、USB、DAC 等。三、RCC 时钟系统实验设计与原理3.1 RCC 的核心定位与实验思路在 STM32 的片上外设体系中RCCReset and Clock Control复位和时钟控制扮演着极其特殊的角色。它不像 GPIO 那样可以直接对外输出高低电平来控制硬件也不像 UART 那样直接进行数据收发。RCC 本质上是一个“对内服务”的模块它的核心任务是为 MCU 内部的其他所有外设提供时钟源。可以说没有 RCC 提供的时钟信号CPU 无法执行指令外设也无法工作。为了直观地验证 RCC 对系统运行速度的影响我们设计了一个基于 LED 闪烁的实验。虽然通常控制 LED 闪烁我们会使用标准库提供的HAL_Delay函数但在本次 RCC 实验中为了更纯粹地观察时钟频率与 CPU 执行效率的关系我们决定摒弃HAL_Delay转而采用一种更底层的延时方式自定义一个基于while循环的CPU_Delay函数。这个函数的逻辑非常简单通过传入一个巨大的计数值例如 654545让 CPU 在一个while(cnt--)的死循环中不断执行减法操作直到计数值减为 0 才退出。这种延时方式的本质是消耗 CPU 的运算周期。既然代码执行的指令数量是固定的那么这段代码跑完所需的时间就完全取决于 CPU 的运行速度即主频 FCLK。因此我们的实验逻辑链条非常清晰编写一个依赖 CPU 运算速度的软件延时函数。通过 STM32CubeMX 动态调整 RCC 的时钟树配置主要是分频系数。观察在相同代码、相同循环次数下LED 闪烁频率随主频降低而变慢的现象。这就相当于把 CPU 变成了一个“时钟频率显示器”主频越快循环跑得越快灯闪得越快主频越慢循环跑得越慢灯闪得越慢。3.2 硬件连接与控制逻辑解析本次实验选用的硬件对象是开发板上的红色 LED4。通过分析原理图可知LED4 对应的网络标号为SYS_LED1其物理引脚连接在 MCU 的PF8端口上。从电路设计上看LED4 采用了共阳极接法阳极连接到 VDD_3.3V 电源。阴极串联了一个 680Ω 的限流电阻R47后连接到 PF8 引脚。型号使用的是 NCD0805G1 封装的红色 LED。基于这种电路结构GPIO 的控制逻辑如下输出高电平HighPF8 为 3.3VLED 两端无压差电流不导通LED 熄灭。输出低电平LowPF8 为 0VLED 两端形成 3.3V 压差电流导通LED 点亮。在 STM32CubeMX 的配置界面中我们需要将 PF8 配置为GPIO_Output模式输出类型选择Output Push Pull推挽输出初始电平设为High默认熄灭并且不需要上拉或下拉电阻。这样的配置确保了我们可以直接通过翻转 PF8 的电平来控制 LED 的亮灭状态。四、项目配置与代码实现细节4.1 CubeMX 关键配置项在生成工程代码之前我们需要在 STM32CubeMX 中完成以下核心配置MCU 选型选择STM32F103ZETx系列芯片。调试接口务必开启SYS-Debug-Serial Wire。这是为了防止芯片锁死导致无法再次下载程序。GPIO 设置找到 PF8 引脚将其设置为 GPIO_Output。在配置栏中确认 Mode 为 Output Push PullPull-up/Pull-down 为 No pull-up and no pull-downMaximum output speed 设为 Low对于点灯实验低速足以减少EMI。时钟源在 Clock Configuration 选项卡中确保 HSE外部高速晶振被选中并设置为 Crystal/Ceramic Resonator 模式频率输入为 8MHz。4.2 自定义延时函数的陷阱与修正这是本实验中最关键的代码环节。为了实现依赖主频的延时我们编写了如下函数voidCPU_Delay(uint32_tcnt){while(cnt--);}在主循环中调用方式如下while(1){HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_8);// 翻转 PF8 电平CPU_Delay(654545);// 延时约 0.2s (72MHz下)}编译器优化的“坑”初看这段代码似乎没有问题但在实际编译运行时你可能会发现 LED 根本不闪烁或者闪烁极快肉眼无法察觉。这是因为现代编译器如 GCC 或 ARMCC非常聪明它会进行激进的优化。当编译器看到while(cnt--)时它发现cnt只是一个局部变量且在循环内部除了自减没有任何其他副作用比如没有被外部中断修改也没有被用来驱动外设。为了提高运行效率编译器可能会将这个变量直接放入 CPU 的高速寄存器中进行运算甚至如果优化等级过高它可能认为这个空循环毫无意义而直接将其“优化掉”删除。一旦变量进入寄存器或被优化原本预期的内存访问延迟就会消失导致延时失效。volatile 关键字的救赎为了解决这个问题必须使用volatile关键字修饰参数。修改后的代码如下voidCPU_Delay(volatileuint32_tcnt){while(cnt--);}volatile的字面意思是“易变的”。它告诉编译器“这个变量随时可能被改变不要把它缓存到寄存器里也不要优化掉对它的读写操作每次都必须老老实实地去内存中读取它的值。”在 Cortex-M3 内核的架构中这意味着禁止寄存器缓存每次执行cnt--时CPU 必须从内存Memory中读取cnt的值到寄存器减 1 后再写回内存。保持内存可见性这种频繁的内存读写操作虽然降低了效率但正是我们想要的——它强制产生了固定的时间开销从而实现了精准的软延时。4.3 时钟树配置策略为了验证不同主频下的效果我们需要在 CubeMX 的 Clock Configuration 页面调整 AHB PrescalerAHB 预分频器。场景一全速运行基准SYSCLK72 MHz由 HSE 8MHz 经过 PLL 9倍频得到。HCLK (AHB Prescaler)/1。结果CPU 主频为 72MHz。此时CPU_Delay(654545)大约耗时 0.2秒左右LED 闪烁节奏适中。场景二降频测试SYSCLK保持 72 MHz 不变。HCLK (AHB Prescaler)修改为 /4。结果CPU 主频变为 72 / 4 18 MHz。现象预期由于 CPU 速度变成了原来的 1/4执行同样的 65万次循环所需的时间将变成原来的 4 倍。肉眼观察到的 LED 闪烁频率会明显变慢。结语
返回列表