Cubemx与HAL库系列教程|系统时钟配置详解及源码分析
STM32时钟系统简介
STM32种类繁多,时钟系统也不尽相同,但基本的还是大差不差,今日小飞哥就F1系列的MCU简单聊一聊STM32的时钟系统
1、时钟种类介绍:
先来看一看时钟树图,包含了整个系统的始终来源及各个外设的始终来源
STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。
接下来,各个时钟的含义听小飞哥白话白话,自己看ST手册也可以哒
1.1 HSI时钟(内部高速时钟)
HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。
HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。
校准
制造工艺决定了不同芯片的RC振荡器频率会不同,这就是为什么每个芯片的HSI时钟频率在出厂前已经被ST校准到1%(25°C)的原因。系统复位时,工厂校准值被装载到时钟控制寄存器的HSICAL[7:0]位。 如果用户的应用基于不同的电压或环境温度,这将会影响RC振荡器的精度。可以通过时钟控制寄存器里的HSITRIM[4:0]位来调整HSI频率。
1.2 HSE时钟(外部高速时钟)
高速外部时钟信号(HSE)由以下两种时钟源产生:
● HSE外部晶体/陶瓷谐振器
● HSE用户外部时钟
为了减少时钟输出的失真和缩短启动稳定时间,晶体/陶瓷谐振器和负载电容器必须尽可能地靠近振荡器引脚。负载电容值必须根据所选择的振荡器来调整。
1.3 PLL
内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟。PLL的设置(选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子)必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。
如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。 如果需要在应用中使用USB接口,PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的USBCLK时钟。
1.4 LSE时钟(外部低速时钟)
LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
在这个模式里必须提供一个32.768kHz频率的外部时钟源。你可以通过设置在备份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位来选择这个模式。具有50%占空比的外部时钟信号(方波、正弦波或三角波)必须连到OSC32_IN引脚,同时保证OSC32_OUT引脚悬空。
1.5 LSI时钟(内部低速时钟)
LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间)。
以上介绍来自STM32手册,总共分为5大时钟源。
2、系统时钟源选择及配置
先来看看cubemx中的时钟树图,可以一目了然的看到整个时钟架构,还是非常的nice的
要注意的是,使用内部高速时钟的话,最大能配置到64MHZ,使用外部高速时钟的话,能配置到72MHZ
了解了基本的时钟架构之后,我们要怎么样配置得到自己想要的系统频率呢?
接下来分别介绍内部高速时钟和外部高速时钟的配置
2.1 内部高速时钟作为时钟输入
使用内部高速时钟的话,就不需要关注外部硬件了,可以看到内部高速时钟有3个去向,其中2个去向是可以到我们的系统时钟
sysclk后面,我们可以看到还有很多的,HCLK,PCLK,等等,这些又是什么含义呢?
系统时钟SYSCLK最大频率为72MHz(64MHZ),它是供STM32中绝大部分部件工作的时钟源。系统时钟可由PLL、HSI或者HSE提供输出,并且它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:
①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。
②、分频后送给STM32芯片的系统定时器时钟(Systick=Sysclk/8=9Mhz)
③、直接送给Cortex的自由运行时钟(free running clock)FCLK。【ARMJISHU注:FCLK 为处理器的自由振荡的处理器时钟,用来采样中断和为调试模块计时。在处理器休眠时,通过FCLK 保证可以采样到中断和跟踪休眠事件。 Cortex-M3内核的“自由运行时钟(free running clock)”FCLK。“自由”表现在它不来自系统时钟HCLK,因此在系统时钟停止时FCLK 也继续运行。FCLK和HCLK 互相同步。FCLK 是一个自由振荡的HCLK。FCLK 和HCLK 应该互相平衡,保证进入Cortex-M3 时的延迟相同。】
④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。
⑤、送给APB2分频器。APB2分频器可选择1、2、4、8、16分频, 其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。该倍频器可选择1或者2倍频,时钟输出 供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。
以上提到3种时钟Fclk、Hclk和Pclk,简单解释如下:
Fclk为供给CPU内核的时钟信号,我们所说的cpu主频为XXXXMHz,就是指的这个时钟信号,相应的,1/Fclk即为cpu时钟周期;
Hclk为优秀的高性能总线(AHB bus peripherals)供给时钟信号(AHB为advanced high-performance bus) HCLK :AHB总线时钟,由系统时钟SYSCLK 分频得到,一般不分频,等于系统时钟,HCLK是高速外设时钟,是给外部设备的,比如内存,flash。
Pclk为优秀的高性能外设总线(APB bus peripherals)供给时钟信号(其中APB为advanced peripherals bus)。
以上介绍让大家对各个时钟有个清晰的认识,接下来言归正传,该如何在cubemx中配置呢?
当使用PLL锁相环的时候,最大系统时钟能倍频至最大64MHZ,倍频系数2-16,可以根据自己的需要选择,设置不同的主频
直接使用HSI的话,系统时钟设置为8MHZ
后面的这些外设时钟需要我们根据自己的需要调整分频系数
至此,使用内部时钟的配置就完了,接下来介绍使用外部时钟:
2.2 外部高速时钟作为时钟输入
使用外部时钟,首先我们硬件上必须设计有外部晶振或者外部输入源,我们一般设计使用外部晶振
外部时钟源(HSE旁路)模式:
该模式下必须提供外部时钟。用户通过设置时钟控制寄存器中的HSEBYP和HSEON位来选择这一模式。外部时钟信号(50%占空比的方波、正弦波或三角波)必须连到SOC_IN引脚,此时OSC_OUT引脚对外呈高阻态。
外部晶体/陶瓷谐振器(HSE晶体)模式:
这种模式用得比较常见,HSE晶体可以为系统提供较为精确的时钟源。在时钟控制寄存器RCC_CR中的HSERDY位用来指示高速外部振荡器是否稳定。在启动时,直到这一位被硬件置’1’,时钟才被释放出来。HSE晶体可以通过设置时钟控制寄存器里RCC_CR中的HSEON位被启动和关闭。
选中之后,MCU的对应引脚会被使用,也即是我们的硬件设计对应的引脚
同样的,HSE的时钟也有不同的路线可以到系统时钟,直接通向SYSCLK的话,就是外部晶振频率,4-16MHZ,走PLL这条路线的话,选择就变得丰富起来
使用外部晶振,最大主频可以达到72MHZ
结合cubemx,对时钟进行配置,对新手了解MCU时钟结构还是非常有好的,配置也是非常的简单,省却了去了解一大堆的寄存器。
系统时钟配置源码分析
系统时钟初始化的参数主要封装在两个结构体里面:
跟晶体状态相关的:
/*** @brief RCC Internal/External Oscillator (HSE, HSI, LSE and LSI) configuration structure definition*/
typedef struct
{uint32_t OscillatorType; /*!< The oscillators to be configured.This parameter can be a value of @ref RCC_Oscillator_Type */#if defined(STM32F105xC) || defined(STM32F107xC)uint32_t Prediv1Source; /*!< The Prediv1 source value.This parameter can be a value of @ref RCCEx_Prediv1_Source */
#endif /* STM32F105xC || STM32F107xC */uint32_t HSEState; /*!< The new state of the HSE.This parameter can be a value of @ref RCC_HSE_Config */uint32_t HSEPredivValue; /*!< The Prediv1 factor value (named PREDIV1 or PLLXTPRE in RM)This parameter can be a value of @ref RCCEx_Prediv1_Factor */uint32_t LSEState; /*!< The new state of the LSE.This parameter can be a value of @ref RCC_LSE_Config */uint32_t HSIState; /*!< The new state of the HSI.This parameter can be a value of @ref RCC_HSI_Config */uint32_t HSICalibrationValue; /*!< The HSI calibration trimming value (default is RCC_HSICALIBRATION_DEFAULT).This parameter must be a number between Min_Data = 0x00 and Max_Data = 0x1F */uint32_t LSIState; /*!< The new state of the LSI.This parameter can be a value of @ref RCC_LSI_Config */RCC_PLLInitTypeDef PLL; /*!< PLL structure parameters */#if defined(STM32F105xC) || defined(STM32F107xC)RCC_PLL2InitTypeDef PLL2; /*!< PLL2 structure parameters */
#endif /* STM32F105xC || STM32F107xC */
} RCC_OscInitTypeDef;
跟时钟源、分频相关的
/*** @brief RCC System, AHB and APB busses clock configuration structure definition*/
typedef struct
{uint32_t ClockType; /*!< The clock to be configured.This parameter can be a value of @ref RCC_System_Clock_Type */uint32_t SYSCLKSource; /*!< The clock source (SYSCLKS) used as system clock.This parameter can be a value of @ref RCC_System_Clock_Source */uint32_t AHBCLKDivider; /*!< The AHB clock (HCLK) divider. This clock is derived from the system clock (SYSCLK).This parameter can be a value of @ref RCC_AHB_Clock_Source */uint32_t APB1CLKDivider; /*!< The APB1 clock (PCLK1) divider. This clock is derived from the AHB clock (HCLK).This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */uint32_t APB2CLKDivider; /*!< The APB2 clock (PCLK2) divider. This clock is derived from the AHB clock (HCLK).This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */
} RCC_ClkInitTypeDef;
配置代码如下:
/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;//时钟源为HSERCC_OscInitStruct.HSEState = RCC_HSE_ON; //打开HSERCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV2;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;//打开PLLRCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;//PLL时钟源选择HSERCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|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_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}
配置参数可以对照cubemx中时钟树图分析分析
跟时钟相关的宏定义基本都在STM32XXX_RCC.h中,就不再多列举了
在HAL_RCC_OscConfig,函数中对HSE的配置如下,贴出一部分代码,其他时钟源配置类似,贴了一些中文注释:
/* Check the parameters */assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));/*------------------------------- HSE Configuration ------------------------*/ if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE){/* Check the parameters */assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));/* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled *///判断HSE是否作为系统时钟,或者作为PLL时钟的来源// RCC_CFGR_SWS_HSE=0x00000004//__HAL_RCC_GET_SYSCLK_SOURCE函数,获取CFGR位 3:2 SWS: 系统时钟切换状态 (System clock switch status)//判断3:2位是否为01,含义:01: HSE 振荡器用作系统时钟,这两个位为只读//也就是判断此时系统时钟或者主PLL时钟是否已经设置为HSE//RCC_CFGR_SWS_PLL=0x00000008 ,判断CFGR的第3为是否为1if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) ||\((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE))){ //PLL作为系统时钟, RCC_PLLCFGR_PLLSRC=0x00400000,第22位,是否选择:1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入//如果HSE作为系统时钟来源,或者作为PLL时钟来源的话if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF)){//此时HSE已经打开了,或HSE没有使能,这里的任何一种情况都会导致失败return HAL_ERROR;}}else //否则的话,系统的时钟还没有进行初始化{/* Reset HSEON and HSEBYP bits before configuring the HSE --------------*///对RCC->CFGR 寄存器的23:16 清零,也就是复位HSEON(关闭振荡器) 和重置就绪位__HAL_RCC_HSE_CONFIG(RCC_HSE_OFF);//获取当前系统时间戳,用于判断关闭HSE是否超时/* Get Start Tick*/tickstart = HAL_GetTick();/* Wait till HSE is disabled */ while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) //等待HSE关闭{if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE){return HAL_TIMEOUT; //超过最大时间为关闭HSE则,出错} }/* Set the new HSE configuration ---------------------------------------*///重新设置HSE,这个值来自于结构体,我们使用就需要使能,通过设置就可以对RCC->CFGR 寄存器的23:16,写此值__HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);/* Check the HSE State *///再一次进行确认,我们是否设置了打开HSEif((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF){//进入说明,我们的确是打开了/* Get Start Tick*///获取当前系统时间戳tickstart = HAL_GetTick();/* Wait till HSE is ready */ //获取HSE就绪标志位,未就绪就等待while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET){if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE){return HAL_TIMEOUT;} }}else //否则没有打开HSE的开关咯{/* Get Start Tick*/tickstart = HAL_GetTick();/* Wait till HSE is bypassed or disabled */while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET){if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE) //HSE_TIMEOUT_VALUE=5000{return HAL_TIMEOUT;} }}}}
摘出来这段,就是等待时钟相关标志位置位,保证系统处于稳定的状态
/* Set the new HSE configuration ---------------------------------------*/__HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);/* Check the HSE State */if (RCC_OscInitStruct->HSEState != RCC_HSE_OFF){/* Get Start Tick */tickstart = HAL_GetTick();/* Wait till HSE is ready */while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET){if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE){return HAL_TIMEOUT;}}}else{/* Get Start Tick */tickstart = HAL_GetTick();/* Wait till HSE is disabled */while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET){if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE){return HAL_TIMEOUT;}}}
关于时钟的配置设计的寄存器是非常多的,小飞哥刚开始学习的时候,有寄存器版本和库函数版本,当时一看寄存器真精简啊,后来发现库函数用着好简单...后来就放弃了寄存器版本...
今天的分享就要OVER了,仅仅进行了比较粗略的介绍,希望对各位小伙伴有一些帮助,有疑问的可以+小飞哥好友,一起交流学习
☞ 专辑|RT-Thread实战笔记
☞ 专辑|cubemx与HAL库系列教程
☞ 如何制定通讯协议及如何解析协议数据
☞ 10分钟教你玩转freemodbus
Cubemx与HAL库系列教程|系统时钟配置详解及源码分析相关推荐
- UCOS你问我答系列之系统时钟节拍详解
前言 系统时钟节拍是多任务得以正常运行的基石,UCOS的系统时钟节拍一般依赖于MCU的硬件定时器.硬件定时器产生固定时间间隔的中断,中断中调用UCOS的系统函数,完成多任务操作系统的基本调度功能. 本 ...
- nightwatch系列教程05——Nightwatch配置详解
本章内容翻译自http://nightwatchjs.org/gettingstarted#settings-file. 测试运行接收一个配置文件作为参数,默认是当前目录下的 nightwatch.j ...
- java log 配置,java日志系统--log4j配置解析过程,源码分析
log4j 可以看成是非常类似jdk logger 结构 ,有个logger 与logManger 都是在logManger的静态块中初始化类,加载配置文件 Logger.getLogger(Test ...
- Spring Security系列(32)- Spring Security Oauth2之authorities授权使用详解及源码分析
前言 在oauth_client_details表中,有一个authorities字段,从字面上来看是授权的意思,在之前我们分析了可以通过resourceId和scope进行授权,那么这个author ...
- 【STM32学习】时钟配置详解
[STM32学习]时钟配置详解 看懂时钟图 结合代码 外部高速时钟修改 看懂时钟图 在刚开始学习32的时候,并不会在意这些,或者即使看了也看的不是很明白.随着学习的深入,我们发现看门狗.定时器.ADC ...
- 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...
- 【STM32】系统时钟RCC详解(超详细,超全面)
转载:https://blog.csdn.net/as480133937/article/details/98845509 1什么是时钟 时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的 ...
- Maven系列一pom.xml 配置详解
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- 关于STM32F105/107时钟配置详解
本文用的是标准库,先给出时钟配置代码,代码在system_stm32f10x.c里面. 首先定义最终系统频率72MHz: 如果是其他频率把72的注释掉,打开你想配置的频率.这个频率并不会影响实际的设置 ...
最新文章
- TypeError: string argument without an encoding
- 【必读】2019年深度学习自然语言处理最新十大发展趋势, 附报告下载
- 多层陶瓷电容器用处_陶瓷电容的作用及特点
- python智能办公系统_用 Python 自动化办公能做到哪些有趣或有用的事情?
- gradle全局使用阿里云镜像
- VC6.0 控件Radio Button的使用
- 中文导致Mybatis无效的列索引
- 自然语言处理中的语言模型与预训练技术的总结
- linux pxe安装视频,Linux—图解PXE实现全自动安装系统(1)
- android如何让gps服务停止,android – 启动/停止GPS(或位置服务)时接收通知(通过BroadcastReceiver)...
- C语言之文件读写探究(三):fputs、fgets、feof(一次读写一行字符(文本操作))
- 人类无法抗拒的10种心理
- 路径规划之基于优化的规划算法
- Markdown语法(一)标题段落分割线
- caffemodel中的参数及特征的抽取
- Atitit. .net c# web 跟客户端winform 的ui控件结构比较
- anylogic和java,基于Anylogic的Java代码入门教程
- 查看linux系统版本命令大全
- 局域网共享文件搭建方法
- python实现离散沃尔什变换_快速沃尔什变换(示例代码)