STM32时钟系统配置程序源码深入分析

虚幻大学 xuhss 192℃ 0评论

? 优质资源分享 ?

学习路线指引(点击解锁) 知识定位 人群定位
? Python实战微信订餐小程序 ? 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
?Python量化交易实战? 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

一、分析程序的目的

最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好stm32的小伙伴也有必要学习好时钟系统的配置,所以我将学习的过程再次记录,有写得不好的地方,望小伙伴指出。

之前我已经记录过一篇关于时钟系统的文章,对程序中不了解的地方可以看我之前的笔记“STM32时钟系统的配置寄存器和源码分析”。

这里我用的芯片是STM32F103C8T6,用的库函数是厂家提供的案例中提取出来的,这里可能和其他型号的mcu有细微差别,但是原理都是一样的。

二、程序执行的过程

当系统复位信号发生的时候,程序将执行复位中断函数,而在复位中断函数中是先执行SystemInit函数后在执行__main函数,如下图所示:
4ed12c0cd973f131f45ec27259829aa7 - STM32时钟系统配置程序源码深入分析

系统调用SystemInit函数后完成系统时钟的配置,系统时钟配置的过程如下所示:
a43f70b5d7e8e3b5b5a519991fe0d571 - STM32时钟系统配置程序源码深入分析

从图中可知,在系统时钟配置的第三步有多个函数可以选择,这里可以根据自己的需求选择相应的配置流程,只需要在stm32f10x.h文件中定义相应的宏即可(默认配置为72MHz),如下图所示:
2ecb32d6763cc9f556e3cba46fe28f79 - STM32时钟系统配置程序源码深入分析

在分析程序之前,需要了解一下相关寄存器的地址以及相应寄存器的作用,如下所示:

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寄存器进行偏移的,如下图所示:
fb346ce63c3ce5ff9d38509f27c36562 - STM32时钟系统配置程序源码深入分析

通过查找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函数即可。如下图所示:
cccacd7ab870ce650dd8f125a0eab95d - STM32时钟系统配置程序源码深入分析

有不明白的地方只需要和相应的寄存器对应一下即可,相关的寄存说明请看“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启动超时,也就是说如下图所示:
4b0e9bd13115890f40dde2c632ac51e8 - STM32时钟系统配置程序源码深入分析
注意:执行完上面程序后,接着判断外部时钟是否就绪,只要当外部时钟就绪后才执行后面的流程,否成启动失败,程序将卡在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)
78ec1e4ab0e11e0ba4f143bde241798a - STM32时钟系统配置程序源码深入分析

  • 设置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
bc1a9be5f832e236e38ce63c2b3609e3 - STM32时钟系统配置程序源码深入分析

  • 使能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时钟就绪
c24bdd1243025c13c872d1336a45f914 - STM32时钟系统配置程序源码深入分析

  • 设置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作为系统时钟源,并判断是否成功
0f9977b729bc844404d47ca8f5b3eb45 - STM32时钟系统配置程序源码深入分析

注意: SetSysClockTo72函数的作用是配置HCLK为72MHz、PCLK1为36MHz、PCLK2为72MHz,如下图所示:
1833ffe269678b65ce8fbf6d0c80f625 - STM32时钟系统配置程序源码深入分析

六、时钟配置系统的库函数

头文件是stm32f10x_rcc.h,源文件是stm32f10x_rcc.c

  1. 时钟使能配置
// 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)
  1. 时钟相关配置
// 配置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)
  1. 其他时钟配置
// 调整内部高速振荡器(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 ()
    {
        // 使能预取值
        未完成,稍后补上........

    }

}

转载请注明:xuhss » STM32时钟系统配置程序源码深入分析

喜欢 (0)

您必须 登录 才能发表评论!