4. 中断系统与外部中断
时钟,是单片机的“心跳”。没有时钟,CPU无法执行指令,外设无法工作。我见过不少新手,外设配置得头头是道,结果程序跑不起来——最后发现是时钟没配对。这一章,咱们就把STM32的时钟树彻底搞明白。
4.1 时钟源:HSI、HSE、PLL
STM32的时钟源,说白了就是几个“振荡器”。它们各有各的脾气,选哪个得看你的需求。
| 时钟源 | 频率 | 精度 | 启动时间 | 典型用途 |
|---|---|---|---|---|
| HSI(内部高速振荡器) | 8 MHz(可校准) | ±1% 校准后 | 几微秒 | 上电默认、低功耗、对精度要求不高的场合 |
| HSE(外部高速振荡器) | 4~26 MHz(常用8/12/16 MHz) | ±0.01%(取决于晶振) | 几毫秒 | 需要精确时钟的场景(如USB、以太网) |
| PLL(锁相环) | 最高可达216 MHz(F4系列) | 取决于输入源 | 需等待锁定 | 将低频时钟倍频到高频,供CPU和高速外设使用 |
4.2 时钟树结构:从源头到外设
STM32的时钟树,就像一个“自来水管道系统”。源头是HSI/HSE,经过PLL增压(倍频),再通过分频器降压,最后送到各个“水龙头”(CPU、AHB总线、APB总线、外设)。
下面这张图,是我自己画的时钟树简化流程,你看一眼就能明白:
你看,从HSI/HSE到PLL,再到SYSCLK,最后分频给各个总线。每一步都有寄存器控制,但CubeMX帮我们做了可视化配置。
4.3 CubeMX时钟配置界面操作
打开CubeMX,切换到“Clock Configuration”选项卡。你会看到一个图形化的时钟树。说实话,我第一次看到这个界面也有点懵,但用熟了就觉得真香。
配置步骤(以STM32F407VGT6为例):
- 选择HSE作为时钟源:在“HSE”输入框里选择“Crystal/Ceramic Resonator”。
- 输入晶振频率:我板子上是8 MHz晶振,所以在“Input frequency”填8。
- 配置PLL:
- PLL源选择HSE
- PLL_M = 8(分频,使VCO输入为1 MHz)
- PLL_N = 336(倍频,VCO输出336 MHz)
- PLL_P = 2(分频,得到SYSCLK = 168 MHz)
- 配置AHB分频器:设置为1,HCLK = 168 MHz。
- 配置APB1/APB2:
- APB1预分频器 = 4,得到42 MHz(最大允许值)
- APB2预分频器 = 2,得到84 MHz(最大允许值)
为什么这么配?
STM32F407的最高主频是168 MHz。PLL的VCO输入必须在1~2 MHz之间,所以先8分频得到1 MHz,再336倍频得到336 MHz,最后2分频得到168 MHz。APB1最高42 MHz,APB2最高84 MHz——这是芯片的硬限制,超了就会出问题。
4.4 HAL_Delay延时函数原理
HAL_Delay(1000) 能延时1秒,这个函数大家肯定都用过。但你知道它背后是怎么工作的吗?
说白了,HAL_Delay依赖一个“滴答计数器”——SysTick定时器。SysTick每1毫秒产生一次中断,在中断里把一个全局变量 uwTick 加1。HAL_Delay就是不断查询这个变量的值,直到达到目标值。
看代码:
// 在 stm32f4xx_hal.c 中
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
// 防止中断影响,加个保护
while((HAL_GetTick() - tickstart) < wait)
{
}
}
// SysTick中断服务函数
void SysTick_Handler(void)
{
HAL_IncTick(); // uwTick++
}
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
你看,逻辑其实很简单。但这里有个坑——HAL_Delay是阻塞的。你在延时期间,CPU一直在空转,什么事都干不了。
4.5 SysTick定时器简介
SysTick是ARM Cortex-M内核自带的24位递减计数器。它不属于STM32的外设,而是内核的一部分。所以,不管你用哪家厂商的Cortex-M芯片,SysTick都是一样的。
| 特性 | 说明 |
|---|---|
| 位宽 | 24位(最大计数值 16,777,215) |
| 时钟源 | HCLK 或 HCLK/8(可配置) |
| 工作模式 | 递减计数,计数到0自动重装载 |
| 中断 | 计数到0时可触发异常(中断号15) |
| 主要用途 | 提供操作系统心跳、HAL库延时、任务调度 |
SysTick的配置通常在 HAL_Init() 里完成。CubeMX生成的代码会自动设置好,你一般不需要手动改。但如果你想调整SysTick的中断频率,可以修改 uwTickFreq 变量。
SysTick配置公式:
重装载值 = (HCLK频率 / 8) / 1000 - 1
以HCLK=168 MHz为例:
重装载值 = (168,000,000 / 8) / 1000 - 1 = 20,999
这样,SysTick每1毫秒中断一次。
嗯,到这里,时钟配置的核心内容就讲完了。你可能会问:“我是不是每次都要手动算这些参数?”其实不用。CubeMX的Clock Configuration界面会自动帮你算好,你只需要在图形界面上点一点、填一填,它就会生成对应的初始化代码。但理解背后的原理,能让你在遇到问题时快速定位——比如PLL锁不住、外设时钟不对、延时不准,这些问题的根因往往就在时钟树上。