Note
Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.
Why Join?
Expert Support: Solve post-sale issues and technical challenges with help from our community and team.
Learn & Share: Exchange tips and tutorials to enhance your skills.
Exclusive Previews: Get early access to new product announcements and sneak peeks.
Special Discounts: Enjoy exclusive discounts on our newest products.
Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.
👉 Ready to explore and create with us? Click [here] and join today!
4.11 Servo Angle Meter
Introduction
In this lesson, you will build a Servo Angle Meter — a visual servo angle indicator that uses a potentiometer to control a servo motor, while displaying the current angle on an OLED screen. The potentiometer provides an analog voltage through the Fusion HAT+’s ADC interface. The servo receives commands based on this reading, and a 128×64 I2C OLED screen shows the numeric servo angle and a graphical bar that moves smoothly across the display.
As you rotate the potentiometer, the servo sweeps between approximately -90° and +90°, and the OLED updates in real time.
What You’ll Need
The following components are required for this project:
COMPONENT INTRODUCTION |
PURCHASE LINK |
|---|---|
- |
|
- |
|
Raspberry Pi |
- |
Wiring Diagram
Refer to the wiring diagram for assembling the components:
Setup Steps
Install the required libraries:
sudo pip3 install adafruit-circuitpython-ssd1306 --break
All example code used in this tutorial is available in the
ai-lab-kitdirectory:cd ~/ai-lab-kit/python/ sudo python3 4.11_ServoAngleMeter.py
When the script runs:
Rotating the potentiometer moves the servo between -90° and +90°.
The OLED shows the numeric angle and a moving bar pointer.
Ctrl+C exits, resets servo to 0°, and clears the display.
Code
Here is the Python script for the Servo Angle Meter:
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
Imports
ADCreads analog values from the potentiometerServocontrols servo rotationPILhandles all OLED graphicsadafruit_ssd1306drives the I2C OLED displayboardprovides hardware I/Otimecontrols loop speed
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()
Servo & Potentiometer
Servo connected to port
P0Potentiometer connected to analog input
A0ADC range:
0..4095
# ==== Servo & potentiometer ==== servo = Servo('P0') # servo on port P0 pot = ADC('A0') # potentiometer on A0 (0..4095)
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
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)
Main Loop
The script repeatedly:
Reads the ADC
Computes servo angle
Updates servo
Draws updated UI
Refreshes 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
Graceful Exit
Ctrl+C:
Servo returns to 0°
OLED is cleared
Troubleshooting
OLED shows nothing
Check I2C wiring
Verify device address is
0x3CEnsure required libraries are installed
Servo does not respond
Check servo power
Confirm servo is connected to
P0Ensure servo signal wire is correct
Movement range incorrect
Adjust:
angle = int(linear_map(raw, 0, 4095, -90, 90))
OLED flickers
Increase delay:
time.sleep(0.1)
Try It Yourself
Add servo angle limits
Prevent mechanical overdrive.
Add calibration
Detect min/max potentiometer values dynamically.
Smooth motion
Apply easing or low-pass filtering.
More display info
Show raw ADC value alongside angle.
Warnings
Blink pointer near limits (±75°).
These additions turn the Servo Angle Meter into a highly capable input-visualization tool.