数字系统中,指令的执行以时钟周期为基本时间单位。常用时钟周期的倒数来表征处理信息的快慢,称为时钟频率。比如主频为72MHz的MCU,其最短时钟周期就是1/72微秒。STM32集成了大多数主流的外设(Peripheral),如USART、DMA、ADC等。不同的外设所需的时钟频率是不同的。为了指挥(同步)不同外设的工作,STM32内建了一套时钟系统。通过配置专门的寄存器,可以对时钟源和时钟频率进行设置,并通过总线架构,实现向不同外设(组)的时钟分配。除了配置时钟频率,时钟控制系统的另一个作用是降低功耗。由于外设众多,为了降低功耗,复位后外设驱动器的时钟一般都是关闭的,需要通过时钟控制系统主动开启。

了解STM32的系统架构和时钟系统,可从宏观上理解STM32工作原理,掌握不同外设时钟的配置和初始化方法,加深对底层硬件和程序基本原理的理解。

注:如非特別声明,以下笔记内容均针对STM32F103ZET6而言。不同型号,细节可能存在差别。

4.1 STM32的系统架构

熟悉MCU的系统架构,对理解外设和时钟系统大有裨益。如下图所示为MCU功能框图。可以看到芯片内部是十分复杂的,包含多个功能模块。我们先分区域来看一下整个芯片的功能布局。

STM32系统架构框图

4.1.1 电源和时钟源

右上角主要是电源和时钟源,负责给芯片供电和提供时钟信号。粉红色填充的4个模块是时钟源,它们产生的时钟信号通过RCC(Reset and Clock Control,复位与时钟控制)模块加载到AHB(Advanced High-performance Bus)总线上。

4.1.2 驱动模块

左上角橙色填充的模块为整个MCU的驱动(Master)模块,包括:

  • Cortex-M3核心:中间为CPU,上方为调试接口,下方为中断控制器NVIC(Nonlinear Vector Interupt Controller)。
  • DMA(Direct Memory Access)控制器:DMA1为7通道,DMA2为5通道。
  • 总线(Bus):负责数据和控制信号的传递。

DMA专门负责处理不同速度的外设对存储器的访问请求和数据搬运。典型的应用就是将数据从一个地址区间复制到另外一个地址区间。 经DMA请求,CPU把总线控制权交给DMA控制器,传输过程由 DMA 控制器来实行和完成,无需CPU主动干预,因此可以降低CPU的负担,提高效率。DMA传输结束后,总线控制权再交回给CPU。一个完整的DMA传输过程包括DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

总线是数据和指令的传输通道。芯片中有多条总线,各自负责不同的信息传递功能:

  • ICode bus:Instruction Code bus,指令码总线,连接内核的ICode总线与闪存指令接口(FLITF,FLash InTerFace),负责从闪存(预)取指令。
  • DCode bus:Data Code bus,连接内核的DCode总线与总线矩阵,然后连接到闪存数据接口,负责数据传输(常量加载、调试访问等)。
  • System bus:系统总线,连接内核的系统总线(外设总线)与总线矩阵。
  • DMA bus:DMA总线,连接DMA与总线矩阵。

可以看出,除了ICode总线是专线外,其余总线均连接到总线矩阵(Bus Matrix)。与Bus Matrix相连的总线有4进4出,分别链接左侧的驱动模块和右侧的从动模块,负责信息的上传下达。由于是一对多的服务,该模块采用了一种称为轮询调度的算法(Round Robin algorithm),来均衡各方需求。

4.1.3 从动模块

从动模块(Slave)又称被动模块,是不能发号施令而只能唯命是从的模块。Bus Matrix右侧的总线从上到下来看,依次去往Flash、SRAM(Static Random-Access Memory, 静态随机存取存储器)、FSMC(Flexible Static Memory Controller,可变静态存储控制器,用于内存扩展)和AHB 系统总线。

AHB总线又通过两个AHB/APB桥,同步连接到两个外设总线APB(Advanced Peripheral Bus)。所有外设都挂载在这两个外设总线上,其中APB2是全速外设总线,最高速度与AHB相同;而APB1是限速总线,最高速度通常只有AHB的一半。以STM32F103ZET6为例,APB2的的速度可达全速72MHz,而APB1的最高限速为36MHz。

另外,SDIO (Security Digital Input/Output,安全数字输入输出)和RCC (Reset & Clock Control)直接挂载在AHB总线上。DMA1和DMA2经Bus Matrix实际上也是与AHB总线相连。RCC通过AHB总线,为整个系统提供复位和时钟控制服务。

注意:

  • 系统复位后,所有外设的时钟都是关闭的,因此使用外设前必须先使能其对应的时钟。AHB总线下方的外设通过设置RCC_AHBENR寄存器使能时钟,APB1和APB2总线下的外设则分别通过设置RCC_APB1ENR和 RCC_APB2ENR寄存器使能时钟。AHB总线上方的SRAM和FLITF的时钟不受系统复位的影响。
  • 不同的芯片型号支持的外设种类和数量可能会有些不同。稳妥起见建议查阅《Reference Manual》-Memory organization章节来确定外设对应的总线及关联的寄存器。

4.2 STM32的时钟系统

再高端的MCU,离开了时钟的同步也无法正常工作。就像人一样,没有了心跳,再好的身体条件也变得毫无意义。时钟系统就是MCU的心脏,RCC就是控制芯片内外各部分协调工作的总指挥。由于支持的外设众多,为了适应不同外设对时钟频率的需求,MCU内部构建了一套复杂的时钟系统,可以通过配置相关寄存器,来调配时钟频率、使能和复位外设时钟。我们先通过STM322CubeIDE或CubeMX中的时钟配置图,从宏观上来了解一下时钟系统的结构,然后再学习相关的寄存器和时钟设置操作。

4.2.1 STM32的时钟源

在STM322CubeIDE或CubeMX中,可以通过图形界面,以交互式的方式来配置时钟。图2所示即为CubeMx中STM32F103ZET6的Clock Configuration界面。我们藉此来来分析一下时钟系统整体架构。

STM32F103ZET6的时钟配置框图

上图中所示的时钟系统可分为三个模块:红色虚线框标示的为低速时钟区、绿色虚线框标示的为主时钟输出区、两个框之外的部分为高速时钟区。系统共有4个时钟源,即LSI、LSE、HSI、HSE。这4个时钟源,若按时钟来源分:

  • LSI和HSI为内部时钟源,二者均为RC振荡源。由于RC时钟的精度较差,因此这两个内部时钟一般用于对时间精度要求不高的场合或在外部时钟出现故障时作为备用应急时钟。
  • LSE和HSE为外部时钟源,一般为高精度石英或陶瓷振荡器。

若按照时钟速度分:

  • LSI和LSE为低速时钟:LSI RC频率约40kHz,LSE的频率为32.678kHz(2^15)。低速时钟主要用于实时时钟RTC(Real-Time Clock)和独立看门狗IWDG(Independent WachDog)。
  • HSI和HSE为高速时钟:HSI RC频率约8MHz,HSE的频率为4-16MHz范围可选。高速时钟是系统的主时钟源,这两个时钟源根据需要经过倍频和分频产生系统时钟SYSCLK(SYStem CLocK)。
  • PLL是个锁相环倍频器(Phase-locking loop),可以将输入频率提高2-16倍,但不能超过系统最高主频。它的时钟来源可以是HSI和HSE。

图中数字框中带除号的为分频器;带乘号的为倍频器;蓝色边框的编辑框可输入数字,右击可锁定/解锁设置好的值。梯形图标是多路选择器,可按需要将某一路选通。从图中部System Clock Mux多路选择器输入端可以看成出,SYSCLK有3个时钟来源:HSI、HSE,以及PLL。SYSCLK经AHB预分频器分频,产生的HCLK即为AHB总线时钟(HCLK中的H取的是AHB中的H)。HCLK再经后面的APB分频器分频,产生PCLK1和PCLK2;两个PCLK还可以再次分频,产生外设所需的频率。应该说明的是,并非时钟频率越高越好,原因有二:时钟频率越高能耗越高;频率越高电磁辐射越严重(电磁感应),可能会增加外围电路设计的难度。所以,在满足应用需求的情况下,应尽可能选择较低的时钟频率。

图2所下角的MCO(Master Clock Output)为主时钟输出端口,可将主时钟输出,用于时钟监控或外部电路同步等。MCO通过选通器可以从4个来源进行选择,但最高输出频率不高于36MHz。

4.2.2 RCC寄存器

时钟源的选择和开关、多路选择器的选通、分频/倍频系数的设置、时钟的锁定和复位等等,均是通过配置相应的RCC寄存器来实现的。STM32F103ZET6共有10个RCC寄存器,图3所示为这些寄存器的地址映射和复位值表。可以看出,RCC寄存器均为32位寄存器。与GPIO寄存器只能按字访问不同,RCC寄存器大都可以按字、字节或位访问。

RCC寄存器地址映射与复位值

说明:

  • 绝大多数可配置功能或外设的复位值为0,使用前需要主动配置寄存器进行使能。
  • 最上方的RCC_CR寄存器有3个位的复位值是1。说明HSI是系统复位后的默认时钟。HSITRIM是用来校准HSI的修饰位。
  • 中部的RCC_AHBENR寄存器有2个位的复位值为1。说明复位后FLITF和SRAM的时钟是默认开启的。
  • 最下方RCC_CSR寄存器有2个位的复位值为1。PORRSETF和PINRSTF是上电复位和外部引脚复位的标志。
  • 黄色背景标示的是一些标志位(Flag),一般由硬件来设置。

下面分别介绍各个RCC寄存器的功能。RCC寄存器配置的实际上是图2所示的时钟系统中相关部分的选通和设值。若计算机上安装了STM32CubeIDE或STM32CubeMx,可以新建一个项目,切换到Clock Configuration界面,对照着学习。

1.RCC-CR

RCC-CR为时钟控制寄存器(Clock Control Register),用以控制时钟源及查询时钟源状态的寄存器。如前所述,系统时钟有3个来源:HSI、HSE和PLL。将图2中的相关部分放大,如下图所示。

系统时钟源构成

RCC-CR寄存器配位表如下图所示。

RCC-CR寄存器配位表

低16用来配置HSI时钟:

  • HSION,1使能,0关闭;
  • HSIRDY,标志位,时钟是否就绪(ReaDY);
  • HSITRIM,对电压和温度影响进行修饰(trimming),提高时钟精度;
  • HSICAL,用于时钟校准(calibration)。

高16为用来配置HSE和PLL:

  • HSEON,开/关时钟;
  • HSERDY,标志位,时钟是否就绪;
  • HSEBYP,时钟旁路(bypass),外接自定义时钟(方波、正弦、三角波等)须旁路HSE;
  • CSSON,开/关时钟安全系统(Clock security system);
  • PLLON,开/关PLL;PLLRDY,标志位,PLL是否就绪。

说明:

  • HSI是RC时钟,环境参数变化(如电压和温度)会对时钟精度造成影响。RCC-CR寄存器设置了校准和温度/电压修饰位,可根据需要设置这些位,以提高时钟精度。ST出厂校正精度为1%@25℃,复位后HSICAL和HSITRIM恢复出厂设置。
  • CSS用来确保HSE时钟的安全运行。开启CSS后,如果HSE出现故障,CSS会触发一个NMI(Non-Maskable Interrupt)中断,自动将时钟切换到HSI。如果原时钟源是HSE+PLL,PLL也会被关闭。
  • 从图3可以看出,复位后HSION的值为1,可见HSI是复位后默认的系统时钟,使用其他时钟源必须主动开启。

2.RCC-CFGR

RCC-CFGR为时钟配置寄存器(Clock Configure Register),用以配置图2中所示的多路选择器的选通、分频器/倍频器系数的寄存器。RCC-CFGR寄存器配位表如图6所示。CubeMX中的对应配置区域参考下图。

RCC-CFGR寄存器配位表

CubeMX中AHB和APB主线的时钟配置

SW(Systme clock sWitch),配置系统时钟选通器(图4中System Clock Mux)。配位逻辑:00 - HSI;01 - HSE;10 - PLL;11 - 禁用。CubeMX中,选通器通过选中图标上的单选选项进行配置(如图中4所示)。

  • SWS(System clock sWitch Status),系统时钟状态标志位。配位逻辑与SW相同。
  • HPRE(AHB prescaler),设置AHB主线预分频系数。系统时钟SYSCLK经AHB Prescaler分频,产生AHB总线时钟HCLK(参考图4和图7)。配位逻辑:0xxx - 1(不分频);1000 - 2;1001 - 4;1010 - 8;1011 - 16:1100 - 64;1101 - 128;1110 - 256;1111 - 512。CubeMX中,分频/倍频系数通过下拉列表进行选择(如图4中所示)。
  • PPRE1和PPRE2 (APB prescaler),分别设置APB1和APB2的预分频系数。HCLK分别经APB1 Prescaler和APB2 Prescaler分频,产生外设总线时钟PCLK1和PCLK2(参考图7)。配位逻辑:0xx-1(不分频);100 - 2;101 - 4;110 - 8;111 - 16。
  • ADCPRE(ADC prescaler),设置ADC预分频系数。ADC的时钟由PCLK2经ADC Prescaler进一步分频得到(参考图7)。配位逻辑:00 - 2;01 - 4;10 - 6;11 - 8。
  • PLLSRC(PLL source),配置PLL时钟选通器(图4中PLL Source Mux)。配位逻辑:0 - HSI2分频;1 - HSE。
  • PLLXTPRE(PLL eXTernal clock prescaler):设置PLL外部时钟源(即HSE)预分频系数 (参考图4,HSE与PLL Source Mux间的分频器)。配位逻辑:0 - HSE不分频;1 - HSE2分频。
  • PLLMUL(PLL multiplication factor):设置PLL倍频系数(参考图4,*PLLMul)。根据时钟源频率设置合适的倍频系数,但最高输出频率不能高于芯片主频。配位逻辑:从0000递增到1110分别对应2-16倍,1111也对应16倍。
  • USBPRE(USB prescaler):设置USB预分频系数。PLLCLK经USB Prescaler分频产生48MHz的USB时钟(参考图4,右下角)。配位逻辑:0 - 1.5分频,PLLCLK为72MHz时对应48MHz;1 - 不分频。
  • MCO:配置主时钟输出选通器(参考图2左下角)。配位逻辑:0xx-关闭MCO;100 - 输出SYSCLK;101 - 输出HSI;110 - 输出HSE;111 - 输出PLL的2分频。

3.RCC_CIR

RCC_CIR为时钟中断寄存器(Clock Interupt Register),用于管理时钟相关的中断。CC-CIR寄存器配位表如下图所示。

RCC_CIR寄存器配位表

按功能可以分为3组:

  • 以E结尾的(8-12位)为时钟就绪(事件)中断使能位(Enable),可读可写。设为1则对应的时钟稳定下来后会触发RDY中断;设为0则关闭中断。
  • 以F结尾的(1-4,7位)为时钟就绪标志位(Flag),只可读。如果某时钟设置并触发了RDY中断,则由硬件将对应的位设为1。如果开启了CSS,HSE发生故障时会触发中断,设置CSSF位。
  • 以C结尾的(16-20,23位)为时钟就绪中断清除位(Clear),只可写。与事件标志位是一一对应的,将某位设为1,可以将对应的标志位清除;设为0,则无影响。中断处理完成后,一般要主动清除标志位。

可以看出,RCC相关的事件有6个,其中5个事件可按需开启中断,而CSS中断只要开启CSS就会自动开启。

4.RCC_APB2ENR和RCC_APB2RSTR

从图3可以看出,与APB2总线上的外设相关的RCC寄存器有两个,分别为RCC_APB2ENR和RCC_APB2RSTR,前者称为APB2外设时钟使能寄存器(APB2 peripheral enable register),后者称为APB2外设复位寄存器(APB2 peripheral reset register)。两个寄存器的功能位是一一对应的。 RCC_APB2ENR和RCC_APB2RSTR的配位表分别如图9和图10所示。

RCC_APB2ENR寄存器配位表

RCC_APB2RSTR寄存器配位表

每个位对应一个外设端口,对应的外设从表格中的名称可以一目了然,毋庸赘言(IOP意为I/O port,即GPIO)。向RCC_APB2ENR的某位写入1,可以使能对应的外设时钟,写入0则关闭对应外设的时钟;向RCC_APB2RST的某位写入1,则可以将对应的外设复位,写入0无影响。

注意:

  • 从图2中可以看出,两个寄存器的复位值均为0,说明系统复位后所有APB2上的外设时钟都是关闭的,因此使用外设前必须主动使能对应的时钟。
  • 这两个寄存器与前面学习过的GPIO的BRR和BSRR寄存器有本质的区别,BRR和BSRR是分别将ODR的对应位置0和置1。而这里的APB2ENR是使能外设的时钟,而APB2RSTR是将外设复位,即将外设的所有寄存器的值设为复位值表中的值。例如,将RCC_APB2RST中的IOPARST位置1,则GPIOA所有的寄存器被复位为默认值。

5.RCC_APB1ENR和RCC_APB1RSTR

与APB1总线上的外设相关的RCC寄存器也有两个,分别为RCCAPB1ENR和RCC_APB1RSTR,配位表分别如图11和图12所示,所有位的复位值也为0。除了管理的外设不一样,功能与APB2总线相关的两个寄存器完全一致,不再赘述。

RCC_APB1ENR寄存器配位表

RCC_APB1RSTR寄存器配位表

6.RCC_AHBENR

RCC_AHBENR为AHB外设时钟使能寄存器(AHB peripheral clock enable register),用于管理与AHB总线相连的外设时钟。图13所示为寄存器配位图。

RCC_AHBENR寄存器配位表

AHB总线的外设只有一个ENR寄存器,没有*RSTR。RCC_AHBENR原理同APB1和APB2的ENR寄存器,向某位写入1,使能对应外设时钟;写入0,关闭对应外设时钟。系统复位后,除FLITFEN和SARAMEN位的复位值为1,其余位复位值为0,说明复位后FLITF和SARAM的时钟是默认开启的。

7.RCC_BDCR

RCC_BDCR为备份区控制寄存器(Backup domain control register),用于管理备份区的复位与时钟选择,以及开关RTC。所谓备份区是一组特殊的寄存器(BKP,backup registers),用于在VDD断电后,依靠板载电池VBAT保存用户数据。备份区主要功能包括:侵入检测和RTC时钟校准。在图1中,备份区位于右上方,LSI时钟(XTAL32kHz)图标的下方,有一个TAMPER引脚引出,用于侵入监测或输出RTC闹钟脉冲等(详情查阅《Reference manual》-BKP寄存器相关章节)。

RCC_BDCR寄存器配位表

RCC_BDCR寄存器的配位图如图14所示。这个寄存器只用到了7位:

  • 前三位:LSEON-使能LSE时钟;LSERDY-LSE就绪标志;LSEBYP-旁路LSE时钟。
  • RTCSEL:选择RTC的时钟源(图2右上角的RTC Clock Mux)。配位逻辑:00-无时钟;01-LSE;10-LSI;11-HSE的128分频。
  • RTCEN:RTC使能位,写入1开启RTC,写入0关闭RTC。
  • BDRST:写入1,整个备份区将被复位。

8.RCC_CSR

RCC_CSR为控制/状态寄存器(Control/status register),用以管理LSI时钟和系统复位标志。STM32系列MCU支持6种系统复位方式,无论哪种方式触发复位,除RCC_CSR和备份区寄存器外的所有寄存器都将被复位。备份域寄存器需要设置RCC_BDCR中的BDRST = 1进行复位,RCC_CSR寄存器只有在发生电源复位是才会被复位。因此,当发生系统复位时,可以用RCC_CSR寄存器记录复位方式。RCC_CSR寄存器的配位图如图15所示。

RCC_CSR寄存器配位表

26-31位为复位方式标志位(ReSeT Flag):

  • LPWRRSTF - Low-power reset flag,低功耗复位标志。系统进入低功耗模式,导致系统复位时,由硬件将该位置1。
  • WWDGRSTF - Window watchdog reset flag,窗口看门狗复位标志。窗口看门狗复位发生时,由硬件将该位置1。
  • IWDGRSTF - Independent watchdog reset flag,独立看门狗复位标志。独立看门狗复位发生时,由硬件将该位置1。
  • SFTRSTF - Software reset flag,软件复位标志。软件复位发生时,由硬件将该位置1。
  • PORRSTF - POR/PDR reset flag,上电/掉电复位标志位。上电/掉电复位发生时,由硬件将该位置1。
  • PINRSTF - PIN reset flag,引脚复位标志位。芯片有个NRST引脚(exteral reset 见图2右上角),该引脚为低电平时触发系统复位。开发板上一般有个RST按钮,按下后会该引脚接地,触发系统复位。

24位RMVF(Remove flag)为复位标志清除位,向该位写入1将会把26-31位中的复位标志全部清除。

0位LSION和1位LSIRDY分别为LSI使能和就绪标志位。如前已述,HSE和HSI的对应功能位在RCC_CR寄存器中设置,LSE的在RCC_BDCR寄存器中设置。

4.3 时钟复位与初始化

4.3.1 寄存器操作

在头文件stm32f10x.h(以F103系列标准库为例,HAL库中对应的头文件为stm32f103xe.h)中定义了大量的宏、枚举类和结构体,对寄存器地址进行了映射。与RCC相关的地址映射摘录如下:

#define PERIPH_BASE      ((uint32_t)0x40000000) /*!< 别名区总线基地址 */
#define AHBPERIPH_BASE   (PERIPH_BASE + 0x20000)
#define RCC_BASE         (AHBPERIPH_BASE + 0x1000)
#define RCC              ((RCC_TypeDef *) RCC_BASE)

typedef struct{
  __IO uint32_t CR;       //指向RCC首地址,后续依次便宜0x04(32bit)
  __IO uint32_t CFGR;
  __IO uint32_t CIR;
  __IO uint32_t APB2RSTR;
  __IO uint32_t APB1RSTR;
  __IO uint32_t AHBENR;
  __IO uint32_t APB2ENR;
  __IO uint32_t APB1ENR;
  __IO uint32_t BDCR;
  __IO uint32_t CSR;
} RCC_TypeDef;

通过别名引用寄存器地址,向对应的位写入0或1,即可实现对寄存器的设置。时钟配置可以参考图2,按顺序依次配置相关寄存器。例如,先将系统时钟源设HSE,频率72MHz,然后开启GPIOB,GPIOE的时钟:

	/* 复位RCC相关寄存器 */ //CR不要清零,HSICAL出厂数据不确定
	RCC->APB1RSTR = 0x00000000;
	RCC->APB2RSTR = 0x00000000;
	RCC->AHBENR = 0x00000014;
	RCC->APB2ENR = 0x00000000;
	RCC->APB1ENR = 0x00000000;
	RCC->CFGR = 0x00000000;
	RCC->CIR = 0x00000000; //关闭所有中断
	/* 配置时钟 */
	RCC->CR |= 0x00010001;//使能HSION/HSEON
	while(!(RCC->CR >> 17)); //等待HSERDY
	// 配置PLL
	RCC->CFGR |= 0x001D0402;//HSE 9倍频 PCLK2=72MHz
	RCC->CR |= 0x01000000; //使能PLL
	FLASH->ACR|=0x32;	  //FLASH 2个延时周期
	while(!(RCC->CR >> 25)); //等待PLLRDY

  RCC->APB2ENR |= 0x00000048;//开启GPIOB,GPIOE的时钟

头文件stm32f10x.h中还定义了与RCC寄存器的功能位相关的宏,利用这些宏上述操作可改写为:

RCC->CR |= RCC_CR_HSION | RCC_CR_HSEON;//使能HSION/HSEON
while((RCC->CR & RCC_CR_HSERDY) == 0);
RCC->CFGR &= ~(RCC_CFGR_PLLXTPRE|RCC_CFGR_SW|RCC_CFGR_PLLMULL|RCC_CFGR_PLLSRC); 
RCC->CFGR |= RCC_CFGR_PLLXTPRE_PREDIV1|RCC_CFGR_SW_PLL|RCC_CFGR_PLLMULL9|RCC_CFGR_PLLSRC_HSE; 
RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == 0);

RCC->APB2ENR |= RCC_APB2ENR_IOPBEN|RCC_APB2ENR_IOPEEN;

小结:

  • 如果新设的所有位均为1,准备好一个合适的Set mask(32bit十六进制数),直接|=即可。
  • 如果新设的位中有0,需要先准备好一个Reset mask,&=定位清零,然后再准备一个Set mask,|=写入新值。
  • 为了便于计算Reset mask和Set mask,可以用Excel结合VBA编程做一个自动计算表。每个sheet对应一组寄存器进行统一管理。
  • 用stm32f10x.h中预定义的宏操代码可读性和复用性好,但代码确实要复杂一些。好在常用的IDE都有代码提示功能,不需要记忆大量的宏名称。

4.3.2 STD库RCC函数

STD库中RCC相关的库函数在stm32f10x_rcc.h中声明,在stm32f10x_rcc.c中实现。头文件stm32f10x_rcc.h中为RCC相关寄存器中的功能位定义了宏名称,包括时钟源、各种外设、分频/倍频系数、多路选择器选项、标志位等定义,这些宏主要是为了配合库函数使用的。为了在库函数中实现按位(bit banding)操作,提高效率,在stm32f10x_rcc.c中,对部分寄存器在别名区的地址也进行了映射,为常用功能位定义了设置/复位掩码(Set/Reset mask)。

库函数是对寄存器操作的封装。STD库中RCC相关的库函数有30多个,下面按函数所操作的寄存器分组并简述其功能,可参照图3查看寄存器所关联的寄存器功能位/组。

1.跨寄存器函数

  • void RCC_DeInit(void) 复位CR、CFGR、CIR寄存器,这三个寄存器管理的是时钟配置和中断配置。复位后CIR寄存器的值与复位值表稍有不同,以C结尾的寄存器值被赋值为1,可能是因为F结尾的位是通过硬件设置的,只能通过软件设置C结尾的位来来触发硬件复位F结尾的位。
  • void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks) 查询SYSCLK/HCLK/PCLK1/PCLK2/ADCCLK时钟的频率。该函数会根据当前时钟配置,计算并返回所查询时钟的 频率,单位Hz。
  • FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG) 查询状态标志位的值。 该函数用于查询CR、BDCR以及CSR中的*RDY位和*RST位(参见图3)。

2.RCC_CR

  • void RCC_HSICmd(FunctionalState NewState) 开/关HSI时钟(HSION位)。
  • void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue) 调整HSI校准参数(HSITRIM[4:0]位)。取值范围0x00-0x1F。
  • void RCC_HSEConfig(uint32_t RCC_HSE) 配置HSE时钟(HSEON和HSEBYP位)。从函数定义可以看出,如果使能HSEBYP位,HSEON位必须同时使能,设置HSEBYP是为了使用别的外部时钟源,将HSE旁路,但HSE端口必须使能。
  • ErrorStatus RCC_WaitForHSEStartUp(void) 等待HSE就绪。通过HSEON开启HSE时钟时,需要6个HSE时钟周期才会稳定下来,稳定下来后将HSERDY位置1。该函数中用一个do while循环调用RCC_GetFlagStatus(),查询RCC_FLAG_HSERDY,即HSERDY位的状态。直至HSERDY=SET或超时(1280次)。时钟就绪时返回SUCCESS,否则返回ERROR。
  • void RCC_ClockSecuritySystemCmd(FunctionalState NewState) 开/关CSS(CSSON位)。位带操作。
  • void RCC_PLLCmd(FunctionalState NewState) 开/关PLL(PLLON位)。位带操作。

3.RCC_CFGR

  • void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource) SYSCLK配置(SW[1:0]位)。
  • void RCC_HCLKConfig(uint32_t RCC_SYSCLK) HCLK配置(HPRE[3:0]位)。
  • void RCC_PCLK1Config(uint32_t RCC_HCLK) PCLK1配置(PPRE1[2:0]位)。
  • void RCC_PCLK2Config(uint32_t RCC_HCLK) PCLK2配置(PPRE2[2:0]位)。
  • void RCC_ADCCLKConfig(uint32_t RCC_PCLK2) ADCCLK配置(ADCPRE[1:0]位)。
  • void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul) 配置PLL时钟源(PLLSRC位和PLLXTPRE位)和倍频系数(PLLMUL位)。
  • void RCC_MCOConfig(uint8_t RCC_MCO) 配置系统时钟输出(MCO[2:0])。
  • uint8_t RCC_GetSYSCLKSource(void) 查询系统时钟源(SWS[1:0])。

4.RCC_CIR

  • void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState) 中断配置(6个*E位)。
  • ITStatus RCC_GetITStatus(uint8_t RCC_IT) 中断状态查询(6个*F位)。
  • void RCC_ClearITPendingBit(uint8_t RCC_IT) 中断状态清除(6个*C位)。

5.RCC_BDCR

  • void RCC_LSEConfig(uint8_t RCC_LSE) LSE时钟配置(LSEON位和LSEBYP位)。
  • void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource) RTC时钟源配置(RTCSEL[1:0]位)。
  • void RCC_RTCCLKCmd(FunctionalState NewState) 开/关RTCRTC时钟(RTCEN位)。
  • void RCC_BackupResetCmd(FunctionalState NewState) 复位备份区(BDRST位)。

6.RCC_CSR

  • void RCC_LSICmd(FunctionalState NewState) 开/关LIS时钟(LSION位)。
  • void RCC_ClearFlag(void) 清除标志位(RMVF位)。项RMVF为写入1,清除复位*RSTF位。

7.开关外设时钟

  • void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) 开/关AHB外设时钟。
  • void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) 开/关APB2外设时钟。
  • void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) 开/关AHB1外设时钟。
  • void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) 复位APB2外设时钟。
  • void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) 复位APB1外设时钟。

小结:

  • 几乎每个寄存器功能位/组,都有与之对应的库函数。
  • *cmd函数为功能使能/关闭函数;*Config为功能配置函数;*Init为复位或初始化函数;Get*为查询函数;Wait*为等待函数;Clear*标志位为清除函数。

示例:

RCC_HSEConfig(RCC_HSE_ON); //使能HSE
RCC_PLLConfig(CC_PLLSource_HSE_Div1,RCC_PLLMul_9);//配置PLL
RCC_PLLCmd(ENABLE); //使能PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选通PLL作为系统时钟源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);//开启GPIOB,GPIOE的时钟

说明:

STD库中的时钟配置可以按上述方式,通过调用库函数配置。其实有更简单的方法,在system_stm32f10x.c中,STMicroelectronicst已经实现了各种可能的时钟配置,我们只需打开相应的宏定义即可。system_stm32f10x.c中的第一个宏定义模块中,去掉/* #define SYSCLK_FREQ_72MHz 72000000 前的注释符即可。在系统初始化函数SystemInit中会自动调用SetSysClock函数完成时钟配置。

4.3.3 HAL库RCC函数

HAL库中与一个外设相关的文件通常有4个:stm32f1xx_hal_ppp.c, stm32f1xx_hal_ppp.h, stm32f1xx_hal_ppp_ex.c, stm32f1xx_hal_ppp_ex.h。前两个文件提供全F1系列芯片外设通用功能的API,后两个文件则是当前型号外设特有功能的API(扩展API,ex是extension的缩写)。扩展API中定义的函数也用*_pppEX_*命名以示区分。

HAL库中RCC相关的普通库函数精简到了17个,同时定义了大量的宏函数。

  • HAL_StatusTypeDef HAL_RCC_DeInit(void) 复位CR、CFGR、CIR、CSR寄存器。功能相当于STD库中的RCC_DeInit+RCC_CC_ClearFlag。
  • HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) 时钟源配置。HAL库中定义了RCC_OscInitTypeDef和RCC_PLLInitTypeDef两个结构体,用以集中管理LSI、LSE、HSI、HSE、PLL5个时钟源的配置参数。
  • HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency) 时钟配置。RCC_ClkInitTypeDef结构体管理时钟类型(SYSCLK/HCLK/PCLK1/PCLK2)、SYSCLK来源、AHB/APB1/APB2的预分频系数。
  • void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv) 配置MCO。
  • void HAL_RCC_EnableCSS(void) 使能CSS。
  • void HAL_RCC_DisableCSS(void) 关闭CSS。
  • uint32_t HAL_RCC_GetSysClockFreq(void) 查询SYSCLK频率。
  • uint32_t HAL_RCC_GetHCLKFreq(void) 查询HCLK频率。
  • uint32_t HAL_RCC_GetPCLK1Freq(void) 查询PCLK1频率。
  • uint32_t HAL_RCC_GetPCLK2Freq(void) 查询PCLK2频率 < *--以上4个函数计算并返回相应时钟频率,单位Hz。
  • void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) 查询当前时钟源配置。输入参数为RCC_OscInitTypeDef型指针,所以无需返回。
  • void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t *pFLatency) 查询当前时钟配置。
  • void HAL_RCC_NMI_IRQHandler(void) CSS中断请求处理函数(Interrupt ReQuest)。CCS中断是NMI(None maskable interrupt)中断,表明时钟源出了问题。系统的所有NMI中断由NMI_Handler()函数处理(stm32f1xx_it.c),所以该函数应该在NMI_Handler()中被。
  • __weak void HAL_RCC_CSSCallback(void) CSS中断回调函数。这是真正的中断处理函数,是个week函数,要由用户具体实现。CSS中断最典型的应用就是系统时钟监控,如果HSE时钟出了故障,调用该回调函数,尝试重启时钟或报错。
  • static void RCC_Delay(uint32_t mdelay) 延时函数。基于CPU时钟周期延时指定的时长(ms)
  • HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
  • void HAL_RCCEx_GetPeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
  • uint32_t HAL_RCCEx_GetPeriphCLKFreq(uint32_t PeriphClk) < *--扩展API函数,上述3个函数用于查询特有外设的时钟配置和频率,包括:RTC、ADC、I2S2、I2S3,USB。这些外设同系列不同型号的芯片可能要求不一样(参见图2)。

小结:

  • HAL库中主要为时钟源和时钟参数配置、时钟查询和中断处理等功能位定义了函数。
  • 外设时钟管理被定义成了宏函数,语法格式:(pppp为外设名称,宏函数以__HAL开头,注意前面是2个下划线)
__HAL_RCC_pppp_CLK_ENABLE()       //使能外设时钟
__HAL_RCC_pppp_CLK_DISABLE()      //关闭外设时钟
__HAL_RCC_pppp_IS_CLK_ENABLED()   //查询是否使能成功
__HAL_RCC_pppp_IS_CLK_DISABLED()  //查询是否关闭成功
__HAL_RCC_pppp_FORCE_RESET()      //复位为1
__HAL_RCC_pppp_RELEASE_RESET()    //复位为0
  • 时钟源的开关/配置/查询,中断开关/清除/查询、状态标志位查询/清除、备份区的设置等也都有对应的宏函数。
  • RCC相关的普通函数其实也只是宏函数的组合封装。函数内部对寄存器的操作大都是通过调用宏函数去实现的。
  • 基于HAL库开发,强烈推荐使用STM32CubeIDE或CubeMX,可以以图形化方式完成时钟和外设端口配置,省却无尽麻烦!!!

示例:

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON; //使能HSE
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //使能PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL源为HSE
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; //9倍频
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){
    Error_Handler();
  }

 __HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB的时钟
 __HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOE的时钟