注釈

こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。

参加する理由

  • 専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。

  • 学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。

  • 限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。

  • 特別割引: 最新製品を特別割引でお楽しみいただけます。

  • 季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。

👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!

4.11 サーボ角度メーター

はじめに

このレッスンでは、 サーボ角度メーター を作成します。 これは、ポテンショメータでサーボモーターの角度を制御し、その現在の角度を OLED 画面に表示するビジュアルインジケーターです。

ポテンショメータは Fusion HAT+ の ADC インターフェースを通じてアナログ電圧を出力します。 サーボはこの読み取り値に基づいて角度を制御され、128×64 の I2C OLED ディスプレイには数値によるサーボ角度と、画面上を滑らかに移動するグラフィカルなバーが表示されます。

ポテンショメータを回すと、サーボはおよそ -90° から +90° の範囲で動作し、OLED の表示もリアルタイムで更新されます。


必要なもの

このプロジェクトで必要なコンポーネントは以下のとおりです。

COMPONENT INTRODUCTION

PURCHASE LINK

ジャンパーワイヤー

購入

ポテンショメータ

購入

サーボ

購入

OLED Display Module

-

Fusion HAT+

-

Raspberry Pi

-


配線図

以下の配線図を参考にして、各コンポーネントを接続してください。

../_images/4.11_servo_angle_meter_bb.png

セットアップ手順

  1. 必要なライブラリをインストールします。

    sudo pip3 install adafruit-circuitpython-ssd1306 --break
    
  2. このチュートリアルで使用するすべてのサンプルコードは ai-lab-kit ディレクトリにあります。

    cd ~/ai-lab-kit/python/
    sudo python3 4.11_ServoAngleMeter.py
    
  3. スクリプトを実行すると次のように動作します。

    • ポテンショメータを回すと、サーボが -90° から +90° の範囲で回転します。

    • OLED には数値の角度と、移動するバー状のポインタが表示されます。

    • Ctrl+C を押すとプログラムが終了し、サーボは 0° に戻り、OLED 画面はクリアされます。


コード

以下は Servo Angle Meter の Python スクリプトです。

from fusion_hat.adc import ADC
from fusion_hat.servo import Servo
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import board, time

# ==== OLED setup ====
WIDTH, HEIGHT = 128, 64
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=0x3C)
oled.fill(0)
oled.show()

# Framebuffer for drawing
image = Image.new("1", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()

def text_size(font, text):
    l, t, r, b = font.getbbox(text)
    return (r - l, b - t)

# ==== Servo & potentiometer ====
servo = Servo('P0')   # servo on port P0
pot   = ADC('A0')     # potentiometer on A0 (0..4095)

def linear_map(x, in_min, in_max, out_min, out_max):
    """Map x from one range to another."""
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

# ---- bar layout ----
BAR_TOP     = 40
BAR_HEIGHT  = 10
BAR_MARGINX = 6
BAR_WIDTH   = WIDTH - BAR_MARGINX * 2
BAR_CENTERX = BAR_MARGINX + BAR_WIDTH // 2

def draw_bar(angle_deg):
    """Draw a centered horizontal bar and pointer for -90..90 degrees."""
    draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)

    # Title
    title = "Servo Angle"
    tw, th = text_size(font, title)
    draw.text(((WIDTH - tw) // 2, 4), title, font=font, fill=255)

    # Numeric angle
    txt = f"{angle_deg:>4} deg"
    nw, nh = text_size(font, txt)
    draw.text(((WIDTH - nw) // 2, 20), txt, font=font, fill=255)

    # Bar outline
    draw.rectangle(
        (BAR_MARGINX, BAR_TOP, BAR_MARGINX + BAR_WIDTH - 1, BAR_TOP + BAR_HEIGHT),
        outline=255, fill=0
    )

    # Ticks
    for x in (BAR_MARGINX, BAR_CENTERX, BAR_MARGINX + BAR_WIDTH - 1):
        draw.line((x, BAR_TOP - 3, x, BAR_TOP + BAR_HEIGHT + 3), fill=255)

    # Map angle to pixel position
    pos = int(linear_map(angle_deg, -90, 90, BAR_MARGINX, BAR_MARGINX + BAR_WIDTH - 1))

    draw.line((pos, BAR_TOP - 2, pos, BAR_TOP + BAR_HEIGHT + 2), fill=255)

    # Fill direction highlight
    if pos >= BAR_CENTERX:
        draw.rectangle((BAR_CENTERX, BAR_TOP + 1, pos, BAR_TOP + BAR_HEIGHT - 1), fill=255)
    else:
        draw.rectangle((pos, BAR_TOP + 1, BAR_CENTERX, BAR_TOP + BAR_HEIGHT - 1), fill=255)

try:
    while True:
        raw = pot.read()
        angle = int(linear_map(raw, 0, 4095, -90, 90))

        servo.angle(angle)

        draw_bar(angle)
        oled.image(image)
        oled.show()

        time.sleep(0.05)

except KeyboardInterrupt:
    servo.angle(0)
    oled.fill(0)
    oled.show()
    print("\nExited.")

コードの解説

  1. Imports

    • ADC はポテンショメータからアナログ値を読み取ります

    • Servo はサーボモーターの回転を制御します

    • PIL は OLED に表示するグラフィックを描画します

    • adafruit_ssd1306 は I2C OLED ディスプレイを制御します

    • board はハードウェア I/O を提供します

    • time はループ速度を制御します

  2. OLED Setup

    128×64 の SSD1306 OLED を初期化してクリアします。 描画はオフスクリーンのフレームバッファに作成され、その後ディスプレイに転送されます。

    # ==== OLED setup ====
    WIDTH, HEIGHT = 128, 64
    i2c = board.I2C()
    oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=0x3C)
    oled.fill(0)
    oled.show()
    
    # Framebuffer for drawing
    image = Image.new("1", (WIDTH, HEIGHT))
    draw = ImageDraw.Draw(image)
    font = ImageFont.load_default()
    
  3. Servo & Potentiometer

    • サーボは P0 ポートに接続

    • ポテンショメータはアナログ入力 A0 に接続

    • ADC の範囲は 0..4095

    # ==== Servo & potentiometer ====
    servo = Servo('P0')   # servo on port P0
    pot   = ADC('A0')     # potentiometer on A0 (0..4095)
    
  4. Mapping Values

    linear_map() 関数は、ポテンショメータの読み取り値を -90..90 のサーボ角度に変換します。

    def linear_map(x, in_min, in_max, out_min, out_max):
       """Map x from one range to another."""
       return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
    
  5. Drawing the UI

    draw_bar() 関数は次の処理を行います。

    • 画面をクリア

    • タイトルを描画

    • 数値の角度を表示

    • 横方向のバーと目盛りを描画

    • ポインタと方向を示す塗りつぶしバーを描画

    def draw_bar(angle_deg):
       """
       Draw a centered horizontal bar with a moving pointer.
       -90° maps to the far left, +90° to the far right.
       0° is at the bar center.
       """
       # Clear screen
       draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
    
       # Title
       title = "Servo Angle"
       tw, th = text_size(font, title)
       draw.text(((WIDTH - tw) // 2, 4), title, font=font, fill=255)
    
       # Numeric angle
       txt = f"{angle_deg:>4} deg"
       nw, nh = text_size(font, txt)
       draw.text(((WIDTH - nw) // 2, 20), txt, font=font, fill=255)
    
       # Static bar background
       draw.rectangle(
          (BAR_MARGINX, BAR_TOP, BAR_MARGINX + BAR_WIDTH - 1, BAR_TOP + BAR_HEIGHT),
          outline=255, fill=0
       )
    
       # Ticks: left (-90), center (0), right (+90)
       for x in (BAR_MARGINX, BAR_CENTERX, BAR_MARGINX + BAR_WIDTH - 1):
          draw.line((x, BAR_TOP - 3, x, BAR_TOP + BAR_HEIGHT + 3), fill=255)
    
       # Map angle (-90..90) to bar position
       pos = int(linear_map(angle_deg, -90, 90, BAR_MARGINX, BAR_MARGINX + BAR_WIDTH - 1))
    
       # Pointer: a solid vertical line
       draw.line((pos, BAR_TOP - 2, pos, BAR_TOP + BAR_HEIGHT + 2), fill=255)
    
       # Optional: filled segment from center to pointer (visualize direction)
       if pos >= BAR_CENTERX:
          draw.rectangle((BAR_CENTERX, BAR_TOP + 1, pos, BAR_TOP + BAR_HEIGHT - 1), outline=0, fill=255)
       else:
          draw.rectangle((pos, BAR_TOP + 1, BAR_CENTERX, BAR_TOP + BAR_HEIGHT - 1), outline=0, fill=255)
    
  6. Main Loop

    プログラムは次の処理を繰り返します。

    • ADC を読み取る

    • サーボ角度を計算する

    • サーボを更新する

    • UI を描画する

    • OLED を更新する

    while True:
       # Read potentiometer (0..4095) and map to angle (-90..90)
       raw = pot.read()
       angle = int(linear_map(raw, 0, 4095, -90, 90))
    
       # Drive servo
       servo.angle(angle)
    
       # Draw UI and push to OLED
       draw_bar(angle)
       oled.image(image)
       oled.show()
    
       # Optional: print for debugging
       # print(f"pot={raw:4d} -> angle={angle:4d} deg")
    
       time.sleep(0.05)  # ~20 FPS
    
  7. Graceful Exit

    Ctrl+C を押すと次の処理が行われます。

    • サーボが 0° に戻る

    • OLED 画面がクリアされる


トラブルシューティング

  • OLED に何も表示されない

    • I2C 配線を確認してください

    • デバイスアドレスが 0x3C であることを確認してください

    • 必要なライブラリがインストールされていることを確認してください

  • サーボが動作しない

    • サーボの電源を確認してください

    • サーボが P0 に接続されていることを確認してください

    • サーボの信号線が正しく接続されていることを確認してください

  • 動作範囲が正しくない

    以下のマッピング範囲を調整してください。

    angle = int(linear_map(raw, 0, 4095, -90, 90))
    
  • OLED がちらつく

    遅延時間を増やします。

    time.sleep(0.1)
    

試してみよう

  1. サーボ角度制限を追加する

    機械的な過回転を防ぎます。

  2. キャリブレーションを追加する

    ポテンショメータの最小値・最大値を自動検出します。

  3. 動きを滑らかにする

    イージングやローパスフィルタを適用します。

  4. 表示情報を追加する

    角度とともに ADC の生データも表示します。

  5. 警告表示を追加する

    角度が ±75° 付近に達したときポインタを点滅させます。

これらの拡張を行うことで、Servo Angle Meter はより高機能な入力可視化ツールへと発展させることができます。