一、分析程序的目的

最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好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

  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)
  2. 时钟相关配置

    // 配置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)
  3. 其他时钟配置

    // 调整内部高速振荡器(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 ()
{
// 使能预取值
未完成,稍后补上........ } }

STM32时钟系统配置程序源码深入分析的更多相关文章

  1. wpf 模拟抖音很火的罗盘时钟,附源码,下载就能跑

    wpf 模拟抖音很火的罗盘时钟,附源码 前端时间突然发现,抖音火了个壁纸,就是黑底蕾丝~~~  错错错,黑底白字的罗盘时钟! 作为程序员的我,也觉得很新颖,所以想空了研究下,这不,空下来了就用wpf, ...

  2. MongoDB源码分析——mongod程序源码入口分析

    Edit 说明:第一次写笔记,之前都是看别人写的,觉得很简单,开始写了之后才发现真的很难,不知道该怎么分析,这篇文章也参考了很多前辈对MongoDB源码的分析,也有一些自己的理解,后续将会继续分析其他 ...

  3. C#实现联通短信Sgip协议程序源码

    此程序为中国联通Sgip协议程序接口,适合在中国联通申请了短信发送端口的公司使用. 短信群发已经成为现在软件系统.网络营销等必不可少的应用工具.可应用在短信验证.信息群发.游戏虚拟商品购买.事件提醒. ...

  4. 复用微信小程序源码包后仍然有原小程序的版本管理怎么处理

    前言: 复用微信小程序源码包后,重新创建项目导入源码包,会发现开发者工具版本管理中仍然有原来小程序的版本,这样就不太好了.毕竟是一个新的小程序,需要有新的版本控制的.那么这个问题怎么处理呢? 解决方案 ...

  5. 德卡Z90读卡器读取社保卡,德卡Z90读卡器CSharp示例程序源码

    前言,最近学习调用 医保卡业务,使用德卡读卡器,主要就是调用一个DLL,动态库文件. 借着自学的机会把心得体会都记录下来,方便感兴趣的小伙伴学习与讨论. 内容均系原创,欢迎大家转载分享,但转载的同时别 ...

  6. 反编译获取线上任何微信小程序源码(转)

    看到人家上线的小程序的效果,纯靠推测,部分效果在绞尽脑汁后能做出大致的实现,但是有些细节,费劲全力都没能做出来.很想一窥源码?查看究竟?看看大厂的前端大神们是如何规避了小程序的各种奇葩的坑?那么赶紧来 ...

  7. 查看Chrome浏览器扩展程序源码的两种方法

    注意:仅在当前最新的版本 55.0.2883.87 m (64-bit)上测试有效 首先获取extensionId: chrome 打开扩展程序页面 chrome://extensions/ 比如我想 ...

  8. 【转】精选十二款餐饮、快递、票务行业微信小程序源码demo推荐

    微信小程序的初衷是为了线下实体业服务的,必须有实体相结合才能显示小程序的魅力.个人认为微信小程序对于餐饮业和快递业这样业务比较单一的行业比较有市场,故整理推荐12款餐饮业和快递业微信小程序源码demo ...

  9. 【最新】破解微信小程序,获取微信小程序源码,破解微信wxapkg,仅需5秒

    一个后端第一次接触iview,就简单写了个网站. 之前看到有人发解析wx小程序源码包的软件,但是因为微信的升级,之前的办法已经不行了.现在重新改了js文件,适配了最新的版本. 之前微信wxapkg包获 ...

随机推荐

  1. 实战-DRF快速写接口(认证权限频率)

    实战-DRF快速写接口 开发环境 Python3.6 Pycharm专业版2021.2.3 Sqlite3 Django 2.2 djangorestframework3.13 测试工具 Postma ...

  2. keytools命令生成证书

    平时开发中可以使用keytools命令生成证书,一般常用格式为: keytool -genkey -alias tzzxxt -keyalg RSA -keypass 123456 -validity ...

  3. Oracle双字段约束

    Oracle里有unique约束,意思是该字段唯一. 但如果是两个字段呢? 比如说一个会员等级表 ID NAME POINT DISCOUNT PRIVILEGE MID 1019 普通会员 0 10 ...

  4. 面试官:RabbitMQ过期时间设置、死信队列、延时队列怎么设计?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 RabbitMQ我们经常的使用, ...

  5. 2021.08.09 P6225 抑或橙子(树状数组)

    2021.08.09 P6225 抑或橙子(树状数组) 重点: 1.异或用法 题意: Janez 喜欢橙子!他制造了一个橙子扫描仪,但是这个扫描仪对于扫描的每个橙子的图像只能输出一个 3232 位整数 ...

  6. 详解javascript的eventloop(二):eventloop和dom渲染

    记住: JS是单线程的,他和dom渲染共用一个线程 JS执行的时候,会给dom渲染留一些时机 上一篇讲到eventloop的执行机制,但是在这个机制中的call stack执行完成后(包括第一遍的ev ...

  7. 上市公司招聘:今天国际直聘DBA

    今天国际一家专业的智慧物流·智能制造系统综合解决方案提供商,为生产制造.流通配送企业提供智慧物流·智能制造系统 的规划设计.系统集成.软件开发.设备定制.电控系统开发.现场安装调试.客户培训和售后服务 ...

  8. 简单了解 TiDB 架构

    一.前言 大家如果看过我之前发过的文章就知道,我写过很多篇关于 MySQL 的文章,从我的 Github 汇总仓库 中可以看出来: 可能还不是很全,算是对 MySQL 有一个浅显但较为全面的理解.之前 ...

  9. 论文解读(IGSD)《Iterative Graph Self-Distillation》

    论文信息 论文标题:Iterative Graph Self-Distillation论文作者:Hanlin Zhang, Shuai Lin, Weiyang Liu, Pan Zhou, Jian ...

  10. idea打开service窗口