321 lines
11 KiB
Markdown
321 lines
11 KiB
Markdown
|
|
# PWM参数计算详解(通俗易懂版)
|
|||
|
|
|
|||
|
|
## 文档说明
|
|||
|
|
|
|||
|
|
本文档专门解释PWM的各个参数计算原理,用通俗的语言和图表帮助理解复杂的PWM概念。适合只熟悉0-255亮度控制的开发者快速理解PWM参数。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 什么是PWM?
|
|||
|
|
|
|||
|
|
PWM就像开关灯一样,快速地开关来控制亮度。想象你手里有个开关,如果一直开着,灯就是100%亮;如果一直关着,灯就是0%亮。但如果你快速地开开关关,开的时间长一点,灯就亮一点;关的时间长一点,灯就暗一点。
|
|||
|
|
|
|||
|
|
### PWM波形图解
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
100%亮度 (一直开着):
|
|||
|
|
████████████████████████████████████████████████████
|
|||
|
|
|
|||
|
|
50%亮度 (一半时间开,一半时间关):
|
|||
|
|
████████ ████████ ████████ ████
|
|||
|
|
|
|||
|
|
25%亮度 (1/4时间开,3/4时间关):
|
|||
|
|
████ ████ ████ ████ ████ ████ ████
|
|||
|
|
|
|||
|
|
0%亮度 (一直关着):
|
|||
|
|
________________________________________________
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 关键概念图解
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
A[频率<br/>每秒开关多少次] --> B[周期<br/>一次开关的总时间]
|
|||
|
|
B --> C[占空比<br/>开的时间占比例]
|
|||
|
|
C --> D[亮度效果<br/>人眼感知的亮度]
|
|||
|
|
|
|||
|
|
style A fill:#e3f2fd
|
|||
|
|
style B fill:#f1f8e9
|
|||
|
|
style C fill:#fff3e0
|
|||
|
|
style D fill:#fce4ec
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 从0-255到实际参数的转换
|
|||
|
|
|
|||
|
|
### 2.1 对应关系表
|
|||
|
|
|
|||
|
|
| 你熟悉的数值 | 百分比 | PWM术语 | 实际效果 |
|
|||
|
|
|-------------|--------|---------|----------|
|
|||
|
|
| 0 | 0% | 0%占空比 | 完全不亮 |
|
|||
|
|
| 64 | 25% | 25%占空比 | 1/4亮度 |
|
|||
|
|
| 128 | 50% | 50%占空比 | 一半亮度 |
|
|||
|
|
| 192 | 75% | 75%占空比 | 3/4亮度 |
|
|||
|
|
| 255 | 100% | 100%占空比 | 最亮 |
|
|||
|
|
|
|||
|
|
### 2.2 转换公式
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// 从你熟悉的0-255转换到百分比
|
|||
|
|
uint8_t brightness = 128; // 你设置的亮度值
|
|||
|
|
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% (最亮)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. PWM参数计算原理
|
|||
|
|
|
|||
|
|
### 3.1 基础概念图解
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
系统就像一个超快的时钟(80MHZ),每秒"滴答(cycle)"8000万次,每个滴答节奏所对应的时间是固定的。
|
|||
|
|
也就是这个PWM的最高精度就是(1/80*1000*1000)s =
|
|||
|
|
|
|||
|
|
如果我们要让PWM 1000HZ,也就是每秒开关1000次:
|
|||
|
|
那么每次开关需要:80M ÷ 1000 = 80000 cycle所对应的时间
|
|||
|
|
所以我们控制PWM本质上是控制这个PWM,一个周期的cycle总个数以及比例分配。
|
|||
|
|
|
|||
|
|
比如我要 PWM 是1KHZ
|
|||
|
|
首先计算,1KHZ 一个PWM周期就是1/1000s 也就是1ms
|
|||
|
|
然后计算1ms 对应多少个cycle: 0.001 / (1/80*1000*1000) = 80,000
|
|||
|
|
然后再根据占空比去分配这 80,000 个数字就行了
|
|||
|
|
只要确保high_time low_time加起来是8000就行
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 时间轴图解
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
一个PWM周期 (1000Hz = 1ms):
|
|||
|
|
|
|||
|
|
0 20000 40000 60000 80000
|
|||
|
|
|---------|---------|---------|---------|
|
|||
|
|
25% 50% 75% 100%
|
|||
|
|
|
|||
|
|
如果要50%亮度:
|
|||
|
|
████████████████████████████████████████
|
|||
|
|
|←----- 开40000-----→|←---- 关40000 ----|
|
|||
|
|
|
|||
|
|
如果要75%亮度:
|
|||
|
|
████████████████████████████████████████████████████████
|
|||
|
|
|←-------- 开 (60000滴答) ---------→|←- 关 (20000滴答) -|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.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;
|
|||
|
|
|
|||
|
|
// 结果:
|
|||
|
|
// on_time = 48000 (开的时间)
|
|||
|
|
// off_time = 32000 (关的时间)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. PWM参数详解图表
|
|||
|
|
|
|||
|
|
### 4.1 pwm_config_t结构体参数图解
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
typedef struct pwm_config {
|
|||
|
|
uint32_t high_time; // 高电平时间("开"的时间,用计数表示)
|
|||
|
|
uint32_t low_time; // 低电平时间("关"的时间,用计数表示)
|
|||
|
|
uint32_t offset_time; // 相位偏移时间(延迟启动时间)
|
|||
|
|
uint16_t cycles; // 重复次数(0=无限重复)
|
|||
|
|
bool repeat; // 连续输出标志(true=一直输出)
|
|||
|
|
} pwm_config_t;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 参数关系图
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
A[一个PWM周期] --> B[high_time<br/>开的时间]
|
|||
|
|
A --> C[low_time<br/>关的时间]
|
|||
|
|
|
|||
|
|
B --> D[占空比 = high_time / (high_time + low_time)]
|
|||
|
|
C --> D
|
|||
|
|
|
|||
|
|
D --> E[最终亮度效果]
|
|||
|
|
|
|||
|
|
F[offset_time<br/>延迟启动] --> G[多通道同步控制]
|
|||
|
|
H[cycles<br/>重复次数] --> I[输出控制]
|
|||
|
|
J[repeat<br/>连续标志] --> I
|
|||
|
|
|
|||
|
|
style A fill:#e3f2fd
|
|||
|
|
style D fill:#f1f8e9
|
|||
|
|
style E fill:#fff3e0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 各参数的通俗解释
|
|||
|
|
|
|||
|
|
#### **high_time(高电平时间)**
|
|||
|
|
- **通俗理解**:就是"开关"中"开"的时间,用时钟计数表示
|
|||
|
|
- **计算方法**:`high_time = 周期计数 × 亮度百分比`
|
|||
|
|
- **例子**:如果high_time = 40000,系统时钟80MHz,那么"开"的时间 = 40000 ÷ 80000000 = 0.5ms
|
|||
|
|
|
|||
|
|
#### **low_time(低电平时间)**
|
|||
|
|
- **通俗理解**:就是"开关"中"关"的时间,用时钟计数表示
|
|||
|
|
- **计算方法**:`low_time = 周期计数 - high_time`
|
|||
|
|
- **例子**:如果low_time = 40000,那么"关"的时间也是0.5ms
|
|||
|
|
|
|||
|
|
#### **offset_time(相位偏移)**
|
|||
|
|
- **通俗理解**:延迟多长时间再开始PWM输出
|
|||
|
|
- **使用场景**:多个LED需要错开时间启动,避免同时启动造成电流冲击
|
|||
|
|
- **大多数情况**:设为0即可
|
|||
|
|
|
|||
|
|
#### **cycles(重复次数)**
|
|||
|
|
- **通俗理解**:PWM波形重复多少次后停止
|
|||
|
|
- **常用设置**:
|
|||
|
|
- `0`:无限重复(最常用)
|
|||
|
|
- `具体数字`:输出指定次数后停止
|
|||
|
|
|
|||
|
|
#### **repeat(连续输出标志)**
|
|||
|
|
- **通俗理解**:是否持续输出PWM波形
|
|||
|
|
- **常用设置**:
|
|||
|
|
- `true`:持续输出(最常用)
|
|||
|
|
- `false`:只输出指定次数后停止
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 实际计算示例
|
|||
|
|
|
|||
|
|
### 5.1 示例1:设置50%亮度
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// 目标:PWM频率1000Hz,50%亮度
|
|||
|
|
|
|||
|
|
// 第1步:计算周期计数
|
|||
|
|
uint32_t period_cnt = 80000000 / 1000; // = 80000
|
|||
|
|
|
|||
|
|
// 第2步:计算高低电平时间
|
|||
|
|
uint32_t high_time = 80000 * 50 / 100; // = 40000
|
|||
|
|
uint32_t low_time = 80000 - 40000; // = 40000
|
|||
|
|
|
|||
|
|
// 第3步:配置PWM
|
|||
|
|
pwm_config_t cfg = {
|
|||
|
|
.high_time = 40000, // "开" 0.5ms
|
|||
|
|
.low_time = 40000, // "关" 0.5ms
|
|||
|
|
.offset_time = 0, // 不延迟
|
|||
|
|
.repeat = true, // 持续输出
|
|||
|
|
.cycles = 0 // 无限重复
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 示例2:设置75%亮度
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// 目标:PWM频率1000Hz,75%亮度
|
|||
|
|
|
|||
|
|
uint32_t period_cnt = 80000;
|
|||
|
|
uint32_t high_time = 80000 * 75 / 100; // = 60000
|
|||
|
|
uint32_t low_time = 80000 - 60000; // = 20000
|
|||
|
|
|
|||
|
|
pwm_config_t cfg = {
|
|||
|
|
.high_time = 60000, // "开" 0.75ms (更长)
|
|||
|
|
.low_time = 20000, // "关" 0.25ms (更短)
|
|||
|
|
.offset_time = 0,
|
|||
|
|
.repeat = true,
|
|||
|
|
.cycles = 0
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 波形对比图
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
50%占空比 (high_time=40000, low_time=40000):
|
|||
|
|
████████████████████████████████████████ ████████████████████████████████████████
|
|||
|
|
|←-------- 0.5ms ------→|←-- 0.5ms --→| |←-------- 0.5ms ------→|←-- 0.5ms --→|
|
|||
|
|
|
|||
|
|
75%占空比 (high_time=60000, low_time=20000):
|
|||
|
|
████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████
|
|||
|
|
|←-------------- 0.75ms ------------→|0.25ms| |←-------------- 0.75ms ------------→|0.25ms|
|
|||
|
|
|
|||
|
|
25%占空比 (high_time=20000, low_time=60000):
|
|||
|
|
████████████████████ ████████████████████
|
|||
|
|
|←-- 0.25ms --→|←---- 0.75ms ----→| |←-- 0.25ms --→|←---- 0.75ms ----→|
|
|||
|
|
```
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 简化使用函数
|
|||
|
|
|
|||
|
|
### 7.1 推荐的简化函数
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// 这个函数让PWM像以前一样简单
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 常见问题解答
|
|||
|
|
|
|||
|
|
### Q1: 为什么不能直接用0-255,要用这些复杂参数?
|
|||
|
|
**A**: PWM硬件需要知道具体的时钟计数,不是百分比。就像你告诉司机"开快点",司机需要知道具体开多少码一样。
|
|||
|
|
|
|||
|
|
### Q2: high_time和low_time的单位是什么?
|
|||
|
|
**A**: 单位是时钟计数次数,不是时间。1个计数 = 1/80000000秒 = 12.5纳秒。
|
|||
|
|
|
|||
|
|
### Q3: 为什么推荐1000Hz频率?
|
|||
|
|
**A**:
|
|||
|
|
- 高于100Hz:人眼看不到闪烁
|
|||
|
|
- 低于20kHz:不会产生高频噪音
|
|||
|
|
- 1000Hz刚好在这个范围内,且计算简单
|
|||
|
|
|
|||
|
|
### Q4: offset_time什么时候用?
|
|||
|
|
**A**: 主要用于多通道同步,比如RGB灯的三个颜色错开启动,避免电流冲击。
|
|||
|
|
|
|||
|
|
### Q5: 如何实现渐变效果?
|
|||
|
|
**A**: 在循环中逐步改变brightness_0_255的值,每次改变后调用set_pwm_brightness_simple()。
|
|||
|
|
|
|||
|
|
---
|