STM32F103学习笔记四 时钟系统

本文简述了自己学习时钟系统的一些框架,参照风水月

1. 单片机中时钟系统的理解

1.1 概述

  • 时钟是单片机的脉搏,是单片机的驱动源
  • 用任何一个外设都必须打开相应的时钟
  • 不使用一个外设的时候,把它的时钟关掉,从而可以降低系统的功耗,达到低功耗的效果
  • 每个时钟打开,系统都会处理一步数据,这样才能让工作不出现紊乱

1.2 分类

时钟发生器用于产生时钟,并提供给CPU和外部硬件设备。
有如下三种系统时钟:

(1)主系统时钟
①通过连接一个振荡器到X1和X2,该振荡电路产生fx=1到20MHZ的时钟;
②使用内部高速振荡器产生fRH=8MHZ的时钟。
(2)副系统时钟
①通过在XT1和XT2之间连接一个fXT=32.768KHZ的振荡器;
②通过XT2引脚提供一个外部副系统时钟fexclks=32.768KHZ。
(3)内部低速振荡时钟(看门狗定时器时钟)
①内部低速振荡器,以fRL=240KHZ的时钟振荡。该时钟不能作为CPU时钟。

2. STM32时钟介绍

2.1、STM32里的时钟分类

  1. HSI是高速内部时钟,RC振荡器,频率为8MHz
  2. HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
  3. LSI是低速内部时钟,RC振荡器,频率为40kHz。(WDG看门狗 使用该时钟源)
  4. LSE是低速外部时钟,接频率为32.768kHz的石英晶体。(RTC实时时钟 使用该时钟源)
  5. PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz
    其中HSE和LSE是通过单片机外部的晶振输入的,一共四个管脚,HSE的输入管脚是OSC_IN和OSC_OUT(通常为8M),
    LSE的输入管脚对应的引脚为OSC32_IN和OSC32_OUT(32.768kHz)

2.2、STM32的时钟系统框图

*- SYSCLK(系统时钟)

  • AHB总线时钟
  • APB1总线时钟(低速),速度最高到36MHz
  • APB2总线时钟(高速),速度最高到72MHz
  • PLL时钟*

(1)梯形表示SYSCLK系统时钟的来源可以使HSI RC、PLLCLK、HSE Osc(即HSI振荡器时钟、HSE振荡器时钟、PLL时钟)

(2)绿色方框 表示 预分频器(prescaler)

按照上图深红色 的路线来解释:PLL的时钟源 经过前面的选择器 假设为8MHz,经过PLL 9倍频后 PLLCLK的频率为72MHz,则经过选择器 SYSCLK(系统时钟)频率为72MHz,经过AHB分频器 1分频后 HCLK输出频率72MHz,经过APB1分频器 2分频后 PCLK1频率为36MHz; 经过APB2分频器 1分频后 PCLK2频率为72MHz。

(3)时钟输出MCO脚(PA8),可以选择PLL输出的2分频、HSI、HSE或者系统时钟

2.3、 用HSE时钟,程序设置时钟参数流程:

01、将RCC寄存器重新设置为默认值 RCC_DeInit;
02、打开外部高速时钟晶振HSE RCC_HSEConfig(RCC_HSE_ON);
03、等待外部高速时钟晶振工作 HSEStartUpStatus = RCC_WaitForHSEStartUp();
04、设置AHB时钟 RCC_HCLKConfig;
05、设置高速AHB时钟 RCC_PCLK2Config;
06、设置低速速AHB时钟 RCC_PCLK1Config;
07、设置PLL RCC_PLLConfig;
08、打开PLL RCC_PLLCmd(ENABLE);
09、等待PLL工作 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
10、设置系统时钟 RCC_SYSCLKConfig;
11、判断是否PLL是系统时钟 while(RCC_GetSYSCLKSource() != 0x08)
12、打开要使用的外设时钟 RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd()

《STM32中文手册》中的时钟图

3. 时钟函数的设置

3.1、 RCC寄存器的结构体定义

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;

3.2、 RCC的配置函数(使用外部8MHz晶振)

/*******************************************************************************
* Function Name  : RCC_Configuration
* Description    :  RCC配置(使用外部8MHz晶振)
* Input            : 无
* Output         : 无
* Return         : 无
*******************************************************************************/
void RCC_Configuration(void)
{/*将外设RCC寄存器重设为缺省值*/RCC_DeInit();/*设置外部高速晶振(HSE)*/RCC_HSEConfig(RCC_HSE_ON);   //RCC_HSE_ON——HSE晶振打开(ON)/*等待HSE起振*/HSEStartUpStatus = RCC_WaitForHSEStartUp();if(HSEStartUpStatus == SUCCESS)        //SUCCESS:HSE晶振稳定且就绪{/*设置AHB时钟(HCLK)*/ RCC_HCLKConfig(RCC_SYSCLK_Div1);  //RCC_SYSCLK_Div1——AHB时钟= 系统时钟/* 设置高速AHB时钟(PCLK2)*/ RCC_PCLK2Config(RCC_HCLK_Div1);   //RCC_HCLK_Div1——APB2时钟= HCLK/*设置低速AHB时钟(PCLK1)*/
RCC_PCLK1Config(RCC_HCLK_Div2);   //RCC_HCLK_Div2——APB1时钟= HCLK / 2/*设置FLASH存储器延时时钟周期数*/FLASH_SetLatency(FLASH_Latency_2);    //FLASH_Latency_2  2延时周期/*选择FLASH预取指缓存的模式*/  FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);       // 预取指缓存使能/*设置PLL时钟源及倍频系数*/ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
// PLL的输入时钟= HSE时钟频率;RCC_PLLMul_9——PLL输入时钟x 9/*使能PLL */RCC_PLLCmd(ENABLE); /*检查指定的RCC标志位(PLL准备好标志)设置与否*/   while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)      {}/*设置系统时钟(SYSCLK)*/ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//RCC_SYSCLKSource_PLLCLK——选择PLL作为系统时钟/* PLL返回用作系统时钟的时钟源*/while(RCC_GetSYSCLKSource() != 0x08)        //0x08:PLL作为系统时钟{ }
}/*使能或者失能APB2外设时钟*/    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC , ENABLE);
//RCC_APB2Periph_GPIOA    GPIOA时钟
//RCC_APB2Periph_GPIOB    GPIOB时钟
//RCC_APB2Periph_GPIOC    GPIOC时钟
//RCC_APB2Periph_GPIOD    GPIOD时钟
}

上面了解了 stm32默认给我们配置的是 什么样的时钟源,若我们想自定义,则需要借助系统库函数来进行修改,则我需要用到 stm32f10x_rcc.h里定义的库函数

时钟使能配置:
RCC_LSEConfig() 、RCC_HSEConfig()、
RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() ……
时钟源相关配置:
RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、
RCC_RTCCLKConfig() …
分频系数选择配置:
RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()…
外设时钟使能:
RCC_APB1PeriphClockCmd(): //APB1线上外设时钟使能
RCC_APB2PeriphClockCmd(); //APB2线上外设时钟使能
RCC_AHBPeriphClockCmd(); //AHB线上外设时钟使能
其他外设时钟配置:
RCC_ADCCLKConfig (); RCC_RTCCLKConfig();
状态参数获取参数:
RCC_GetClocksFreq();
RCC_GetSYSCLKSource();
RCC_GetFlagStatus()
RCC中断相关函数 :
RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()…

3.3、 系统时钟初始化

上面仅仅是对 时钟控制器 做了一个概念性的介绍,在编写程序时我们一般不直接操作 RCC_TypeDef结构体,而是调用相应的库函数来编程。
在此我们先看一下 库函数中 系统默认如何初始化这些 寄存器的,我们先看一段汇编程序,我们知道c中默认的程序入口是main函数,那main函数的如何调用的呢,我们先来看如下程序(该程序在startup_stm32f10x_hd.s中可以找到):

#ifdef  STM32F10X_CLuint32_t prediv1source = 0, prediv1factor = 0, prediv2factor = 0, pll2mull = 0;
#endif /* STM32F10X_CL */#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)uint32_t prediv1factor = 0;
#endif /* STM32F10X_LD_VL or STM32F10X_MD_VL or STM32F10X_HD_VL *//* Get SYSCLK source -------------------------------------------------------*/tmp = RCC->CFGR & RCC_CFGR_SWS;switch (tmp){case 0x00:  /* HSI used as system clock */SystemCoreClock = HSI_VALUE;break;case 0x04:  /* HSE used as system clock */SystemCoreClock = HSE_VALUE;break;case 0x08:  /* PLL used as system clock *//* Get PLL clock source and multiplication factor ----------------------*/pllmull = RCC->CFGR & RCC_CFGR_PLLMULL;pllsource = RCC->CFGR & RCC_CFGR_PLLSRC;#ifndef STM32F10X_CL      pllmull = ( pllmull >> 18) + 2;if (pllsource == 0x00){/* HSI oscillator clock divided by 2 selected as PLL clock entry */SystemCoreClock = (HSI_VALUE >> 1) * pllmull;}else{#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;/* HSE oscillator clock selected as PREDIV1 clock entry */SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; #else/* HSE selected as PLL clock entry */if ((RCC->CFGR & RCC_CFGR_PLLXTPRE) != (uint32_t)RESET){/* HSE oscillator clock divided by 2 */SystemCoreClock = (HSE_VALUE >> 1) * pllmull;}else{SystemCoreClock = HSE_VALUE * pllmull;}#endif}
#elsepllmull = pllmull >> 18;if (pllmull != 0x0D){pllmull += 2;}else{ /* PLL multiplication factor = PLL input clock * 6.5 */pllmull = 13 / 2; }if (pllsource == 0x00){/* HSI oscillator clock divided by 2 selected as PLL clock entry */SystemCoreClock = (HSI_VALUE >> 1) * pllmull;}else{/* PREDIV1 selected as PLL clock entry *//* Get PREDIV1 clock source and division factor */prediv1source = RCC->CFGR2 & RCC_CFGR2_PREDIV1SRC;prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;if (prediv1source == 0){ /* HSE oscillator clock selected as PREDIV1 clock entry */SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull;          }else{/* PLL2 clock selected as PREDIV1 clock entry *//* Get PREDIV2 division factor and PLL2 multiplication factor */prediv2factor = ((RCC->CFGR2 & RCC_CFGR2_PREDIV2) >> 4) + 1;pll2mull = ((RCC->CFGR2 & RCC_CFGR2_PLL2MUL) >> 8 ) + 2; SystemCoreClock = (((HSE_VALUE / prediv2factor) * pll2mull) / prediv1factor) * pllmull;                         }}
#endif /* STM32F10X_CL */ break;default:SystemCoreClock = HSI_VALUE;break;}/* Compute HCLK clock frequency ----------------*//* Get HCLK prescaler */tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];/* HCLK clock frequency */SystemCoreClock >>= tmp;
}/*** @brief  Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.* @param  None* @retval None*/
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSESetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHzSetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHzSetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHzSetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHzSetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHzSetSysClockTo72();
#endif/* If none of the define above is enabled, the HSI is used as System clocksource (default after reset) */
}

(1)从上面代码逐行往下看,我们知道我们前面的程序 定义的宏定义是 STM32F10X_HD,如下图
(2)从而在初始化器确定了系统时钟频率为(72MHz):‘#define SYSCLK_FREQ_72MHz 72000000’
初始化智慧的状态为:

SYSCLK 72MHz
AHB 72MHz
PCLK1 36MHz
PCLK2 72MHz
PLL 72MHz

(3)SystemInit()函数的第一行代码RCC->CR |= (uint32_t)0x00000001;我们查手册 CR寄存器第一位置1,表示开启8MHz内部振荡器,后续的代码阅读如该句,都需要参照手册 来理解,在此不再说明。
(4)初始化之后可以通过变量SystemCoreClock获取系统变量。如果SYSCLK=72MHz,那么变量SystemCoreClock=72000000。

3.3、时钟频率

STM32F103内部8M的内部震荡,经过倍频后最高可以达到72M。目前TI的M3系列芯片最高频率可以达到80M。
在stm32固件库3.0中对时钟频率的选择进行了大大的简化,原先的一大堆操作都在后台进行。系统给出的函数为SystemInit()。但在调用前还需要进行一些宏定义的设置,具体的设置在system_stm32f10x.c文件中。
文件开头就有一个这样的定义:

//#define SYSCLK_FREQ_HSE HSE_Value
//#define SYSCLK_FREQ_20MHz 20000000
//#define SYSCLK_FREQ_36MHz 36000000
//#define SYSCLK_FREQ_48MHz 48000000
//#define SYSCLK_FREQ_56MHz 56000000
#define SYSCLK_FREQ_72MHz 72000000

ST 官方推荐的外接晶振是 8M,所以库函数的设置都是假定你的硬件已经接了 8M 晶振来运算的.以上东西就是默认晶振 8M 的时候,推荐的 CPU 频率选择.在这里选择了:
#define SYSCLK_FREQ_72MHz 72000000
也就是103系列能跑到的最大值72M
然后这个 C文件继续往下看

#elif defined SYSCLK_FREQ_72MHz
const uint32_t SystemFrequency = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_SysClk = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_AHBClk = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_APB1Clk = (SYSCLK_FREQ_72MHz/2);
const uint32_t SystemFrequency_APB2Clk = SYSCLK_FREQ_72MHz;

这就是在定义了CPU跑72M的时候,各个系统的速度了.他们分别是:硬件频率,系统时钟,AHB总线频率,APB1总线频率,APB2总线频率.再往下看,看到这个了:

#elif defined SYSCLK_FREQ_72MHz
static void SetSysClockTo72(void);

这就是定义 72M 的时候,设置时钟的函数.这个函数被 SetSysClock ()函数调用,而
SetSysClock ()函数则是被 SystemInit()函数调用.最后 SystemInit()函数,就是被你调用的了
所以设置系统时钟的流程就是:

首先用户程序调用 SystemInit()函数,这是一个库函数,然后 SystemInit()函数里面,进行了一些寄存器必要的初始化后,就调用 SetSysClock()函数. SetSysClock()函数根据那个#define SYSCLK_FREQ_72MHz 72000000 的宏定义,知道了要调用SetSysClockTo72()这个函数,于是,就一堆麻烦而复杂的设置~!@#$%^然后,CPU跑起来了,而且速度是 72M. 虽然说的有点累赘,但大家只需要知道,用户要设置频率,程序中就做的就两个事情:
第一个: system_stm32f10x.c 中 #define SYSCLK_FREQ_72MHz 72000000
第二个:调用SystemInit()

STM32F103学习笔记四 时钟系统相关推荐

  1. STM32F103学习笔记(5)——数码管驱动TM1650使用

    一.简介 TM1650 是一种带键盘扫描接口的 LED(发光二极管显示器)驱动控制专用电路.内部集成有 MCU 输入输出控制数字接口.数据锁存器.LED 驱动.键盘扫描.辉度调节等电路.TM1650 ...

  2. 【C51单片机学习笔记--DS1302时钟芯片蜂鸣器I2C总线AT24C02存储器】

    C51单片机学习笔记–DS1302时钟芯片&&蜂鸣器&&I2C总线&&AT24C02存储器 文章目录 一.DS1302时钟芯片介绍 二.DS1302时钟 ...

  3. STM32F103学习笔记(2)——收音机模块TEA5767使用

    一.简介 TEA5767是由菲利普公司推出的一款低功耗立体声收音机接收器芯片.频率范围从76-108MHz自动数字调谐.高灵敏度,高稳定性,低噪音,收音模块.一片低功耗电调谐调频立体声收音机电路,其内 ...

  4. mysql新增表字段回滚_MySql学习笔记四

    MySql学习笔记四 5.3.数据类型 数值型 整型 小数 定点数 浮点数 字符型 较短的文本:char, varchar 较长的文本:text, blob(较长的二进制数据) 日期型 原则:所选择类 ...

  5. ROS学习笔记四:理解ROS节点

    ROS学习笔记四:理解ROS节点 本节主要介绍ROS图形概念,讨论ROS命令行工具roscore.rosnode和rosrun. 要求 要求已经在Linux系统中安装一个学习用的ros软件包例子: s ...

  6. Linux学习笔记之——Linux系统内部相关介绍

    Linux学习笔记之--Linux系统内部相关介绍 摘要:主要记录一些比较有用的能够帮助理解和使用Linux的知识.比如一些相关概念.没兴趣的看看就好.知道有这么个东西.注意事项.和一些常用目录的作用 ...

  7. MySQL高级学习笔记(四)

    文章目录 MySQL高级学习笔记(四) 1. MySql中常用工具 1.1 mysql 1.1.1 连接选项 1.1.2 执行选项 1.2 mysqladmin 1.3 mysqlbinlog 1.4 ...

  8. 【http学习笔记四】安全篇

    [http学习笔记四]安全篇 文章目录 [http学习笔记四]安全篇 一.HTTPS 与 SSL/TLS ① 什么是安全? 机密性 完整性 身份认证 不可否认 ② 什么是HTTPS? ③ SSL/TL ...

  9. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

最新文章

  1. cp: /usr/bin/chromedriver: Operation not permitted
  2. Linux之父警告全球程序员:我刚发布的5.12内核有bug,你们千万别用
  3. struts2控制标签(一)选择标签,iterator标签,append标签
  4. 关于在asp.net中播放MP4格式的视频(好吧,只兼容支持html5的浏览器,ie8及以下的都歇菜了)...
  5. s5pv210——I2C的代码实践
  6. 学习前端开发,可提高Web开发效率的15类工具
  7. Liferay教程– Liferay门户Portlet教程
  8. 用面向对象思想设计奥赛罗游戏
  9. [教程] ios 4 以上安装mobile terminal的最简单方法
  10. IDEA单击打开文件
  11. java.io.IOException: Unable to read entire header; 275 bytes read; expected 512 bytes
  12. JTextField:单行文本框组件
  13. Linux/Android——input_handler之evdev (四)
  14. 智能化转型浪潮下,百度EasyDL的“AI普惠”之路
  15. OpenCV库中watershed函数(分水岭算法)的详细使用例程
  16. MATLAB巴特沃斯滤波器C语言离散实现
  17. 如何实现同一IP的不同端口访问不同的网站
  18. 7月销量被哪吒、零跑反超,“蔚小理”怎么了?
  19. 文件不以%PDF格式打开
  20. Radmin FAQ

热门文章

  1. Ubuntu搜狗输入法, 输入中文时不显示选择框
  2. 我劝你别再做流量的奴隶了...
  3. 宾夕法尼亚大学机器人学学习笔记(2)
  4. 全额包揽!广和通中标中国联通物联网5G数传模组采购项目
  5. 安全区块链(区块链合约安全查询)
  6. 数据摆渡服务器_信息安全华能国际:烟气监测数据网络传输的安全防护
  7. CentOS 连接错误解决办法
  8. Spring框架原理 | IOC/DI | Bean
  9. 阿斯利康携“AI+医疗”创新合作平台与最新成果亮相2021世界人工智能大会
  10. 【python爬虫逆向】某电子商务平台cookie值acw_sc__v2生成逻辑