Project 3.4: "Joystick Controlled Servo"
🚀 Project 3.4 – Joystick Controlled Servo
🎯 What you’ll learn
- ✅ Goal 1: Move a 180° servo to basic positions
- ✅ Goal 2: Map joystick movement (X/Y) to servo angle
- ✅ Goal 3: Make smooth motion, presets, and simple tracking behavior
Key ideas
- Short definition: A servo turns to an angle when you send it a command.
- Real‑world link: Servos steer robots, move camera gimbals, and control grippers.
🧱 Blocks glossary (used in this project)
- Analog input (ADC): Reads a smooth number (0–4095) from a pin (joystick axes).
- Digital input (pull‑up): Reads button press (A–F on the joystick shield).
- Servo 180°: Turns the shaft to a given angle (0–180 degrees).
- Variable: Stores values like an angle or a threshold.
- if / else: Chooses actions based on a condition.
- Loop: Repeats the control steps continuously.
- Delay: Small waits to make motion readable and stable.
🧰 What you need
| Part | How many? | Pin connection |
|---|---|---|
| D1 R32 | 1 | USB cable (30 cm) |
| Joystick Shield | 1 | X → Pin 2 (ADC), Y → Pin 4 (ADC) |
| SG90 Servo (180°) | 1 | Signal → Pin 13 (PWM/servo output) |
- Buttons (for presets): A(26), B(25), C(17), D(16), E(27), F(14)
- Keep the servo powered per your kit (5V and GND to the board or driver, Signal to Pin 13).
🔌 Wiring tip: The servo has three wires: brown/black = GND, red = 5V, orange/yellow = signal. Signal goes to Pin 13.
📍 Pin map snapshot: ADC pins for joystick are 2 and 4. Buttons use pull‑up and are active LOW.
✅ Before you start
- Plug in the USB and open your serial monitor.
- Test print shows:
print("Ready!") # Confirm serial is working
🎮 Microprojects (5 mini missions)
🎮 Microproject 3.4.1 – Basic servo position control
Goal: Move the servo to three fixed angles: 0°, 90°, 180°.
Blocks used:
- Servo 180°: Set angle
- Serial print: Announce each angle
- Delay: Pause to see movement
Block sequence:
- Connect servo on Pin 13
- Move to 0° → print
- Move to 90° → print
- Move to 180° → print
MicroPython code:
# Microproject 3.4.1 – Basic servo positions (0°, 90°, 180°)
import servo # Load servo control library
import time # Load time library for delays
print("[Init] Servo control on Pin 13") # Serial: announce setup
servo.servo180_angle(13, 0) # Move servo to 0 degrees
print("[Servo] Angle = 0°") # Serial: confirm 0°
time.sleep_ms(800) # Wait to see the movement
servo.servo180_angle(13, 90) # Move servo to 90 degrees
print("[Servo] Angle = 90°") # Serial: confirm 90°
time.sleep_ms(800) # Wait to see the movement
servo.servo180_angle(13, 180) # Move servo to 180 degrees
print("[Servo] Angle = 180°") # Serial: confirm 180°
time.sleep_ms(800) # Wait to see the movement
Reflection: You made your robot’s arm turn to exact positions.
Challenge:
- Easy: Try 45° and 135°.
- Harder: Create a small “wave” (0° → 90° → 45° → 135° → 180°).
🎮 Microproject 3.4.2 – Joystick to servo mapping
Goal: Map joystick X (0–4095) to servo angle (0–180).
Blocks used:
- Analog input (ADC): Read Pin 2 (X)
- Servo 180°: Set angle based on mapping
- Variable: Store computed angle
Block sequence:
- Read X from Pin 2
- Map 0–4095 → 0–180
- Send angle to the servo
- Print values for clarity
MicroPython code:
# Microproject 3.4.2 – Map joystick X to servo angle
import machine # Load hardware/ADC library
import servo # Load servo library
import time # Load time library for delays
adc2 = machine.ADC(machine.Pin(2)) # ADC on Pin 2 for X-axis
adc2.atten(machine.ADC.ATTN_11DB) # Set X range to 0–3.3V
adc2.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution (0–4095)
print("[Init] Joystick X=2 → Servo Pin 13") # Serial: input/output mapping
while True: # Continuous control loop
x_value = adc2.read() # Read X-axis (0–4095)
angle = int((x_value * 180) / 4095) # Map X to 0–180 (simple linear map)
servo.servo180_angle(13, angle) # Move servo to the mapped angle
print("[Map] X:", x_value, "→ Angle:", angle) # Serial: show mapping result
time.sleep_ms(120) # Small delay for smooth control
Reflection: Your hand’s motion now directly controls the servo.
Challenge:
- Easy: Slow the update to 200 ms.
- Harder: Map Y (Pin 4) instead of X and compare smoothness.
🎮 Microproject 3.4.3 – Smooth servo movement
Goal: Reduce jerk by moving gradually toward the target angle.
Blocks used:
- Analog input: Read X as target
- Variable: Keep current angle
- if / else: Step towards target
Block sequence:
- Read target angle from X
- Compare to current angle
- Step by ±2 degrees per loop
- Print progress
MicroPython code:
# Microproject 3.4.3 – Smoothly move toward target angle
import machine # Load hardware/ADC library
import servo # Load servo library
import time # Load time library
adc2 = machine.ADC(machine.Pin(2)) # ADC on Pin 2 (X-axis)
adc2.atten(machine.ADC.ATTN_11DB) # Full range 0–3.3V
adc2.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution (0–4095)
current_angle = 90 # Start centered at 90°
print("[Init] Smooth start at 90°") # Serial: start angle
while True: # Continuous smoothing loop
x_value = adc2.read() # Read joystick X
target_angle = int((x_value * 180) / 4095) # Map to target angle
if target_angle > current_angle: # If target is higher
current_angle = current_angle + 2 # Step up by 2°
elif target_angle < current_angle: # If target is lower
current_angle = current_angle - 2 # Step down by 2°
# Clamp to safe range 0–180
if current_angle < 0: # If below 0°
current_angle = 0 # Clamp to 0°
if current_angle > 180: # If above 180°
current_angle = 180 # Clamp to 180°
servo.servo180_angle(13, current_angle) # Move servo to current step
print("[Smooth] Target:", target_angle, "Now:", current_angle) # Serial: progress
time.sleep_ms(60) # Small delay for smooth motion
Reflection: Small steps make the servo feel natural and controlled.
Challenge:
- Easy: Change step size to 3°.
- Harder: Use a bigger step when far away and smaller step near the target.
🎮 Microproject 3.4.4 – Predefined positions (buttons)
Goal: Use buttons A/B/C to jump to 0°, 90°, 180° instantly.
Blocks used:
- Digital input (pull‑up): Read buttons
- Servo 180°: Set angle presets
- Serial print: Announce preset
Block sequence:
- Setup A(26), B(25), C(17) as inputs with pull‑up
- If A pressed → 0°
- If B pressed → 90°
- If C pressed → 180°
MicroPython code:
# Microproject 3.4.4 – Preset angles with buttons A/B/C
import machine # Load hardware/pin library
import servo # Load servo library
import time # Load time library
pin26 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP) # Button A (active LOW)
pin25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP) # Button B (active LOW)
pin17 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP) # Button C (active LOW)
print("[Init] Presets: A=0°, B=90°, C=180°") # Serial: explain button mapping
while True: # Continuous button check loop
if pin26.value() == 0: # If A pressed (LOW)
servo.servo180_angle(13, 0) # Move servo to 0°
print("[Preset] A → 0°") # Serial: confirm action
time.sleep_ms(250) # Debounce delay
if pin25.value() == 0: # If B pressed (LOW)
servo.servo180_angle(13, 90) # Move servo to 90°
print("[Preset] B → 90°") # Serial: confirm action
time.sleep_ms(250) # Debounce delay
if pin17.value() == 0: # If C pressed (LOW)
servo.servo180_angle(13, 180) # Move servo to 180°
print("[Preset] C → 180°") # Serial: confirm action
time.sleep_ms(250) # Debounce delay
Reflection: Quick presets make control easy—like snapping to known positions.
Challenge:
- Easy: Add D(16) = 45°.
- Harder: Use E(27) = “center” (90°) and F(14) = “park” (0°).
🎮 Microproject 3.4.5 – Automatic tracking (Y controls follow‑speed)
Goal: Use Y to control how fast the servo follows X (tracking).
Blocks used:
- Analog input (ADC): Read X and Y
- Variable: Compute angle and speed
- if / else: Adjust step size by Y
Block sequence:
- Read target from X
- Read speed factor from Y
- Step size = 1..5 based on Y
- Move toward target with variable speed
MicroPython code:
# Microproject 3.4.5 – Track with variable speed (Y sets follow-speed)
import machine # Load hardware/ADC library
import servo # Load servo library
import time # Load time library
adc2 = machine.ADC(machine.Pin(2)) # ADC on Pin 2 (X-axis)
adc4 = machine.ADC(machine.Pin(4)) # ADC on Pin 4 (Y-axis)
adc2.atten(machine.ADC.ATTN_11DB) # Full X range
adc2.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution for X
adc4.atten(machine.ADC.ATTN_11DB) # Full Y range
adc4.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution for Y
current_angle = 90 # Start centered
print("[Init] Tracking: X→angle, Y→speed") # Serial: tracking mode
while True: # Continuous tracking loop
x_value = adc2.read() # Read X (target source)
y_value = adc4.read() # Read Y (speed control)
target_angle = int((x_value * 180) / 4095) # Map X to 0–180
# Map Y to step size: low→slow, high→fast (1..5)
if y_value < 1200: # Very low Y
step = 1 # Slowest follow
elif y_value < 2400: # Mid-low Y
step = 2 # Slow follow
elif y_value < 3000: # Mid Y
step = 3 # Medium follow
elif y_value < 3600: # Mid-high Y
step = 4 # Fast follow
else: # Very high Y
step = 5 # Fastest follow
if target_angle > current_angle: # If target above current
current_angle = current_angle + step # Increase by step
elif target_angle < current_angle: # If target below current
current_angle = current_angle - step # Decrease by step
if current_angle < 0: # Clamp below 0°
current_angle = 0 # Set to 0°
if current_angle > 180: # Clamp above 180°
current_angle = 180 # Set to 180°
servo.servo180_angle(13, current_angle) # Move servo to current
print("[Track] Y:", y_value, "Step:", step,
"Target:", target_angle, "Now:", current_angle) # Serial: report
time.sleep_ms(70) # Small delay for smooth tracking
Reflection: You built a “follow me” behavior with speed control—smart and fun.
Challenge:
- Easy: Make step range 1..3 only.
- Harder: Add a “dead zone” near the target (±3°) to reduce jitter.
✨ Main project – Joystick‑controlled servo arm
🔧 Blocks steps (with glossary)
- Analog input (ADC): Read X for angle and Y for speed
- Digital input (pull‑up): Use buttons for presets
- Servo 180°: Move arm to angle
- Variable: Store current angle, target, and step
- Delay: Smooth motion, readable logs
Block sequence:
- Setup ADC on Pin 2 (X) and Pin 4 (Y)
- Setup buttons A(26), B(25), C(17) for presets
- Compute target angle from X and step from Y
- Move smoothly and allow preset snaps
- Print status and repeat
🐍 MicroPython code (mirroring blocks)
# Project 3.4 – Joystick-controlled Servo Arm
import machine # Load hardware/ADC and pin library
import servo # Load servo control library
import time # Load time library for delays
adc2 = machine.ADC(machine.Pin(2)) # ADC on Pin 2 (joystick X)
adc4 = machine.ADC(machine.Pin(4)) # ADC on Pin 4 (joystick Y)
adc2.atten(machine.ADC.ATTN_11DB) # Full X range 0–3.3V
adc2.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution for X
adc4.atten(machine.ADC.ATTN_11DB) # Full Y range 0–3.3V
adc4.width(machine.ADC.WIDTH_12BIT) # 12-bit resolution for Y
pin26 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP) # Button A (0°)
pin25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP) # Button B (90°)
pin17 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP) # Button C (180°)
current_angle = 90 # Start at a neutral center
print("[Init] Servo=13, X=2, Y=4 | A=0°, B=90°, C=180°") # Serial: mapping info
while True: # Main control loop
x_value = adc2.read() # Read joystick X (0–4095)
y_value = adc4.read() # Read joystick Y (0–4095)
target_angle = int((x_value * 180) / 4095) # Map X to 0–180 target angle
if y_value < 1200: # Speed selection (from Y)
step = 1 # Slow
elif y_value < 2400:
step = 2 # Slow-medium
elif y_value < 3000:
step = 3 # Medium
elif y_value < 3600:
step = 4 # Fast
else:
step = 5 # Fastest
if pin26.value() == 0: # If A pressed (LOW)
current_angle = 0 # Snap to 0°
print("[Preset] A → 0°") # Serial: confirm preset
if pin25.value() == 0: # If B pressed (LOW)
current_angle = 90 # Snap to 90°
print("[Preset] B → 90°") # Serial: confirm preset
if pin17.value() == 0: # If C pressed (LOW)
current_angle = 180 # Snap to 180°
print("[Preset] C → 180°") # Serial: confirm preset
if target_angle > current_angle: # Move toward target (up)
current_angle = current_angle + step # Increase by step
elif target_angle < current_angle: # Move toward target (down)
current_angle = current_angle - step # Decrease by step
if current_angle < 0: # Clamp min bound
current_angle = 0 # Set to 0°
if current_angle > 180: # Clamp max bound
current_angle = 180 # Set to 180°
servo.servo180_angle(13, current_angle) # Command servo to current angle
print("[Servo] Y:", y_value, "Step:", step,
"Target:", target_angle, "Now:", current_angle) # Serial: status
time.sleep_ms(70) # Short delay for smoothness
📖 External explanation
- What it teaches: Translating joystick values into servo motion with smooth control and presets.
- Why it works: The joystick’s analog values map to an angle; stepping reduces jerk; buttons jump to known positions.
- Key concept: Mapping ranges (0–4095 → 0–180) and controlling speed with simple step sizes.
✨ Story time
You’ve turned your joystick into a robotic wrist. Sweep, snap to positions, and follow targets—like steering a camera on a rover.
🕵️ Debugging (2)
🐞 Debugging 3.4.A – Incorrect range of motion
Problem: Servo hits the end or barely moves.
Clues: Angle prints go below 0 or above 180; servo buzzes.
Broken code:
current_angle = current_angle + 10 # Moves too fast, no clamping
Fixed code:
current_angle = current_angle + step # Move gradually
if current_angle > 180: # Clamp upper bound
current_angle = 180 # Keep safe range
Why it works: Small steps and clamping prevent over‑travel and buzzing.
Avoid next time: Always keep angles within 0–180.
🐞 Debugging 3.4.B – Erratic movement
Problem: Servo shakes or twitches near the target.
Clues: Angle toggles around a value; jitter in final position.
Broken code:
# Direct mapping without dead zone
servo.servo180_angle(13, int((adc2.read()*180)/4095))
Fixed code:
# Add a small dead zone
if abs(target_angle - current_angle) < 3: # If within 3°
pass # Do not move (reduce jitter)
else:
# Step toward target as usual
Why it works: A dead zone stops tiny back‑and‑forth changes near the target.
Avoid next time: Use smoothing or thresholds to stabilize motion.
✅ Final checklist
- I moved the servo to 0°, 90°, and 180°
- I mapped joystick movement to the servo angle
- I made motion smoother with steps
- I used buttons for presets
- I controlled follow‑speed with Y
📚 Extras
- 🧠 Student tip: Hold the joystick gently near the center and watch the servo settle.
- 🧑🏫 Instructor tip: Encourage students to print target/current values to see the effect of smoothing.
- 📖 Glossary:
- Servo: A motor that moves to a specific angle when commanded.
- Mapping: Converting one range of numbers to another range.
- Dead zone: A small range where we ignore changes to reduce jitter.
- 💡 Mini tips:
- Keep update delays short (60–150 ms).
- Use presets for quick tests.
- Always clamp angles to safe limits.