4、PWM电机调速:LEDC外设原理、PWM频率与占空比设置、电机转速控制、死区时间处理
好,咱们进入第四讲。这一讲要啃的,是智能窗帘电机控制里最核心的硬骨头——PWM调速。
说白了,就是让电机转得快一点、慢一点,甚至精确地停在某个位置。你想想看,如果窗帘只能开和关,那跟普通窗帘有什么区别?智能窗帘的灵魂,就在于能“听话”地停在任意位置。
ESP32 内部有个专门干这活的硬件模块,叫 LEDC。名字虽然叫 LED 控制,但它驱动电机一样好使。我个人习惯,能用硬件 PWM 绝不用软件模拟,省心、精准、不占 CPU。
LEDC 外设原理
LEDC 的本质,是一个计数器加比较器。计数器从0开始,一直数到某个最大值(比如1023),然后归零重新数。比较器里存着你设定的值,当计数器的值小于比较值时,输出高电平;大于时,输出低电平。
这样,就产生了一串方波。方波的周期由计数器的最大值和时钟频率决定,高电平的宽度由比较值决定。
嗯,这里要注意:LEDC 是 ESP32 的硬件模块,不占用 CPU 资源。你设好参数后,它自己就一直在那跑,CPU 可以干别的事。我在项目中遇到过,有人用定时器中断模拟 PWM,结果电机一启动,WiFi 就断连了——就是因为 CPU 被频繁打断,没空处理网络协议栈。
核心要点:LEDC 是硬件 PWM,不是软件模拟。它独立运行,不干扰主程序。
PWM 频率与占空比设置
频率和占空比,是 PWM 的两个基本参数。频率决定了电机转动的“细腻程度”,占空比决定了电机的“力气大小”。
对于直流电机,频率太低,电机会发出“嗡嗡”声,甚至抖动。频率太高,电机的电感会滤掉 PWM 信号,导致电机不转。我一般选 1000Hz 到 5000Hz 之间,具体看电机参数。
占空比,就是高电平占整个周期的比例。50% 占空比,意味着电机得到一半的电压,转速大概是一半。但注意,不是线性的!电机有死区,占空比太低,电机根本转不起来。
设置代码很简单,我直接贴出来:
#include <driver/ledc.h>
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // 13位分辨率,范围0~8191
#define LEDC_FREQUENCY 5000 // 5kHz 频率
void pwm_init(void) {
ledc_timer_config_t timer_conf = {
.speed_mode = LEDC_MODE,
.timer_num = LEDC_TIMER,
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&timer_conf);
ledc_channel_config_t channel_conf = {
.gpio_num = 18,
.speed_mode = LEDC_MODE,
.channel = LEDC_CHANNEL,
.timer_sel = LEDC_TIMER,
.duty = 0,
.hpoint = 0
};
ledc_channel_config(&channel_conf);
}
void set_motor_speed(uint32_t duty) {
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
}
你看,13位分辨率,意味着占空比可以从0到8191之间任意设置。5kHz 的频率,电机运行起来很安静。
我的经验:分辨率越高,调速越细腻。但分辨率太高,频率就上不去。ESP32 的 LEDC 支持 1 到 20 位分辨率,我常用 13 位,兼顾精度和频率。
电机转速控制
有了 PWM,控制转速就是改占空比的事。但这里有个坑:电机启动时需要较大的占空比才能克服静摩擦力。我曾经做过一个项目,占空比从0直接跳到10%,电机纹丝不动,我还以为电路坏了。
后来我加了个软启动:占空比从0开始,每10ms增加一点,直到目标值。这样电机就能平稳启动。
代码实现:
void soft_start(uint32_t target_duty, uint32_t step_ms) {
uint32_t current_duty = 0;
while (current_duty < target_duty) {
current_duty += 50; // 每次增加50
if (current_duty > target_duty) current_duty = target_duty;
set_motor_speed(current_duty);
vTaskDelay(pdMS_TO_TICKS(step_ms));
}
}
为什么是50?因为我试过,步进太小启动太慢,步进太大又会抖动。50 是我试出来的经验值,你可以根据电机调整。
转速控制还有个关键点:闭环。开环控制下,电机负载变化时转速会波动。窗帘拉起来和放下去,阻力完全不同。我建议加个霍尔传感器或者编码器,实时读取转速,用 PID 算法调整占空比。
警告:不要长时间让电机堵转!占空比很大但电机不转,电流会飙升,可能烧坏驱动芯片。我在代码里加了堵转检测:如果设定转速和实际转速差距超过阈值,立即停止 PWM 输出。
死区时间处理
死区时间,这个词听起来有点吓人,其实很简单。电机从停止到开始转动,需要一定的占空比阈值。低于这个阈值,电机就是不转。这个阈值对应的占空比范围,就是死区。
为什么会这样?因为电机内部的摩擦力、线圈电阻、驱动器的死区电压等因素共同作用。不同电机,死区不一样。同一个电机,在不同温度下,死区也会变化。
我处理死区的方法很简单:
- 标定死区:上电后,让占空比从0逐渐增加,记录电机开始转动时的占空比。这个值就是死区下限。
- 映射占空比:把用户期望的0~100%速度,映射到死区下限到100%占空比之间。
- 反向死区:电机停止时,占空比要降到死区下限以下,否则电机不会停。
代码示例:
#define DEAD_ZONE_LOW 800 // 死区下限,电机开始转的最小占空比
#define DUTY_MAX 8191 // 13位分辨率最大值
uint32_t map_speed_to_duty(uint8_t speed_percent) {
if (speed_percent == 0) return 0; // 停止
uint32_t duty_range = DUTY_MAX - DEAD_ZONE_LOW;
return DEAD_ZONE_LOW + (duty_range * speed_percent / 100);
}
你看,这样处理后,用户设置50%速度,电机就能真正转到一半的转速,而不是在死区里空转。
避坑指南:我曾经遇到过,电机正转和反转的死区不一样。因为正反转时,电刷和换向器的接触角度不同。所以,正反转要分别标定死区,不能偷懒用一个值。
最后,总结一下这一讲的核心:
- LEDC 是硬件 PWM,用起来简单,跑起来稳定
- 频率选 1k~5kHz,分辨率选 13 位,兼顾噪音和精度
- 软启动和死区处理,是电机控制的必修课
- 闭环控制才是王道,开环只能应付玩具项目
下一讲,我们会把电机控制跟定时任务结合起来,实现真正的智能窗帘联动。到时候,你会看到 PWM 调速在真实场景中是怎么用的。