31 KiB
31 KiB
WS63 PWM 技术使用文档
文档概述
本文档基于WS63平台的PWM驱动实现,为不同经验的开发人员提供PWM(脉宽调制)功能的配置与控制指导。
适用人群:
- 🌱 新手开发者:只熟悉0-255亮度控制,希望快速上手PWM
- 🛠️ 进阶开发者:需要深入了解PWM参数配置和高级功能
- 🔍 系统调试者:需要问题诊断和性能优化
平台基础参数:
- MCU平台:WS63 (HiSilicon)
- 系统时钟:80 MHz
- 计数器位宽:32-bit(u32)
- PWM版本:CONFIG_PWM_USING_V151
- 支持特性:多通道、PWM组、相位控制、运行时更新
文档导航
🌱 新手必读(建议阅读顺序)
- 章节1 - 快速入门: 5分钟了解PWM和快速上手
- 章节5 - 参数详解: 从0-255到PWM参数的转换和理解
- 章节11 - 错误处理: 常见问题解决
🛠️ 进阶内容(可选阅读)
- 章节2-4: PWM系统架构、API和初始化流程
- 章节6-10: 高级功能(频率配置、相位控制、PWM组等)
- 章节12: 完整流程图和技术参考
1. 快速入门(新手必读)
1.1 PWM是什么?
PWM就像一个非常快的开关,通过控制"开"和"关"的时间比例来调节LED的亮度、电机的速度等。
简单理解:
- 如果一直开着 = 100%亮度
- 如果一直关着 = 0%亮度
- 如果一半时间开、一半时间关 = 50%亮度
1.2 你之前的使用方式 vs 现在的PWM方式
你之前的方式:
// 简单直观,但是功能有限
set_led_brightness(128); // 0-255范围
现在的PWM方式:
// 功能强大,但是参数复杂
pwm_config_t cfg = {
.high_time = 40000, // 高电平时间(计数)
.low_time = 40000, // 低电平时间(计数)
.offset_time = 0, // 相位偏移
.repeat = true, // 是否重复
.cycles = 0 // 循环次数
};
**不用担心!**本文档提供了简化函数,让你像以前一样使用0-255的数值。
1.3 5分钟快速上手
// 第1步:初始化PWM系统
uapi_pwm_init();
// 第2步:使用简化函数设置亮度(就像以前一样)
set_pwm_brightness_simple(0, 128); // 通道0,50%亮度
// 第3步:启动PWM输出
uapi_pwm_start(0);
// 完成!LED就会以50%亮度点亮
简化函数(复制到你的代码里):
// 这个函数让PWM像以前一样简单
errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255)
{
uint32_t frequency = 1000; // 固定1000Hz,适合LED调光
float duty_percent = (brightness_0_255 / 255.0f) * 100.0f;
uint32_t period_cnt = 80000000 / frequency; // 80000
uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.offset_time = 0,
.repeat = true,
.cycles = 0
};
return uapi_pwm_open(channel, &cfg);
}
2. PWM系统架构(进阶内容)
2.1 驱动层次结构
graph TB
A[应用层] --> B[PWM驱动接口 pwm.h]
B --> C[HAL层 hal_pwm_v151.h]
C --> D[寄存器操作层 hal_pwm_v151_regs_op.h]
D --> E[硬件寄存器层]
A --> F[GPIO配置]
F --> G[PWM输出引脚]
A --> I[PWM组控制]
I --> J[多通道同步控制]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
2.2 PWM工作原理
graph LR
A[频率配置] --> B[周期计算]
B --> C[占空比设置]
C --> D[high_time/low_time计算]
D --> E[PWM波形输出]
F[相位控制] --> E
G[PWM组同步] --> E
style B fill:#e3f2fd
style D fill:#f1f8e9
style E fill:#fff3e0
3. PWM核心数据结构与API(进阶内容)
3.1 PWM配置结构体
// 源码位置:include/driver/pwm.h
typedef struct pwm_config {
uint32_t low_time; // 低电平持续的计数数
uint32_t high_time; // 高电平持续的计数数
uint32_t offset_time; // PWM相位偏移时间
uint16_t cycles; // PWM重复周期,范围:0~32767
bool repeat; // 连续输出标志
} pwm_config_t;
3.2 关键API函数
// PWM系统初始化和控制
errcode_t uapi_pwm_init(void); // 初始化PWM系统
errcode_t uapi_pwm_open(uint8_t channel, const pwm_config_t *cfg); // 打开PWM通道
errcode_t uapi_pwm_start(uint8_t channel); // 启动单个PWM通道
errcode_t uapi_pwm_close(uint8_t channel); // 关闭PWM通道
uint32_t uapi_pwm_get_frequency(uint8_t channel); // 获取PWM工作频率
// PWM组控制
errcode_t uapi_pwm_set_group(uint8_t group, const uint8_t *channel_set, uint32_t channel_set_len);
errcode_t uapi_pwm_start_group(uint8_t group); // 同步启动PWM组
errcode_t uapi_pwm_stop_group(uint8_t group); // 同步停止PWM组
errcode_t uapi_pwm_clear_group(uint8_t group); // 清理PWM组
// 运行时占空比更新
errcode_t uapi_pwm_update_duty_ratio(uint8_t channel, uint32_t low_time, uint32_t high_time);
// 中断处理
errcode_t uapi_pwm_register_interrupt(uint8_t channel, pwm_callback_t callback);
errcode_t uapi_pwm_unregister_interrupt(uint8_t channel);
4. PWM初始化流程(进阶内容)
4.1 初始化时序图
sequenceDiagram
participant App as 应用层
participant PWM as PWM驱动
participant HAL as HAL层
participant GPIO as GPIO系统
App->>PWM: uapi_pwm_init()
PWM->>HAL: hal_pwm_v151初始化
HAL-->>PWM: 返回success
App->>PWM: uapi_pwm_get_frequency(channel)
PWM-->>App: 返回基础频率(80MHz)
App->>App: 计算周期计数值
App->>GPIO: 配置GPIO引脚为PWM功能
GPIO-->>App: GPIO配置完成
App->>PWM: uapi_pwm_open(channel, config)
PWM-->>App: PWM通道配置完成
App->>PWM: uapi_pwm_start(channel)
PWM-->>App: PWM开始输出
Note over App: PWM波形输出
4.2 初始化代码实现
// PWM初始化示例
void pwm_init_example(uint8_t gpio_pin, uint32_t target_frequency)
{
pwm_config_t cfg = {0};
// 1. 初始化PWM驱动
errcode_t ret = uapi_pwm_init();
if (ret != ERRCODE_SUCC) {
printf("PWM初始化失败: %d\n", ret);
return;
}
// 2. 获取PWM基础频率 (通常为80MHz)
uint8_t channel = gpio_pin % 8; // GPIO转PWM通道
uint32_t base_frequency = uapi_pwm_get_frequency(channel);
// 3. 计算PWM周期计数值
uint32_t period_cnt = base_frequency / target_frequency;
// 4. 配置PWM参数(初始50%占空比)
cfg.high_time = period_cnt / 2;
cfg.low_time = period_cnt - cfg.high_time;
cfg.offset_time = 0; // 无相位偏移
cfg.repeat = true; // 连续输出
cfg.cycles = 0; // 无限循环
// 5. 打开PWM通道
ret = uapi_pwm_open(channel, &cfg);
if (ret != ERRCODE_SUCC) {
printf("PWM通道打开失败: %d\n", ret);
return;
}
// 6. 启动PWM输出
ret = uapi_pwm_start(channel);
if (ret != ERRCODE_SUCC) {
printf("PWM启动失败: %d\n", ret);
return;
}
printf("PWM初始化成功: 频率=%dHz, 周期=%d计数\n",
target_frequency, period_cnt);
}
5. PWM参数详解(小白入门篇)
5.1 什么是PWM?
PWM就像开关灯一样,快速地开关来控制亮度。想象你手里有个开关,如果一直开着,灯就是100%亮;如果一直关着,灯就是0%亮。但如果你快速地开开关关,开的时间长一点,灯就亮一点;关的时间长一点,灯就暗一点。
关键概念:
- 频率:一秒钟开关多少次(就像你手开关灯的频率)
- 占空比:一个开关周期中,"开"的时间占多少比例
- 周期:完成一次"开-关"动作需要的时间
5.2 从0-255到实际参数的转换
你熟悉的0-255其实就是占空比的另一种表示方法:
// 你熟悉的方式:0-255的数值
uint8_t brightness = 128; // 相当于50%亮度
// 转换成PWM需要的参数
float duty_percent = (brightness / 255.0f) * 100.0f; // 转换成百分比
// 例子:
// brightness = 0 → duty_percent = 0% (完全不亮)
// brightness = 64 → duty_percent = 25% (1/4亮度)
// brightness = 128 → duty_percent = 50% (一半亮度)
// brightness = 192 → duty_percent = 75% (3/4亮度)
// brightness = 255 → duty_percent = 100% (最亮)
5.3 参数计算原理(用通俗的话解释)
// 第1步:确定系统时钟(这是硬件固定的,不用改)
uint32_t system_clock = 80000000; // 80MHz = 每秒8000万次计数
// 第2步:设定PWM频率(就是每秒开关多少次)
uint32_t pwm_frequency = 1000; // 1000Hz = 每秒开关1000次
// 第3步:计算一个开关周期需要多少个时钟计数
// 打个比方:如果系统每秒数8000万下,你要让它每秒开关1000次
// 那么每次开关就需要:8000万 ÷ 1000 = 80000个计数
uint32_t period_count = system_clock / pwm_frequency;
// 第4步:根据亮度(占空比)计算开和关的时间
// 如果要60%亮度,那么80000个计数中,48000个用来"开",32000个用来"关"
float brightness_percent = 60.0; // 60%亮度
uint32_t on_time = (uint32_t)(period_count * brightness_percent / 100.0);
uint32_t off_time = period_count - on_time;
5.4 简单的参数设置函数(推荐使用)
// 简化版本:像以前一样用0-255设置亮度
errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255)
{
// 固定使用1000Hz频率(适合大多数LED调光应用)
uint32_t frequency = 1000;
// 将0-255转换为百分比
float duty_percent = (brightness_0_255 / 255.0f) * 100.0f;
// 计算周期计数(80MHz ÷ 1000Hz = 80000)
uint32_t period_cnt = 80000000 / frequency;
// 计算高低电平时间
uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
// 配置PWM
pwm_config_t cfg = {
.high_time = high_time, // "开"的时间(计数值)
.low_time = low_time, // "关"的时间(计数值)
.offset_time = 0, // 不需要相位偏移
.repeat = true, // 持续输出
.cycles = 0 // 无限循环
};
return uapi_pwm_open(channel, &cfg);
}
// 使用示例:
// set_pwm_brightness_simple(0, 0); // 通道0,0%亮度(关闭)
// set_pwm_brightness_simple(0, 128); // 通道0,50%亮度
// set_pwm_brightness_simple(0, 255); // 通道0,100%亮度(最亮)
5.5 高级参数详解(可选阅读)
如果你想深入了解PWM的各个参数,这里详细解释每个参数的含义:
**pwm_config_t结构体中的参数:**
1. **high_time(高电平时间)**:
- 就是"开关"中"开"的时间,用时钟计数表示
- 例如:如果high_time = 40000,系统时钟80MHz,那么"开"的时间 = 40000 ÷ 80000000 = 0.5ms
2. **low_time(低电平时间)**:
- 就是"开关"中"关"的时间,用时钟计数表示
- 例如:如果low_time = 40000,那么"关"的时间也是0.5ms
- high_time + low_time = 一个完整周期的时间
3. **offset_time(相位偏移)**:
- 用来控制多个PWM通道之间的时间差
- 大多数情况下设为0即可
- 高级应用:比如多色LED,可以让红绿蓝三色错开时间启动
4. **cycles(重复次数)**:
- 设为0表示无限重复(常用)
- 设为具体数字表示输出多少个周期后停止
5. **repeat(连续输出标志)**:
- true:持续输出PWM波形(常用)
- false:只输出指定次数后停止
```c
// 标准的占空比设置函数
errcode_t set_pwm_duty_cycle(uint8_t channel, float duty_cycle_percent)
{
// 参数检查
if (duty_cycle_percent < 0.0f) duty_cycle_percent = 0.0f;
if (duty_cycle_percent > 100.0f) duty_cycle_percent = 100.0f;
// 使用固定的1000Hz频率
uint32_t period_cnt = 80000; // 80MHz ÷ 1000Hz = 80000
// 计算高低电平时间
uint32_t high_time = (uint32_t)(period_cnt * duty_cycle_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
// 配置PWM
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.offset_time = 0, // 无相位偏移
.repeat = true, // 持续输出
.cycles = 0 // 无限循环
};
return uapi_pwm_open(channel, &cfg);
}
// 运行时更新占空比(V151版本)
errcode_t update_pwm_duty_runtime(uint8_t channel, float duty_cycle_percent)
{
// V151需要重新配置整个通道来更新占空比
return set_pwm_duty_cycle(channel, duty_cycle_percent);
}
### 5.6 完整的PWM使用示例(推荐新手使用)
```c
// 完整的PWM初始化和使用流程
void pwm_simple_example(void)
{
// 第1步:初始化PWM系统
if (uapi_pwm_init() != ERRCODE_SUCC) {
printf("PWM初始化失败!\n");
return;
}
// 第2步:配置GPIO引脚为PWM功能(这里假设已经配置好)
// 第3步:设置PWM通道0的亮度
uint8_t channel = 0; // 使用PWM通道0
// 设置不同亮度测试
printf("开始PWM亮度测试...\n");
// 0% 亮度(关闭)
set_pwm_brightness_simple(channel, 0);
uapi_pwm_start(channel);
printf("当前亮度: 0%% (关闭)\n");
// delay_ms(1000); // 延时1秒
// 25% 亮度
set_pwm_brightness_simple(channel, 64);
printf("当前亮度: 25%%\n");
// delay_ms(1000);
// 50% 亮度
set_pwm_brightness_simple(channel, 128);
printf("当前亮度: 50%%\n");
// delay_ms(1000);
// 75% 亮度
set_pwm_brightness_simple(channel, 192);
printf("当前亮度: 75%%\n");
// delay_ms(1000);
// 100% 亮度(最亮)
set_pwm_brightness_simple(channel, 255);
printf("当前亮度: 100%% (最亮)\n");
printf("PWM测试完成!\n");
}
// 渐变效果示例
void pwm_fade_example(void)
{
uint8_t channel = 0;
// 从暗到亮的渐变
printf("渐亮效果...\n");
for (int brightness = 0; brightness <= 255; brightness += 5) {
set_pwm_brightness_simple(channel, brightness);
// delay_ms(50); // 每步延时50ms,实现平滑渐变
}
// 从亮到暗的渐变
printf("渐暗效果...\n");
for (int brightness = 255; brightness >= 0; brightness -= 5) {
set_pwm_brightness_simple(channel, brightness);
// delay_ms(50);
}
printf("渐变效果完成!\n");
}
6. PWM频率与周期配置(进阶内容)
6.1 常用频率配置表
基于80MHz系统时钟的配置参数:
| 目标频率 | 周期计数值 | 周期时间 | 最小占空比步进 | 应用场景 | 说明 |
|---|---|---|---|---|---|
| 100 Hz | 800,000 | 10.0 ms | 0.000125% | 低频控制 | 人眼可见闪烁 |
| 1 kHz | 80,000 | 1.0 ms | 0.00125% | LED调光 | 推荐频率 - 无闪烁,适合照明 |
| 10 kHz | 8,000 | 100 μs | 0.0125% | 音频PWM | 超出听觉范围 |
| 20 kHz | 4,000 | 50 μs | 0.025% | 电机控制 | 无噪音,适合电机驱动 |
| 100 kHz | 800 | 10 μs | 0.125% | 高频开关 | 开关电源应用 |
| 1 MHz | 80 | 1 μs | 1.25% | 快速响应 | 高速信号生成 |
6.2 频率配置示例
// 不同应用场景的频率配置
typedef struct {
uint32_t frequency;
uint32_t period_cnt;
const char* application;
} pwm_freq_preset_t;
const pwm_freq_preset_t pwm_presets[] = {
{100, 800000, "低频控制信号(有闪烁)"},
{1000, 80000, "LED调光(推荐使用)"},
{20000, 4000, "电机控制(无噪音)"},
{100000, 800, "高频开关电源"},
{1000000, 80, "高速信号生成"}
};
// 快速配置函数
errcode_t pwm_quick_setup(uint8_t channel, uint8_t preset_index, float duty_percent)
{
if (preset_index >= sizeof(pwm_presets)/sizeof(pwm_presets[0])) {
return ERRCODE_INVALID_PARAM;
}
const pwm_freq_preset_t* preset = &pwm_presets[preset_index];
uint32_t high_time = (uint32_t)(preset->period_cnt * duty_percent / 100.0f);
uint32_t low_time = preset->period_cnt - high_time;
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.repeat = true
};
printf("配置PWM: %s\n", preset->application);
printf(" - 频率: %dHz\n", preset->frequency);
printf(" - 占空比: %.1f%%\n", duty_percent);
printf(" - 周期时间: %.2fms\n", 1000.0f / preset->frequency);
return uapi_pwm_open(channel, &cfg);
}
7. PWM波形与相位控制(进阶内容)
7.1 PWM波形分析
基本PWM波形 (以1kHz频率,60%占空比为例):
周期 = 1ms (80000计数)
高电平时间 = 0.6ms (48000计数)
低电平时间 = 0.4ms (32000计数)
████████████████████████████████ ████████████████████████████████
____ ________ ____
|←─────── 48000 cnt ────────→| |←─────── 48000 cnt ────────→|
|←─────────────── 80000 cnt (1ms) ──────────→|←──────────── 80000 cnt ──────→|
7.2 相位偏移控制
// 相位偏移示例:两个PWM通道错相输出
void setup_phase_shifted_pwm(uint8_t ch1, uint8_t ch2, uint32_t frequency, float duty_percent)
{
uint32_t period_cnt = 80000000 / frequency;
uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
// 通道1:正常输出
pwm_config_t cfg1 = {
.high_time = high_time,
.low_time = low_time,
.offset_time = 0, // 无偏移
.repeat = true
};
// 通道2:相位偏移180度
pwm_config_t cfg2 = {
.high_time = high_time,
.low_time = low_time,
.offset_time = period_cnt / 2, // 半周期偏移
.repeat = true
};
uapi_pwm_open(ch1, &cfg1);
uapi_pwm_open(ch2, &cfg2);
uapi_pwm_start(ch1);
uapi_pwm_start(ch2);
}
7.3 互补输出波形
互补PWM输出 (避免直通,添加死区时间):
Channel A: ████████ ████████ ████████
Channel B: ████████ ████████ ████████
|dt| |dt| |dt| |dt| |dt| |dt|
死区 死区 死区 死区 死区 死区
死区时间计算:
dead_time_ns = 500; // 500ns死区
dead_time_cnt = (500 * 80000000) / 1000000000 = 40计数
8. PWM组同步控制(进阶内容)
8.1 PWM组管理
// PWM组配置和同步控制
#define PWM_GROUP_0 0
#define MAX_CHANNELS_PER_GROUP 8
errcode_t setup_pwm_group(uint8_t group_id, uint8_t* channels, uint8_t channel_count)
{
errcode_t ret;
// 1. 清除现有组配置
ret = uapi_pwm_clear_group(group_id);
if (ret != ERRCODE_SUCC) return ret;
// 2. 设置新的PWM组
ret = uapi_pwm_set_group(group_id, channels, channel_count);
if (ret != ERRCODE_SUCC) return ret;
printf("PWM组%d配置完成,包含%d个通道\n", group_id, channel_count);
return ERRCODE_SUCC;
}
// 同步启动PWM组
errcode_t start_pwm_group_sync(uint8_t group_id)
{
return uapi_pwm_start_group(group_id);
}
// 同步停止PWM组
errcode_t stop_pwm_group_sync(uint8_t group_id)
{
return uapi_pwm_stop_group(group_id);
}
8.2 组同步流程
sequenceDiagram
participant App as 应用层
participant Group as PWM组控制
participant CH0 as 通道0
participant CH1 as 通道1
participant CH2 as 通道2
App->>Group: uapi_pwm_set_group(0, [0,1,2])
Group->>CH0: 加入组0
Group->>CH1: 加入组0
Group->>CH2: 加入组0
App->>CH0: uapi_pwm_open(0, config0)
App->>CH1: uapi_pwm_open(1, config1)
App->>CH2: uapi_pwm_open(2, config2)
App->>Group: uapi_pwm_start_group(0)
Group->>CH0: 同步启动
Group->>CH1: 同步启动
Group->>CH2: 同步启动
Note over CH0,CH2: 所有通道同时开始PWM输出
9. 动态PWM调整(进阶内容)
9.1 运行时参数更新
// 动态调整PWM参数
errcode_t dynamic_pwm_update(uint8_t channel, uint32_t new_frequency, float new_duty_percent)
{
// 计算新参数
uint32_t period_cnt = 80000000 / new_frequency;
uint32_t high_time = (uint32_t)(period_cnt * new_duty_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
// 重新配置PWM
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.repeat = true
};
errcode_t ret = uapi_pwm_open(channel, &cfg);
if (ret != ERRCODE_SUCC) {
return ret;
}
return uapi_pwm_start(channel);
}
// 渐变调节函数
typedef struct {
uint8_t channel;
float current_duty;
float target_duty;
float step_size;
uint32_t interval_ms;
} pwm_fade_t;
void pwm_fade_step(pwm_fade_t* fade)
{
if (abs(fade->current_duty - fade->target_duty) < fade->step_size) {
fade->current_duty = fade->target_duty;
return; // 渐变完成
}
if (fade->current_duty < fade->target_duty) {
fade->current_duty += fade->step_size;
} else {
fade->current_duty -= fade->step_size;
}
set_pwm_duty_cycle(fade->channel, fade->current_duty);
}
9.2 PWM调制模式
// PWM调制模式示例
typedef enum {
PWM_MODE_CONSTANT, // 恒定输出
PWM_MODE_BREATHING, // 呼吸模式
PWM_MODE_BLINKING, // 闪烁模式
PWM_MODE_SAWTOOTH, // 锯齿波模式
} pwm_modulation_mode_t;
typedef struct {
uint8_t channel;
pwm_modulation_mode_t mode;
uint32_t period_ms; // 调制周期
float min_duty; // 最小占空比
float max_duty; // 最大占空比
uint32_t step_count; // 步数
} pwm_modulation_t;
void pwm_modulation_update(pwm_modulation_t* mod, uint32_t current_step)
{
float duty = mod->min_duty;
switch (mod->mode) {
case PWM_MODE_BREATHING:
// 正弦波呼吸
duty = mod->min_duty + (mod->max_duty - mod->min_duty) *
(1.0f + sinf(2 * 3.14159f * current_step / mod->step_count)) / 2.0f;
break;
case PWM_MODE_SAWTOOTH:
// 锯齿波
duty = mod->min_duty + (mod->max_duty - mod->min_duty) *
current_step / mod->step_count;
break;
case PWM_MODE_BLINKING:
// 方波闪烁
duty = (current_step < mod->step_count/2) ? mod->max_duty : mod->min_duty;
break;
default:
duty = mod->max_duty;
break;
}
set_pwm_duty_cycle(mod->channel, duty);
}
10. PWM应用示例(进阶内容)
10.1 电机PWM控制
// 电机PWM控制示例
void motor_pwm_control(uint8_t motor_channel, float speed_percent, bool direction)
{
// 电机PWM频率通常选择20kHz以避免听到噪音
uint32_t motor_frequency = 20000;
// 限制速度范围
if (speed_percent < 0) speed_percent = 0;
if (speed_percent > 100) speed_percent = 100;
// 计算周期和占空比
uint32_t period_cnt = 80000000 / motor_frequency;
uint32_t high_time = (uint32_t)(period_cnt * speed_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.repeat = true
};
uapi_pwm_open(motor_channel, &cfg);
uapi_pwm_start(motor_channel);
// 方向控制需要额外的GPIO控制
// gpio_set_direction_pin(direction);
printf("电机PWM: 速度=%.1f%%, 方向=%s\n",
speed_percent, direction ? "正转" : "反转");
}
10.2 伺服电机控制
// 伺服电机PWM控制 (50Hz, 1-2ms脉宽)
void servo_pwm_control(uint8_t servo_channel, float angle_degrees)
{
// 伺服PWM频率固定为50Hz (20ms周期)
uint32_t servo_frequency = 50;
uint32_t period_cnt = 80000000 / servo_frequency; // 1,600,000计数 (20ms)
// 角度范围限制 (-90° 到 +90°)
if (angle_degrees < -90) angle_degrees = -90;
if (angle_degrees > 90) angle_degrees = 90;
// 脉宽计算:1ms(-90°) 到 2ms(+90°)
float pulse_width_ms = 1.5f + (angle_degrees / 90.0f) * 0.5f;
uint32_t high_time = (uint32_t)(80000 * pulse_width_ms); // 80000计数/ms
uint32_t low_time = period_cnt - high_time;
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.repeat = true
};
uapi_pwm_open(servo_channel, &cfg);
uapi_pwm_start(servo_channel);
printf("伺服控制: 角度=%.1f°, 脉宽=%.2fms\n", angle_degrees, pulse_width_ms);
}
10.3 音频PWM输出
// 简单音频PWM输出
void audio_pwm_tone(uint8_t audio_channel, uint32_t tone_frequency, uint32_t duration_ms)
{
// 音频PWM载波频率选择高于20kHz
uint32_t carrier_frequency = 44100; // 44.1kHz载波
uint32_t period_cnt = 80000000 / carrier_frequency;
// 音频调制(简单50%占空比)
uint32_t high_time = period_cnt / 2;
uint32_t low_time = period_cnt - high_time;
pwm_config_t cfg = {
.high_time = high_time,
.low_time = low_time,
.repeat = true
};
uapi_pwm_open(audio_channel, &cfg);
uapi_pwm_start(audio_channel);
// 持续时间控制
// delay_ms(duration_ms);
// uapi_pwm_stop(audio_channel);
printf("音频输出: 载波=%dHz, 持续=%dms\n", carrier_frequency, duration_ms);
}
11. 错误处理与调试
11.1 常见问题诊断
| 问题现象 | 可能原因 | 解决方案 | 验证方法 |
|---|---|---|---|
| PWM无输出 | 通道未启动 | 调用uapi_pwm_start() | 示波器检查引脚 |
| 频率不正确 | 计算错误 | 检查period_cnt计算 | 频率计测量 |
| 占空比异常 | high_time设置错误 | 检查占空比计算公式 | 占空比测量 |
| 多通道不同步 | 未使用PWM组 | 使用uapi_pwm_start_group() | 示波器多通道观察 |
| PWM抖动 | 中断影响 | 关闭不必要中断 | 波形稳定性分析 |
11.2 调试代码示例
// PWM状态监控和调试
void pwm_debug_info(uint8_t channel)
{
uint32_t frequency = uapi_pwm_get_frequency(channel);
printf("=== PWM通道%d调试信息 ===\n", channel);
printf("基础频率: %d Hz\n", frequency);
printf("系统时钟: 80MHz\n");
// 假设当前配置
uint32_t target_freq = 1000;
uint32_t period_cnt = 80000000 / target_freq;
float duty_cycle = 60.0f;
uint32_t high_time = (uint32_t)(period_cnt * duty_cycle / 100.0f);
uint32_t low_time = period_cnt - high_time;
printf("目标频率: %d Hz\n", target_freq);
printf("周期计数: %d\n", period_cnt);
printf("高电平计数: %d (%.2fms)\n", high_time, (float)high_time/80000);
printf("低电平计数: %d (%.2fms)\n", low_time, (float)low_time/80000);
printf("占空比: %.1f%%\n", duty_cycle);
printf("实际周期: %.2fms\n", (float)period_cnt/80000);
}
// PWM波形验证
bool verify_pwm_output(uint8_t channel, uint32_t expected_freq, float expected_duty)
{
// 这里需要硬件支持或外部测量设备
// 返回验证结果
printf("验证PWM通道%d: 期望频率=%dHz, 期望占空比=%.1f%%\n",
channel, expected_freq, expected_duty);
return true; // 简化示例
}
11.3 性能优化建议
// PWM性能优化建议
void pwm_performance_tips(void)
{
// 1. 使用PWM组进行批量操作
uint8_t channels[] = {0, 1, 2, 3};
uapi_pwm_set_group(0, channels, 4);
uapi_pwm_start_group(0); // 比逐个启动效率高
// 2. 避免频繁的参数更改
// 缓存计算结果,批量更新
// 3. 合理选择PWM频率
// 过高频率会增加功耗,过低可能产生可见闪烁
// 4. 使用合适的占空比精度
// 不需要过高精度时使用整数运算
// 5. 中断优化
// 只在必要时注册PWM中断回调
}
12. 完整PWM控制流程
12.1 PWM完整使用流程
flowchart TD
A[系统启动] --> B[uapi_pwm_init 初始化]
B --> C[GPIO配置为PWM功能]
C --> D[计算PWM参数]
D --> E{单通道?}
E -->|是| F[uapi_pwm_open 配置通道]
E -->|否| G[uapi_pwm_set_group 配置组]
F --> H[uapi_pwm_start 启动单通道]
G --> I[批量配置各通道]
I --> J[uapi_pwm_start_group 同步启动]
H --> K[PWM波形输出]
J --> K
K --> L{需要调整?}
L -->|是| M[更新参数]
L -->|否| N[持续运行]
M --> O[uapi_pwm_open 重新配置]
O --> K
N --> P{停止?}
P -->|否| L
P -->|是| Q[uapi_pwm_close 关闭]
Q --> R[结束]
style B fill:#e3f2fd
style D fill:#f1f8e9
style K fill:#fff3e0
style O fill:#e8f5e8
附录:技术参考
A.1 WS63 PWM技术规格
| 规格项 | 参数值 | 说明 |
|---|---|---|
| 系统时钟 | 80 MHz | PWM计数基准 |
| 计数器位宽 | 32-bit | 支持超长周期 |
| PWM通道数 | 8 | 每组最多8通道 |
| PWM组数 | 多组 | 支持同步控制 |
| 最高频率 | 40 MHz | 理论最大值 |
| 最低频率 | 1.2 Hz | 32位计数器极限 |
| 占空比精度 | 0.0000012% | 32位分辨率 |
| 相位控制 | 支持 | offset_time参数 |
A.2 应用场景频率推荐
| 应用场景 | 推荐频率 | 原因 |
|---|---|---|
| LED调光 | 1-20 kHz | 避免频闪,超出人眼感知 |
| 电机控制 | 10-50 kHz | 超出听觉范围,降低噪音 |
| 伺服控制 | 50 Hz | 伺服电机标准频率 |
| 开关电源 | 100-500 kHz | 减小磁性元件尺寸 |
| 音频输出 | 20-100 kHz | 超出人类听觉范围 |
| 数字通信 | 1-10 MHz | 高速数据传输 |
文档版本:V3.0
更新日期:2025年
适用平台:WS63 (HiSilicon)
PWM版本:CONFIG_PWM_USING_V151
系统时钟:80MHz
本文档提供通用PWM技术指导,适用于各种基于WS63平台的PWM应用开发。