PR

MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす:PWM制御とキャリブレーション実践

ハードウェア制御 / 電子工作

MG996R × Pico 2W

スポンサーリンク

はじめに

こんにちは。ニンジンです🥕

今回はRaspberry Pi Pico 2Wと、ホビー用の定番サーボモーター「MG996R」を組み合わせて動かしてみます。タクトスイッチを押すとサーボが指定した角度まで動く、というシンプルな工作ですが、実際にやってみると「コード通りの角度に動いてくれない」「サーボホーンが狙った位置に取り付けられない」など、実機ならではのつまずきがいろいろありました。この記事では、その試行錯誤も含めて紹介します。

この記事で分かること

  • MicroPythonでPWM制御してサーボモーターを動かす基本
  • 安価なサーボは公称スペック通りに動くとは限らない、実機キャリブレーションの手順
  • サーボホーンの取付角度のズレをソフトウェアで補正する方法
  • タクトスイッチで複数の動作パターンを切り替える実装

スポンサーリンク

MG996Rとは?Raspberry Pi Pico 2Wで制御できる180度サーボモーターの基礎知識

MG996Rは、ラジコンやロボット工作で定番のメタルギア・デジタルサーボです。

MG996Rの主要スペック(トルク・可動範囲・消費電流)

  • 可動範囲: 0〜180度(公称値。個体差があり、後述の通り今回は実測で補正が必要でした)
  • 動作電圧: 4.8V〜7.2V(6V駆動が標準)
  • 制御方式: PWM(パルス幅変調)、周波数50Hz
  • ギア: メタルギア(耐久性が高く、負荷のかかる可動部に向く)

サーボモーターは、送られてくるパルス幅(us)によって回転する角度が変わる仕組みです。一般的には「パルス幅500us=0度、パルス幅2500us=180度」という目安が使われますが、これはあくまで目安です。後述の通り、今回使用した個体はこの通りには動きませんでした。


スポンサーリンク

必要な部品

今回使用した部品です。

今回使用する部品一式(開封して並べたところ)
▲ MGR996Rを開封して並べたところ
MG996R メタルギア・デジタルサーボ
実機使用

今回の実機はこちら

MG996R メタルギア・デジタルサーボ

180度の可動範囲を持つ、ホビー向けの定番メタルギアサーボです。今回の記事はこの実機を使って検証しています。

メタルギア高トルク180度

Amazonは在庫切れのため掲載していません(楽天市場のみ)

MG995 メタルギア・デジタルサーボ(2個セット)
代替品

MG996Rが入手困難な場合に

MG995 メタルギア・デジタルサーボ(2個セット)

MG996Rが品薄・入手困難な場合の代替候補です。型番は異なりますが、メタルギア仕様でスペックはほぼ同等です。

メタルギア2個セット代替品
Raspberry Pi Pico 2WH
必須

無線対応・ヘッダー実装済み

Raspberry Pi Pico 2WH

新世代RP2350搭載、Wi-Fi/Bluetooth対応で、ヘッダーピンもはんだ付け済みのモデルです。届いてすぐブレッドボードで使えます。

RP2350搭載Wi-Fi/Bluetoothヘッダー実装済み
ブレッドボード 400穴
必須

配線の土台に

ブレッドボード 400穴

サーボ・タクトスイッチ・電源周りをまとめて配線するための土台です。400穴サイズなら今回の構成には十分な広さです。

ハンダ付け不要400ポイント
Freenove ブレークアウトボード
必須

配線ミスの防止に

Freenove ブレークアウトボード

Pico(全世代対応)を差し込むだけで全ピンを分かりやすくラベル表示してくれる拡張ボードです。

全世代対応ピン名表示
ELEGOO 50pcsオスメスジャンパーワイヤ
必須

配線に足りないワイヤーはこれで解決

ELEGOO 50pcs オスメスジャンパーワイヤ

ミニブレッドボード付属で、追加購入なしですぐ使い始められます。

オスメス200mmミニブレッドボード付属
USB電源ブレイクアウトボード
必須

USB電源をブレッドボードに引き込む

USB電源ブレイクアウトボード

USB Type-Aメスコネクタをブレッドボードに挿せるピンヘッダー形状に変換する部品です。ケーブルを傷めずにUSB電源を引き込めます。

USB Type-ADIP変換
タクトスイッチ 10種類セット
必須

0°→90°/180°を切り替える2個入り

タクトスイッチ(LeTradeJP 10種類セット)

サイズ違いのタクトスイッチが10種類入ったセットです。今回はこのうち2個を、A/Bボタンとして使用します。

タクトスイッチ10種類セット
上記に加え、以下は手持ちの汎用部品を使用しました。特定商品ではないため、紹介は割愛します。
  • USB ACアダプタ(5V/2A)+ USBケーブル1本(被覆を剥いて電源線として使用)
  • 電解コンデンサ 16V/220μF(電源ラインの安定化用)

スポンサーリンク

配線方法:MG996RとRaspberry Pi Pico 2Wの結線

電源をPicoの3.3V/USBから取ってはいけない理由

MG996Rは動作時に数百mA、場合によっては1A近い電流が流れます。Pico側のUSBや3V3ピンから直接給電すると、Pico側のレギュレータの許容電流を超えてリセットや破損のリスクがあるため、サーボ用の電源はPicoとは別に用意します。今回はUSB ACアダプタ(5V/2A)から、USBケーブルを1本潰して電源線として使いました。

ポイント:USB電源ブレイクアウトボードを使えば、ケーブルを潰さずにUSB Type-Aコネクタからブレッドボードへ電源を引き込めます。

GPIOピンの割り当てと信号線の接続

線・部品接続先
MG996R 信号線(オレンジ)Pico 2W GP15
MG996R 電源線(赤)USB電源 +5V
MG996R GND線(茶)USB電源GND、かつPico 2WのGNDと共通接続
タクトスイッチAGP14 – GND間
タクトスイッチBGP13 – GND間
今回の結線図

▲ 今回の結線図

今回の配線全体を図にまとめました。MG996Rの3本の線(信号・電源・GND)と、タクトスイッチ2個の配線が中心です。

結線全体の様子

▲ 結線全体の様子

ブレッドボード上に実際に組んだ結線の全体像です。図と見比べながら配線すると分かりやすいです。

USBケーブルを潰して電源供給

▲ ブレッドボードの5V/GNDに、USBケーブルを潰して電源供給している様子

USB ACアダプタから伸びるUSBケーブルを1本潰し、赤線(+5V)・黒線(GND)をブレッドボードの電源レールに接続しています。

電解コンデンサとサーボ電源ラインの接続

▲ 電解コンデンサとMG996Rの電源ラインを接続している様子

電源ライン(5V-GND間)に電解コンデンサ(16V/220μF)を入れることで、サーボ起動時の突入電流やノイズによる電源の乱れを抑えられます。

Pico側GPIOへの接続

▲ Pico+ブレイクアウトボードのGP13/GP14/GP15/GNDへの接続

Freenoveブレークアウトボード経由で、GP15(サーボ信号線)・GP14(スイッチA)・GP13(スイッチB)・GNDへ配線しています。

タクトスイッチの配線

▲ タクトスイッチをブレッドボードに配置・接続した様子

タクトスイッチ2個をブレッドボードに配置し、それぞれGP14-GND間、GP13-GND間に接続しています。

注意:MG996RのGND線は、USB電源のGNDだけでなく、Pico 2WのGNDピンにも必ず接続してください。これを忘れると、Pico側とサーボ側で電気的な基準(0V)がズレてしまい、PWM信号が正しく認識されず誤動作します。実際に共通GNDを外した状態で動かすと、以下の動画のような不安定な動きになりました。

共通GNDを接続しなかった場合の誤動作の様子


スポンサーリンク

MicroPythonでPWM制御してMG996Rを動かす

サーボ制御の基本:PWM周波数とデューティサイクル(角度)の関係

サーボモーターの角度制御には、周波数50Hz(20ms周期)のPWM信号を使い、そのパルス幅(HIGHの時間)で角度を指定します。一般的な目安は「500us〜2500usで0〜180度」です。

まずは動かしてみる(サンプルコード)

from machine import Pin, PWM
import time

# GP15にMG996Rの信号線(オレンジ)を接続している前提
servo = PWM(Pin(15))
servo.freq(50)  # サーボ制御の標準周波数: 50Hz

def set_angle(angle):
    # 0〜180度をパルス幅500us〜2500usにマッピング
    min_us = 500
    max_us = 2500
    pulse_width_us = min_us + (angle / 180) * (max_us - min_us)
    servo.duty_ns(int(pulse_width_us * 1000))

while True:
    set_angle(0)
    time.sleep(1)
    set_angle(90)
    time.sleep(1)
    set_angle(180)
    time.sleep(1)

対応ファイル: servo-moter-test001.py

Thonnyでservo-moter-test001.pyを開いた画面
▲ Thonnyでservo-moter-test001.pyを開いた画面

0°→90°→180°で動かした様子

✓ 動作確認方法

  • Thonnyでコードを開き、F5キーで実行する
  • MG996Rが0度→90度→180度の順に往復動作すれば成功

スポンサーリンク

実測したら公称180度のはずが198度まで回転した

sleep不足で「動いている途中」を最終角度と誤認していた

実は最初、set_angle(180)を実行した際に「180度のはずが実機は120度くらいしか動いていない」ように見えて混乱しました。原因を探るため、パルス幅を直接指定して3秒間静止させて確認するテストコードを作ったところ、同じus値でも今度は約190度動いていることが判明。これは、MG996Rの移動速度(0.17秒/60度程度)に対してtime.sleep(0.5)が短すぎて、まだ回転している「移動途中」の角度を最終角度と誤認していたことが原因でした。sleep時間を1〜3秒に伸ばすことで、正確な最終角度を確認できるようになりました。

実測キャリブレーション:公称180度のはずが実測198度だった

パルス幅を手動で指定して実機の角度を確認するテストコードです。

from machine import Pin, PWM
import time

servo = PWM(Pin(15))
servo.freq(50)

def set_pulse_us(us):
    servo.duty_ns(int(us * 1000))

test_values = [500, 1500, 2500]

for us in test_values:
    print(f"パルス幅 {us}us に設定します。実機の角度を確認してください。")
    set_pulse_us(us)
    time.sleep(3)

print("テスト終了")

対応ファイル: servo-moter-test003.py

自作の簡易分度器(A4印刷)をサーボに当てて実測したところ、以下の結果になりました。

us実測角度
5000°(基準)
150097°
2500198°
印刷して使用した簡易分度器
▲ 印刷して使用した簡易分度器
us=500で実行した様子
▲ us=500で実行した様子
us=1500で実行した様子
▲ us=1500で実行した様子
us=2500で実行した様子
▲ us=2500で実行した様子

想定していた「500〜2500usで0〜180度」という前提に対し、実際は「500〜2500usで0〜198度」動いてしまう個体でした。安価なクローン品では、この手の個体差がよくあります。

サーボホーンが狙った角度に物理的に取り付けられない問題

さらに、サーボホーン(アーム)はスプライン(ギザギザの溝)構造になっているため、任意の角度では取り付けられません。今回、狙った0度の位置には取り付けられず、実測で約-7度の位置にしか固定できませんでした(上のus=500の写真はこの状態です)。

これは物理的に付け直すのではなく、プログラム側にオフセット値を持たせて吸収します。

HORN_OFFSET_DEG = 7

def set_angle(angle):
    corrected_angle = angle + HORN_OFFSET_DEG
    pulse_width_us = MIN_US + (corrected_angle / 180) * (MAX_US - MIN_US)
    servo.duty_ns(int(pulse_width_us * 1000))

下限側はus=470でストールした:上限と下限で余裕が全然違った

補正の方向を検討する際、逆方向(HORN_OFFSET_DEG = -7)も試したところ、サーボが唸ってストールしてしまいました。下限側を細かく調べたところ、us=470で唸り始めることが判明。一方、上限側はus=2500(198度相当)まで唸らずに動いていたので、上限には約18度分の余裕があったのに対し、下限にはわずか3度分程度の余裕しかない、という非対称な結果でした。この結果、ホーンのズレ補正には安全な上限方向(+7)を採用し、ホーン自体もマイナス側に寄せて物理的に取り付けることにしました。

キャリブレーション後の実測確認

最終的なキャリブレーション値(MIN_US=500, MAX_US=2340, HORN_OFFSET_DEG=7)で、以下の値を実測しました。

指定us値実測角度
0度571意図した0度付近
90度1491意図した90度付近
180度2411意図した180度付近
test003.pyでus=571を実行した際のコンソール出力
▲ test003.pyでus=571を実行した際のコンソール出力
us=571で実行した様子
▲ us=571で実行した様子
us=1491で実行した様子
▲ us=1491で実行した様子
us=2411で実行した様子
▲ us=2411で実行した様子

スポンサーリンク

タクトスイッチ2個で「Aで90度サイクル」「Bで180度サイクル」を作る

タクトスイッチA(GP14)を押すと0度→90度→0度、タクトスイッチB(GP13)を押すと0度→180度→0度のサイクルで動作し、今何が行われているかをThonnyのコンソールにprint表示するプログラムです。

from machine import Pin, PWM
import time

# ===== サーボ設定 =====
servo = PWM(Pin(15))
servo.freq(50)

# 実測キャリブレーション値(us=500→0°, us=2340→180°)
MIN_US = 500
MAX_US = 2340

# サーボホーンの取付角度のズレ補正
HORN_OFFSET_DEG = 7

def set_angle(angle):
    corrected_angle = angle + HORN_OFFSET_DEG
    pulse_width_us = MIN_US + (corrected_angle / 180) * (MAX_US - MIN_US)
    servo.duty_ns(int(pulse_width_us * 1000))

# ===== タクトスイッチ設定 =====
# NOタイプ、内部プルアップ使用(押下でLOWになる)
button_a = Pin(14, Pin.IN, Pin.PULL_UP)
button_b = Pin(13, Pin.IN, Pin.PULL_UP)

def run_cycle_a():
    print("スイッチA押下: 0度 → 90度 → 0度 のサイクルを開始します")
    print("  0度に移動")
    set_angle(0)
    time.sleep(1)
    print("  90度に移動")
    set_angle(90)
    time.sleep(1)
    print("  0度に戻す")
    set_angle(0)
    time.sleep(1)
    print("サイクル完了")

def run_cycle_b():
    print("スイッチB押下: 0度 → 180度 → 0度 のサイクルを開始します")
    print("  0度に移動")
    set_angle(0)
    time.sleep(1)
    print("  180度に移動")
    set_angle(180)
    time.sleep(1)
    print("  0度に戻す")
    set_angle(0)
    time.sleep(1)
    print("サイクル完了")

def wait_release(button):
    while button.value() == 0:
        time.sleep(0.05)

# ===== メイン処理 =====
set_angle(0)  # 初期位置
print("準備完了。スイッチAまたはBを押してください。")

while True:
    if button_a.value() == 0:
        run_cycle_a()
        wait_release(button_a)
        time.sleep(0.2)  # チャタリング対策の簡易ウェイト
    elif button_b.value() == 0:
        run_cycle_b()
        wait_release(button_b)
        time.sleep(0.2)

対応ファイル: servo-moter-test005.py

Aボタンを押した際のコンソール出力
▲ Aボタンを押した際のコンソール出力

✓ 動作確認方法

  • タクトスイッチA・Bをそれぞれ押して、意図したサイクル(90度/180度)で動作するか確認する
  • コンソールに動作内容(現在の移動先)が表示されるか確認する
  • ボタンを押しっぱなしにしても、1回の押下につき1サイクルだけ実行され、最後は必ず0度に戻ることを確認する

ボタン押しっぱなしで0度に戻らなくなった話

このコードには当初wait_release()が無く、ボタンを押しっぱなしにすると「サイクル終了直後に即座に次のサイクルが再トリガーされる」という不具合がありました。0度に戻った瞬間にまた次のサイクルが始まってしまうため、見た目には「0度で止まってくれない」ように見えていたのです。ボタンが離されるまで次のトリガーを待つwait_release()を追加することで解決しました。


スポンサーリンク

Q&A(よくある質問)

QMG996Rの動作電圧は何Vが正しいですか?5Vでも動きますか?

A公称の動作電圧は4.8V〜7.2Vで、6V駆動が標準的です。今回は手持ちのUSB電源(5V)で動作しましたが、6V駆動時と比べるとトルク・スピードはやや控えめになります。より力強く動かしたい場合は、単3電池4本(6V)などの電源を検討してください。

Qなぜコード上の「500〜2500usで0〜180度」という想定通りに動かなかったのですか?

A安価なクローン品はメーカー・ロットによって個体差が大きく、パルス幅と実際の回転角度の対応関係(特に可動域の上限・下限)が公称値からズレていることがよくあります。分度器などで実測し、コード側の数値を補正するのが確実です。

Qサーボホーンが狙った角度に取り付けられないときはどうすればいいですか?

Aホーンはスプライン(ギザギザの溝)構造のため、取り付けられる角度が離散的です。無理に物理的な取り付け角度を追い込むより、コード側にオフセット値を持たせて電気的に補正する方が実用的です。

Qパルス幅の安全な上限・下限はどうやって調べればいいですか?

A少しずつパルス幅を広げる/狭めるテストコードを実行し、サーボが唸り始める・振動し始める一歩手前を安全マージンとして採用します。今回の個体では上限に約18度分の余裕があった一方、下限はわずか3度分程度しか余裕がなく、上下で大きな非対称性がありました。

Qタクトスイッチを押しっぱなしにすると動作がおかしくなるのはなぜですか?

Aボタンの状態を単純にポーリングするだけのコードだと、押しっぱなしの間ずっと条件が真になり続け、サイクル終了直後に即座に再トリガーされてしまいます。ボタンが離されるまで待つ処理を入れることで、1回の押下につき1サイクルだけ実行されるようになります。

QPico側とサーボ側のGNDを共通化しないとどうなりますか?

APico側とサーボ側で電気的な基準電位がズレてしまい、PWM信号が正しく認識されず誤動作します。電源配線の際は、サーボのGND線をPicoのGNDピンにも必ず接続してください。


スポンサーリンク

まとめ

MG996RとRaspberry Pi Pico 2WをPWM制御でつなぎ、タクトスイッチで角度サイクルを切り替えるところまで作りました。単に「動かしてみた」だけでなく、実測してみて初めて分かる個体差やクセ(可動域のズレ、ホーンの取付角度、上限下限の非対称性、ボタン制御のバグ)まで含めて紹介しました。


この記事が役に立ったら、シェアしてもらえると嬉しいです🥕

Xでポスト はてブ LINE Pocket
ニンジン🥕アイコン

Follow me on X

本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕

@ninjin_py_vba をフォロー

PROJECT LOG — SERIES INDEX
スポンサーリンク

シリーズ記事一覧

電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。


超音波距離センサーHC-SR04をRaspberry Pi Picoで使う|仕組み・配線・精度検証
.uds1-block { position: relative; overflow: hidden; border-radius: 12px; border: 1px solid #f0cba8; background: linear-g…
今週の注目ガジェット〜09/07週〜|電子工作・自作向け新製品まとめ(TickrCast/Radxa Linkr ほか)
ニンジンのガジェット通信 09/07週。ESP32-P4の自作気象モニター、極小IP-KVM「Radxa Linkr」、ESP32のDINレール型PLC、LTE開発キットなど、電子工作で使えそうな新製品5つを解説。
サーボモータの角度をタクトスイッチで可変|OLED(SSD1306)に現在角度をリアルタイム表示
Raspberry Pi Pico 2W×MG996RにタクトスイッチとOLED(SSD1306)を追加。ボタンで角度を可変しながら現在角度をリアルタイム表示するMicroPython実装を、framebufの文字拡大やサーボの遊び対策まで実機ベースで解説します。
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす:PWM制御とキャリブレーション実践
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす方法を解説。公称180度のはずが実測198度だった個体差の原因究明から、ホーンのズレ補正、タクトスイッチでの角度制御まで実機検証の様子を紹介します。
Tapo P110Mの消費電力をExcelレポート化する【年次集計・電気代試算・セキュリティ判断編】
FastAPI+openpyxlでTapo P110Mの消費電力をExcelレポート化。15秒間隔の生ログを日次集計する設計や、31円/kWhでの電気代試算、外部公開を見送った理由まで解説します。

コメント