STM32 时钟树配置快速入门
layout: post
tags: [STM32]
comments: true
为什么要了解时钟树?
最近项目开发的时候,外部时钟源是16MHz
,结果配置错了系统时钟,STM32F103
的系统时钟频率最高为72MHz
,错误地配置到了144MHz
,但是AHB
总线时钟又正确地配置到72MHz
,最终导致了以下几种情况:
- 程序会意外跑飞,然后进入
Hard fault
,甚至不知道发生了什么,就出现这样的错误; - 数据意外错误,
int32
负数乘法的时候,第30位数据异常;
以上的问题,较难排查,而且给人一种芯片不稳定的错觉,对的,没错,这就是超频的代价,高速行驶的汽车,更容易翻车,所以,对于芯片的时钟配置有一个全面的了解,有着弥足轻重的作用。
树的根
先看一下这张从网上扒来的图,说到树,不由自主想到了二叉树,设备树,森林里的树,而时钟树,也不例外,和这些树一样,都有根,有叶子,做一下简单的类比;
根是树汲取营养的地方,是树赖以生存的一个部位;
而时钟树的根就是时钟输入源,用于产生系统的时钟节拍,即系统时钟;
系统时钟源:
- 高速内部时钟
HSI
,8MHz
; - 高速外部时钟
HSE
,4MHz
–25MHz
; - 锁相环时钟
PLL
;
常用的配置方案例如:选用外部时钟HSE
,频率为8MHz
,经过锁相环时钟,PLL
进行9倍频,则系统时钟频率:
SysClock = 8MHz*9 = 72Mhz
或者使用外部时钟HSE
,频率为16MHz
,则经过锁相环时钟,PLL
2分频,然后9倍频,则系统时钟频率:
SysClock = 16MHz/2*9 = 72Mhz
可见,PLL
时钟源的使用很灵活,可以灵活运用;
标准库的时钟配置
从STM32
标准库的启动文件中可以发现,在main()
运行前,已经运行了SystemInit()
函数,代码如下,IDE是MDK
,代码如下,具体可以参考
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
标准库是默认按照外部高速时钟频率为8MHz
进行配置的,SystemInit()
函数原型位于system_stm32f10x.c
,具体的调用关系如下,具体源码可以参考标准库;
SystemInit()
-->SetSysClock()
-->SetSysClockTo72()
在函数SetSysClock()
通过宏定义选择系统所需要配置的时钟频率;
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}
函数SetSysClockTo72
默认定义系统时钟为72MHz
#define SYSCLK_FREQ_72MHz 72000000
初始化后系统的状态:
时钟 | 频率 |
---|---|
SYSCLK | 72MHz |
AHB | 72MHz |
PCLK1 | 36MHz |
PCLK2 | 72MHz |
PLL | 72MHz |
外部时钟源16M
SetSysClockTo72
如果外部时钟源的频率是16M
,需要进行哪些修改?
根据源码可以知,最终在SetSysClock
中进行修改即可,因为要配置到72M
的系统时钟频率,则直接进入函数SetSysClockTo72()
进行修改;
SetSysClockTo72
源码如下;或者跳过源码直接看patch
文件;
/**
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2
* and PCLK1 prescalers.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef STM32F10X_CL
/* Configure PLLs ------------------------------------------------------*/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
/* Enable PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* Wait till PLL2 is ready */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}
/* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
#endif
patch
需要修改两个地方:
stm32f10x.h
中HSE_VALUE
的值;system_stm32f10x.c
中SetSysClockTo72(void)
函数的PLL
配置;
patch
如下所示;
diff --git a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
index 8bf7624..e0ad316 100644
--- a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
+++ b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
@@ -116,7 +116,9 @@
#ifdef STM32F10X_CL
#define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
#else
- #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
+// #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
diff --git a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
index 71efc85..4ff040a 100644
--- a/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
+++ b/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
@@ -1053,7 +1053,7 @@ static void SetSysClockTo72(void)
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
- RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
+ RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLXTPRE_HSE_Div2);
#endif /* STM32F10X_CL */
/* Enable PLL */
这样就完成了基本配置的修改。
其他细节
- RTC的时钟来源:
LSE
,LSI
,LSE的128分频
- 独立看门狗IWDGCLK的时钟来源:
LSI
- APB1总线的时钟,最大到36M
- APB2总线的时钟,最大到72M
- APB,APB1,APB2为外设提供时钟。