前言:本文章目的是透过ST官方固件包(STM32CubeF1 firmware package)内的参考手册(Reference manual)UM1847与STM32F10xxx家族芯片的参考手册RM0008,分析HAL库是如何初始化时钟的。
分析这问题的原因有:
- 时钟树是整个系统中十分关键的部分,没有时钟,其他模块就无法正常运作。
- HAL_Delay(uint32_t) 延迟功能函数为什么能在不同系统时钟(SYSCLK)情况下提供相同的延时功能。
HAL库文件的组成部分
想分析HAL库从启动到初始化时钟的整个流程,就需要先了解HAL库文件的组成部分。
在STM32CubeF1的固件库文件的文档中,就提供了下图关于STM32CubeF1 firmware package structure(固件包的文件结构示意图)
![f3c7fd2d92f7a7d198fc6eca856027ce.png](https://i-blog.csdnimg.cn/blog_migrate/d03cdb89076cbbe847c31b90b303bc52.jpeg)
从图中给出的信息中可以知道的是,一个工程文件中(一般由STM32CubeMX自动生成)"Drivers"目录内的包括了BSP(板级支持包)、CMSIS(微控制器软件接口标准)、HAL库和LL库。"Drivers"可以理解为C语言中的库文件,里面包含了ST提供的各种功能函数。而"Middlewares"中间层则是算应用层,提供给需要相关应用开发的开发者使用。
在工程文件中,除了上述的库文件外,还有3个比较重要的文件:main.c(主程序文件)、system_stm32f1xx.c(CMSIS标准接口的入口文件)、startup_stm32f103rbtx.s(芯片的启动文件)。其中,后两个是特定芯片的(本文是基于STM32F103RBT6)文件,但对应不同芯片的功能基本是一致的,可以打开对应的文件看里面的@brief(简述)就基本了解该文件的功能了。下面简单例举该文件都做了些什么事情:
- startup_stm32f103rbtx.s
![6b51330db8024c6f91ae6dd52b9bd36a.png](https://i-blog.csdnimg.cn/blog_migrate/cb5848524119c5c6f94930db8fdf10e8.jpeg)
-
- 初始化设置SP(堆栈指针)
- 初始化设置PC(程序指针)
- 设置中断向量表(中断入口地址)
- 配置时钟系统
- 进入main主程序
- system_stm32f1xx.c
![a614095aaf61b07639966066fb70aea5.png](https://i-blog.csdnimg.cn/blog_migrate/35cf5bd85f330bd8ad2a34a3eebafb0c.jpeg)
-
- SystemInit() 初始化系统时钟函数——该功能是在"startup_stm32f1xx_xx.s"文件中调用
在还未进入main主程序前,主要涉及的的文件就是上面这两个针对特定芯片的配置文件。这里还未涉及到主要的配置时钟树的过程,这里是在为后面使用HAL库做前期的必要准备(类似于OSI模型的最底层,目的是服务于上层,对于上层HAL库来说下层是透明的,实现HAL库编写的程序代码可以简单的移植到不同芯片中)
时钟树(Clock tree)——相关配置HAL库函数
![91aeebf4bd5c19962c9c8f7189062571.png](https://i-blog.csdnimg.cn/blog_migrate/807342f6bfea3d5a87ff6adf2dab3b12.jpeg)
接触STM32系列芯片的的不会对该时钟树图陌生,这里做了点简单的标注,辅助理解时钟是怎么经过分频、倍频、选择器最后被系统时钟SYSCLK选用的。主要突出了STM32有5个时钟源,分别是HSE、HSI、LSI、LSE和PLLCLK(PLL由HSI或HSE提供输入源)。SYSCLK(系统时钟)则是来源于HSI、PLLCLK或HSE其中一个来提供,最大支持72MHz。
- 配置时钟的HAL库文件——stm32f1xx_hal_rcc.c、stm32f1xx_hal_rcc_ex.c
![v2-2e26c48c98e8896514472efdc48f6af0_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=ad15f047-9c31-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-2e26c48c98e8896514472efdc48f6af0_b.jpg)
![bce6a939ce2431b9b59248c67e55d601.png](https://i-blog.csdnimg.cn/blog_migrate/9542317ace98eda85f5ff1cd693935fe.jpeg)
注意:使用HAL库函数前,必须在main函数中先对HAL库进行初始化(HAL_Init()函数)。另外,如果在STM32CubeMX配置中没有进行初始化配置,而自己编写代码调用HAL库来进行初始化配置时,需要在"stm32f1xx_hal_conf.h"文件中的"Module Selection"中,通过取消相关HAL库的注释开启支持。
- stm32f1xx_hal_rcc.c => HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
![a8897ff7c4b44a811e134a543ee253a1.png](https://i-blog.csdnimg.cn/blog_migrate/634a32f599ed18b19c26b6d31068abdd.jpeg)
HAL_RCC_OscConfig函数——生成SYSCLK(系统时钟源)
- RCC_OscInitTypeDef结构体成员
- OscillatorType: 选择配置目标Oscillators(晶振)
- HSEState: 高速外部时钟开启或关闭
- HSEPredivValue: HSE预分频(CLK TREE中的PLLXTPRE)
- LSEState: 低速外部时钟开启或关闭
- HSIState: 高速内部时钟开启或关闭
- HSICalibrationValue: 一般设置默认值: RCC_HSICALIBRATION_DEFAULT
- LSIState: 低速内部时钟开启或关闭
- PLL:
- PLLState: PLLCLK开启或关闭
- PLLSource: PLLSRC选择器(HSE或HSI/2)
- PLLMUL: PLLMUL倍频器倍率(2-16)
注意:HSE的开启再分为External source (HSE bypass)和External crystal/ceramic resonator (HSE crystal) 具体信息见RM0008文档7.2.1HSE Clock。
![b3e95a05adba602ed1592689938c8580.png](https://i-blog.csdnimg.cn/blog_migrate/d8bb1174cf4dbfe5a9d7040891167463.jpeg)
- stm32f1xx_hal_rcc.c => HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
![940d1e1df98f591c77023158359df8bf.png](https://i-blog.csdnimg.cn/blog_migrate/201e67cdbac54d4be1a2b1a536b4ed4b.jpeg)
HAL_RCC_ClockConfig函数——配置CPU、AHB(APB2)、APB(APB1)
- RCC_ClkInitTypeDef结构体成员
- ClockType: 选择配置时钟目标
- SYSCLKSource: SYSCLK的时钟源(HSI、PLLCLK、HSE)
- AHBCLKDivider: AHP Prescaler(/1, 2, ...512)分频
- APB1CLKDivider: APB1 Prescaler(/1, 2, 4, 8, 16)分频
- APB2CLKDivider: APB2 Prescaler(/1, 2, 4, 6, 16)分频
注意:HAL_RCC_ClockConfig函数中还有一个参数FLatency,其值取决于SYSCLK频率(stm32f1xx_hal_rcc.c中描述)
![5ac2087516f6557ca238e6eef7037288.png](https://i-blog.csdnimg.cn/blog_migrate/18e29b0cd9c78be7d64edcc8dbd7a7d3.jpeg)
只需要在主函数中通过结构体配置好相关参数,再调用HAL_RCC_OscConfig()和 HAL_RCC_ClockConfig()就完成了整体时钟树的配置。
总结分析——HAL_Delay()的实现过程
通过逐步剖析函数,有个大概宏观认知HAL库是如何配置时钟树的就已经足够,因为现在已经不需要再要求用户编写初始化配置代码,交给STM32CubeMX应用程序完成即可。
回到文章开头,分析从启动到整个时钟配置过程,一方面是更具体了解STM32的Clock Tree配置过程,及整体的时钟架构,另一方面也是熟悉HAL库。还有一点就是想解决第二个问题,HAL_Delay()是怎样实现在不同SYSCLK下具实现相同功能的?好像剖析至此都没得到答案,但其实在这过程中HAL库已经在用户无感间,通过配置SysTick(AHB经过8分频后的Cortex system timer)生成1毫秒中断(HAL_Delay通过SysTick定时中断实现)。SysTick配置是在HAL_InitTick()函数中完成。
![c90ec19e4923602e9fe5b59e71f122d8.png](https://i-blog.csdnimg.cn/blog_migrate/10e3ab9130d6a75007032ad33ac5a33b.jpeg)
在HAL_InitTick的@note中明确写出了该函数会在HAL_Init()和通过HAL_RCC_ClockConfig()配置时钟时调用,再结合HAL_Init()的解释。
![bf819f541d052f0a1dbcb261516102c5.png](https://i-blog.csdnimg.cn/blog_migrate/8d4a4a80c464b3e3ae44a6d12eb1ce7b.jpeg)
"Configures the SysTick to generate an interrupt each 1 millisecond"问题迎刃而解。
后话:探索HAL_Delay()延迟函数如何实现的,本以为是个很轻易的过程。但实际却从启动文件->初始化HAL库->配置Clock Tree(时钟树)的分析过程中才得到线索。最终才从较宏观的层面知道是在系统时钟配置过程中完成SysTick定时器的初始化——默认配置为1ms延迟中断。HAL_Delay()函数正是借助着SysTick定时器中断实现的,从中也知道HAL_Delay()函数使用过程中需要注意的地方(需要借助中断)。文章内容看起来是零零散散的(可能还有点杂乱无章),主要原因还是本意是想作为个学习笔记之类的方式记录思考的过程,同时也分享下分析问题时的思路。在面对其他类似问题时,利用本文的分析方法相信也能得到解决方法。这也是我想分享给阅读者文章的人——多点自己的思考分析过程。
后面估计还会写一篇关于STM32CubeIDE(MacOS)入门使用指南。有兴趣的可以关注期待下(我这文笔...可能也没谁想看吧...Anyway, 主要还是总结笔记同时提供思路)