3.16. サーボ制御

ホビー(RC)サーボは、密閉ケースの中に組み込まれた閉ループ位置制御付きの小型ギヤードモーターです。ケースの中には、DCモーター、減速ギヤボックス、出力軸に接続されたポテンショメータ、そしてポテンショメータの読み取り値を外部から入ってくる設定値と比較する小さなドライバ基板が収められています。ドライバは誤差を減らす方向にモーターを回し、位置が一致すると停止します。カメラ側からはこれらは一切見えません。サーボにどこへ動くかを指示するだけです。

3.16.1. PWM信号

サーボは設定値を固定の50 HzフレームレートのPWM信号として受け取り、パルス幅が位置を選択します。

  • 1.0 msのパルスは軸を可動範囲の一方の端へ駆動します。

  • 1.5 msのパルスは軸を中央に保持します。

  • 2.0 msのパルスは軸をもう一方の端へ駆動します。

その中間の値は中間位置に対応します。

矩形波の波形が縦に3行積み重なっている。各行は50 HzのPWMの20 ms周期1つ分を示し、先頭に幅の狭いハイパルスがある。上の行は1.0 ms、中央は1.5 ms、下は2.0 ms。

サーボのPWMフレームは20 msの長さで、パルス幅(1.0 -- 2.0 ms)が位置を選択します。

LEDやモーターとは異なり、サーボはPWMを平均しません。パルス幅そのものが指令です。サーボの内部ロジックは各パルスを測定し、それに応じて目標を設定し、出力が一致するまでモーターを回します。デューティサイクルの割合(全範囲で5 %から10 %の間)は副次的なもので、重要なのは絶対パルス幅であり、それがソフトウェアで制御すべきものです。

3.16.2. 配線

ホビーサーボは3線式コネクタを使用します。

  • 電源(一般的に赤):サーボ自身の電源で、通常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)