3.16. 舵机控制

业余(RC)舵机是装在密封外壳内、内置闭环位置控制的小型齿轮电机。外壳内部有一个直流电机、一个减速齿轮箱、一个连接到输出轴的电位器,以及一块小型驱动板,用于将电位器的读数与来自外部的设定值进行比较。驱动板会让电机朝着减小误差的方向转动,并在位置匹配时停止。从摄像头一侧看,这一切都不可见——你只需告诉舵机要去到哪里。

3.16.1. PWM 信号

舵机以固定 50 Hz 帧率的 PWM 信号作为其设定值,其中脉冲宽度决定位置:

  • 1.0 ms 的脉冲驱动轴转到其行程的一端。

  • 1.5 ms 的脉冲使轴停在中心位置。

  • 2.0 ms 的脉冲驱动轴转到行程的另一端。

介于两者之间的任何值都映射到一个中间位置。

三行方波波形垂直堆叠。 每一行展示一个 20 ms 周期的 50 Hz PWM,其起始处有一个 窄的高电平脉冲:顶行为 1.0 ms, 中间为 1.5 ms,底行为 2.0 ms。

舵机的 PWM 帧长为 20 ms;脉冲宽度(1.0 -- 2.0 ms)决定位置。

与 LED 和电机不同,舵机并不对 PWM 取平均。脉冲宽度本身就是命令:舵机的内部逻辑测量每个脉冲,据此设定目标,并驱动电机直到输出与之匹配。占空比作为一个比例(在整个范围内介于 5 % 和 10 % 之间)只是附带的——真正起作用的是绝对脉冲宽度,这也是软件需要控制的对象。

3.16.2. 接线

业余舵机使用三线连接器:

  • 电源(通常为红色):舵机自身的供电,通常为 4.8 V 到 6 V。不要用摄像头的 3.3 V 电平轨为舵机供电——它无法提供堵转电流,且该电平轨会出现欠压。

  • (通常为黑色或棕色):舵机电源的回流路径,连接到摄像头的地,使信号也具有共同的参考基准。

  • 信号(通常为白色、黄色或橙色):来自摄像头 GPIO 的 PWM 线。

3.16.3. 代码

duty_u16() 也可以用,但它是按周期的比例来设定占空比的——对于一个真正起作用的是绝对脉冲宽度、且周期固定的信号来说,这很不方便。duty_ns() 则直接以纳秒为单位设定脉冲宽度:

from machine import PWM, Pin

servo = PWM(Pin("P7"), freq=50, duty_ns=1_500_000)  # centre

载波为 50 Hz(20 ms 周期);每个周期的高电平时间正好是 1500 µs。一个小辅助函数让位置到脉冲的映射变得清晰明了:

def set_position(angle):
    # angle: 0..180 degrees mapped to 1.0..2.0 ms
    pulse_us = 1000 + (angle * 1000) // 180
    servo.duty_ns(pulse_us * 1000)

set_position(0)      # full one way
set_position(90)     # centre
set_position(180)    # full the other way

在整个范围内进行一次缓慢扫描:

import time

for angle in range(0, 181, 5):
    set_position(angle)
    time.sleep_ms(20)
for angle in range(180, -1, -5):
    set_position(angle)
    time.sleep_ms(20)

1.0 -- 2.0 ms 范围是标准值,但许多舵机为获得完整行程会接受更宽的范围(通常为 500 µs 到 2500 µs)。舵机的数据手册会列出确切的脉冲宽度极限;超出该范围的数值可能会让电机猛撞到其机械限位上。

对于范围非标准的舵机,将这些极限值提取为常量,并将映射参数化:

PULSE_MIN_US = 500     # full one way (from the data sheet)
PULSE_MAX_US = 2500    # full the other way

def set_position(angle):
    span_us = PULSE_MAX_US - PULSE_MIN_US
    pulse_us = PULSE_MIN_US + (angle * span_us) // 180
    servo.duty_ns(pulse_us * 1000)