3. STM32启动文件与系统时钟配置
好,咱们进入第三章。这一章要解决一个很实在的问题——怎么让STM32一上电就按我们的想法跑起来,并且给FreeRTOS提供一个稳定的心跳。
启动文件和时钟配置,说白了就是芯片的「开机流程」。我刚开始学的时候也觉得这部分挺枯燥的,不就是复制粘贴嘛。但后来踩过几次坑才发现,这里要是搞错了,后面RTOS跑起来各种诡异问题,你根本不知道从哪查起。
3.1 启动文件在干什么
STM32的启动文件(startup_stm32fxxx.s)是汇编写的。它主要干三件事:
- 设置堆栈指针(SP)
- 初始化中断向量表
- 调用SystemInit(),然后跳转到main()
嗯,这里要注意。启动文件里默认调用了SystemInit(),这个函数在system_stm32fxxx.c里。我们的时钟配置,就是从这里开始的。
3.2 系统时钟配置——HSE + PLL
FreeRTOS需要系统节拍,一般是1ms一次。这个节拍来自SysTick定时器,而SysTick的时钟源又来自系统时钟。所以,系统时钟准不准,直接决定了RTOS的节拍准不准。
我常用的配置是:外部8MHz晶振(HSE) → PLL倍频 → 72MHz系统时钟。这是STM32F103最经典的配置。
来看看代码:
void SystemInit(void)
{
// 1. 开启HSE,等待就绪
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
// 2. 配置FLASH预取指、等待周期
// 72MHz下,等待周期设为2
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
// 3. 配置PLL:HSE 8MHz * 9 = 72MHz
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;
RCC->CFGR |= RCC_CFGR_PLLMULL9;
// 4. 配置AHB、APB1、APB2分频
// AHB = 72MHz, APB1 = 36MHz, APB2 = 72MHz
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB不分频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 2分频
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2不分频
// 5. 开启PLL,等待锁定
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
// 6. 切换系统时钟为PLL输出
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
这段代码看起来不长,但每一步都有讲究。我一个个说。
3.3 关键细节与避坑
第一个坑:HSE起振时间。
HSE起振需要时间,尤其是外部晶振质量一般的时候。我遇到过一块板子,HSE死活起不来,后来发现是晶振负载电容焊错了。所以while循环等待HSE就绪这一步,不能省略,也不能用超时替代——除非你做了容错处理。
第二个坑:FLASH等待周期。
很多人会忽略这一步。STM32F103在72MHz下,FLASH等待周期必须设为2,否则程序会跑飞。你想想看,CPU比FLASH快那么多,不等一下怎么行?
第三个坑:APB1和APB2的分频。
APB1最高只能到36MHz,APB2可以到72MHz。如果你把APB1设成72MHz,外设(比如USART、I2C)会工作异常。我见过有人把APB1设成72MHz,然后USART波特率怎么算都不对,其实就是这里的问题。
3.4 配置SysTick为FreeRTOS提供节拍
系统时钟配好了,接下来要让SysTick产生1ms中断。FreeRTOS的vPortSetupTimerInterrupt()会帮我们做这件事,但前提是configCPU_CLOCK_HZ和configTICK_RATE_HZ要配对。
在FreeRTOSConfig.h里:
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
SysTick的重装载值 = 系统时钟频率 / 节拍频率 = 72MHz / 1000Hz = 72000。
这个值会在vPortSetupTimerInterrupt()里自动计算。你只要确保configCPU_CLOCK_HZ和实际系统时钟一致就行。
SystemInit(),然后再初始化FreeRTOS。顺序不要搞反。我曾经试过先创建任务再配时钟,结果SysTick中断来了,系统时钟还没稳定,直接进HardFault。
3.5 验证时钟是否配置正确
配完了怎么验证?最简单的方法:用示波器看MCO引脚。STM32的PA8可以输出系统时钟。
// 使能MCO输出,选择系统时钟
RCC->CFGR |= RCC_CFGR_MCO_SYSCLK;
// 配置PA8为复用推挽输出
GPIOA->CRH &= ~0x0000000F;
GPIOA->CRH |= 0x0000000B;
用示波器量PA8,如果看到72MHz的方波,说明时钟配对了。如果没有波形或者频率不对,回头检查PLL倍频系数和晶振频率。
嗯,这里有个小技巧。如果你没有示波器,也可以用定时器来间接验证。比如配置一个定时器,让它1秒中断一次,然后在中断里翻转一个GPIO,用逻辑分析仪看周期。虽然精度差点,但够用。
3.6 总结一下
这一章的核心就三件事:
- 启动文件负责初始化,我们一般不动它
- 系统时钟用HSE+PLL配到72MHz,注意FLASH等待周期和APB分频
- SysTick的配置依赖
configCPU_CLOCK_HZ,一定要和实际时钟一致
时钟配好了,FreeRTOS才能有个准的节拍。下一章我们会开始写第一个任务,让LED灯闪烁起来。到时候你就知道,时钟不准的话,那个闪烁频率会多离谱。