全部例程

RCC - 使用 HSE/HSI 配置时钟

W55MH32

更新于 2026年8月13日

本篇总结

使用 RCC,分别通过 HSE 和 HSI 配置系统时钟。

🔑 KEIL MDK、UART调试、RCC

本示例演示 W55MH32L 的 RCC(复位与时钟控制)外设,分别使用 HSE(高速外部时钟,8MHz 无源晶振)和 HSI(高速内部 RC 时钟,16MHz)作为 PLL 输入源配置系统时钟 SYSCLK。程序通过 USART1 把各总线时钟频率(SYSCLK / HCLK / PCLK1 / PCLK2 / ADCCLK)打印出来,方便验证配置是否生效。

RCC简介

RCC(Reset and Clock Control)是 W55MH32 的"心脏",负责给整个芯片分发时钟。主要干三件事:

  • 选择系统时钟源:HSI(内部 RC 16M)、HSE(外部晶振 4–16M)、PLL(倍频后输出)三选一作为 SYSCLK。
  • 配置总线分频:SYSCLK 经 AHB 预分频得到 HCLK,HCLK 再经 APB1/APB2 预分频得到 PCLK1/PCLK2,决定各外设最高工作频率。
  • 控制外设时钟开关:每条总线、每个外设都有独立的时钟使能位,按需开启以降低功耗。

本例程常用配置:HSE 8M×9=72M(经典稳定方案),或 HSI 16M/2×32=256M(超频运行,官方标称最高 216MHz)。HCLK=SYSCLK,PCLK1=HCLK/2(APB1 最高 108M),PCLK2=HCLK。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口日志观察工具:点击下载

硬件连接

W55MH32L-EVB 板载 8MHz 无源晶振接到 OSC_IN/OSC_OUT 引脚作为 HSE 时钟源,USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC 即可。

引脚功能方向模式
OSC_IN/OSC_OUTHSE 8MHz 晶振外部晶振输入
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\RCC\,核心文件如下:

RCC/
├── USER/
│   ├── main.c            ← RCC 时钟配置 + USART 初始化 + 时钟频率打印
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(HSE 默认时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:HSE 时钟配置(外部晶振 72MHz)

文件:USER/system_w55mh32.c | 函数:SetSysClockTo72() — 直接操作 RCC 寄存器,将 HSE 8MHz 经 PLL 9 倍频得到 72MHz SYSCLK。该函数在启动文件 SystemInit() 中被调用,是系统默认时钟配置。


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:48M < SYSCLK <= 72M */
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;

        /* ② 设置 AHB、APB2、APB1 预分频因子 */
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;   /* HCLK  = SYSCLK     */
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;  /* PCLK2 = HCLK       */
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;  /* PCLK1 = HCLK/2     */

        /* ③ 设置 PLL 时钟来源 HSE,倍频因子 9,PLLCLK = 8M * 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 启动失败,用户可在此添加错误处理代码 */
    }
}

七步流程:使能 HSE → 等待稳定 → 配置 Flash 延时(72M 必须设 LATENCY_2)→ AHB/APB 分频 → PLL 倍频(HSE×9)→ 使能 PLL → 切换 SYSCLK 源。

如果你想自定义

  • 改 PLL 倍频 → 修改 RCC_CFGR_PLLMULL9,可选 2–16 倍。例如 PLLMULL16 得 8M×16=128M(超频,需自评稳定性)
  • HSE 启动失败的处理 → 在 else 分支可点亮 LED 报错或回退到 HSI 配置,避免程序"裸奔"在未知时钟下

步骤2:HSI 时钟配置(内部 RC 256MHz)

文件:USER/main.c | 函数:RCC_ClkConfiguration() — 用标准库函数把 HSI 16MHz 二分频后送入 PLL,32 倍频得到 256MHz SYSCLK,超越官方标称超频至 216MHz。


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

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

    /* (2) 配置 PLL:输入 = HSI/2 = 8MHz,倍频 = 32,输出 256MHz */
    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);

    /* (3) 选择 PLL 作为系统时钟源(256MHz) */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    /* (4) 配置总线时钟分频 */
    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) */

    /* (5) 启用 LSI(低速内部时钟,约 40kHz,供独立看门狗/RTC 使用) */
    RCC_LSICmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
}

与 HSE 流程对应:复位 RCC → 使能 HSI → 配 PLL(HSI/2×32=256M)→ 切 SYSCLK 源 → 配总线分频。注意 WIZ_RCC_PLLConfig() 是 W55MH32 扩展的库函数(位于 wiz.h),相比标准库多支持更高倍频因子。

HSI vs HSE 选型建议

  • HSI(内部 RC):无需外部晶振,省 BOM;但精度 ±1%,受温度影响大,不适合 USB/CAN 等对时钟精度敏感的应用
  • HSE(外部晶振):精度高(±20ppm),稳定性好;USB、CAN 通信必须用 HSE

步骤3:USART 配置(用于打印时钟频率)

函数:UART_Configuration() — 配置 USART1 为 115200 / 8N1 全双工,PA9(TX) 复用推挽,PA10(RX) 浮空输入,用于把时钟频率打印到 PC 串口工具。


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

    /* 使能 USART1 和 GPIOA 时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* PA9: USART1_TX → 复用推挽输出 */
    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);

    /* PA10: USART1_RX → 浮空输入 */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* USART 参数:115200 / 8N1 / 全双工 */
    USART_InitStructure.USART_BaudRate            = bound;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_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);
}

步骤4:主函数(时钟频率输出)

函数:main() — 调用 RCC_ClkConfiguration() 切到 HSI 256MHz → 初始化延时和串口 → 用 RCC_GetClocksFreq() 取各总线频率并 printf 打印。


int main(void)
{
    RCC_ClocksTypeDef clocks;

    RCC_ClkConfiguration();       /* 配置系统时钟(HSI + PLL → 256MHz) */
    delay_init();                 /* 初始化延时函数(基于 SysTick) */
    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) {
    }
}

关键 API:RCC_GetClocksFreq(&clocks) 把当前 SYSCLK/HCLK/PCLK1/PCLK2/ADCCLK 实际频率回填到结构体,是验证时钟配置是否生效的最直接方法。若打印值与预期一致,说明 PLL 倍频与总线分频配置正确。

如果你想自定义

  • 切回 HSE 72M 配置 → 删除 RCC_ClkConfiguration() 调用即可,启动阶段 SystemInit() 已用 HSE 配好 72M
  • 对比两种配置 → 注释/取消注释 RCC_ClkConfiguration(),重新编译下载,观察串口打印的 SYSCLK 在 72M 和 256M 之间切换

步骤5:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。


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 到串口 */
}

编译与下载

Keil MDK 中打开 RCC.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。下载成功后按 RESET 键启动新固件。

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口输出(HSI 256M 配置):

RCC HSI Config Test.

SYSCLK: 256.0Mhz,
HCLK: 256.0Mhz,
PCLK1: 128.0Mhz,
PCLK2: 256.0Mhz,
ADCCLK: 128.0Mhz

若注释掉 RCC_ClkConfiguration() 改用默认 HSE 配置,则预期输出:

RCC HSI Config Test.

SYSCLK: 72.0Mhz,
HCLK: 72.0Mhz,
PCLK1: 36.0Mhz,
PCLK2: 72.0Mhz,
ADCCLK: 36.0Mhz

打印值与预期一致即说明时钟配置生效。W55MH32L 的官方标称最大 SYSCLK 为 216MHz,APB1 最大 108MHz。本例程通过扩展 PLL 倍频达到 256MHz,此时 PCLK1=128M 超过 APB1 最大值,量产场景建议将 SYSCLK 降到 216MHz 以内以确保稳定性。

常见问题

Q: 串口打印 SYSCLK 是 16Mhz 而不是 256Mhz

说明 PLL 没启动或没被选为系统时钟源。检查 RCC_ClkConfiguration() 是否被调用,RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK) 后是否等待 RCC_FLAG_PLLRDY

Q: 编译报错 "WIZ_RCC_PLLConfig undefined"

该函数声明在 wiz.h。检查 main.c 是否 #include "wiz.h",并确认工程包含 SDK 的 Libraries 目录。

Q: HSE 配置下程序卡死,串口无输出

多半是 HSE 启动失败(晶振虚焊、谐振电容错值、晶振损坏)。SetSysClockTo72() 在 HSE 失败时进入 else 空分支,程序仍以 HSI 16M 运行但 Flash 延时未配,取指可能出错。可在 else 分支点亮 LED 报错或回退 HSI 配置。

Q: 串口输出乱码或完全无输出

先确认 SYSCLK 打印值是否正常。若 SYSCLK 异常(如 16M 而非 256M),delay_init() 与波特率会跟着错,导致串口乱码。务必先保证时钟正确,再调串口。

Q: HSI 配置下 USB 或 CAN 通信不稳定

HSI 是内部 RC 振荡器,精度 ±1% 且随温度漂移,USB/CAN 要求时钟精度优于 ±0.25%。这类应用必须改用 HSE 外部晶振作为 PLL 输入源。

总结

本文基于 W55MH32L 的 RCC 外设演示了两种系统时钟配置方式:

  • HSE 配置SetSysClockTo72()):外部 8MHz 晶振 × 9 倍频 = 72MHz,寄存器级实现,作为系统默认时钟在启动阶段执行
  • HSI 配置RCC_ClkConfiguration()):内部 16MHz RC / 2 × 32 倍频 = 256MHz,标准库函数实现,用户代码运行时再切换
  • 总线分频统一为 HCLK=SYSCLKPCLK1=HCLK/2PCLK2=HCLK,APB1 受 108M 上限约束
  • RCC_GetClocksFreq() 回读实际频率并 printf 打印,是验证时钟配置最直接的方法
  • 选型建议:USB/CAN 等精度敏感场景必用 HSE;对成本敏感、无 USB 的应用可用 HSI 省 BOM

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家