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度」という目安が使われますが、これはあくまで目安です。後述の通り、今回使用した個体はこの通りには動きませんでした。
配線方法:MG996RとRaspberry Pi Pico 2Wの結線
電源をPicoの3.3V/USBから取ってはいけない理由
MG996Rは動作時に数百mA、場合によっては1A近い電流が流れます。Pico側のUSBや3V3ピンから直接給電すると、Pico側のレギュレータの許容電流を超えてリセットや破損のリスクがあるため、サーボ用の電源はPicoとは別に用意します。今回はUSB ACアダプタ(5V/2A)から、USBケーブルを1本潰して電源線として使いました。
GPIOピンの割り当てと信号線の接続
| 線・部品 | 接続先 |
|---|---|
| MG996R 信号線(オレンジ) | Pico 2W GP15 |
| MG996R 電源線(赤) | USB電源 +5V |
| MG996R GND線(茶) | USB電源GND、かつPico 2WのGNDと共通接続 |
| タクトスイッチA | GP14 – GND間 |
| タクトスイッチB | GP13 – GND間 |
USB ACアダプタから伸びるUSBケーブルを1本潰し、赤線(+5V)・黒線(GND)をブレッドボードの電源レールに接続しています。
電源ライン(5V-GND間)に電解コンデンサ(16V/220μF)を入れることで、サーボ起動時の突入電流やノイズによる電源の乱れを抑えられます。
Freenoveブレークアウトボード経由で、GP15(サーボ信号線)・GP14(スイッチA)・GP13(スイッチB)・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
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 | 実測角度 |
|---|---|
| 500 | 0°(基準) |
| 1500 | 97° |
| 2500 | 198° |
想定していた「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度付近 |
タクトスイッチ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・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制御でつなぎ、タクトスイッチで角度サイクルを切り替えるところまで作りました。単に「動かしてみた」だけでなく、実測してみて初めて分かる個体差やクセ(可動域のズレ、ホーンの取付角度、上限下限の非対称性、ボタン制御のバグ)まで含めて紹介しました。
Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォローシリーズ記事一覧
電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。
-
FILE.01 — IoT
Raspberry Pi Pico W × GASラズパイPico W×GASで自作!「LINEで動く温湿度&スマートリモコン」完全ロードマップ
「外出中のペットの室温が心配…」そんな思いからスタートしたIoT自作プロジェクトの総集編。温湿度監視からエアコン遠隔操作まで、仕組みをゼロから作りたい方のための全5回の開発記録です。
シリーズを読む -
FILE.02 — CNC
Arduino × レーザー刻印CNCCNC自作シリーズ
高精度な加工を目指し、本格的な自作CNC製作に挑戦中。現在は基幹パーツであるオリエンタルモーターの納品を待つ「設計・準備編」を公開。ハードとソフトの両面から、理想のマシンを形にする過程をリアルタイムにお届けします。
シリーズを読む -
FILE.03 — XY軸制御
Raspberry Pi × ステッピングモーターXYテーブルシリーズ
Raspberry Piとステッパモーターを使い、ゼロから2軸制御に挑む記録。OS設定から回路設計、多軸制御のPythonコードまで、躓きやすいポイントを徹底図解。電子工作初心者が「動く感動」を味わうための実戦ガイドです。
シリーズを読む -
FILE.04 — 業務自動化
Python × Excel × CustomTkinter【Python開発記】NINJIN Mail制作記
PythonとExcelを連携させ、実務で即戦力となるメール送信ツールを開発。CustomTkinterによるUI構築や、ミスを防ぐテンプレート活用術など、現場の「痒い所に手が届く」自動化ノウハウを細部まで丁寧に解説します。
シリーズを読む -
FILE.05 — 業務自動化
Python × Excel × GUIアプリ化Invoice Maker(請求書・見積書自動作成)
手作業の請求書作成から卒業!PythonでExcelデータを読み込み一括PDF化する「Invoice_Maker」の作り方を全3回で解説。基本ロジックからGUIアプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む



コメント