一、分析程序的目的
最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好stm32的小伙伴也有必要学习好时钟系统的配置,所以我将学习的过程再次记录,有写得不好的地方,望小伙伴指出。
之前我已经记录过一篇关于时钟系统的文章,对程序中不了解的地方可以看我之前的笔记“STM32时钟系统的配置寄存器和源码分析”。
这里我用的芯片是STM32F103C8T6,用的库函数是厂家提供的案例中提取出来的,这里可能和其他型号的mcu有细微差别,但是原理都是一样的。
二、程序执行的过程
当系统复位信号发生的时候,程序将执行复位中断函数,而在复位中断函数中是先执行SystemInit函数后在执行__main函数,如下图所示:
系统调用SystemInit函数后完成系统时钟的配置,系统时钟配置的过程如下所示:
从图中可知,在系统时钟配置的第三步有多个函数可以选择,这里可以根据自己的需求选择相应的配置流程,只需要在stm32f10x.h文件中定义相应的宏即可(默认配置为72MHz),如下图所示:
在分析程序之前,需要了解一下相关寄存器的地址以及相应寄存器的作用,如下所示:
typedef struct { __IO uint32_t CR; // HSI、HSE、CSS、PLL等的使能和就绪标志位 __IO uint32_t CFGR; // PLL等的时钟源选择,分频系数设定 __IO uint32_t CIR; // 清除/使能时钟就绪中断 __IO uint32_t APB2RSTR; // APB2线上外设复位寄存器 __IO uint32_t APB1RSTR; // APB1线上外设复位寄存器 __IO uint32_t AHBENR; // DMA、SDIO等时钟使能 __IO uint32_t APB2ENR; // APB2线上外设时钟使能 __IO uint32_t APB1ENR; // APB1线上外设时钟使能 __IO uint32_t BDCR; // 备用域控制寄存器 __IO uint32_t CSR; // 控制状态寄存器 } RCC_TypeDef;
以上的寄存器都是相对RCC寄存器进行偏移的,如下图所示:
通过查找stm32f10x.h文件中的定义可以知道寄存器RCC的地址,如下所示:
RCC = RCC_BASE = AHBPERIPH_BASE + 0x1000 = PERIPH_BASE(0x40000000) + 0x20000 = 0x40021000
三、SystemInit函数
程序如下所示:
/* 将RCC时钟配置重置为默认重置状态 */ void SystemInit (void) { /* 打开HSION位(内部高速时钟使能) */ RCC->CR |= (uint32_t)0x00000001; /* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */ RCC->CFGR &= (uint32_t)0xF8FF0000; /* 复位 HSEON, CSSON 和 PLLON 位 */ RCC->CR &= (uint32_t)0xFEF6FFFF; /* 复位 HSEBYP 位 */ RCC->CR &= (uint32_t)0xFFFBFFFF; /* 复位 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位 */ RCC->CFGR &= (uint32_t)0xFF80FFFF; /* 禁用所有中断并清除挂起位 */ RCC->CIR = 0x00000000; /* 配置系统时钟频率、HCLK、PCLK2和PCLK1预分频器 */ /* 配置闪存延迟周期并启用预取缓冲区 */ SetSysClock(); }
从上面的代码可以看出,和库函数中的RCC_DeInit所执行的代码一下,所以在用户程序中需要从新配置系统时钟的话,不需要通过上面的代码将时钟配置为默认状态,只要调用RCC_DeInit函数即可。如下图所示:
有不明白的地方只需要和相应的寄存器对应一下即可,相关的寄存说明请看“STM32时钟系统的配置寄存器和源码分析”。
四、SetSysClock函数
static void SetSysClock(void) { #ifdef SYSCLK_FREQ_HSE SetSysClockToHSE(); #elif defined SYSCLK_FREQ_20MHz SetSysClockTo20(); #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
这是根据文件中的宏定义选择相应的系统时钟配置函数,有需要更改的直接定义相应的宏即可,系统默认是的72MHz
五、SetSysClockTo72函数
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 != HSEStartUp_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; /*!< PLLCLK = 8MHz * 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 | RCC_CFGR_PLLMULL9); /*!< 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 */ /*!< Go to infinite loop */ while (1) { } } }
-
使能外部高速时钟
// #define RCC_CR_HSEON ((uint32_t)0x00010000) RCC->CR |= ((uint32_t)RCC_CR_HSEON); RCC->CR |= ((uint32_t)RCC_CR_HSEON); do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; }
从定义为文件中可知RCC_CR_HSEON为0x00010000,也就是CR寄存器的第17位为1。HSEStartUp_TimeOut为0x0500表示HSE启动超时,也就是说如下图所示:
注意:执行完上面程序后,接着判断外部时钟是否就绪,只要当外部时钟就绪后才执行后面的流程,否成启动失败,程序将卡在while位置 -
FLASH处理
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;
由于CPU的速度比flash的速度要快,所以这里需要让cpu等待两个时钟
-
设置AHB、APB1、APB2预分频的值
// RCC_CFGR_HPRE_DIV1 = 0x00000000 // RCC_CFGR_PPRE2_DIV1 = 0x00000000 // RCC_CFGR_PPRE1_DIV2 = 0x00000400 /*!< HCLK = SYSCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /*!< PCLK2 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /*!< PCLK1 = HCLK/2 */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
从注释中可知AHB和APB2的预分频为1,APB1的预分频为2(因为PCLK1的最大频率为36MHz)
-
设置PLL的时钟源和倍频
// RCC_CFGR_PLLSRC = 0x00010000 // RCC_CFGR_PLLXTPRE = 0x00020000 // RCC_CFGR_PLLMULL = 0x003C0000 // RCC_CFGR_PLLMULL9 = 0x001C0000 /*!< PLLCLK = 8MHz * 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 | RCC_CFGR_PLLMULL9);
第一行代码的作用是将CFGR的[16:21]寄存器复制为0,第二行是将HSE设置为PLL的时钟源,HSE分频器不分频,PLL倍频系数设置为9
-
使能PLL时钟
// RCC_CR_PLLON = 0x01000000 // RCC_CR_PLLRDY = 0x02000000 /*!< Enable PLL */ RCC->CR |= RCC_CR_PLLON; /*!< Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { }
使能PLL时钟,并等待PLL时钟就绪
-
设置PLL作为系统时钟源
// RCC_CFGR_SW = 0x00000003 // RCC_CFGR_SW_PLL = 0x00000002 // RCC_CFGR_SWS = 0x0000000C /*!< 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) { }
设置PLL作为系统时钟源,并判断是否成功
注意: SetSysClockTo72函数的作用是配置HCLK为72MHz、PCLK1为36MHz、PCLK2为72MHz,如下图所示:
六、时钟配置系统的库函数
头文件是stm32f10x_rcc.h,源文件是stm32f10x_rcc.c
-
时钟使能配置
// HSE时钟使能 void RCC_HSEConfig(uint32_t RCC_HSE); // HSI时钟使能 void RCC_HSICmd(FunctionalState NewState); // PLL时钟使能 void RCC_PLLCmd(FunctionalState NewState); // 启用或禁用指定的RCC中断 void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState) // 使能LSI时钟 void RCC_LSICmd(FunctionalState NewState); // 使能RTC时钟 void RCC_RTCCLKCmd(FunctionalState NewState) // 使能AHB外围时钟 void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) // 使能高速APB(APB2)外围时钟 void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) // 使能低速APB(APB1)外围时钟 void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) // 使能时钟安全系统 void RCC_ClockSecuritySystemCmd(FunctionalState NewState)
-
时钟相关配置
// 配置PLL时钟源,仅当PLL禁用时,才能使用此功能。 void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul) // 配置系统时钟(SYSCLK)。 void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource) // 配置AHB时钟(HCLK) void RCC_HCLKConfig(uint32_t RCC_SYSCLK) // 配置低速APB时钟(PCLK1) void RCC_PCLK1Config(uint32_t RCC_HCLK) // 配置高速APB时钟(PCLK2) void RCC_PCLK2Config(uint32_t RCC_HCLK) // 配置USB时钟(USBCLK) void RCC_USBCLKConfig(uint32_t RCC_USBCLKSource) // 配置ADC时钟(ADCCLK) void RCC_ADCCLKConfig(uint32_t RCC_PCLK2) // 配置外部低速振荡器(LSE) void RCC_LSEConfig(uint8_t RCC_LSE) // 配置RTC时钟(RTCCLK) void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource)
-
其他时钟配置
// 调整内部高速振荡器(HSI)校准 void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue) // 获取时钟源 uint8_t RCC_GetSYSCLKSource(void) // 等待HSE时钟启动 ErrorStatus RCC_WaitForHSEStartUp(void) // 获取对应的时钟频率 void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks) // 强制复位高速APB(APB2)外围设备 void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) // 强制复位低速APB(APB1)外围设备 void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) // 强制重置备份域 void RCC_BackupResetCmd(FunctionalState NewState) // 选择要在MCO引脚上输出的时钟源 void RCC_MCOConfig(uint8_t RCC_MCO) // 检查是否设置了指定的RCC标志 FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG) // 清除RCC重置标志 void RCC_ClearFlag(void) // 检查是否发生了指定的RCC中断 ITStatus RCC_GetITStatus(uint8_t RCC_IT) // 清除RCC中断挂起位 void RCC_ClearITPendingBit(uint8_t RCC_IT)
七、通过库函数配置时钟系统
void HSE_SetClk(uint32_t RCC_PLLMul_x) { ErrorStatus HSEStaus; // 使能外部时钟(HSE) RCC_HSEConfig(RCC_HSE_ON); HSEStaus = RCC_WaitForHSEStartUp(); if () { // 使能预取值 未完成,稍后补上........ } }