Skip to content

Instantly share code, notes, and snippets.

@yuanoook
Last active January 11, 2025 02:23
Show Gist options
  • Save yuanoook/deeddea3b17088c3adbed879470a98c7 to your computer and use it in GitHub Desktop.
Save yuanoook/deeddea3b17088c3adbed879470a98c7 to your computer and use it in GitHub Desktop.
Joystick controls 2 SG90 9g Micro Servos with ESP32-S3
import machine
import time
from engine import Engine
from joystick import Joystick
# Define pins
led_pin = 1
eyelid_angle_limit = 70
# Create objects
leftEyeLidUp = Engine(9, eyelid_angle_limit)
leftEyeLidDown = Engine(13, eyelid_angle_limit)
rightEyeLidUp = Engine(8, eyelid_angle_limit)
rightEyeLidDown = Engine(5, eyelid_angle_limit)
eyeBallUpdown = Engine(7, 70)
eyeBallLeftRight = Engine(6, 70)
led = machine.Pin(led_pin, machine.Pin.OUT)
# Initialize joystick with custom pins (X=12, Y=11, Switch=10)
joystick = Joystick(x_pin=12, y_pin=11, switch_pin=10)
last_x_value = 2047
left_eye_lid_up_offset = - 400
left_eye_lid_down_offset = 400
def reset_eyelids():
leftEyeLidUp.move_servo(last_x_value + left_eye_lid_up_offset)
rightEyeLidUp.move_servo(4095 - last_x_value)
down_speed = 1
if last_x_value < 2047:
down_speed = 0.5
leftEyeLidDown.move_servo((last_x_value - 2047) * down_speed + 2047 + left_eye_lid_down_offset)
rightEyeLidDown.move_servo((4095 - last_x_value) * down_speed)
def close_eyelids():
print("Closing eyelids.")
leftEyeLidUp.move_servo(4095 + left_eye_lid_up_offset)
leftEyeLidDown.move_servo(0 + left_eye_lid_down_offset)
rightEyeLidUp.move_servo(0)
rightEyeLidDown.move_servo(4095)
def joystick_callback(x_value, y_value, switch_value):
global last_x_value
# Update the previous switch value
last_x_value = x_value
print(f"Joystick: {x_value} {y_value} {switch_value}")
if switch_value == 0:
close_eyelids()
else:
reset_eyelids()
# Move eye balls based on joystick
eyeBallUpdown.move_servo(4095 - x_value)
eyeBallLeftRight.move_servo(4095 - y_value)
def loop(func, *args, **kwargs):
try:
while True:
func(*args, **kwargs)
time.sleep(0.05)
except KeyboardInterrupt:
servo.deinit() if 'servo' in globals() else None
loop(joystick.read, joystick_callback)
import machine
import time
class Engine:
def __init__(self, pin, angle_range=90):
"""
Initialize the Engine with the specified pin and angle range.
Default angle range is 90 degrees (45 degrees on each side).
"""
self.servo = machine.PWM(machine.Pin(pin))
self.servo.freq(50)
self.angle_range = angle_range
self.center_angle = 90 # Assuming the center position is 90 degrees (neutral position)
def interval_mapping(self, x, in_min, in_max, out_min, out_max):
"""
Maps a value from one range to another.
"""
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
def servo_write(self, angle):
"""
Moves the servo to a specific angle.
"""
pulse_width = self.interval_mapping(angle, 0, 180, 0.5, 2.5) # Map angle to pulse width in ms
duty = int(self.interval_mapping(pulse_width, 0, 20, 0, 65535)) # Map pulse width to duty cycle
self.servo.duty_u16(duty) # Set PWM duty cycle
def move_servo(self, x_value):
"""
Move the servo based on joystick X value.
x_value is assumed to be between 0 and 4095.
"""
# Calculate the total possible range of motion based on the configured angle range
half_range = self.angle_range / 2
angle = self.center_angle + int(((x_value / 4095) * self.angle_range) - half_range)
self.servo_write(angle)
from machine import Pin, ADC
class Joystick:
def __init__(self, x_pin=12, y_pin=11, switch_pin=10):
self.x_pin = ADC(Pin(x_pin)) # X-axis (analog input)
self.y_pin = ADC(Pin(y_pin)) # Y-axis (analog input)
self.switch_pin = Pin(switch_pin, Pin.IN, Pin.PULL_UP) # Switch (digital input)
self.x_pin.atten(ADC.ATTN_11DB)
self.y_pin.atten(ADC.ATTN_11DB)
def read(self, callback):
x_value = self.x_pin.read()
y_value = self.y_pin.read()
# Read the switch value (high or low)
switch_value = self.switch_pin.value()
# Pass the values to the callback function
callback(x_value, y_value, switch_value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment