4、PWM与舵机控制:PWM原理、舵机角度控制、定时器库使用、实战:药盒舱门自动开合
各位同学,欢迎来到第四章。这一章我们要让药盒“动”起来。
前面几章,我们让药盒有了“大脑”(主控)和“眼睛”(传感器),也学会了联网。但药盒最终是要开门的,对吧?总不能让人用手去掰。所以,这一章的核心就是——控制舵机,实现舱门自动开合。
说白了,就是让一个电机转到指定的角度。听起来简单,但里面有个关键概念:PWM。我当年第一次接触PWM时,觉得它就是“一闪一闪的灯”。后来才发现,它几乎是所有电机控制的基石。
4.1 PWM原理:不是“开关”,是“占空比”
PWM,全称 Pulse Width Modulation,脉冲宽度调制。名字很唬人,其实原理很简单。
想象一下,你用手电筒照一个房间。如果你一直开着,房间最亮。如果你快速开关,房间就会变暗。PWM就是这种“快速开关”的电子版。
关键参数有两个:
- 频率:每秒开关多少次。比如50Hz,就是一秒开关50次。
- 占空比:一个周期内,高电平(开)的时间占比。50%占空比,就是一半时间开,一半时间关。
舵机控制,用的就是固定频率(通常是50Hz),通过改变占空比来控制角度。
核心公式:
占空比 = 高电平时间 / 周期时间 × 100%
对于舵机:周期 = 20ms(50Hz),高电平时间在 0.5ms ~ 2.5ms 之间。
为什么会这样?因为舵机内部有个比较器。它把输入的PWM信号和内部电位器反馈的信号做比较。差值越大,电机转得越猛,直到两者相等,电机停止。所以,PWM的占空比直接决定了舵机的目标角度。
嗯,这里要注意:不同品牌的舵机,PWM范围可能略有差异。我遇到过一款便宜的MG996R,它的0度对应0.5ms,180度对应2.5ms。但有些舵机是0.6ms到2.4ms。所以,买舵机后,第一件事就是看数据手册。
4.2 舵机角度控制:从0°到180°
舵机通常有三根线:VCC(红)、GND(棕/黑)、Signal(橙/黄)。信号线接开发板的PWM输出引脚。
角度和脉宽的对应关系,我一般用线性映射。比如0°对应0.5ms,180°对应2.5ms。那么90°就是1.5ms。
换算成占空比:
- 0°:0.5ms / 20ms = 2.5%
- 90°:1.5ms / 20ms = 7.5%
- 180°:2.5ms / 20ms = 12.5%
你看,角度和占空比是线性关系。写代码时,直接用公式算就行。
我的小技巧:
我习惯把角度和脉宽的映射关系写成一个函数。这样换舵机时,只需要改两个参数(最小脉宽、最大脉宽),不用改主逻辑。
4.3 定时器库使用:别自己造轮子
要产生精确的PWM信号,需要用到定时器。ESP32有4个定时器,每个定时器可以输出多路PWM。
我个人强烈建议使用LEDC库(ESP32内置)。它专门用来产生PWM信号,使用起来非常方便。
基本步骤:
- 配置定时器:设置频率和分辨率(比如50Hz,12位分辨率)。
- 绑定通道:把定时器通道和GPIO引脚绑定。
- 设置占空比:直接写占空比数值,库会自动处理。
代码示例:
#include <ESP32Servo.h>
Servo myServo; // 创建舵机对象
void setup() {
myServo.attach(13); // 信号线接GPIO13
// 默认频率50Hz,脉宽范围500~2500微秒
}
void loop() {
myServo.write(0); // 转到0度
delay(1000);
myServo.write(90); // 转到90度
delay(1000);
myServo.write(180); // 转到180度
delay(1000);
}
你看,用库就是这么简单。但注意,ESP32Servo库底层也是用LEDC实现的。它自动帮你管理了定时器通道。
避坑指南:
我曾经犯过一个低级错误:同时用了多个舵机,但没注意定时器通道冲突。结果一个舵机转,另一个也跟着抖。后来才发现,ESP32的LEDC只有8个通道,每个通道只能绑定一个定时器。如果多个舵机共用同一个定时器,频率必须一致。所以,不同频率的舵机,要用不同的定时器。
4.4 实战:药盒舱门自动开合
好了,理论讲完,咱们直接上实战。目标:当收到服药指令时,舵机转动90度打开舱门;5秒后,自动关闭。
硬件连接:
- 舵机VCC → 5V(外部电源,别用开发板3.3V,电流不够)
- 舵机GND → GND(共地)
- 舵机Signal → GPIO13
代码实现:
#include <ESP32Servo.h>
Servo doorServo;
const int servoPin = 13;
const int openAngle = 90; // 打开角度
const int closeAngle = 0; // 关闭角度
const int openDuration = 5000; // 打开持续时间(毫秒)
void setup() {
Serial.begin(115200);
doorServo.attach(servoPin, 500, 2500); // 设置脉宽范围
doorServo.write(closeAngle); // 初始关闭
Serial.println("药盒初始化完成,舱门已关闭");
}
void loop() {
// 模拟收到服药指令(实际项目中,这里会接MQTT或按键)
if (Serial.available()) {
char cmd = Serial.read();
if (cmd == 'o') { // 收到 'o' 打开舱门
openDoor();
} else if (cmd == 'c') { // 收到 'c' 关闭舱门
closeDoor();
}
}
}
void openDoor() {
Serial.println("打开舱门...");
doorServo.write(openAngle);
delay(500); // 给舵机一点时间转到目标位置
Serial.println("舱门已打开");
delay(openDuration); // 保持打开状态
closeDoor(); // 自动关闭
}
void closeDoor() {
Serial.println("关闭舱门...");
doorServo.write(closeAngle);
delay(500);
Serial.println("舱门已关闭");
}
这段代码很简单,但有几个细节要注意:
- 脉宽范围:
attach()函数里我传了500和2500,对应0.5ms和2.5ms。如果你的舵机不同,记得改。 - 延时问题:
delay(500)是为了让舵机有足够时间转到目标位置。如果立即执行下一个动作,舵机可能还没到位。 - 自动关闭:打开后5秒自动关闭,这是药盒的基本逻辑。实际项目中,你可能需要检测舱门是否被卡住(比如加个限位开关)。
扩展思考:
如果药盒有多个舱门,怎么办?很简单,多买几个舵机,每个舵机接不同的GPIO。但要注意电源功率。一个舵机堵转时电流可能到1A,三个同时堵转,5V电源得至少3A。我建议每个舵机单独供电,或者用一个大功率电源。
嗯,这一章的内容就到这里。PWM和舵机控制,是嵌入式开发中非常基础但又极其重要的技能。你想想看,从无人机到机械臂,从智能家居到机器人,哪个离得开舵机?
下一章,我们会把舵机控制和WiFi联网结合起来,实现远程开药盒。到时候,你就能在手机上点一下,家里的药盒就自动打开了。是不是很酷?
好,今天的课就到这里。有问题随时在群里问我。记住,动手实践是最好的学习方式。别光看代码,去接个舵机试试。