stm32时钟初始化分析与systick跳动时间分析
程序员文章站
2022-06-09 10:50:42
...
stm32时钟初始化分析
要想了解stm32时钟配置,我们首先当了解其时钟树,以下是一张经典的stm32系统频率为72M的时钟树图
在系统启动时会首先调用SystemInit()函数,并在其中进行系统的一系列初始化,SystemInit()函数相关代码如下
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
//置位HSION(Internal high-speed clock enable),开启内部8M高速时钟(HSI)
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
//将SW(System clock switch)系统时钟切换为HSI,且将MCO(Microcontroller clock output)微控制器时钟输出置为没有时钟输出,
//将HPRE, PPRE1, PPRE2, ADCPRE(AHB PRESCALER,APB1 PRESCALER,APB2 PRESCALER, ADC PRESCALER)设置为0(不分频)
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
//关闭HSE,时钟安全系统(CSSON:Clock security system enable,如果外部4-16MHz振荡器就绪,时钟监测器开启),关闭PLL(重要)
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
//关闭HSE旁路模式(旁路模式:外部时钟信号(50%占空比的方波、正弦波或三角波)连到OSC_IN引脚,此时OSC_OUT为高阻态),有源晶振(加电就能输出,贵!!!)适用该模式。)
//然而大部分情况使用无源晶振,此处关了也没见再打开了
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
//初始化PLLSRC(PLL entry clock source)为0(HSI振荡器时钟经2分频后作为PLL输入时钟)
//PLLXTPRE (HSE divider for PLL entry HSE输入分频)为0(HSE不分频)
//PLLMUL(PLL multiplication factor,PLL输出倍频系数)为0000(PLL 2倍频输出),在初始化这三项前必须保证PLL关闭(此处PLL关闭了,系统时钟为HSI,所以初始化后PLL也没有工作)
//初始化USB分频系数
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifndef STM32F10X_CL
/* Disable all interrupts and clear pending bits */
//该寄存器为与CSS,PLL,HSE,HSI,LSE,LSI相关中断,作用依次为清除对应中断标志位,使能中断和中断标志位状态(不打开中断当事件发生时interrupt flag会置1吗?)
RCC->CIR = 0x009F0000;
#else
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#endif /* STM32F10X_CL */
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();//开始配置系统时钟
}
在系统初始化结尾处会调用SetSysClock()函数配置系统时钟,其内容如下
/**
* @brief Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
* @param None
* @retval None
*/
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) */
}
通过预定义宏来决定系统主频,我们这里是72M,所以需要定义SYSCLK_FREQ_72MHz
接下来将会调用SetSysClockTo72();函数,其内容如下
/**
* @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 */
//打开HSE
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
//取出RCC->CR中RCC_CR_HSERDY位
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
//该位由硬件置’1’代表HSE就绪,此处判断HSE是否启动成功或超时(HSEStartUp_TimeOut = 1280)
} while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));
//如果HSE启动成功
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
//记录HSE启动状态为成功
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
//如果HSE启动成功
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
//打开指令预取器
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state
0等待周期,当 0 < SYSCLK < 24MHz
1等待周期,当 24MHz < SYSCLK ≤ 48MHz
2等待周期,当 48MHz < SYSCLK ≤ 72MHz(此处设置为2等待周期)
*/
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
//set AHB Prescaler(分频后得到HCLK) = SYSCLK/1
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
//set AHB2 Prescaler = HCLK/1
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK/2 */
//set AHB1 Prescaler = HCLK/2
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//如果是从systemInit进入则此时时钟为HSI = SYSCLK = 8M
#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 */
/*初始化PLLSRC(PLL entry clock source)为0(HSI振荡器时钟经2分频后作为PLL输入时钟)
PLLXTPRE (HSE divider for PLL entry HSE输入分频)为0(HSE不分频)
PLLMUL(PLL multiplication factor,PLL输出倍频系数)为0000(PLL 2倍频输出
在初始化这三项前必须保证PLL关闭*/
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
//设置PLLSRC为HSE,PLL倍频系数为9。
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* Enable PLL */
//打开PLL
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
//初始化sw(System clock switch)位(必要,因为此函数不一定从systemInit进入,导致此位未必被初始化)
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
//将系统时钟切换为PLL(HSE * PLLMULL9 = 8M * 9 = 72M)
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
//通过SWS位 (System clock switch status,硬件置位)判断时钟源切换是否成功
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
//至此设置成功程序结束
}
else //HSE启动失败,程序走死,emmm,:(
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
/* Go to infinite loop */
while (1)
{
}
}
}
对于Systick定时器,我们主要关心四个寄存器
其中SysTick的CTRL寄存器(控制及状态寄存器)中的CLKSOURCE位是用于选择 SysTick 定时器时钟来源,如果该位为 1, 表示其时钟由HCLK直接提供即 72M(FCLK)。如果为 0, 表示其时钟由HCLK8分频后提供即 72/8=9M(STCLK),由时钟树图可知我们这里设置的时钟源为72M。即每秒跳动72M次,计数一次时间为1/72 us。
配置代码如下
/** \brief System Tick Configuration
The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\param [in] ticks Number of ticks between two interrupts.
\return 0 Function succeeded.
\return 1 Function failed.
\note When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
must contain a vendor-specific implementation of this function.
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible 即ticks > 26位*/
SysTick->LOAD = ticks - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | //内核时钟fclk
SysTick_CTRL_TICKINT_Msk | //数到0时产生异常请求(tick interrupt)
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ //打开tick
return (0); /* Function successful */
}