第九章 RCC——使用HSE/HSI配置时钟

第九章 RCC——使用HSE/HSI配置时钟

目录

第九章 RCC——使用HSE/HSI配置时钟

1 RCC主要作用——时钟部分

2 RCC框图剖析——时钟部分

2.1 系统时钟

2.1.1 HSE高速外部时钟信号

2.1.2 设置系统时钟库函数

2.2 其他时钟

2.2.1 USB时钟

2.2.2 Cortex系统时钟

2.2.3 ADC时钟

2.2.4 RTC时钟、独立看门狗时钟

2.2.5 MCO时钟输出

3 时钟配置

3.1 RCC_HSIConfig

3.1.1 实验现象

3.2 RCC_HighFrequencyConfig

3.2.1 实验现象


本章参考资料:《W55MH32参考手册》RCC章节。

学习本章时,配合《W55MH32参考手册》RCC章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

RCC :reset clock control 复位和时钟控制器。本章我们主要讲解时钟部分, 特别是要着重理解时钟树,理解了时钟树,W55MH32的一切时钟的来龙去脉都会了如指掌。

1 RCC主要作用——时钟部分

        设置系统时钟SYSCLK、设置AHB分频因子(决定HCLK等于多少)、设置APB2分频因子(决定PCLK2等于多少)、 设置APB1分频因子(决定PCLK1等于多少)、设置各个外设的分频因子;控制AHB、APB2和APB1这三条总线时钟的开启、 控制每个外设的时钟的开启。对于SYSCLK、HCLK、PCLK2、PCLK1这四个时钟的配置一般是: PCLK2 = HCLK = SYSCLK=PLLCLK = 72M, PCLK1=HCLK/2 = 36M。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。

2 RCC框图剖析——时钟部分

1. 当 HSI 被用于作为 PLL 时钟的输入时,系统时钟能得到的最大频率是 108MHz。

2. 对于内部和外部时钟源的特性,请参考相应产品数据手册中“电气特性”章节。

        用户可通过多个预分频器配置 AHB、高速 APB(APB2)和低速 APB(APB1)域的频率。AHB 和 APB2 域的最大频率是 216MHz。APB1 域的最大允许频率是 108MHz。SDIO 接口的时钟频率固定为 HCLK/2。RCC 通过 AHB 时钟(HCLK)8 分频后作为 Cortex 系统定时器(SysTick)的外部时钟。通过对 SysTick控制与状态寄存器的设置,可选择上述时钟或 Cortex(HCLK)时钟作为 SysTick 时钟。ADC 时钟由高速 APB2 时钟经 2、4、6 或 8 分频后获得。

定时器时钟频率分配由硬件按以下 2 种情况自动设置:

1. 如果相应的 APB 预分频系数是 1,定时器的时钟频率与所在 APB 总线频率一致。

2. 否则,定时器的时钟频率被设为与其相连的 APB 总线频率的 2 倍。

        FCLK 是 Cortex™-M3 的自由运行时钟。详情见 ARM 的 Cortex™-M3 技术参考手册。RCC时钟树如下:

2.1 系统时钟

2.1.1 HSE高速外部时钟信号

        HSE是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从4-16MHZ不等。当使用有源晶振时, 时钟从OSC_IN引脚进入,OSC_OUT引脚悬空,当选用无源晶振时,时钟从OSC_IN和OSC_OUT进入,并且要配谐振电容。

        HSE最常使用的就是8M的无源晶振。当确定PLL时钟来源的时候,HSE可以不分频或者2分频, 这个由时钟配置寄存器CFGR的位17:PLLXTPRE设置,我们设置为HSE不分频。

PLL时钟源

        内部 PLL 可以用来倍频 HSIRC 的输出时钟或 HSE 晶体输出时钟。参考图 7 和时钟控制寄存器。

        PLL 的设置(选择 HIS 振荡器除 2 或 HSE 振荡器为 PLL 的输入时钟,和选择倍频因子)必须在其被激活前完成。一旦 PLL 被激活,这些参数就不能被改动。

        如果 PLL 中断在时钟中断寄存器里被允许,当 PLL 准备就绪时,可产生中断申请。

        如果需要在应用中使用 USB 接口,PLL 必须被设置为输出 48 或 72MHZ 时钟,用于提供 48MHz 的USBCLK 时钟。

PLL时钟PLLCLK

        通过设置PLL的倍频因子,可以对PLL的时钟来源进行倍频,倍频因子可以是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], 具体设置成多少,由时钟配置寄存器CFGR的位21-18:PLLMUL[3:0]设置。我们这里设置为9倍频, 因为上一步我们设置PLL的时钟来源为HSE=8M,所以经过PLL倍频之后的PLL时钟:PLLCLK = 8M *9 = 72M。 72M是ST官方推荐的稳定运行时钟,如果你想超频的话,增大倍频因子即可,最高为128M。 我们这里设置PLL时钟:PLLCLK = 8M *9 = 72M

系统时钟(SYSCLK)选择

        系统复位后,HSI 振荡器被选为系统时钟。当时钟源被直接或通过 PLL 间接作为系统时钟时,它将不能被停止。

        只有当目标时钟源准备就绪了(经过启动稳定阶段的延迟或 PLL 稳定),从一个时钟源到另一个时钟源的切换才会发生。在被选择时钟源没有就绪时,系统时钟的切换不会发生。直至目标时钟源就绪,才发生切换。

        在时钟控制寄存器(RCC_CR)里的状态位指示哪个时钟已经准备好了,哪个时钟目前被用作系统时钟。

AHB总线时钟HCLK

        系统时钟SYSCLK经过AHB预分频器分频之后得到时钟叫APB总线时钟,即HCLK,分频因子可以是:[1,2,4,8,16,64,128,256,512], 具体的由时钟配置寄存器CFGR的位7-4 :HPRE[3:0]设置。片上大部分外设的时钟都是经过HCLK分频得到, 至于AHB总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置, 我们这里只需粗线条的设置好APB的时钟即可。

APB2总线时钟PCLK2

        APB2总线时钟PCLK2由HCLK经过高速APB2预分频器得到,分频因子可以是:[1,2,4,8,16],具体由时钟配置寄存器CFGR的位13-11:PPRE2[2:0]决定。 PCLK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的GPIO、USART1、SPI1等。至于APB2总线上的外设的时钟设置为多少, 得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好APB2的时钟即可。

APB1总线时钟PCLK1

        APB1总线时钟PCLK1由HCLK经过低速APB预分频器得到,分频因子可以是:[1,2,4,8,16],具体的由时钟配置寄存器CFGR的位10-8:PRRE1[2:0]决定。 PCLK1属于低速的总线时钟,最高为36M,片上低速的外设就挂载到这条总线上,比如USART2/3/4/5、SPI2/3,I2C1/2等。 至于APB1总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗略的设置好APB1的时钟即可。

2.1.2 设置系统时钟库函数

        上面的7个步骤对应的设置系统时钟库函数如下,该函数截取自固件库文件system_w55mh32.c。为了方便阅读, 我已把互联型相关的代码删掉,把英文注释翻译成了中文,并把代码标上了序号,总共七个步骤。该函数是直接操作寄存器的, 有关寄存器部分请参考数据手册的RCC的寄存器描述部分。

代码清单:RCC-1 设置系统时钟库函数。

static void SetSysClockTo72(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // ① 使能HSE,并等待HSE稳定
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    // 等待HSE启动稳定,并做超时处理
    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;
    }
    // HSE启动成功,则继续往下处理
    if (HSEStatus == (uint32_t)0x01) {

        //-----------------------------------------------------------
        // 使能FLASH 预存取缓冲区 */
        FLASH->ACR |= FLASH_ACR_PRFTBE;

        // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
        // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
        // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
        // 0:0 < SYSCLK <= 24M
        // 1:24< SYSCLK <= 48M
        // 2:48< SYSCLK <= 72M */
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
        //------------------------------------------------------------

        // ② 设置AHB、APB2、APB1预分频因子
        // 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;

        // ③ 设置PLL时钟来源,设置PLL倍频因子,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);

        // ④ 使能 PLL
        RCC->CR |= RCC_CR_PLLON;

        // ⑤ 等待PLL稳定
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) {
        }

        // ⑥ 选择PLL作为系统时钟来源
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

        // ⑦ 读取时钟切换状态位,确保PLLCLK被选为系统时钟
        while ((RCC->CFGR&(uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){
        }
    } else {// 如果HSE启动失败,用户可以在这里添加错误代码出来
    }
}

2.2 其他时钟

        通过对系统时钟设置的讲解,整个时钟树我们已经把握的有六七成,剩下的时钟我们讲解几个重要的部分。

2.2.1 USB时钟

        USB时钟是由PLLCLK经过USB预分频器得到,分频因子可以是:[1,1.5],具体的由时钟配置寄存器CFGR的位22:USBPRE配置。 USB的时钟最高是48M,根据分频因子反推过来算,PLLCLK只能是48M或者是72M。一般我们设置PLLCLK=72M,USBCLK=48M。 USB对时钟要求比较高,所以PLLCLK只能是由HSE倍频得到,不能使用HSI倍频。

2.2.2 Cortex系统时钟

        Cortex系统时钟由HCLK 8分频得到,等于9M, Cortex系统时钟用来驱动内核的系统定时器SysTick,SysTick一般用于操作系统的时钟节拍,也可以用做普通的定时。

2.2.3 ADC时钟

        ADC时钟由PCLK2经过ADC预分频器得到,分频因子可以是[2,4,6,8],具体的由时钟配置寄存器CFGR的位15-14:ADCPRE[1:0]决定。 很奇怪的是怎么没有1分频。ADC时钟最高只能是14M,如果采样周期设置成最短的1.5个周期的话,ADC的转换时间可以达到最短的1us。 如果真要达到最短的转换时间1us的话,那ADC的时钟就得是14M,反推PCLK2的时钟只能是:28M、56M、84M、112M。

2.2.4 RTC时钟、独立看门狗时钟

        RTC时钟可由HSE/128分频得到,也可由低速外部时钟信号LSE提供,频率为32.768KHZ,也可由低速内部时钟信号LSI提供, 具体选用哪个时钟由备份域控制寄存器BDCR的位9-8:RTCSEL[1:0]配置。独立看门狗的时钟由LSI提供, 且只能是由LSI提供,LSI是低速的内部时钟信号,频率为30~60KHZ直接不等,一般取40KHZ。

2.2.5 MCO时钟输出

        MCO是microcontroller clock output的缩写,是微控制器时钟输出引脚,在W55MH32系列中 由 PA8复用所得, 主要作用是可以对外提供时钟,相当于一个有源晶振。MCO的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK, 具体选哪个由时钟配置寄存器CFGR的位26-24:MCO[2:0]决定。除了对外提供时钟这个作用之外, 我们还可以通过示波器监控MCO引脚的时钟输出来验证我们的系统时钟配置是否正确。

3 时钟配置

3.1 RCC_HSIConfig

1. 头文件包含与全局定义

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "delay.h"
#include "w55mh32.h"
#include "wiz.h"

USART_TypeDef *USART_TEST = USART1;

头文件:包含标准库和硬件相关头文件(w55mh32.h)、延时函数(delay.h)、自定义硬件抽象层(wiz.h)。

全局变量:定义串口外设指针 USART_TEST 指向 USART1。

2. 函数声明

void UART_Configuration(uint32_t bound);
void RCC_ClkConfiguration(void);

UART_Configuration:配置串口通信。

RCC_ClkConfiguration:配置系统时钟(RCC,复位与时钟控制)。

3. main ()函数:系统初始化与主逻辑

int main(void)
{
    RCC_ClocksTypeDef clocks;

    RCC_ClkConfiguration();       // 配置系统时钟
    delay_init();                 // 初始化延时函数
    UART_Configuration(115200);   // 配置串口(波特率 115200)

    printf("RCC HSI Config Test.\n");
    RCC_GetClocksFreq(&clocks);  // 获取系统时钟频率

    // 打印各时钟频率
    printf("\nSYSCLK: %3.1fMhz, \nHCLK: %3.1fMhz, \nPCLK1: %3.1fMhz, \nPCLK2: %3.1fMhz, \nADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);

    while (1) {}  // 主循环(空,等待事件)
}

功能:

  • 初始化系统时钟(HSI + PLL)。
  • 初始化串口,通过 printf 输出时钟频率信息。
  • 进入无限循环,程序无实际业务逻辑,仅验证时钟和串口配置。

4. RCC_ClkConfiguration ()函数:系统时钟配置

void RCC_ClkConfiguration(void)
{
    RCC_DeInit();                   // 复位 RCC 到默认状态

    // 启用 HSI(高速内部时钟,默认 16MHz)
    RCC_HSICmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);  // 等待 HSI 就绪

    // 配置 PLL(PLL 输入 = HSI/2,PLL 倍频 = 32)
    RCC_PLLCmd(DISABLE);            // 先禁用 PLL
    WIZ_RCC_PLLConfig(
        RCC_PLLSource_HSI_Div2,     // PLL 时钟源:HSI/2(8MHz)
        RCC_PLLMul_32,              // PLL 倍频:32(8MHz × 32 = 256MHz)
        1                           // 保留参数(未使用)
    );

    RCC_PLLCmd(ENABLE);             // 启用 PLL
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);  // 等待 PLL 就绪

    // 设置系统时钟源为 PLL(256MHz)
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    // 配置总线时钟分频
    RCC_HCLKConfig(RCC_SYSCLK_Div1);   // HCLK = SYSCLK(256MHz)
    RCC_PCLK1Config(RCC_HCLK_Div2);    // PCLK1 = HCLK/2(128MHz)
    RCC_PCLK2Config(RCC_HCLK_Div1);    // PCLK2 = HCLK(256MHz)

    // 启用 LSI(低速内部时钟,默认 40kHz)
    RCC_LSICmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);

    // 重复启用 HSI(冗余代码,HSI 已启用)
    RCC_HSICmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
}

时钟配置流程:

  1. 复位 RCC:重置所有时钟配置。
  2. 启用 HSI:使用内部 16MHz 时钟。
  3. 配置 PLL:输入:HSI/2 = 8MHz。
  4. 输出:8MHz × 32 = 256MHz(系统时钟 SYSCLK)。
  5. 选择 PLL 作为系统时钟源。
  6. 总线分频:
    1. HCLK(AHB 总线):256MHz(不分频)。
    2. PCLK1(APB1 总线):128MHz(HCLK/2)。
    3. PCLK2(APB2 总线):256MHz(不分频)。
  7. 启用 LSI:用于独立看门狗或 RTC(未在代码中使用)。
  8. 冗余操作:重复启用 HSI(无实际意义,HSI 已就绪)。

注意:

  • PLL 输出频率需符合芯片规格(如 W55MH32 的最大 SYSCLK 为 256MHz)。
  • WIZ_RCC_PLLConfig ()是自定义函数(来自 wiz.h),用于扩展 PLL 配置。

5. UART_Configuration ()函数:串口初始化

void UART_Configuration(uint32_t bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 使能串口和 GPIO 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 配置 TX 引脚(PA9):复用推挽输出
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置 RX 引脚(PA10):浮空输入
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 串口参数配置
    USART_InitStructure.USART_BaudRate            = bound;       // 波特率
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;  // 8 位数据
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;    // 1 位停止位
    USART_InitStructure.USART_Parity              = USART_Parity_No;     // 无校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  // 无流控
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;  // 收发模式

    USART_Init(USART_TEST, &USART_InitStructure);  // 初始化串口
    USART_Cmd(USART_TEST, ENABLE);                // 使能串口
}

功能:

  • 使能 USART1 和 GPIOA 时钟。
  • 配置 PA9(TX)为复用推挽输出,PA10(RX)为浮空输入。
  • 初始化串口参数(波特率、数据位、停止位等)。
  • 使能串口。

6. 串口输出函数

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));  // 等待发送完成
    USART_SendData(USART_TEST, (uint8_t)ch);                  // 发送字符
    return ch;
}

int fputc(int c, FILE *f)
{
    if (c == '\n') SER_PutChar('\r');  // 换行符添加回车(适配终端)
    return SER_PutChar(c);             // 重定向 printf 到串口
}

核心功能:

  • 系统时钟配置:
    • 使用 HSI(16MHz)作为 PLL 输入,PLL 倍频 32 倍,生成 256MHz 系统时钟。
    • 总线分频:HCLK=256MHz,PCLK1=128MHz,PCLK2=256MHz。
  • 串口通信:配置 USART1 为 115200 波特率,用于 printf 输出。
  • 测试验证:打印系统各时钟频率,验证时钟配置正确性。

3.1.1 实验现象

把编译好的程序下载到开发板,可以看到设置不同的系统时钟:

3.2 RCC_HighFrequencyConfig

程序运行:

1. 头文件与全局变量

        引入了标准库的头文件以及自定义的delay.h、w55mh32.h、wiz.h头文件,为程序提供所需的函数和数据类型。

定义全局变量USART_TEST并将其指向USART1,用于后续的串口通信操作。

2. 函数声明

        对UART_Configuration()和RCC_ClkConfiguration()函数进行声明,前者用于配置串口,后者用于配置系统时钟。

主函数main():

int main(void)
{
    RCC_ClocksTypeDef clocks;

    RCC_ClkConfiguration();
    delay_init();
    UART_Configuration(115200);

    printf("RCC Clock Config Test.\n");
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, \nHCLK: %3.1fMhz, \nPCLK1: %3.1fMhz, \nPCLK2: %3.1fMhz, \nADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);

    while (1)
    {
    }
}

        初始化完成之后,使用printf()函数打印输出系统时钟、AHB 总线时钟、APB1 总线时钟、APB2 总线时钟和 ADC 时钟的频率信息,随后进入无限循环,使程序持续运行。

4. RCC_ClkConfiguration()函数的主要流程:

  1. 复位 RCC 寄存器,为后续的时钟配置做准备。
  2. 使能外部高速时钟(HSE),并等待其稳定。
  3. 禁用 PLL,重新配置 PLL 的时钟源和倍频系数。
  4. 使能 PLL,并等待其稳定。
  5. 把系统时钟源设置为 PLL 输出。
  6. 配置 AHB、APB1 和 APB2 总线的时钟分频系数。
  7. 使能内部低速时钟(LSI)和内部高速时钟(HSI),并等待它们稳定。

5. UART_Configuration()函数

  1. 使能 USART1 和 GPIOA 的时钟。
  2. 配置 USART1 的发送引脚(PA9)为复用推挽输出模式,接收引脚(PA10)为浮空输入模式。
  3. 配置 USART1 的波特率、数据位、停止位、奇偶校验位等参数。使能 USART1。

6. SER_PutChar()函数:此函数用于向串口发送一个字符,会等待发送完成后再发送下一个字符。

7. fputc()函数:重定向标准输出函数,把字符发送到串口。若遇到换行符\n,会先发送回车符\r。

3.2.1 实验现象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值