1. 项目概述
K210作为一款国产RISC-V架构的双核64位AIoT处理器,凭借其出色的算力和丰富的硬件接口,在嵌入式AI领域获得了广泛应用。正点原子推出的DNK210开发板搭载了K210芯片,并提供了完善的CanMV开发环境支持。本次实验聚焦machine.PWM类的使用,这是控制电机、LED亮度调节等场景的核心功能模块。
在实际项目中,PWM(脉冲宽度调制)技术几乎无处不在。从调节电机转速到控制LED呼吸灯效果,再到舵机角度控制,都离不开精准的PWM信号输出。K210芯片内置了8个独立的PWM硬件通道,每个通道支持4路输出,这意味着单颗芯片最多可同时输出32路PWM信号,这在多电机控制场景中显得尤为珍贵。
2. 硬件基础与原理剖析
2.1 K210的PWM硬件架构
K210的PWM控制器采用双计数器设计,包含一个周期计数器和一个占空比计数器。其工作时钟源可选择内部PLL0或外部时钟,最高支持100MHz输入频率。每个PWM通道具有以下关键寄存器:
- 周期寄存器(PWM_CFG):决定PWM波形的完整周期
- 占空比寄存器(PWM_DUTY):控制高电平持续时间
- 死区控制寄存器(PWM_DB):用于H桥电路中的互补信号控制
硬件上,PWM输出引脚与GPIO复用,需要通过FPIOA(现场可编程IO阵列)进行映射。例如,实验板上PWM0通道默认映射到IO8引脚,对应开发板上的LED控制接口。
2.2 PWM参数计算原理
PWM信号有三个核心参数:
- 频率(f):单位时间内完整周期数,f = 1/T(T为周期)
- 占空比(D):高电平时间占整个周期的比例,D = t/T × 100%
- 分辨率:占空比可调节的最小步进值
在K210中,这些参数通过以下公式计算:
code复制实际频率 = 输入时钟频率 / (period + 1)
占空比 = (duty + 1) / (period + 1) × 100%
其中period和duty都是写入寄存器的整数值。例如要实现1kHz频率、50%占空比的PWM,假设时钟源为25MHz:
code复制period = 25MHz/1kHz - 1 = 24999
duty = period × 50% = 12499
3. CanMV环境下的PWM编程
3.1 machine.PWM类详解
CanMV对K210的PWM功能进行了面向对象封装,主要提供以下方法:
python复制class PWM:
def __init__(self, channel, freq, duty, timer=0, pin=None)
def freq([value]) # 获取/设置频率
def duty([value]) # 获取/设置占空比
def deinit() # 释放PWM资源
关键参数说明:
- channel:PWM通道号(0-7)
- freq:初始频率(Hz),建议范围1Hz-1MHz
- duty:初始占空比(0-100)
- timer:硬件定时器编号(0-3)
- pin:可选参数,指定输出引脚(需先配置FPIOA)
3.2 基础实验:LED呼吸灯实现
以下是完整的LED呼吸灯实现代码:
python复制from machine import PWM
import time
pwm = PWM(0, freq=1000, duty=0, pin=8) # 使用PWM0通道,IO8引脚
def breathing_led():
step = 1
current_duty = 0
while True:
pwm.duty(current_duty)
time.sleep_ms(20)
current_duty += step
if current_duty >= 100:
step = -1
elif current_duty <= 0:
step = 1
breathing_led()
代码解析:
- 初始化PWM0通道,设置基础频率1kHz
- 通过循环动态调整占空比,实现亮度渐变
- 每次调整后加入20ms延时控制变化速度
- 当占空比达到边界时反转变化方向
3.3 多通道PWM同步控制
K210支持同时输出多路PWM,下面示例展示如何控制两个LED:
python复制pwm1 = PWM(0, freq=2000, duty=30, pin=8)
pwm2 = PWM(1, freq=2000, duty=70, pin=9)
# 同步调整两路PWM占空比
for i in range(0, 101, 5):
pwm1.duty(i)
pwm2.duty(100-i)
time.sleep_ms(100)
重要提示:当多个PWM通道使用相同定时器时,它们的频率将保持同步。如果需要独立频率控制,必须分配不同的定时器资源。
4. 进阶应用与性能优化
4.1 电机控制实战
以常见的直流有刷电机为例,典型控制参数:
- 工作频率:5-20kHz(避免可闻噪声)
- 死区时间:500ns-1μs(防止H桥直通)
示例代码:
python复制# 电机驱动初始化
motor_pwm = PWM(2, freq=10000, duty=0, pin=10)
dir_pin = Pin(11, Pin.OUT) # 方向控制引脚
def set_motor(speed):
direction = 1 if speed >=0 else 0
dir_pin.value(direction)
motor_pwm.duty(abs(speed)) # speed范围-100~100
# 电机加速过程
for speed in range(0, 101, 5):
set_motor(speed)
time.sleep_ms(200)
4.2 PWM测量与校准
为确保输出精度,可以使用逻辑分析仪或示波器进行测量校准。常见问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 频率偏差大 | 时钟源配置错误 | 检查PLL时钟配置 |
| 占空比不准确 | 寄存器舍入误差 | 使用更高时钟频率 |
| 输出抖动明显 | 电源噪声干扰 | 增加滤波电容 |
校准代码示例:
python复制def calibrate_pwm(target_freq):
measured = []
for period in range(1000, 5000, 100):
pwm = PWM(0, freq=target_freq, duty=50, pin=8)
actual = measure_with_scope() # 实际测量函数
pwm.deinit()
measured.append((period, actual))
return optimal_period(measured) # 寻找最佳period值
5. 常见问题与调试技巧
5.1 典型错误排查表
| 错误类型 | 现象描述 | 排查步骤 |
|---|---|---|
| 无输出 | LED不亮/电机不转 | 1. 检查引脚映射 2. 验证PWM初始化返回值 3. 测量引脚电压 |
| 频率异常 | 效果不符合预期 | 1. 计算理论period值 2. 检查时钟树配置 3. 确认timer冲突 |
| 占空比异常 | 亮度/速度不线性 | 1. 检查duty计算 2. 验证寄存器写入值 3. 测量实际波形 |
5.2 性能优化建议
-
低延迟应用:
- 使用硬件PWM而非软件模拟
- 选择更高时钟源频率
- 避免频繁的duty值修改
-
多通道管理:
python复制# 高效的多通道控制方案 pwm_group = [ PWM(0, freq=1000, duty=0, pin=8), PWM(1, freq=1000, duty=0, pin=9), PWM(2, freq=1000, duty=0, pin=10) ] def update_all(duties): for i, pwm in enumerate(pwm_group): pwm.duty(duties[i]) -
资源释放:
python复制# 正确释放PWM资源 try: pwm = PWM(0, freq=1000, duty=50) # ...使用过程... finally: pwm.deinit() # 确保资源释放
6. 扩展应用场景
6.1 舵机角度控制
SG90舵机典型控制参数:
- 频率:50Hz(周期20ms)
- 脉宽:0.5ms(0°)- 2.5ms(180°)
控制函数示例:
python复制def set_servo_angle(angle):
pulse_width = 500 + angle * (2000 / 180)
duty = pulse_width * 100 / 20000 # 转换为占空比
pwm.duty(duty)
# 控制舵机从0°转到180°
for angle in range(0, 181, 10):
set_servo_angle(angle)
time.sleep_ms(300)
6.2 音频信号生成
利用PWM实现DAC功能,播放简单音频:
python复制# 音符频率定义
NOTE_C4 = 262
NOTE_D4 = 294
NOTE_E4 = 330
melody = [NOTE_C4, NOTE_D4, NOTE_E4, NOTE_C4]
def play_tone(note, duration):
pwm.freq(note)
pwm.duty(50) # 50%占空比
time.sleep_ms(duration)
pwm.duty(0) # 静音
for note in melody:
play_tone(note, 200)
专业提示:对于高质量音频,建议使用K210的I2S接口配合外部DAC。PWM音频适合简单的提示音场景。