Bemerkung

Hallo, willkommen in der SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasten-Community auf Facebook! Tauchen Sie mit anderen Enthusiasten tiefer in Raspberry Pi, Arduino und ESP32 ein.

Warum beitreten?

  • Expertenunterstützung: Lösen Sie Probleme nach dem Kauf und technische Herausforderungen mit Hilfe unserer Community und unseres Teams.

  • Lernen & Teilen: Tauschen Sie Tipps und Tutorials aus, um Ihre Fähigkeiten zu verbessern.

  • Exklusive Vorschauen: Erhalten Sie frühzeitigen Zugang zu neuen Produktankündigungen und Sneak Peeks.

  • Sonderrabatte: Genießen Sie exklusive Rabatte auf unsere neuesten Produkte.

  • Festliche Aktionen und Gewinnspiele: Nehmen Sie an Gewinnspielen und Feiertagsaktionen teil.

👉 Bereit, mit uns zu entdecken und zu gestalten? Klicken Sie auf [here] und treten Sie noch heute bei!

4.11 Servo-Winkelmesser

Einführung

In dieser Lektion bauen Sie einen Servo-Winkelmesser – eine visuelle Servo-Winkelanzeige, die ein Potentiometer verwendet, um einen Servomotor zu steuern, während der aktuelle Winkel auf einem OLED-Bildschirm angezeigt wird. Das Potentiometer liefert eine analoge Spannung über die ADC-Schnittstelle des Fusion HAT+. Der Servo erhält auf Grundlage dieses Messwerts Steuerbefehle, und ein 128×64-I2C-OLED-Bildschirm zeigt den numerischen Servowinkel sowie einen grafischen Balken an, der sich flüssig über das Display bewegt.

Wenn Sie das Potentiometer drehen, bewegt sich der Servo ungefähr zwischen -90° und +90°, und das OLED wird in Echtzeit aktualisiert.


Was Sie benötigen

Für dieses Projekt werden die folgenden Komponenten benötigt:

KOMPONENTENBESCHREIBUNG

KAUFLINK

Jumper-Kabel

BUY

Potentiometer

BUY

Servo

BUY

OLED Display Module

-

Fusion HAT+

-

Raspberry Pi

-


Verdrahtungsdiagramm

Verwenden Sie das folgende Verdrahtungsdiagramm, um die Komponenten korrekt zu verbinden:

../_images/4.11_servo_angle_meter_bb.png

Einrichtungsschritte

  1. Installieren Sie die erforderlichen Bibliotheken:

    sudo pip3 install adafruit-circuitpython-ssd1306 --break
    
  2. Der gesamte in diesem Tutorial verwendete Beispielcode befindet sich im Verzeichnis ai-lab-kit:

    cd ~/ai-lab-kit/python/
    sudo python3 4.11_ServoAngleMeter.py
    
  3. Wenn das Skript ausgeführt wird:

    • Durch Drehen des Potentiometers bewegt sich der Servo zwischen -90° und +90°.

    • Das OLED zeigt den numerischen Winkel und einen beweglichen Balkenzeiger an.

    • Mit Ctrl+C wird das Programm beendet, der Servo auf 0° zurückgesetzt und das Display gelöscht.


Code

Hier ist das Python-Skript für den Servo-Winkelmesser:

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.")

Understanding the Code

  1. Imports

    • ADC reads analog values from the potentiometer

    • Servo controls servo rotation

    • PIL handles all OLED graphics

    • adafruit_ssd1306 drives the I2C OLED display

    • board provides hardware I/O

    • time controls loop speed

  2. OLED Setup

    A 128×64 SSD1306 OLED is initialized and cleared. An off-screen framebuffer holds the graphics for each frame before being pushed to the display.

    # ==== 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

    • Servo connected to port P0

    • Potentiometer connected to analog input A0

    • ADC range: 0..4095

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

    linear_map() converts the potentiometer reading into a servo angle in the range -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

    The draw_bar() function:

    • Clears the display

    • Draws the title

    • Shows numeric angle

    • Draws a horizontal bar and tick marks

    • Draws a pointer and filled segment indicating the angle direction

    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. Hauptschleife

    Das Skript führt wiederholt folgende Schritte aus:

    • Es liest den ADC-Wert.

    • Es berechnet den Servowinkel.

    • Es aktualisiert den Servo.

    • Es zeichnet die aktualisierte Benutzeroberfläche.

    • Es aktualisiert das OLED-Display.

    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. Sauberes Beenden

    Mit Ctrl+C:

    • kehrt der Servo auf 0° zurück

    • wird das OLED-Display gelöscht


Fehlerbehebung

  • OLED zeigt nichts an

    • Überprüfen Sie die I2C-Verdrahtung.

    • Stellen Sie sicher, dass die Geräteadresse 0x3C lautet.

    • Vergewissern Sie sich, dass die erforderlichen Bibliotheken installiert sind.

  • Servo reagiert nicht

    • Überprüfen Sie die Stromversorgung des Servos.

    • Stellen Sie sicher, dass der Servo mit P0 verbunden ist.

    • Prüfen Sie, ob das Signalkabel des Servos korrekt angeschlossen ist.

  • Bewegungsbereich ist falsch

    Passen Sie Folgendes an:

    angle = int(linear_map(raw, 0, 4095, -90, 90))
    
  • OLED flackert

    Erhöhen Sie die Verzögerung:

    time.sleep(0.1)
    

Probieren Sie es selbst aus

  1. Servowinkelbegrenzung hinzufügen

    Verhindern Sie eine mechanische Übersteuerung.

  2. Kalibrierung hinzufügen

    Ermitteln Sie die minimalen und maximalen Potentiometerwerte dynamisch.

  3. Sanftere Bewegung

    Wenden Sie Easing oder Tiefpassfilterung an.

  4. Mehr Anzeigeinformationen

    Zeigen Sie zusätzlich zum Winkel auch den rohen ADC-Wert an.

  5. Warnhinweise

    Lassen Sie den Zeiger in der Nähe der Grenzwerte (±75°) blinken.

Diese Erweiterungen machen den Servo-Winkelmesser zu einem leistungsfähigen Werkzeug zur Visualisierung von Eingaben.