第23章:舵机与电机控制
本章介绍舵机、直流电机、130电机的驱动原理与控制方法。
本章的三类执行器在控制方式上差别很大:舵机收脉宽、直流电机收方向和占空比、风扇模块收使能信号。它们共同的前提是电源——大电流负载不能从 Pico 的 3V3 或 VBUS 直接取电,否则电压跌落会让板子复位。接线之前先把供电方案定好,再谈代码。
23.1 舵机控制
23.1.1 舵机工作原理
舵机内部包含:
- 直流电机
- 减速齿轮组
- 位置传感器
- 控制电路
通过 PWM 信号控制角度。
23.1.2 PWM 信号规范
舵机不是普通的直流电机,它内部有减速齿轮、位置反馈和控制电路,接收的是角度指令而不是电压。控制信号是周期 20ms(50Hz)的 PWM,高电平宽度决定目标角度:0.5ms 对应一端,2.5ms 对应另一端,中间大致线性。这个范围只是常见标准,不同型号的舵机实际行程有偏差,有的只接受 0.4-2.6ms,有的把 500-2500 微秒映射到 180°。用满量程之前建议先小幅测试,找到不顶机械限位的两侧边界,再写进程序。
周期: 20ms (50Hz)
脉宽: 0.5ms (0°) ~ 2.5ms (180°)
RP2040 的 PWM 外设给出的是 16 位占空比,duty_u16 的取值 0-65535 对应一个完整周期。50Hz 时周期是 20ms,每个计数约 0.305 微秒,所以 0.5ms 对应 65535×0.5/20≈1638,2.5ms 对应 65535×2.5/20≈8192——代码里两个常数就是这么来的。弄不清这个换算,改频率或改量程时就不知道该填多少。
23.1.3 舵机驱动类
'''
舵机驱动类
'''
from machine import Pin, PWM
class Servo:
def __init__(self, pin):
self.pwm = PWM(Pin(pin))
self.pwm.freq(50)
self.min_duty = 1638 # 0.5ms
self.max_duty = 8192 # 2.5ms
self.current_angle = 90
def set_angle(self, angle):
"""设置角度 (0-180)"""
angle = max(0, min(180, angle))
duty = int(self.min_duty + (self.max_duty - self.min_duty) * angle / 180)
self.pwm.duty_u16(duty)
self.current_angle = angle
def smooth_move(self, target, step=1, delay_ms=20):
"""平滑移动"""
import time
while self.current_angle != target:
if self.current_angle < target:
self.current_angle = min(self.current_angle + step, target)
else:
self.current_angle = max(self.current_angle - step, target)
self.set_angle(self.current_angle)
time.sleep_ms(delay_ms)
def release(self):
"""释放舵机"""
self.pwm.duty_u16(0)
# 使用
servo = Servo(16)
servo.set_angle(0)
time.sleep(1)
servo.smooth_move(180)
这个类把换算封装在 set_angle 里,对外只暴露角度。set_angle 先用 max/min 把输入夹到 0-180,避免越界脉宽把舵机顶到限位;然后按线性关系把角度映射成占空比,写入 PWM 并记录当前角度,供 smooth_move 使用。 smooth_move 从当前角度逐度靠近目标,每步等 20ms:从 0 到 180 需要 180 步,约 3.6 秒,这个速度对多数小舵机合适。步长和延迟是两个可调参数,减小它们动作更快,但太小会让舵机来不及响应,表现为抖动或电流声。release 把占空比清零,舵机失去保持力矩,好处是不耗电、不发热,代价是外力可以把轴转动;机械臂这类需要保持位置的场合不要随便调用。
23.2 直流电机控制
23.2.1 驱动原理
GPIO 不能直接驱动直流电机,原因有三个:引脚输出电流只有毫安级,电机启动电流可达安培级;电机是感性负载,断电瞬间的反电动势会击穿引脚;正反转需要改变电流方向,单端输出做不到。所以中间必须加驱动芯片,常见的是 L298N、L9110、TB6612 这类 H 桥。上图的接法把两个 GPIO 分别接到 H 桥的两个输入,芯片输出接电机两端:一个高一个低决定转向,两个都低是自由停车,两个都高是短接绕组刹车。
直流电机需要驱动电路(如 L298N、L9110):
GPIO1 → 驱动芯片 → 电机+
GPIO2 → 驱动芯片 → 电机-
正转: GPIO1=高, GPIO2=低
反转: GPIO1=低, GPIO2=高
停止: GPIO1=低, GPIO2=低
23.2.2 PWM 调速
PWM 调速的本质是快速通断,用占空比等效出不同的平均电压。频率选 1kHz,避开了人耳最敏感的中频段,又不会让 H 桥来不及开关。占空比用 16 位表示:speed 是百分比,乘 65535 再除 100 得到计数值。代码里 forward 时 pwm1 输出占空比、pwm2 输出 0,backward 反过来,stop 两个都置 0。刹车函数把两个都置 65535,等于让电机两端同电位,绕组被短接,动能转成制动电流,停得快但电流大;自由停车则靠惯性滑行,适合不需要精确定位的场合。
'''
直流电机 PWM 调速
'''
from machine import Pin, PWM
class DCMotor:
def __init__(self, pin1, pin2):
self.pwm1 = PWM(Pin(pin1))
self.pwm2 = PWM(Pin(pin2))
self.pwm1.freq(1000)
self.pwm2.freq(1000)
def forward(self, speed=100):
"""正转"""
duty = int(speed / 100 * 65535)
self.pwm1.duty_u16(duty)
self.pwm2.duty_u16(0)
def backward(self, speed=100):
"""反转"""
duty = int(speed / 100 * 65535)
self.pwm1.duty_u16(0)
self.pwm2.duty_u16(duty)
def stop(self):
"""停止"""
self.pwm1.duty_u16(0)
self.pwm2.duty_u16(0)
def brake(self):
"""刹车"""
self.pwm1.duty_u16(65535)
self.pwm2.duty_u16(65535)
# 使用
motor = DCMotor(14, 15)
motor.forward(50) # 50% 速度正转
time.sleep(2)
motor.backward(30) # 30% 速度反转
time.sleep(2)
motor.stop()
这段实现有一个可以改进的地方:PWM 加在两个方向引脚上,靠通断来调速,低速时 H 桥频繁开关,效率和噪音都不理想。更常见的做法是把其中一个引脚设成固定电平定方向,另一个引脚接 H 桥的使能端,用 PWM 调使能。另外,代码假设电机两端不会同时为高,两路初始化后不会出现直通,这一点必须在驱动芯片允许的范围内确认。
23.3 130 电机风扇模块
23.3.1 模块介绍
130 电机风扇模块包含:
- 130 微型直流电机
- 风扇叶片
- 驱动电路
23.3.2 控制代码
130 电机风扇模块把微型直流电机、扇叶和驱动电路做在一起,对外只留电源与信号线。信号线接的是模块上的使能/控制端,不是电机绕组,所以可以直接用 GPIO 的 PWM 控制转速。模块的电源要按电机额定电压单独供,Pico 只负责给信号。
'''
实验:130 风扇电机控制
'''
from machine import Pin, PWM
fan = PWM(Pin(14))
fan.freq(1000)
def set_fan_speed(percent):
"""设置风扇速度 (0-100%)"""
duty = int(percent / 100 * 65535)
fan.duty_u16(duty)
# 风速等级
speeds = [0, 30, 60, 100]
for speed in speeds:
print(f"风速: {speed}%")
set_fan_speed(speed)
time.sleep(3)
这段代码把百分比换算成 16 位占空比,然后用列表依次试 0、30、60、100 四档,每档停 3 秒。实际使用有两点经验:一是风扇启动需要克服静摩擦,占空比太低时可能只发出嗡嗡声而不转,堵转电流反而更大,通常要给 20%-30% 的起步占空比;二是 1kHz 的 PWM 对风扇可能产生可听啸叫,能试的频率范围取决于模块驱动电路,宁可用 20kHz 以上,也要先确认模块支持。
23.3.3 温控风扇
'''
实验:温控风扇
'''
from machine import ADC
temp_sensor = ADC(4)
def read_temperature():
raw = temp_sensor.read_u16()
voltage = raw * 3.3 / 65535
return 27 - (voltage - 0.706) / 0.001721
while True:
temp = read_temperature()
# 温度-速度映射
if temp < 30:
speed = 0
elif temp < 35:
speed = 30
elif temp < 40:
speed = 60
else:
speed = 100
set_fan_speed(speed)
print(f"温度: {temp:.1f}°C 风速: {speed}%")
time.sleep(2)
温度来源是 RP2040 片内温度传感器,接在 ADC 通道 4 上,不是外部引脚。read_u16 返回 0-65535,按 3.3V 参考折算成电压,再套数据手册给的经验公式:27°C 时输出约 0.706V,温度每升高 1°C 电压下降约 1.721mV,所以 T = 27 − (V − 0.706) / 0.001721。要注意这个值是芯片结温,比环境温度高一些,而且受板子发热影响,用它做风扇控制时最好实测标定一个偏移量。另外,分段阈值在边界附近会反复切换,比如温度正好在 35°C 上下波动时风扇会频繁启停,加一点迟滞(35°C 开、33°C 关)就能解决。
23.4 多舵机协调
23.4.1 机械臂控制
多个舵机一起用时,最容易被忽略的是电源和机械限位。四个小舵机同时动作,峰值电流可以到 2-3A,远超 Pico 板载稳压器的能力,必须用独立的 5V 电源,并且与 Pico 共地——只连信号不连地,舵机会乱抖。机械限位则要在软件里留余量:程序里的 0-180 是舵机脉宽范围,不等于机械臂实际能到的角度,装上连杆后先手动找两侧极限,再把软件范围收窄十几度,避免每次都顶到限位。
'''
实验:简易机械臂
'''
class RobotArm:
def __init__(self, base_pin, shoulder_pin, elbow_pin, gripper_pin):
self.base = Servo(base_pin)
self.shoulder = Servo(shoulder_pin)
self.elbow = Servo(elbow_pin)
self.gripper = Servo(gripper_pin)
self.home()
def home(self):
"""归位"""
self.base.set_angle(90)
self.shoulder.set_angle(90)
self.elbow.set_angle(90)
self.gripper.set_angle(90)
def grab(self):
"""抓取"""
self.gripper.smooth_move(30)
def release(self):
"""释放"""
self.gripper.smooth_move(90)
def pick_and_place(self, from_pos, to_pos):
"""拾取放置"""
# 移动到拾取位置
self.base.smooth_move(from_pos[0])
self.shoulder.smooth_move(from_pos[1])
self.elbow.smooth_move(from_pos[2])
# 抓取
self.grab()
time.sleep(0.5)
# 移动到放置位置
self.shoulder.smooth_move(90) # 抬起
self.base.smooth_move(to_pos[0])
self.shoulder.smooth_move(to_pos[1])
self.elbow.smooth_move(to_pos[2])
# 释放
self.release()
# 使用
arm = RobotArm(16, 17, 18, 19)
arm.pick_and_place((45, 60, 120), (135, 60, 120))
这个类把四个舵机包装成一组关节。home 把所有关节回到 90° 中位,grab 与 release 通过小角度变化控制夹爪,pick_and_place 按「基座—肩—肘」的顺序靠近目标,抓起后先抬肩再转基座,最后在放置点展开。动作顺序有实际意义:如果先转基座再抬肩,末端会沿着弧线扫过工作区,容易碰到旁边的物体。代码里两处肩关节都给了 90°,相当于每次返回同一个安全高度,真实应用应该把中间位单独定义成常量,便于调整。
23.5 本章小结
本章介绍了执行器控制:
- 舵机控制:
- PWM 信号控制角度
- 平滑移动实现
- 直流电机:
- H 桥驱动原理
- PWM 调速
- 应用案例:
- 温控风扇
- 机械臂控制
下一章将学习综合项目实践。