MicroPythonでSG90が途中停止する原因候補とduty_u16の切り分け

W5100S-EVB-Pico(RP2040)上のSG90 MicroPython制御で、大きな往復は動くのに細かい角度変更で途中停止し、異音や振動が出る、という症状が出ることがあります。
手で軸を動かすと再び動き出すように見える場合も、コードのバグだけとは限りません。

本記事では、確認できた症状と仮説を分けて整理します。
duty_u16()とPWM値・パルス幅・電圧の関係、メーカー公式の仕様の読み方、Thonnyの警告との切り分け、安全な確認手順と改善サンプルをまとめます。

ポイント

原因を1つに断定せず、「再現した現象」と「仮説」を分けてください。
電圧を上げる前に負荷時の実測を行い、定格を超える給電は勧めません。

確認できた症状と、断定しない理由

今回の話題でよく揃う症状は、次のセットです。
いずれも「観察事実」として扱い、原因ラベルは後段の仮説として置きます。

  • 広い角度の往復ではサーボが動く
  • 細かい角度変更や小刻みな指令で途中停止する
  • 停止中に異音・振動が出ることがある
  • 手で軸を動かすと、再び追従するように見える

サーボはPWM指令に対して内部フィードバックで位置を合わせる機構です。
途中停止は、ソフトのデューティ計算、電源の瞬時低下、機械的な端当たり、ギアやポテンショの不調など、複数経路で同じ見た目になり得ます。

そのため「この症状=電源不足」や「この症状=コード誤り」と決めつけず、測定と再現条件で候補を絞る方が安全です。
特に市販のSG90相当品はクローンが多く、個体差も大きい点に注意します。

W5100S-EVB-PicoとSG90の前提(公式ベース)

基板側は、WIZnetのW5100S-EVB-Pico資料を基準にします。
RP2040ベースでRaspberry Pi Pico互換のピン配置を持ち、Ethernet用にGPIO16〜21がW5100Sへ接続されています。

公式の動作条件では、VBUSは公称5V(±10%)、VSYSは4.3V〜5.5V、3V3はオンボードLDO出力です。
サーボ信号線はGPIO(3.3V IO)から出せますが、モータ電源を3V3から取るのは一般に不向きです。

サーボ側は、Tower ProのSG90製品ページを優先します。
Analog/Digitalとも仕様欄では Operating voltage が 4.8v、Power Supply は Through External Adapter と記載されています。

Operating voltage: 4.8v
Power Supply: Through External Adapter

引用:SG90 Analog – Tower Pro

Digital版の製品ページコメントでは、運用電流がおおよそ0.5A〜2A、4.8V〜6Vで問題ない、というメーカー側の返答もあります。
これはサポート回答であり、製品仕様欄の「4.8v」表記より広い範囲です。
本稿では「仕様欄は4.8V、サポート回答では4.8〜6V」と区別し、実測なしに電圧を上げる判断はしません。

可動角についても、同ページのメーカー返答では「通常サーボは指定がなければ0〜150度」とあります。
ネット上の「180度固定」や「必ず1〜2ms」といった値を、根拠なく断定しない方がよいです。

duty_u16による角度制御と、PWM・パルス幅・電圧の関係

MicroPythonのmachine.PWMでは、duty_u16はデューティ比を 0〜65535 の整数で表します。
公式どおり、設定値は value / 65535 の比率です。

With a single value argument the duty cycle is set to that value, measured as the ratio value / 65535.

引用:class PWM – MicroPython documentation

アナログサーボは、多くの場合およそ50Hz(周期20ms)のPWMで、ハイ時間(パルス幅)が目標角に対応します。
パルス幅そのものを扱うなら duty_ns の方が直感的ですが、duty_u16 を使う場合は次の関係で換算できます。

# freq = 50 Hz のとき周期 T = 20 ms
# pulse_ms = (duty_u16 / 65535) * 20
# duty_u16 = int(pulse_ms / 20 * 65535)
目安パルス幅freq=50Hz の duty_u16(計算値)意味の扱い
1.0 ms3277広く流通する「一端付近」の目安
1.5 ms4915中立付近の目安
2.0 ms6554広く流通する「他端付近」の目安

ここが重要です。
Tower Proの製品ページ自体は、パルス幅の詳細表を仕様欄に載せていません。

したがって上表は「計算の出発点」であり、個体の安全な可動範囲ではありません。
最初から極端なパルスへ飛ばさず、中立付近から少しずつ広げて、端当たりや異音が出る直前で止めるのが安全です。

電圧との関係も混同しやすいです。
PWMのデューティは「信号の時間比率」であり、信号線の論理レベル(RP2040では3.3V系)と、サーボのモータ電源電圧は別物です。

モータ電源が低下するとトルクが落ち、細かい位置合わせでストールしやすくなります。
逆に、信号デューティが機械端を超える指令だと、電源が十分でも端で唸り続けます。

原因候補(仮説):事実と推測を分ける

以下は断定ではなく、症状セットと照合しやすい候補です。
切り分けでは、1回の実験で仮説を1つだけ動かすのが基本です。

1. 可動範囲の超過(PWM指令が機械端を超える)

メーカー返答どおり、通常品は0〜150度想定の可能性があります。
コード上で「0〜180度」や「0〜65535の広いデューティ」へ線形マップすると、端でギアやストッパに押し付け続けます。

往復の大きな動きでは一瞬で通過して気づきにくく、細かい角度変更では端付近に留まり、異音・振動・発熱として現れやすいです。
手で軸を動かすと、押し付けが一時的に解けて再び追従するように見えることがあります。

2. 電源電圧の低下(特に起動・微調整時の電流)

公式仕様欄は4.8V、サポート回答では運用電流が0.5A〜2A級とされています。
USB給電や細い配線、ブレッドボード接点だと、ストール時に電圧が瞬時低下しやすいです。

大きな往復では慣性と回復で動いて見え、小刻み指令では電流が断続的に増え、途中停止に見えることがあります。
仮説のまま電圧を上げる前に、動作中のVSYS/サーボ電源を実測してください。

3. ギアの引っ掛かり・摩耗

POMギアのSG90は、過負荷や端押し付けで歯飛びや偏摩耗が起きやすいです。
特定角だけ止まる、手回しで軽い箇所と重い箇所がある、という再現なら機構側を疑います。

4. ポテンショメータの不具合・接触不良

内部フィードバック用ポテンショの接触不良でも、指令角と実角が食い違い、唸りやハンチングが起きます。
ソフトを変えても特定個体だけ再現するなら、交換比較が有効です。

5. 配線・共有GND・ピン干渉

W5100S-EVB-PicoではGPIO16〜21がEthernet用です。
信号ピンの取り違えや、電源GND未共有も、不安定動作の原因候補になります。

注意

定格超過の給電は勧めません。
まず負荷時電圧を測り、外部電源と太めの配線、共通GNDを整えてください。

ThonnyのSyntaxWarningとKeyboardInterruptは別問題

サーボが止まった直後にThonnyのシェルへ警告や例外が出ると、因果を取り違えやすいです。
結論から言うと、これらはサーボ機構の不具合そのものではありません。

SyntaxWarning

Pythonの文字列リテラルで、認識されないエスケープ(例: パスや正規表現のバックスラッシュ)があると、SyntaxWarning: invalid escape sequence が出ます。
実行時のモータ制御失敗ではなく、ソース表記の警告です。

raw文字列(r"...")や二重バックスラッシュへ直す対象であり、デューティ計算や電源とは無関係です。
警告があってもサーボが動くことは普通にあります。

KeyboardInterrupt

KeyboardInterrupt は、実行中に割り込み(多くの環境ではCtrl+C、Thonnyでは停止操作)が入ったときに上がる例外です。
Python公式チュートリアルでも、割り込み入力でこの例外になることが説明されています。

つまり「サーボが固まったから例外が出た」のではなく、「停止操作をしたから例外が見えた」ことが多いです。
停止時にPWMをdeinitしないと、最後のデューティが残り、端で唸り続けることがあります。
これは別途安全停止処理で防ぎます。

安全な切り分け手順

分解や電圧アップの前に、次の順で事実を集めます。
各STEPで変える条件は1つにしてください。

  1. 配線を確認する(信号GPIO、外部電源、共通GND、Ethernet占有ピンの回避)
  2. 無負荷で中立付近(目安1.5ms)だけ出し、静止できるか見る
  3. パルス幅を±0.1msずつ広げ、異音が出る直前で上限・下限をメモする
  4. 大きな往復と、2〜5度相当の細かい往復を別々に試し、再現条件を記録する
  5. 動作中のサーボ電源電圧をテスタ/オシロで実測する(停止時だけでなく動かしながら)
  6. 別個体のSG90へ交換し、同一コードで症状が追従するか見る
  7. Thonnyの警告・停止例外はログとして分け、サーボ症状と因果付けしない

電圧が負荷時に大きく落ちるなら、電源経路の改善が先です。
電圧が十分でも特定角だけ唸るなら、可動範囲マップか機構側を疑います。

改善策を反映したMicroPythonサンプル

次の例は、W5100S-EVB-Pico想定で信号をGPIO15へ出す構成です。
Ethernetを使う場合はGPIO16〜21を避けてください。

ポイントは、(1) パルス幅を直接管理する、(2) 実測した安全範囲へクランプする、(3) 細かい移動はステップと待機を入れる、(4) 割り込み時にPWMを止める、の4点です。

from machine import Pin, PWM
import time

# 信号ピン例(Ethernet占有の GP16-21 は避ける)
SERVO_PIN = 15
FREQ_HZ = 50

# 実機で異音が出ない範囲へ更新する(単位: ns)
# 初期値は一般的な目安。個体キャリブレーション必須。
PULSE_NS_MIN = 1_000_000   # 1.0 ms
PULSE_NS_MID = 1_500_000   # 1.5 ms
PULSE_NS_MAX = 2_000_000   # 2.0 ms

# 角度マップは「表示用」。機械角の断定ではない。
ANGLE_MIN = 20
ANGLE_MAX = 160


def pulse_ns_from_angle(angle):
    angle = max(ANGLE_MIN, min(ANGLE_MAX, angle))
    ratio = (angle - ANGLE_MIN) / (ANGLE_MAX - ANGLE_MIN)
    return int(PULSE_NS_MIN + ratio * (PULSE_NS_MAX - PULSE_NS_MIN))


def duty_u16_from_pulse_ns(pulse_ns, freq_hz=FREQ_HZ):
    period_ns = 1_000_000_000 // freq_hz
    return int(pulse_ns * 65535 / period_ns)


class Sg90Safe:
    def __init__(self, pin=SERVO_PIN):
        self.pwm = PWM(Pin(pin))
        self.pwm.freq(FREQ_HZ)
        self._angle = (ANGLE_MIN + ANGLE_MAX) // 2
        self.set_angle(self._angle, settle_ms=300)

    def set_pulse_ns(self, pulse_ns):
        pulse_ns = max(PULSE_NS_MIN, min(PULSE_NS_MAX, pulse_ns))
        # duty_ns が使える環境ではこちらが分かりやすい
        try:
            self.pwm.duty_ns(pulse_ns)
        except (AttributeError, ValueError):
            self.pwm.duty_u16(duty_u16_from_pulse_ns(pulse_ns))

    def set_angle(self, angle, settle_ms=200):
        self._angle = angle
        self.set_pulse_ns(pulse_ns_from_angle(angle))
        time.sleep_ms(settle_ms)

    def move_smooth(self, target, step=2, step_ms=30):
        """細かい移動で電流スパイクと端押し付けを抑える"""
        target = max(ANGLE_MIN, min(ANGLE_MAX, target))
        cur = self._angle
        direction = 1 if target >= cur else -1
        for a in range(cur, target, direction * step):
            self.set_angle(a, settle_ms=step_ms)
        self.set_angle(target, settle_ms=step_ms)

    def deinit(self):
        self.pwm.deinit()


servo = Sg90Safe()

try:
    # 往復(大きめ)
    servo.move_smooth(40)
    servo.move_smooth(140)

    # 細かい動き(ここで止まっていた場合の改善例)
    for ang in range(80, 101, 1):
        servo.move_smooth(ang, step=1, step_ms=40)
    for ang in range(100, 79, -1):
        servo.move_smooth(ang, step=1, step_ms=40)

except KeyboardInterrupt:
    # Thonny停止時。サーボ不具合ではない。
    pass
finally:
    servo.deinit()

改善の意図を短くまとめます。
duty_u16 直書きのマジックナンバーをやめ、パルス幅へ一度落としてから換算します。

可動マップは20〜160度のように余白を残し、実機で縮めます。
細かい指令は一発変更せず、move_smooth で電流と機構負荷を分散します。

KeyboardInterrupt ではPWMを解放し、最後の端デューティが残らないようにします。
電源は公式どおり外部アダプタ側を基本とし、USBの3V3からのモータ給電は避けてください。

まとめ

SG90 MicroPython制御で「往復は動くが細かく動かすと止まる」ときは、コード・電源・機構のどれか一つに決めつけないことが先です。
duty_u16 は周期に対する比率であり、パルス幅とモータ電圧は分けて考えます。

Tower Pro公式の仕様欄は4.8V・外部電源を示し、可動角やパルス幅の詳細はページ上で断定しにくいです。
だからこそ、中立から実測で安全範囲を決め、負荷時電圧を確認してから対策するのが最短です。

ThonnyのSyntaxWarningやKeyboardInterruptは、サーボ故障の証拠ではありません。
警告は文字列表記、割り込みは停止操作として切り分け、PWMの安全停止と電源・可動範囲の確認を優先してください。


Amazonベストセラー

返信を残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA