📡 Level 3 – Advanced Communication

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

PartHow many?Pin connection
D1 R321USB cable (30 cm)
Joystick Shield1X → Pin 2 (ADC), Y → Pin 4 (ADC)
SG90 Servo (180°)1Signal → 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:

  1. Connect servo on Pin 13
  2. Move to 0° → print
  3. Move to 90° → print
  4. 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:

  1. Read X from Pin 2
  2. Map 0–4095 → 0–180
  3. Send angle to the servo
  4. 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:

  1. Read target angle from X
  2. Compare to current angle
  3. Step by ±2 degrees per loop
  4. 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:

  1. Setup A(26), B(25), C(17) as inputs with pull‑up
  2. If A pressed → 0°
  3. If B pressed → 90°
  4. 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:

  1. Read target from X
  2. Read speed factor from Y
  3. Step size = 1..5 based on Y
  4. 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:

  1. Setup ADC on Pin 2 (X) and Pin 4 (Y)
  2. Setup buttons A(26), B(25), C(17) for presets
  3. Compute target angle from X and step from Y
  4. Move smoothly and allow preset snaps
  5. 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.
On this page