【STM32单片机学习】第9章 基础重点—时钟系统
2025-11-04 来源:bilibili
9.1 关于时钟
时钟对于一款芯片非常重要,其作用相当于人的心脏,人只有在心率正常稳定的情况下才能健康生活,同样的,芯片只有工作在合法正常的时钟频率下才能保证程序得到正常的运行。
本章就将从时钟树开始分析STM32F103的时钟系统,其中包括内部高速/低速时钟源、外部高速/低速时钟源、PLL(锁相环)和系统滴答定时器。以高屋建瓴的形式让用户对STM32F103的时钟系统有一个整体的认识,并在后续的时钟配置实验中让用户进一步的了解HAL库下的时钟配置流程。
首先读者要学会如何读STM32F103系列的时钟树,如图 9.1.1 所示,左侧的①HSI(内部高速时钟)、②HSE(外部高速时钟)、③LSE(外部低速时钟)、④LSI(内部低速时钟)为时钟源,右侧的各种片上外设。图中矩形框内用“/”加数字表示分频器,数字表示几分频;矩形框内用“X”加数字表示的为锁相环,数字表示几倍频;倒梯形表示选择器,长边表示多个输入,短边表示选择其中一个输出。
⑥系统时钟SYSCLK最高为72MHz,从图中左侧的选择器SW可以看到来源有三个,分别是:①内部高速时钟HSI(绿色)、⑤锁相环时钟PLLCLK(紫色)和②外部高速时钟HSE(黄色),而锁相环时钟PLLCLK由内部高速时钟HSI和外部高速时钟HSE,经过分频和PLL锁相环倍频而来。
内部高速时钟HSI可直接经过选择器SW给系统时钟SYSCLK,此时系统时钟SYSCLK为8MHz;内部高速时钟HSI先2分频,再经过选择器PLLSRC进入锁相环PLLMUL,最大倍频为16倍,得到64MHz的锁相环时钟PLLCLK给系统时钟SYSCLK;当外部高速时钟HSE(假设外接晶振为8MHz时)直接给选择器SW,则系统时钟SYSCLK为8MHz;当外部高速时钟HSE(假设外接晶振为8MHz时)直接经过选择器PLLXTPRE给PLLSRC,再经过PLLMUL 9倍频,得到72MHz的PLLCLK给系统时钟SYSCLK。
⑩RTCCLK(实时时钟)的时钟源也有三个,分别是②外部高速时钟HSE的128倍分频(黄色)、③外部低速时钟LSE的32.768kHz(蓝色)、④内部低速时钟LSI的40kHz(橙色)。
11.WDGCLK(独立看门狗)的时钟来源于④内部低速时钟LSI的40kHz(橙色)。
理清各个时钟来源后,再来看各总线的时钟。⑦高速接口总线AHB由⑥SYSCLK系统时钟分频得到,最高是系统时钟的72MHz。⑧外设总线APB1和⑨外设总线APB2,来源于⑦高速接口总线AHB,APB1的输出时钟最高是36MHz,APB2的输出时钟最高是72MHz。APB1和APB2下有各种外设,比如GPIO、USART等。

图 9.1.1 时钟树
9.2 硬件设计
内部时钟HSI不涉及硬件,外部时钟HSE参考前面最小系统的时钟电路,“5.2.2 时钟电路”。
9.3 软件设计
9.3.1 软件设计思路
实验目的:分别使用内部时钟HSI和外部时钟HSE作为系统时钟。
1) 使用内部时钟HSI配置系统时钟到最大值64Mhz;
2) 调用库函数读取系统时钟值以验证;
3) 使用外部时钟HSE配置系统时钟到最大值72Mhz;
4) 调用库函数读取系统时钟值以验证;
本实验配套代码位于“5_程序源码3_基础重点—时钟系统”。
9.3.2 软件设计讲解
1) 使用内部时钟源HSI作为PLL的时钟源,然后将PLL作为系统时钟源
内部时钟HSI配置如代码段 9.3.1 所示,HAL库定义了两个结构体,只需要设置这两个结构体成员,就完成对时钟的设置。
代码段 9.3.1 使用HSI作为系统时钟(stm32f1xx_clk.c)
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSI)
* SYSCLK(Hz) = 64000000
* HCLK(Hz) = 64000000
* AHB Prescaler = 1
* APB1 Prescaler = 2
* APB2 Prescaler = 1
* PLLMUL = 16
* Flash Latency(WS) = 2
* @param None
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSEState = RCC_HSE_OFF;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
while(1);
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | 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)
{
while(1);
}
}
17行:定义RCC_OscInitTypeDef结构体变量,该结构体主要是选择时钟源、设置时钟开关状态、PLL倍频;
20行:设置时钟来源为内部高速时钟HSI;
21行:不使用外部高速时钟,则HSE关闭;
22行:设置HSE预分频,这里没用上HSE,默认为1分频即可;
23行:内部高速时钟HSI打开;
24行:锁相环PLL打开;
25行:选择PLL时钟源,为HSI的二分频;
26行:PLL的倍频为16倍频;
27行:使用“HAL_RCC_OscConfig()”函数设置RCC_OscInitStruct;
18行:定义RCC_ClkInitTypeDef结构体变量,该结构体主要是配置系统时钟、AHB、APB1、APB2的分频;
34-35行:设置哪些时钟将被设置;
36行:设置系统时钟SYSCLK的来源为PLLCLK;
37行:设置HCLK时钟(AHB Clock)为1分频(不能超过最大72MHz);
38行:设置PCLK1时钟(APB1 Clock)为2分频(不能超过最大36MHz);
39行:设置PCLK2时钟(APB2 Clock)为1分频(不能超过最大72MHz);
41行:用“HAL_RCC_ClockConfig()”函数设置RCC_ClkInitStruct;不同的系统时钟需要设置不同的等待周期(LATENCY),这里设置为2,原因参考后面的“29.1 关于内部FLASH”读Flash部分,也可先跳过,不去深究;
将代码和时钟树进行对照,如图 9.3.1 所示,红色数字为对应代码行号,蓝色数字为时钟频率,红色线段为时钟走向。内部高速时钟HSI经过二分频为4MHz,再经过PLL 16倍频为64MHz,作为系统时钟SYSCLK,再1分频给AHB,AHB再2分频给APB1,1分频给APB2。

图 9.3.1 设置HSI和时钟树对照示意图
2) 获取系统时钟的函数
主函数里调用HAL库提供的“HAL_RCC_GetSysClockFreq()”函数获取系统时钟验证。
代码段 9.3.2 获取系统时钟(main.c)
// 此处定义全局变量以便在debug的时候可以看到这个变量的值
uint32_t sys_clk = 0;
int main(void)
{
// 初始化HAL库函数必须要调用此函数
HAL_Init();
/*
* 系统时钟即AHB/APB时钟配置
* 当使用内部高速时钟HSI(8MHz)配置系统时钟时,使用PLL前会默认先二分频得到4MHz的PLL分频输入频率
* 然后经过锁相环放大,最大放大倍数为16,即4*16=64MHz是能配置的最大系统频率,F103的最大系统频率为72MHz,64MHz显然是合法的系统频率
*/
SystemClock_Config();
// 调用库函数来检验自己的配置是否成功配置为系统频率64MHz
sys_clk = HAL_RCC_GetSysClockFreq();
while(1);
}
3) 使用外部时钟源HSE作为PLL的时钟源,然后将PLL作为系统时钟源
与前面类似,这里设置外部高速时钟HSE作为系统时钟,仍是设置两个结构体。
代码段 9.3.3 使用HSE作为系统时钟(stm32f1xx_clk.c)
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 72000000
* HCLK(Hz) = 72000000
* AHB Prescaler = 1
* APB1 Prescaler = 2
* APB2 Prescaler = 1
* PLLMUL = 9
* Flash Latency(WS) = 2
* @param None
* @retval None
*/
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.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
while(1);
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
| 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)
{
while(1);
}
}
17行:定义RCC_OscInitTypeDef结构体变量,该结构体主要是选择时钟源、设置时钟开关状态、PLL倍频;
20行:设置时钟来源为外部高速时钟HSE;
21行:内部高速时钟HSE打开;
22行:设置HSE预分频,设置为1分频;
23行:不使用内部高速时钟,则HSI关闭;
24行:锁相环PLL打开;
25行:选择PLL时钟源,为HSE;
26行:PLL的倍频为9倍频;
27行:使用“HAL_RCC_ClockConfig()”函数设置RCC_OscInitStruct;
18行:定义RCC_ClkInitTypeDef结构体变量,该结构体主要是配置系统时钟、AHB、APB1、APB2的分频;
34-35行:设置哪些时钟将被设置;
36行:设置系统时钟SYSCLK的来源为PLLCLK;
37行:设置HCLK时钟(AHB Clock)为1分频(不能超过最大72MHz);
38行:设置PCLK1时钟(APB1 Clock)为2分频(不能超过最大36MHz);
39行:设置PCLK2时钟(APB2 Clock)为1分频(不能超过最大72MHz);
41行:用“HAL_RCC_ClockConfig()”函数设置RCC_ClkInitStruct;
将代码和时钟树进行对照,如图 9.3.2 所示,红色数字为对应代码行号,蓝色数字为时钟频率,红色线段为时钟走向。外部高速时钟HSE不分频,通过选择器PLLXTPRE和选择器PLLRC,再PLL 9倍频为72MHz,作为系统时钟SYSCLK,再1分频给AHB,AHB再2分频给APB1,1分频给APB2。当前状态,为MCU全速工作状态。

图 9.3.2 设置HSE 和时钟树对照示意图
4) 获取系统时钟的函数
与前面类似,主函数里调用HAL库提供的“HAL_RCC_GetSysClockFreq()”函数获取系统时钟验证。
代码段 9.3.4 获取系统时钟(main.c)
uint32_t sys_clk = 0;
int main(void)
{
// 初始化HAL库函数必须要调用此函数
HAL_Init();
/*
* 系统时钟即AHB/APB时钟配置
* 当使用外部高速时钟HSE(8MHz)配置系统时钟时,使用PLL前可以选择不分频或者二分频,我们要配置到最大72MHz的系统频率此处当然是不分频
* 然后经过锁相环放大,最大放大倍数为16,我们要想得到72MHz,此处选择9倍放大系数,即8*9=72MHz即可达到目标
*/
SystemClock_Config();
// 调用库函数来检验自己的配置是否成功配置为系统频率72MHz
sys_clk = HAL_RCC_GetSysClockFreq();
while(1);
}
9.4 实验效果
本实验对应配套资料的“5_程序源码3_基础重点—时钟系统”下的“3.1_HSI配置系统时钟64M”和“3.2_HSE配置系统时钟72M”。打开工程后,参考前面“4.4 下载程序和调试”进入调试模式,在代码中找到“sys_clk”选中,右键选择“Add ‘sys_clk’ to …”->“Watch 1”,即可在右下角显示该变量查看窗口,可以看到当前“sys_clk”为0,如图 9.4.1 所示。

图 9.4.1 打开变量查看窗口
在“while(1);”处加上断点,运行程序,可以看到“sys_clk”分别为64000000和72000000,如图 9.4.3 和图 9.4.4 所示,与代码设计吻合。如果显示16进制,可选中该数字,右键去掉勾选“Hexadecimal Dispaly”,如图 9.4.2 所示。

图 9.4.2 改为十进制显示

图 9.4.3 HSI作为系统时钟效果

图 9.4.4 HSE作为系统时钟效果
上一篇:什么是stm32单片机
下一篇:STM32驱动WS2812B
- 意法半导体中国本地造STM32微控制器启动规模量产
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 基于机智云与STM32的智能拐杖安全监测系统在养老物联网中的应用
- 内置全栈安全,一站式满足CRA法案与IEC 62443标准——米尔STM32MP257核心板
- 如何用 STM32 FLASH 实现等效 100 万次擦写的 EEPROM 功能?
- 实战解析:通过一个小项目掌握STM32所有外设
- STM32学了两年半,却还是不会做项目
- 意法半导体推出最新STM32MP21微处理器,兼具高性价比、低功耗、高灵活性
- 基于STM32的矿井作业环境监测系统设计与实现
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




