ProblemWhy an electronic differential
A fixed rear axle forces both wheels to turn at the same speed. In a corner the inside wheel travels a shorter arc, so one wheel has to slip, which shows up as understeer and lost traction.
Giving each rear wheel its own motor lets the control code set a speed offset from the steering input, so each wheel turns at the speed the corner asks for.
FirmwareControl path
- A PS5 controller pairs to a Python script that mixes the triggers and stick into separate left and right rear-motor speeds.
- Those commands go wirelessly to C++ firmware on the Arduino, which turns them into PWM signals for the motor driver.
- The loop is short, so the car responds without noticeable lag.
PythonThe controller script
This is the laptop side of the control path above. It reads the PS5 controller with pygame and sends commands to the car's Bluetooth module with pyserial at 9600 baud.
- R2 is gas, L2 is brake and reverse, and the left stick steers, with a 10% deadzone.
- Throttle and steering become separate left and right rear-motor commands from −255 to 255, sent as a
left,rightline. The car turns by driving its two rear wheels at different speeds. - The controller is polled every 20 ms, and a command is only sent when it changes.
- ✕ toggles an autopilot mode (
AUTO/MAN), and Ctrl+C sends0,0so the car stops before the script exits.
import pygame
import serial
import time
# ── Configuration ──────────────────────────────────────────────
BLUETOOTH_PORT = 'COM5' # <--- CHANGE TO YOUR PORT
BAUD_RATE = 9600
UPDATE_RATE = 0.02
DEADZONE = 0.1
try:
bt = serial.Serial(BLUETOOTH_PORT, BAUD_RATE, timeout=1)
print("Bluetooth connected successfully!")
except Exception as e:
print(f"Error connecting to Bluetooth: {e}")
exit()
pygame.init()
pygame.joystick.init()
if pygame.joystick.get_count() == 0:
print("No controller detected.")
exit()
controller = pygame.joystick.Joystick(0)
controller.init()
print(f"Controller Connected: {controller.get_name()}")
# PS5 Controller Mappings
AXIS_STEER = 0
AXIS_L2 = 4
AXIS_R2 = 5
BTN_CROSS = 0 # The 'X' Button on PS5
last_command = ""
auto_mode = False
last_btn_state = False
print("\n--- PS5 Controls Active ---")
print("R2: Gas | L2: Brake/Reverse | L3: Steer")
print("Press 'X' to toggle Autopilot!")
try:
while True:
pygame.event.pump()
# --- AUTOPILOT TOGGLE LOGIC ---
btn_state = controller.get_button(BTN_CROSS)
if btn_state and not last_btn_state: # Button was just pushed down
auto_mode = not auto_mode # Toggle the state
if auto_mode:
bt.write(b"AUTO\n")
print("\n[!] AUTOPILOT ENGAGED [!]\n")
else:
bt.write(b"MAN\n")
print("\n[-] MANUAL CONTROL RESTORED [-]\n")
last_btn_state = btn_state
# --- MANUAL DRIVING LOGIC ---
# We only calculate and send joystick data if Autopilot is OFF
if not auto_mode:
steer_axis = controller.get_axis(AXIS_STEER)
l2_axis = controller.get_axis(AXIS_L2)
r2_axis = controller.get_axis(AXIS_R2)
if abs(steer_axis) < DEADZONE:
steer_axis = 0.0
brake = max(0.0, (l2_axis + 1.0) / 2.0)
gas = max(0.0, (r2_axis + 1.0) / 2.0)
throttle = gas - brake
left_motor = throttle + steer_axis
right_motor = throttle - steer_axis
left_motor = max(-1.0, min(1.0, left_motor))
right_motor = max(-1.0, min(1.0, right_motor))
left_pwm = int(left_motor * 255)
right_pwm = int(right_motor * 255)
command = f"{left_pwm},{right_pwm}\n"
if command != last_command:
bt.write(command.encode('utf-8'))
last_command = command
# Only print the dashboard occasionally so it doesn't flood the terminal
print(f"\rMANUAL >> L2: {brake:.2f} | R2: {gas:.2f} | Motor L: {left_pwm:4d} | Motor R: {right_pwm:4d}",
end="")
time.sleep(UPDATE_RATE)
except KeyboardInterrupt:
print("\nExiting. Stopping car...")
bt.write(b"0,0\n")
bt.close()
pygame.quit()ElectricalPower and motor hardware
- An 11.1 V 3S LiPo powers the whole car, split by dual buck converters so logic and actuators never share a rail.
- An L298N dual H-bridge drives both rear motors.
- The steering servo has its own supply, so a stall cannot brown out the microcontroller mid-corner.
- I designed the circuit and wiring layout and selected every part against a BOM.
MechanicalChassis
I modelled the chassis in SolidWorks to package the steering servo, motors and electronics, then completed the mechanical assembly. The car was demonstrated live to club leadership, which tested control response, chassis integrity and firmware stability under real driving.
DemoSee it drive
FiguresBuild photos and CAD
Select any figure to open it full size.