RCC - 使用 HSE/HSI 配置时钟
更新于 2026年8月13日
本篇总结
使用 RCC,分别通过 HSE 和 HSI 配置系统时钟。
本示例演示 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_OUT | HSE 8MHz 晶振 | — | 外部晶振输入 |
| PA9 | USART1_TX | 开发板 → PC | 复用推挽输出 (AF_PP) |
| PA10 | USART1_RX | PC → 开发板 | 浮空输入 (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.uvprojx,F7 编译,确认 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=SYSCLK、PCLK1=HCLK/2、PCLK2=HCLK,APB1 受 108M 上限约束
- 用
RCC_GetClocksFreq()回读实际频率并 printf 打印,是验证时钟配置最直接的方法 - 选型建议:USB/CAN 等精度敏感场景必用 HSE;对成本敏感、无 USB 的应用可用 HSI 省 BOM