1056 lines
31 KiB
Markdown
1056 lines
31 KiB
Markdown
# WS63 PWM 技术使用文档
|
||
|
||
## 文档概述
|
||
|
||
本文档基于WS63平台的PWM驱动实现,为不同经验的开发人员提供PWM(脉宽调制)功能的配置与控制指导。
|
||
|
||
**适用人群**:
|
||
- 🌱 **新手开发者**:只熟悉0-255亮度控制,希望快速上手PWM
|
||
- 🛠️ **进阶开发者**:需要深入了解PWM参数配置和高级功能
|
||
- 🔍 **系统调试者**:需要问题诊断和性能优化
|
||
|
||
**平台基础参数**:
|
||
- MCU平台:WS63 (HiSilicon)
|
||
- 系统时钟:80 MHz
|
||
- 计数器位宽:32-bit(u32)
|
||
- PWM版本:CONFIG_PWM_USING_V151
|
||
- 支持特性:多通道、PWM组、相位控制、运行时更新
|
||
|
||
## 文档导航
|
||
|
||
### 🌱 新手必读(建议阅读顺序)
|
||
1. **章节1 - 快速入门**: 5分钟了解PWM和快速上手
|
||
2. **章节5 - 参数详解**: 从0-255到PWM参数的转换和理解
|
||
3. **章节11 - 错误处理**: 常见问题解决
|
||
|
||
### 🛠️ 进阶内容(可选阅读)
|
||
- **章节2-4**: PWM系统架构、API和初始化流程
|
||
- **章节6-10**: 高级功能(频率配置、相位控制、PWM组等)
|
||
- **章节12**: 完整流程图和技术参考
|
||
|
||
---
|
||
|
||
## 1. 快速入门(新手必读)
|
||
|
||
### 1.1 PWM是什么?
|
||
|
||
PWM就像一个非常快的开关,通过控制"开"和"关"的时间比例来调节LED的亮度、电机的速度等。
|
||
|
||
**简单理解**:
|
||
- 如果一直开着 = 100%亮度
|
||
- 如果一直关着 = 0%亮度
|
||
- 如果一半时间开、一半时间关 = 50%亮度
|
||
|
||
### 1.2 你之前的使用方式 vs 现在的PWM方式
|
||
|
||
**你之前的方式**:
|
||
```c
|
||
// 简单直观,但是功能有限
|
||
set_led_brightness(128); // 0-255范围
|
||
```
|
||
|
||
**现在的PWM方式**:
|
||
```c
|
||
// 功能强大,但是参数复杂
|
||
pwm_config_t cfg = {
|
||
.high_time = 40000, // 高电平时间(计数)
|
||
.low_time = 40000, // 低电平时间(计数)
|
||
.offset_time = 0, // 相位偏移
|
||
.repeat = true, // 是否重复
|
||
.cycles = 0 // 循环次数
|
||
};
|
||
```
|
||
|
||
**不用担心!**本文档提供了简化函数,让你像以前一样使用0-255的数值。
|
||
|
||
### 1.3 5分钟快速上手
|
||
|
||
```c
|
||
// 第1步:初始化PWM系统
|
||
uapi_pwm_init();
|
||
|
||
// 第2步:使用简化函数设置亮度(就像以前一样)
|
||
set_pwm_brightness_simple(0, 128); // 通道0,50%亮度
|
||
|
||
// 第3步:启动PWM输出
|
||
uapi_pwm_start(0);
|
||
|
||
// 完成!LED就会以50%亮度点亮
|
||
```
|
||
|
||
**简化函数**(复制到你的代码里):
|
||
```c
|
||
// 这个函数让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 驱动层次结构
|
||
|
||
```mermaid
|
||
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工作原理
|
||
|
||
```mermaid
|
||
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配置结构体
|
||
|
||
```c
|
||
// 源码位置: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函数
|
||
|
||
```c
|
||
// 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 初始化时序图
|
||
|
||
```mermaid
|
||
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 初始化代码实现
|
||
|
||
```c
|
||
// 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其实就是占空比的另一种表示方法:
|
||
|
||
```c
|
||
// 你熟悉的方式: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 参数计算原理(用通俗的话解释)
|
||
|
||
```c
|
||
// 第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 简单的参数设置函数(推荐使用)
|
||
|
||
```c
|
||
// 简化版本:像以前一样用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 高级参数详解(可选阅读)
|
||
|
||
```c
|
||
如果你想深入了解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 频率配置示例
|
||
|
||
```c
|
||
// 不同应用场景的频率配置
|
||
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 相位偏移控制
|
||
|
||
```c
|
||
// 相位偏移示例:两个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组管理
|
||
|
||
```c
|
||
// 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 组同步流程
|
||
|
||
```mermaid
|
||
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 运行时参数更新
|
||
|
||
```c
|
||
// 动态调整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调制模式
|
||
|
||
```c
|
||
// 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控制
|
||
|
||
```c
|
||
// 电机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 伺服电机控制
|
||
|
||
```c
|
||
// 伺服电机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输出
|
||
|
||
```c
|
||
// 简单音频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 调试代码示例
|
||
|
||
```c
|
||
// 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 性能优化建议
|
||
|
||
```c
|
||
// 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完整使用流程
|
||
|
||
```mermaid
|
||
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应用开发。 |