[标准库]STM32F103R8T6 标准库配置RCC时钟和超频
前言
这篇博客总结一下学习到的配置时钟的方法。
从启动文件来看,MCU复位之后,执行到SystemInit()这个函数之后,会进入系统初始化设置,比如根据当前的MCU型号进入不同的条件编译语句,再配置相应的寄存器初始值,并且让MCU的时钟运行起来。
这个SystemInit()函数的定义在system_stm32f10x.c(主要用来配置时钟)这个文件中,内容如下:
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* 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;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
比如上面的第一步 RCC->CR |= (uint32_t)0x00000001; 这一步是把RCC时钟配置成默认的复位状态,开启内部高速时钟,后面还有很多初始化语句,由于教程没讲,我暂且不管这些语句,不确定后续如果使用LL库编程是否需要自己初始化这些寄存器的值,这里先放过去。
文件到最后调用了一个 SetSysClock(); 函数用于设置时钟频率。这个函数里面根据不同的宏来配置系统的时钟频率。宏定义在system_stm32f10x.c的头部:
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
我这里是默认使能了配置频率到72Mhz的宏定义。
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();这个函数,内容如下:(由于篇幅太长,我只挑我使用的R8T6即普通型的配置来说、互联型M5、M7用CL那部分条件编译的语句)
__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;
/* 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);
/* 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 */
}
这部分是普通型的即M0、M3、M4的时钟初始化语句,重新用std库写一个时钟配置函数放在bsp.rcc.c文件中,内容如下:
void rcc_systempclock_init(uint32_t RCC_PLLMul_x)
{
ErrorStatus HSEStatus;
RCC_DeInit(); //把RCC寄存器复位成默认值
RCC_HSEConfig(RCC_HSE_ON); //开启HSE(外部时钟输入)
HSEStatus = RCC_WaitForHSEStartUp();
if(HSEStatus == SUCCESS)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //使能预取指
FLASH_SetLatency(FLASH_Latency_2); //设置预取指的间隔时间 2个时钟间隔
RCC_HCLKConfig(RCC_SYSCLK_Div1); //设置AHB分频器 设置1分频
RCC_PCLK1Config(RCC_HCLK_Div2); //设置APB1分频器 因为APB1最高速度只能到36MHz 输入APB1的时钟经过AHB分频器处理后还是72MHz 所以这里要进行二分频
RCC_PCLK2Config(RCC_HCLK_Div1); //设置APB2分频器 因为APB2最高速度为72MHz 所以这里设置为1分频就行
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_x); //选择PLL锁相环倍频器的时钟输入为 HSE的1分频输入 传参(倍频倍数)进来
RCC_PLLCmd(ENABLE); //开启PLL倍频器
// 等待 PLL 倍频时钟 稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //获取RCC模块中 PLL倍频时钟是否设置成功 标志位
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while (RCC_GetSYSCLKSource() != (uint32_t)0x08);
}
else //如果HSE启动失败 在这个区域添加错误处理代码
{
while(1);
}
}
其中的语句与上面那个官方写的SetSysClockTo72();函数是一样的,只不过这里是使用std库去替代操作寄存器。后续用到的时候可以对照着看看。
自己重写的这个rcc_systempclock_init(uint32_t RCC_PLLMul_x);函数,形参传进去的是倍频的倍数。
打开时钟树来推导,比如我选择外部晶振的高速时钟HSE输入,经过PLLXTPRE寄存器1分频输入到PLLSRC寄存器,选择HSE分频后的时钟信号作为倍频器的输入,经过倍频器倍频9倍出来的就是72MHz的正弦波信号,再把SYSCLK这个寄存器选择输入源为倍频之后的72MHz正弦波信号,进入到AHB分频器,选择1分频,然后就能作为APH1和APH2的输入信号了,其中APH1和APH2这两条时钟线上又挂载了很多外设,不同的外设又有不同的分频器,因为某些外设的时钟输入不能太高。
写好这个函数之后,在main函数进入while循环之前调用一下,rcc_systempclock_init(RCC_PLLMul_10),这样就是10倍频,出来的时钟频率就是80MHz的,可以使用MCO通过PA8引脚输出时钟波形出来,通过判断波形的频率我们就知道是否倍频成功了。
在bsp_rcc.c文件中添加函数原型:
void MCO_GPIO_init()
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
这句就是初始化PA8为MCO输出引脚,基本的配置跟配置LED的输出IO差不多,只不过GPIO_Mode 要配置为复用推挽输出。
完成上述的设置之后,main函数的内容如下:
int main(void)
{
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
rcc_systempclock_init( RCC_PLLMul_10 );
MCO_GPIO_init();
while(1)
{
}
}
在PA8引脚上挂一个示波器,因为时钟的波形是80MHz的,所以示波器的带宽要为测试波形的7倍,最起码要500MHz带宽的示波器,同时表笔也要选择200MHz以上的表笔,打到x10档,才能捕捉到MCO输出出来的正弦波信号。
这是最简单的检查是否超频成功的方法,其实还有很多其他方法,比如开一个中断计时1ms,观察超频之后时间缩短是否符合设计预期,就知道是否超频成功了。
我这边最高是倍频16倍,最终时钟输入频率是128MHz的正弦波,并且用示波器捕捉测试过符合预期。不过ST官方推荐的稳定运行频率是72MHz,而且APB1和APB2有最高的时钟频率限制,所以一般不会超频使用。