MG996R × Pico 2W 発展編
はじめに
こんにちは。ニンジンです🥕
前回の記事で、Raspberry Pi Pico 2Wとメタルギアサーボ「MG996R」を組み合わせ、タクトスイッチを押すと決まった角度サイクル(0°→90°→0° など)で動かすところまで作りました。実測してみると公称180°のはずが198°まで回ったり、サーボホーンが狙った角度に取り付けられなかったりと、実機ならではのクセがいろいろ出てきた回でした。
今回はその発展編です。前回あえて使わなかった0.96インチのOLEDディスプレイ(SSD1306)を追加し、次のものを作ります。
- タクトスイッチの「+」「−」ボタンで角度を1°ずつ連続的に可変できる
- 押しっぱなしにすると早送りで角度が変わる(押し続けるほど速くなる)
- 現在の角度をOLEDに数字と横バーグラフでリアルタイム表示する
前回同様、作りながら出てきたつまずき(サーボのギアの遊び、OLED描画が早送りの足を引っ張る問題など)も含めて紹介します。まずは完成した様子をご覧ください。
▲ +/−ボタンで角度を可変し、OLEDに現在角度が表示される様子
この記事で分かること
- framebufで標準8×8フォントを拡大描画し、OLEDに大きな数字を表示する方法
- time.ticks_ms()ベースの状態管理で、sleepに頼らないキビキビしたボタン操作を作る方法
- タクトスイッチの「単押しで1°・長押しで早送り(加速リピート)」の実装
- 金属ギアサーボのバックラッシュ(遊び)など、実機で初めて分かるクセと対処
+/−タクトスイッチでMG996Rの角度を可変しOLED(SSD1306)に現在角度を表示する構成
今回作るものの全体像です。
- タクトスイッチ「+」を押す → 角度が1°増える(押しっぱなしで早送り)
- タクトスイッチ「−」を押す → 角度が1°減る(押しっぱなしで早送り)
- 現在角度(0〜180°)をOLEDに大きな数字+横バーで表示
- 0°と180°で頭打ち(それ以上は動かない)
前回 servo-moter001 の配線に、OLEDのI2C配線4本を足すだけです。
使用部品(servo-moter001に0.96インチOLED SSD1306を追加)
前回使った部品に、OLEDディスプレイを1点追加します。OLEDモジュールの端子は、基板のシルク印字が左から GND / VDD / SCK / SDA の順です(SCKはSCLと同じ意味)。


今回の実機はこちら
MG996R メタルギア・デジタルサーボ
180度の可動範囲を持つ、ホビー向けの定番メタルギアサーボです。前回・今回ともこの実機で検証しています。
Amazonは在庫切れのため掲載していません(楽天市場のみ)




- USB ACアダプタ(5V/2A)+ USBケーブル1本(被覆を剥いて電源線として使用)
- 電解コンデンサ 16V/220μF(電源ラインの安定化用)
結線:OLEDのSDA=GP0 / SCL=GP1 / VDD=3V3(OUT)、サーボとスイッチは前回のまま
配線は下図の通りです。サーボの信号線・電源・GND、タクトスイッチ2個は前回 servo-moter001 とまったく同じ配置のまま。そこにOLEDの4本(SDA / SCL / VDD / GND)を追加します。
| 線・部品 | 接続先(Pico 2W) | 物理ピン |
|---|---|---|
| MG996R 信号線(オレンジ) | GP15 | 20 |
| MG996R 電源線(赤) | 外部USB電源 +5V | — |
| MG996R GND線(茶) | 外部USB電源GND + Pico GND(共通) | — |
| タクトスイッチ「+」 | GP14 – GND間 | 19 |
| タクトスイッチ「−」 | GP13 – GND間 | 17 |
| OLED SDA | GP0 | 1 |
| OLED SCL(SCK) | GP1 | 2 |
| OLED VDD | 3V3(OUT) | 36 |
| OLED GND | GND | 38 |
OLED(SSD1306)に大きな数字を表示する — framebufで8×8フォントを拡大描画する
MicroPythonのssd1306ライブラリには文字を描くoled.text()がありますが、フォントは8×8ピクセル固定です。128×64のOLEDに「128 deg」と出しても7文字×8ピクセル=56ピクセルぶんしかなく、離れると読めません。今回は角度をパッと見たいので、この標準フォントを拡大して大きく表示します。
i2c.scan()で0x3Cを確認する
まずは配線が正しいか、I2Cバス上にOLEDが見えているかを確認します。
from machine import Pin, I2C
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
print("I2Cデバイス検出:", i2c.scan()) # [60] (0x3C) が出れば正常I2C(0, ...)はI2Cコントローラー0番を使う指定です。Raspberry Pi Picoでは、I2C0で使えるSDA/SCLのピンの組み合わせが決まっており(GP0/GP1、GP4/GP5、GP8/GP9 …)、今回はGP0=SDA、GP1=SCLを使います。実行してI2Cデバイス検出: [60]と表示されれば配線OKです(60は0x3C)。[](空リスト)が出たら、SDA/SCLの挿し間違い、ジャンパーの半挿し、VDD/GNDの未接続を疑ってください。
FrameBufferに描いて拡大コピーする(text_scaled)
標準フォントを拡大する仕組みはシンプルです。小さなFrameBufferに一度text()で普通に描き、その1ピクセルずつを読み取って、点いているピクセルをscale×scaleの四角として本番のoledに打ち直します。
import framebuf
def text_scaled(fb, s, x, y, scale, color=1):
# 標準8x8フォントの文字列を scale 倍に拡大して描画する
w = len(s) * 8
tmp = framebuf.FrameBuffer(bytearray(8 * ((w + 7) // 8)), w, 8, framebuf.MONO_HLSB)
tmp.fill(0)
tmp.text(s, 0, 0, 1)
for ty in range(8):
for tx in range(w):
if tmp.pixel(tx, ty):
fb.fill_rect(x + tx * scale, y + ty * scale, scale, scale, color)framebuf.FrameBuffer(buffer, width, height, format)は、自前のバイト列をピクセルバッファとして扱えるようにするクラスです。MONO_HLSBは1ビット=1ピクセルのモノクロ形式で、高さ8・幅wなら必要なバイト数は8 * ((w + 7) // 8)になります。scale=2にすると「128 deg」で約88ピクセル、128幅のOLEDにちょうど収まります。
横バーグラフをrect()とfill_rect()で描く
数字だけだと直感的に分かりにくいので、0〜180°を横バーでも表示します。枠をrect()、中身をfill_rect()で描くだけです。
def draw_status(angle):
oled.fill(0)
text_scaled(oled, "{:>3d} deg".format(angle), 4, 6, 2) # 上段: 数字(2倍角)
bx, by, bw, bh = 6, 42, 116, 16 # 下段: バー
oled.rect(bx, by, bw, bh, 1) # 枠
fill_w = int((angle / 180) * (bw - 4)) # 角度に応じた塗り幅
oled.fill_rect(bx + 2, by + 2, fill_w, bh - 4, 1) # 中身
oled.show()"{:>3d}".format(angle)で右寄せ3桁にしておくと、1桁・2桁・3桁で数字の位置がガタつきません。まずはOLED単体の動作確認用に、0/45/90/135/180°を順番に表示し続けるスクリプトを作りました。
対応ファイル: servo-moter002-test001.py
▲ 表示が0°→180°まで切り替わる様子。ここまで表示できれば結線OK
✓ 動作確認方法
- ThonnyでコードをF5実行し、シェルに
I2Cデバイス検出: [60]が出る - OLEDに「0 deg」「45 deg」…と1秒おきに切り替わる
- 数字の下のバーが角度に応じて伸びる
タクトスイッチの「単押しで1°・長押しで早送り」をMicroPythonで実装する
次に、タクトスイッチで角度を可変する部分を作ります。前回同様、タクトスイッチはNO(ノーマルオープン)タイプで、内部プルアップを使い「押すとLOW(0)になる」形で読み取ります。
button_up = Pin(14, Pin.IN, Pin.PULL_UP) # +
button_down = Pin(13, Pin.IN, Pin.PULL_UP) # −sleepで待つ素朴版は方向転換の反応が鈍い(test002)
最初に書いたのが次の形です(OLEDはまだ付けず、コンソールに角度をprintして確認)。
while True:
for pin, delta in ((button_up, +1), (button_down, -1)):
if not pressed(pin):
continue
step(delta) # 1回目は即時
time.sleep_ms(REPEAT_DELAY_MS) # 単押しならここで指を離す
interval = START_INTERVAL_MS
while pressed(pin): # 押し続けている間はリピート
if not step(delta):
break
time.sleep_ms(interval)
interval = max(MIN_INTERVAL_MS, interval - ACCEL_MS)
time.sleep_ms(30) # チャタリング対策
time.sleep_ms(10)対応ファイル: servo-moter002-test002.py
一応動くのですが、time.sleep_ms()で処理を止めて待つ作りなので、以下の問題があります。
- +を押した直後、必ず
REPEAT_DELAY_MS(0.4秒)のsleepが入るため、その間はほかのボタンを一切見ていない - +で角度を上げてすぐ−を押す、というとき最悪0.4秒ぶん反応が遅れる
- 端(0°/180°)でボタンを押しっぱなしにしていると、何もせず0.4秒ごとに空回りする
実際に触ると「キビキビ感がない」とすぐ分かります。この素朴版は、次のtest003で直す「悪い例」として置いておきます。
▲ +/−ボタンでサーボが動く様子(OLEDなし)
time.ticks_ms()ベースの状態管理に書き換える(test003)
sleepで待つのをやめ、「今の時刻」と「次に動かす予定時刻」を比べる方式に変えます。ループは止めずに回し続け、各ボタンが「今、動かすべきか?」を毎回自分で判断します。
class RepeatButton:
def __init__(self, pin, delta):
self.pin = pin
self.delta = delta
self.holding = False
self.t_next = 0
self.interval = FAST_START_MS
self.t_release = 0
def poll(self, now):
# 今このボタンで動かすべきなら delta を、そうでなければ 0 を返す
down = self.pin.value() == 0
if not down:
if self.holding:
self.holding = False
self.t_release = now
return 0
if not self.holding:
if time.ticks_diff(now, self.t_release) < DEBOUNCE_MS:
return 0 # 離した直後のチャタリングを弾く
self.holding = True
self.interval = FAST_START_MS
self.t_next = time.ticks_add(now, REPEAT_DELAY_MS)
return self.delta # 押した瞬間の1回
if time.ticks_diff(now, self.t_next) >= 0:
self.t_next = time.ticks_add(now, self.interval)
self.interval = max(FAST_MIN_MS, self.interval - ACCEL_MS)
return self.delta # 早送り
return 0時刻の比較にtime.ticks_ms()をそのまま<や-で扱わず、time.ticks_diff()・time.ticks_add()を使うのがポイントです。ticks_ms()の値は一定時間でオーバーフロー(桁あふれ)して0に戻るため、素朴に引き算すると桁あふれの瞬間におかしくなります。
メインループはこうなります。sleepが3msだけになり、+↔−の切り替えは体感で分からないレベルまで即応になりました。
while True:
now = time.ticks_ms()
for b in buttons:
d = b.poll(now)
if d == 0:
continue
new_angle = max(0, min(180, angle + d))
if new_angle != angle:
angle = new_angle
set_angle(angle)
pending_draw = True
...
time.sleep_ms(3)押し続けるほど早送りが加速する処理
早送りの「だんだん速くなる」は、リピート間隔intervalを1ステップごとにACCEL_MSずつ縮め、FAST_MIN_MSで頭打ちにするだけです。
REPEAT_DELAY_MS = 300 # 単押し -> 早送りに入るまでの待ち
FAST_START_MS = 150 # 早送り開始直後の間隔
FAST_MIN_MS = 12 # 最速の間隔
ACCEL_MS = 14 # 1ステップごとに間隔を縮める量- ちょい押し → 1°だけ動く
- 押し続ける → 0.3秒後に早送り開始、150ms間隔から徐々に12ms間隔まで加速
数値は好みで調整してください。FAST_MIN_MSを小さくしすぎると、後述のOLED描画が追いつかなくなります。
角度を0〜180にクランプして可動域外でサーボを唸らせない
角度の計算は前回 servo-moter001 の実測キャリブレーション値をそのまま流用します。set_angle()の中で0〜180にクランプしておくことで、可動域の外を指令してサーボが唸る事故を防ぎます。
MIN_US = 500
MAX_US = 2340
HORN_OFFSET_DEG = 7
def set_angle(angle):
angle = max(0, min(180, angle)) # 0〜180にクランプ
corrected = angle + HORN_OFFSET_DEG
pulse_us = MIN_US + (corrected / 180) * (MAX_US - MIN_US)
servo.duty_ns(int(pulse_us * 1000))さらにメインループ側でも、new_angleが現在角度と同じ(=すでに0°または180°)ならset_angle()を呼ばないようにしているので、端で押し続けてもサーボには何も送られず、静かに止まったままになります。
実機で分かったつまずきポイント
方向を変えると最初の1〜2°が動かない — 金属ギアサーボのバックラッシュ(遊び)
test003を触っていて気づいた違和感がこれです。例えば180°まで上げてから「−」ボタンを押していくと、コンソールには 角度: 179 178 177 …とちゃんと出ているのに、サーボのホーンは177°になるまで動きませんでした。そこから先は1°ずつ普通に動きます。
コンソールに数字が出ている=set_angle()は毎回呼ばれているので、これはプログラムのバグではありません。サーボのギアの「遊び(バックラッシュ)」です。ギア同士のかみ合わせにはわずかな隙間があり、ある方向に回した後で逆向きの指令を出すと、まずこの隙間を詰めるぶんだけ空走してから、やっとホーンが動き出します。範囲の端だけでなく、90°付近で方向転換しても同じだけ遅れます(=方向反転のときだけ)。
対処としては、「安い金属ギアサーボにはこの遊びがある。正確な位置決め用途には向かない」と割り切るのが現実的です。今回作っているのは「表示された数字=実際の角度」であることが売りの装置なので、下手にプログラムで補正するとかえって表示と実機がズレます。
バックラッシュの量は0°側と180°側で非対称だった
さらに細かく見ると、遊びの量が可動域の端で違いました。
| 方向転換した位置 | 動き出すまでの遊び |
|---|---|
| 0°付近 | 約1°(1回ぶん動かず、次で2°ぶんまとめて動く) |
| 180°付近 | 約2°(2回ぶん動かず、その後まとめて動く) |
前回 servo-moter001 で、この個体は「パルス幅の上限側に約18°ぶんの余裕があり、下限側は約3°ぶんしか余裕がない」という非対称なクセがありました。今回のバックラッシュの非対称(上=ルーズ、下=タイト)も同じ傾向で、この個体のギアやポテンショメータの特性が上下で偏っているのだと思われます。安価なクローン品ではこの手の個体差が普通にあります。
OLEDのshow()(I2C通信)が早送りのボトルネックになる — 描画を間引く
最初、角度が変わるたびにdraw_status()(内部でoled.show())を呼んでいたところ、早送りの最高速を上げても頭打ちになる現象が出ました。oled.show()は128×64=1024バイトのバッファをI2C(400kHz)で丸ごと送るため、1回あたり20ms以上かかります。早送りの最速間隔は12msなので、ステップごとに描画すると、サーボの動きが描画速度に引きずられて遅くなるわけです。
対策として、サーボのステップ処理とOLED描画を分離しました。
DRAW_MIN_INTERVAL_MS = 40 # OLED再描画の最短間隔
while True:
now = time.ticks_ms()
for b in buttons:
d = b.poll(now)
...
if new_angle != angle:
angle = new_angle
set_angle(angle) # サーボは毎ステップ即反映
pending_draw = True
# OLED は最短 40ms おきにまとめて描画する
if pending_draw and time.ticks_diff(now, last_draw) >= DRAW_MIN_INTERVAL_MS:
draw_status(angle)
last_draw = now
pending_draw = False
time.sleep_ms(3)サーボは1°ごとに滑らかに動き、OLEDの数字・バーは最大25fps程度で追従します。早送りを止めたときの最終角度はpending_drawフラグで必ず描画されるので、表示がズレて止まることはありません。
【気になる人向け】方向反転時に1°余分に送る簡易バックラッシュ補正とそのトレードオフ
どうしてもバックラッシュが気になる場合、「直前の動作方向を覚えておき、方向が反転した最初の1回だけ、余分に動かして遊びを詰める」という補正が考えられます。
BACKLASH_DEG = 1
last_move_dir = 0
def move(delta):
global angle, last_move_dir
target = max(0, min(180, angle + delta))
if target == angle:
return False
d = 1 if delta > 0 else -1
extra = 0
if last_move_dir != 0 and d != last_move_dir:
extra = BACKLASH_DEG * d # 方向反転時だけ遊びを先に詰める
set_angle(target + extra) # サーボには少し多めに送る
angle = target # 表示は指令値どおり
last_move_dir = d
return Trueただしトレードオフがあります。
- 遊びがちょうど
BACKLASH_DEGならホーン位置と表示が一致するが、遊びの量が違えば最大1°ズレる - 方向反転後、ホーンは常に
BACKLASH_DEGぶん先に送られた位置で保持される(停止位置が表示と1°ズレる) - 前述の通り遊びは0°側と180°側で非対称なので、片方に合わせるともう片方が合わない
「表示=実角度」を厳密に守りたいこの装置では、補正は入れないほうがすっきりします。あくまで「こういう手もある」という参考です。
完成:本番プログラム(main.py)全文
ここまでの要素をすべて統合したものが以下です。Pico本体にmain.pyとして保存すれば、PCなしで単体動作します。
# servo-moter002 本番プログラム (main.py)
# +/−タクトスイッチで MG996R の角度を可変し、OLED(SSD1306) に
# 現在角度を数値+横バーグラフでリアルタイム表示する
from machine import Pin, PWM, I2C
import framebuf
import ssd1306
import time
# ===== サーボ設定 (servo-moter001 の実測キャリブレーション値) =====
servo = PWM(Pin(15))
servo.freq(50)
MIN_US = 500
MAX_US = 2340
HORN_OFFSET_DEG = 7
def set_angle(angle):
angle = max(0, min(180, angle))
corrected = angle + HORN_OFFSET_DEG
pulse_us = MIN_US + (corrected / 180) * (MAX_US - MIN_US)
servo.duty_ns(int(pulse_us * 1000))
# ===== OLED 設定 =====
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
print("I2Cデバイス検出:", i2c.scan())
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
def text_scaled(fb, s, x, y, scale, color=1):
# 標準8x8フォントの文字列を scale 倍に拡大して描画する
w = len(s) * 8
tmp = framebuf.FrameBuffer(bytearray(8 * ((w + 7) // 8)), w, 8, framebuf.MONO_HLSB)
tmp.fill(0)
tmp.text(s, 0, 0, 1)
for ty in range(8):
for tx in range(w):
if tmp.pixel(tx, ty):
fb.fill_rect(x + tx * scale, y + ty * scale, scale, scale, color)
def draw_status(angle):
oled.fill(0)
text_scaled(oled, "{:>3d} deg".format(angle), 4, 6, 2)
bx, by, bw, bh = 6, 42, 116, 16
oled.rect(bx, by, bw, bh, 1)
fill_w = int((angle / 180) * (bw - 4))
oled.fill_rect(bx + 2, by + 2, fill_w, bh - 4, 1)
oled.show()
# ===== ボタン(状態管理方式) =====
REPEAT_DELAY_MS = 300 # 単押し -> 早送りに入るまでの待ち
FAST_START_MS = 150 # 早送り開始直後の間隔
FAST_MIN_MS = 12 # 最速の間隔
ACCEL_MS = 14 # 1ステップごとに間隔を縮める量
DEBOUNCE_MS = 20 # 離した後、この時間は次の押下を無視する
DRAW_MIN_INTERVAL_MS = 40 # OLED再描画の最短間隔
class RepeatButton:
def __init__(self, pin, delta):
self.pin = pin
self.delta = delta
self.holding = False
self.t_next = 0
self.interval = FAST_START_MS
self.t_release = 0
def poll(self, now):
# 今このボタンで動かすべきなら delta を、そうでなければ 0 を返す
down = self.pin.value() == 0
if not down:
if self.holding:
self.holding = False
self.t_release = now
return 0
if not self.holding:
if time.ticks_diff(now, self.t_release) < DEBOUNCE_MS:
return 0 # チャタリング中の誤検出を弾く
self.holding = True
self.interval = FAST_START_MS
self.t_next = time.ticks_add(now, REPEAT_DELAY_MS)
return self.delta # 押した瞬間の1回
if time.ticks_diff(now, self.t_next) >= 0:
self.t_next = time.ticks_add(now, self.interval)
self.interval = max(FAST_MIN_MS, self.interval - ACCEL_MS)
return self.delta # 早送り
return 0
button_up = Pin(14, Pin.IN, Pin.PULL_UP)
button_down = Pin(13, Pin.IN, Pin.PULL_UP)
buttons = [RepeatButton(button_up, +1), RepeatButton(button_down, -1)]
# ===== メイン =====
angle = 90
set_angle(angle)
draw_status(angle)
print("準備完了。現在角度:", angle)
last_draw = time.ticks_ms()
pending_draw = False
while True:
now = time.ticks_ms()
for b in buttons:
d = b.poll(now)
if d == 0:
continue
new_angle = max(0, min(180, angle + d))
if new_angle != angle:
angle = new_angle
set_angle(angle) # サーボはステップごとに即反映
pending_draw = True
if pending_draw and time.ticks_diff(now, last_draw) >= DRAW_MIN_INTERVAL_MS:
draw_status(angle)
print("角度:", angle)
last_draw = now
pending_draw = False
time.sleep_ms(3)対応ファイル: servo-moter002-test003.py(内容はmain.pyと同じ)
▲ +/−ボタンで角度を可変し、OLEDがリアルタイムに追従する様子
✓ 動作確認方法
- +ボタンの単押しで1°、押しっぱなしで早送り(だんだん速く)になる
- −ボタンも同様
- 0°と180°で頭打ちになり、そこで押し続けてもサーボが唸らない
- OLEDの数字とバーが操作に追従する
- 方向転換時に最初の1〜2°ホーンが動かない(バックラッシュ。仕様)
main.pyとしてPico本体に保存しPCなしで単体動作させる
MicroPythonは電源投入時にboot.py→main.pyの順で自動実行します。Pico本体のストレージにmain.pyという名前で保存しておけば、PCもThonnyも繋がず、モバイルバッテリーなどの電源だけで単体動作します。
Thonnyで「ファイル」→「名前を付けて保存」を選び、保存先のダイアログで「Raspberry Pi Pico」を選択、ファイル名をmain.pyにして保存します。
main.pyにしてください。保存後、USBケーブルを一度抜き差しして、PCから切り離した状態(モバイルバッテリー給電など)でもボタン操作でサーボが動き、OLEDに角度が出ることを確認します。サーボ用の外部5V電源は別途必要です。
Q&A(よくある質問)
QOLEDに「度」や「°」の記号を表示できますか?
AMicroPython標準の8×8フォントはASCII(半角英数記号)のみ対応で、「°」や漢字はそのままでは表示できません。本記事では deg 表記にしています。記号を出したい場合は、小さな図形として自前で丸を描くか、日本語対応のビットマップフォントを別途読み込む必要があります。
Q単押ししたつもりなのに、角度が2°進むことがあります。
Aサーボのギアの遊び(バックラッシュ)です。直前と逆方向に動かした最初の1回は、ホーンが動かず次のステップでまとめて動くように見えます。コンソールの数字が1°ずつ進んでいれば、プログラムは正常です。安価な金属ギアサーボの特性で、方向反転時のみ発生します。
Q早送り(反応の速さ)を変えたいです。
AREPEAT_DELAY_MS(早送りが始まるまでの待ち)、FAST_START_MS(早送り開始直後の間隔)、FAST_MIN_MS(最速の間隔)、ACCEL_MS(加速の強さ)を調整してください。FAST_MIN_MSを10ms以下にすると、OLEDの描画(show()は1回20ms超)が追いつかなくなるので、その場合はDRAW_MIN_INTERVAL_MSも合わせて見直します。
QOLEDに何も表示されません。i2c.scan()が[]になります。
AI2C配線の接続不良が主な原因です。SDA(GP0)とSCL(GP1)の挿し間違い、ジャンパーの半挿し、VDD(3V3)/GNDの未接続を確認してください。ブレイクアウトボードとPico本体の挿し込みが甘い「半挿入」でも起こります。
QGP0/GP1以外のピンでOLEDを繋げますか?
A繋げます。ただしRaspberry PicoのI2Cは、コントローラーごとに使えるピンの組み合わせが決まっています。I2C(0)ならGP0/GP1、GP4/GP5、GP8/GP9、GP12/GP13、GP16/GP17、GP20/GP21のいずれか。I2C(1)ならGP2/GP3、GP6/GP7、GP10/GP11などです。使うピンに合わせてI2C()のscl=・sda=を書き換えてください(今回はGP13をボタンで使っているので、I2C0のGP0/GP1を選んでいます)。
Qサーボが停止中に小刻みに震えます(ジッタ)。
A電源のノイズや、指令パルスの微妙なゆらぎが原因です。電源ライン(5V-GND間)に電解コンデンサを入れる、共通GNDを確実にする、といった対策で軽減します。ただし安価なサーボはある程度のジッタが残ることが多く、完全には消えないと考えたほうがよいです。目標角度に達してしばらく操作がなければPWM出力を止める(servo.duty_ns(0))方法もありますが、外力で押し戻される用途では保持力がなくなる点に注意してください。
まとめ
前回作ったMG996R×Raspberry Pi Pico 2Wの回路に0.96インチOLED(SSD1306)を足し、タクトスイッチの+/−で角度を1°ずつ・長押しで早送りしながら、現在角度をOLEDにリアルタイム表示するところまで作りました。
framebufで標準8×8フォントを拡大描画し、数字を大きく見せるtime.ticks_ms()ベースの状態管理で、sleepに頼らないキビキビしたボタン操作を作る- 実機で初めて分かるクセ(方向反転時のバックラッシュ、その0°/180°非対称、OLED描画が早送りの足を引っ張る問題)にどう対処したか
前回に続き、「動かしてみて初めて分かること」を中心に紹介しました。
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アプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む






コメント