PM Clock
PM Clock 驱动 | 无 sample
学习目标
- 理解 PM Clock 两大核心功能——时钟门控(Clock Gating)和 CRG 时钟源/分频配置
- 掌握
uapi_clock_clken_config开关外设时钟以节省功耗 - 掌握
uapi_clock_crg_config配置时钟源和分频系数 - 理解时钟分频器、选择器、CRG 三种配置路径的区别
基本概念
时钟树架构
WS63 的时钟系统分层管理:时钟源(PLL/XTAL/RC)→ CRG(时钟源选择 + 分频)→ 时钟门控(开/关)→ 外设模块。
flowchart TB
PLL[PLL 锁相环] -->|高频| CRG
XTAL[XTAL 晶振] -->|基准| CRG
RC[RC 振荡器] -->|低频| CRG
CRG[CRG 时钟控制] -->|源选择 + 分频| DIV[分频器]
DIV -->|具体频率| CLKEN[时钟门控]
CLKEN -->|clk_en = true| UART[UART 模块]
CLKEN -->|clk_en = false| OFF[关闭 省电]
DDS[时钟选择器] -->|clk_sel| DIV2[独立分频]
DIV2 -->|独立频率| SPI[SPI 模块]
三项配置路径
| 路径 | API | 作用 | 粒度 |
|---|---|---|---|
| 时钟门控 | uapi_clock_clken_config |
开/关外设时钟 | 模块级 |
| CRG 配置 | uapi_clock_crg_config |
时钟源选择 + 分频 | 模块级 |
| 选择器+分频器 | uapi_clock_clksel_config + uapi_clock_clkdiv_config |
独立配置选择和分频 | 信号级 |
时钟门控——省电的核心
CPU 空闲时可休眠,但外设模块只要有时钟就会消耗动态功耗。Clock Gating 思路简单:不用某外设时关其时钟。例如:
- UART 不在通信时 → 关 UART 时钟
- SPI 传输完成后 → 关 SPI 时钟
- GPIO 模块永远需要 → 始终开启
CRG 频率计算
例如:PLL 输出 480MHz,clk_div = 4 → SPI 时钟 = 120MHz。通过 uapi_clock_crg_get_freq 查询当前实际频率。
涉及 API
| API | 用途 | 头文件 |
|---|---|---|
uapi_clock_clken_config(clock_clken_id_t id, bool clk_en) |
外设时钟门控开关 | pm_clock.h |
uapi_clock_crg_config(clock_crg_id_t id, clock_clk_src_t clk_src, uint8_t clk_div) |
配置 CRG 时钟源和分频(需 CONFIG_PM_SUPPORT_CLOCK_CRG_API) |
pm_clock.h |
uapi_clock_crg_get_freq(clock_crg_id_t id) |
获取 CRG 模块当前频率 | pm_clock.h |
uapi_clock_clksel_config(clock_clksel_id_t id, uint8_t clk_sel) |
配置时钟选择器 | pm_clock.h |
uapi_clock_clkdiv_config(clock_clkdiv_id_t id, uint8_t clk_div) |
配置时钟分频系数 | pm_clock.h |
uapi_clock_get_clkdiv(clock_clkdiv_id_t id) |
查询分频系数 | pm_clock.h |
uapi_clock_control(clock_control_type_t type, uint8_t param) |
通用时钟控制接口 | pm_clock.h |
clock_clken_id_t和clock_crg_id_t均为芯片 porting 层定义的枚举——具体 ID 值参考pm_clock_porting.h。
案例说明
做什么
- 关闭空闲 UART 模块的时钟以节省功耗
- 配置 SPI 模块 CRG:时钟源 PLL,分频系数 4
- 查询并打印 SPI 实际工作频率
规格与功能
| 规格项 | 说明 |
|---|---|
| UART 处理 | 关闭 CLOCK_CLKEN_UART2 时钟 |
| SPI CRG | 源 CLOCK_CLK_SRC_PLL,分频 /4 |
| 验证方法 | crg_get_freq 查询频率并打印 |
程序运行流程:clken_config 关 UART2 → crg_config 设 SPI PLL/4 → crg_get_freq 查 SPI 频率 → 打印。
案例流程
sequenceDiagram
participant A as 应用
participant C as 时钟控制器
participant U as UART2模块
participant S as SPI0模块
A->>C: clken_config UART2=false
C->>U: 关闭时钟
Note over U: UART2 停止功耗
A->>C: crg_config SPI0 src=PLL div=4
C->>S: 源PLL 分频4
Note over S: SPI0 频率 = PLL/4
A->>C: crg_get_freq SPI0
C->>A: freq = 120000000
A->>A: print SPI clock=120MHz
案例操作指导
第一步:编译
第二步:烧录和验证
烧录后串口输出 SPI 配置前后的频率变化。可用功耗仪对比:UART2 时钟开启和关闭时的电流差异。
第三步:预期输出
PM Clock: disable UART2 clock
PM Clock: config SPI0 CRG: src=PLL, div=4
PM Clock: SPI0 freq = 120000000 Hz
PM Clock: verify: div=4
关键配置
| 参数 | 值 | 说明 |
|---|---|---|
| 时钟源选择 | PLL / XTAL / RC | PLL 高频高性能,XTAL 稳定,RC 低功耗 |
| 分频系数 | 1~255 | 过大 → 频率过低外设不工作;过小 → 超频 |
| 门控开关 | true/false | 关错时钟导致外设不响应——确认模块空闲再关 |
| CRG 频率查询 | 返回 0 表示失败 | 先确认 CRG 配置成功再查询 |
时钟源选择
| 时钟源 | 频率 | 精度 | 功耗 | 适用 |
|---|---|---|---|---|
| PLL | 数百 MHz | 中等(锁相环锁定时间) | 高 | 高速外设(SPI/SDIO) |
| XTAL | 稳定(如 40MHz) | 高(晶振精度) | 中 | 低抖动需求(UART 精确波特率) |
| RC | 数 MHz | 低(温漂) | 极低 | 休眠时维持计时 |
功耗优化策略
| 策略 | 实现 | 省电效果 |
|---|---|---|
| 关空闲模块时钟 | clken_config(id, false) |
显著 |
| 降低非活跃模块频率 | crg_config 增大分频 |
中等 |
| 休眠时切 RC 源 | crg_config 切换源 |
显著 |
| 动态频率调节 | 任务负载低时降频 | 中等 |
代码详解
概念性代码,基于 SDK 头文件
pm_clock.h中的 API 签名。
#include "pm_clock.h"
#include "errcode.h"
#include "osal_printk.h"
#include "app_init.h"
static void pm_clock_entry(void)
{
errcode_t ret;
uint32_t freq;
uint8_t div;
/* ---- 1. 关闭空闲 UART 时钟——省电 ---- */
uapi_clock_clken_config(CLOCK_CLKEN_UART2, false);
osal_printk("PM Clock: disable UART2 clock\n");
/* ---- 2. 配置 SPI0 CRG:PLL 源,4 分频 ---- */
ret = uapi_clock_crg_config(CLOCK_CRG_SPI0,
CLOCK_CLK_SRC_PLL,
4);
if (ret != ERRCODE_SUCC) {
osal_printk("PM Clock: SPI0 CRG config failed: %d\n", ret);
return;
}
osal_printk("PM Clock: config SPI0 CRG: src=PLL, div=4\n");
/* ---- 3. 查询 SPI0 实际频率 ---- */
freq = uapi_clock_crg_get_freq(CLOCK_CRG_SPI0);
if (freq > 0) {
osal_printk("PM Clock: SPI0 freq = %u Hz\n", freq);
} else {
osal_printk("PM Clock: SPI0 freq query failed\n");
}
/* ---- 4. 验证分频系数 ---- */
div = uapi_clock_get_clkdiv(CLOCK_CLKDIV_SPI0);
osal_printk("PM Clock: verify: div=%u\n", div);
/* ---- 5. SPI 使用完毕后关闭时钟 ---- */
uapi_clock_clken_config(CLOCK_CLKEN_SPI0, false);
osal_printk("PM Clock: disable SPI0 clock (idle)\n");
}
app_run(pm_clock_entry);
时钟门控和 CRG 配置互不影响——可先配 CRG 决定频率,再通过门控开关控制启停。关外设时钟前需确认该外设没有正在进行的传输——否则数据丢失。CRG 配置受
CONFIG_PM_SUPPORT_CLOCK_CRG_API宏控制——若不启用,仅门控和选择器/分频器路径可用。