
Firmware for a 4-legged, 12 DOF quadruped robot dog running on an ESP32.
Features
| Part | Notes |
|---|---|
| ESP32 dev board (ESP32-WROOM, classic ESP32) | Bluetooth gamepad support needs the original ESP32 |
| PCA9685 16-channel servo driver | I²C address 0x40 |
| 12 × DS3218 servos | 3 per leg |
| MPU6050-class IMU | I²C address 0x68. |
| 2" ST7789 TFT, 240×320 (SPI) | |
| Voltage divider on battery | Read on GPIO 32 |
| 3S battery + buck converters |
Power warning: never power the servos from USB or from the ESP32 board. Use buck converters sized for servo stall current, with a common ground. Don't run USB and battery servo power at the same time while testing unless the grounds and logic supply are set up for it.
| Function | ESP32 pin |
|---|---|
| I²C SDA (PCA9685 + IMU) | GPIO 21 |
| I²C SCL (PCA9685 + IMU) | GPIO 22 |
| Battery voltage sense | GPIO 32 |
| TFT | see TFT_eSPI setup |
Logical servo ID → channel is group = id / 3; channel = group * 4 + id % 3.
| Leg | Channels |
|---|---|
| FR (front right) | 0, 1, 2 |
| FL (front left) | 4, 5, 6 |
| RR (rear right) | 8, 9, 10 |
| RL (rear left) | 12, 13, 14 |
Channels 3, 7, 11 and 15 are unused.
Arduino IDE 2.x with the ESP32 board package by Espressif (or the Bluepad32 package, see below).
Recommended Tools settings:
| Setting | Value |
|---|---|
| Board | ESP32 Dev Module |
| Upload speed | 921600 (drop to 115200 if uploads fail) |
| CPU frequency | 240 MHz |
| Partition scheme | Default (or Huge APP if using Bluepad32) |
| Serial monitor baud | 115200 |
| Library | Author |
|---|---|
| Adafruit PWM Servo Driver Library | Adafruit |
| Adafruit MPU6050 | Adafruit |
| Adafruit Unified Sensor | Adafruit |
| Adafruit BusIO | Adafruit (installed automatically as a dependency) |
| TFT_eSPI | Bodmer |
Built in with the ESP32 core (nothing to install): Wire, WiFi, WiFiUdp, SPI, Preferences.
Optional, for the Bluetooth gamepad: Bluepad32. It is not in the Library Manager; it ships as its own board package (esp32_bluepad32). Without it the firmware still compiles and runs app-only. Setup steps are in Optional: Bluetooth gamepad (Bluepad32) below.
TFT_eSPI is configured in the library's own User_Setup.h (in Arduino/libraries/TFT_eSPI/), not in this sketch. Edit it to match your screen and wiring, for example:
#define ST7789_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define TFT_BL 5 // backlight, if wired
These are the pins used on my build. Adjust them to your wiring and, if the colors look inverted or the image is mirrored/rotated, try TFT_INVERSION_ON / TFT_INVERSION_OFF or the TFT_RGB_ORDER setting. An upgrade of the TFT_eSPI library overwrites User_Setup.h, so keep a copy.
The gamepad is optional. With the normal ESP32 board package the Bluetooth code compiles to no-ops and the robot runs app-only.
To enable it:
https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.jsonPairing: put the gamepad in Bluetooth pairing mode while the robot is powered. After the first pairing it reconnects automatically. Bluepad32 on the classic ESP32 supports both Bluetooth Classic and BLE pads. See the settings at the top of Robot_Bluetooth.h (ENABLE_BT_CONTROLLER, axis sign flips, pairing wipe).
Quadruped_Firmware folder into one sketch folder (the folder name must match the .ino name).Quadruped_Firmware.ino, select the board and port, and upload.The ESP32 starts its own Wi-Fi access point:
| Setting | Value |
|---|---|
| SSID | QUADRUPED_ESP32 |
| Password | 12345678 |
| UDP port | 5000 |
Join that network from your phone, then open the Android control app. Change the password in Quadruped_Firmwarev1.ino (AP_PASSWORD) before using the robot anywhere other than your workshop, and keep the app in sync.
The app sends control packets and receives telemetry over UDP. The emote list in the app must match the firmware EMOTES[] order, because the app sends the emote index.
Same logic for the app and the gamepad.
| Input | Action |
|---|---|
| Left stick | Walk (forward/back, strafe) |
| Right stick X | Turn on the spot |
| L1 / R1 | Body height down / up (hold both 2 s = stand / sit) |
| L2 / R2 | Pitch trim nose down / up |
| A | Body leveling (balance) on/off |
| B | Gait toggle: trot ↔ walk |
| Y | Emote mode on/off |
| X | Play / stop selected emote |
| D-pad left/right | Choose emote (in emote mode) |
| R3 | Re-run IMU auto-calibration (standing only) |
| SELECT / BACK | Emergency stop while held |
While sitting, the same buttons navigate the on-screen settings menu (L1/R1 up/down, L2/R2 left/right, A enter/save, B back/cancel). Entering emote or puppet mode resets the gait to trot.
Main constants are at the top of Quadruped_Firmwarev1.ino. The ones you're most likely to touch:
| Constant | Default | Meaning |
|---|---|---|
STEP_HEIGHT | menu-adjustable | Foot lift in trot |
WALK_STEP_HEIGHT | 0.010f (1 cm) | Foot lift in walk gait |
LEVEL_GAIN_DEFAULT | 0.6f | Body-leveling strength |
CAL_ZERO_DURATION_S / CAL_BOW_DURATION_S / CAL_HOLD_DURATION_S / CAL_RETURN_DURATION_S | 1.0 / 0.8 / 0.5 / 1.0 | Timing of the IMU auto-calibration "bow" routine |
CAL_TILT_MAGNITUDE_M | 0.025f | Tilt amount used during calibration |
AP_SSID, AP_PASSWORD, UDP_PORT | see above | Wi-Fi settings |
9 emotes: Curious Head Tilt, Cautious Object Tap, The Wiggle, Play Bow, Happy Dance into Sneak, Breathing into Foot Stomp, Matrix Gyro Roll, Push-Ups, Sit & Wave Hello.
Emote mode runs with balance disabled.
| File | Purpose |
|---|---|
Quadruped_Firmwarev1.ino | Main sketch: settings, setup, RTOS tasks |
Robot_Gait_Mechanism.h | Gait engine, IMU/Kalman filter, balance, servo output |
Robot_Emotes.h | Emote animations |
Screen_Settings.h | TFT telemetry screen, settings menu, UDP telemetry |
Robot_Bluetooth.h | Bluepad32 gamepad input |
| Symptom | Check |
|---|---|
Won't compile: TFT_eSPI.h setup errors | Edit User_Setup.h in the TFT_eSPI library folder |
| Won't compile with Bluepad32 / sketch too large | Use the Huge APP partition scheme |
| Blank/white screen | TFT pins, driver (ST7789_DRIVER), backlight pin |
| Servos twitch or the ESP32 resets | Servo supply undersized; add bulk capacitance; check common ground |
| IMU not found | I²C wiring (SDA 21 / SCL 22), address 0x68; run the IMU test sketches |
| Robot leans while standing | Re-run IMU calibration (R3) on a flat surface |
| App can't connect | Phone must be joined to QUADRUPED_ESP32; disable "auto-switch to mobile data" |
| Gamepad won't pair | Bluepad32 board package selected? Put pad in pairing mode; see serial [BT] messages |
Servos can pinch fingers and the robot can fall. Keep the emergency stop (SELECT/BACK) within reach, test on a stand first, and use a properly fused battery.
Licensed under Creative Commons Attribution–NonCommercial 4.0 International (CC BY-NC 4.0).
You are free to remix, adapt and build upon this design for non-commercial purposes, with appropriate credit. Commercial use of any kind is not permitted.

Firmware for a 4-legged, 12 DOF quadruped robot dog running on an ESP32.
Features
| Part | Notes |
|---|---|
| ESP32 dev board (ESP32-WROOM, classic ESP32) | Bluetooth gamepad support needs the original ESP32 |
| PCA9685 16-channel servo driver | I²C address 0x40 |
| 12 × DS3218 servos | 3 per leg |
| MPU6050-class IMU | I²C address 0x68. |
| 2" ST7789 TFT, 240×320 (SPI) | |
| Voltage divider on battery | Read on GPIO 32 |
| 3S battery + buck converters |
Power warning: never power the servos from USB or from the ESP32 board. Use buck converters sized for servo stall current, with a common ground. Don't run USB and battery servo power at the same time while testing unless the grounds and logic supply are set up for it.
| Function | ESP32 pin |
|---|---|
| I²C SDA (PCA9685 + IMU) | GPIO 21 |
| I²C SCL (PCA9685 + IMU) | GPIO 22 |
| Battery voltage sense | GPIO 32 |
| TFT | see TFT_eSPI setup |
Logical servo ID → channel is group = id / 3; channel = group * 4 + id % 3.
| Leg | Channels |
|---|---|
| FR (front right) | 0, 1, 2 |
| FL (front left) | 4, 5, 6 |
| RR (rear right) | 8, 9, 10 |
| RL (rear left) | 12, 13, 14 |
Channels 3, 7, 11 and 15 are unused.
Arduino IDE 2.x with the ESP32 board package by Espressif (or the Bluepad32 package, see below).
Recommended Tools settings:
| Setting | Value |
|---|---|
| Board | ESP32 Dev Module |
| Upload speed | 921600 (drop to 115200 if uploads fail) |
| CPU frequency | 240 MHz |
| Partition scheme | Default (or Huge APP if using Bluepad32) |
| Serial monitor baud | 115200 |
| Library | Author |
|---|---|
| Adafruit PWM Servo Driver Library | Adafruit |
| Adafruit MPU6050 | Adafruit |
| Adafruit Unified Sensor | Adafruit |
| Adafruit BusIO | Adafruit (installed automatically as a dependency) |
| TFT_eSPI | Bodmer |
Built in with the ESP32 core (nothing to install): Wire, WiFi, WiFiUdp, SPI, Preferences.
Optional, for the Bluetooth gamepad: Bluepad32. It is not in the Library Manager; it ships as its own board package (esp32_bluepad32). Without it the firmware still compiles and runs app-only. Setup steps are in Optional: Bluetooth gamepad (Bluepad32) below.
TFT_eSPI is configured in the library's own User_Setup.h (in Arduino/libraries/TFT_eSPI/), not in this sketch. Edit it to match your screen and wiring, for example:
#define ST7789_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define TFT_BL 5 // backlight, if wired
These are the pins used on my build. Adjust them to your wiring and, if the colors look inverted or the image is mirrored/rotated, try TFT_INVERSION_ON / TFT_INVERSION_OFF or the TFT_RGB_ORDER setting. An upgrade of the TFT_eSPI library overwrites User_Setup.h, so keep a copy.
The gamepad is optional. With the normal ESP32 board package the Bluetooth code compiles to no-ops and the robot runs app-only.
To enable it:
https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.jsonPairing: put the gamepad in Bluetooth pairing mode while the robot is powered. After the first pairing it reconnects automatically. Bluepad32 on the classic ESP32 supports both Bluetooth Classic and BLE pads. See the settings at the top of Robot_Bluetooth.h (ENABLE_BT_CONTROLLER, axis sign flips, pairing wipe).
Quadruped_Firmware folder into one sketch folder (the folder name must match the .ino name).Quadruped_Firmware.ino, select the board and port, and upload.The ESP32 starts its own Wi-Fi access point:
| Setting | Value |
|---|---|
| SSID | QUADRUPED_ESP32 |
| Password | 12345678 |
| UDP port | 5000 |
Join that network from your phone, then open the Android control app. Change the password in Quadruped_Firmwarev1.ino (AP_PASSWORD) before using the robot anywhere other than your workshop, and keep the app in sync.
The app sends control packets and receives telemetry over UDP. The emote list in the app must match the firmware EMOTES[] order, because the app sends the emote index.
Same logic for the app and the gamepad.
| Input | Action |
|---|---|
| Left stick | Walk (forward/back, strafe) |
| Right stick X | Turn on the spot |
| L1 / R1 | Body height down / up (hold both 2 s = stand / sit) |
| L2 / R2 | Pitch trim nose down / up |
| A | Body leveling (balance) on/off |
| B | Gait toggle: trot ↔ walk |
| Y | Emote mode on/off |
| X | Play / stop selected emote |
| D-pad left/right | Choose emote (in emote mode) |
| R3 | Re-run IMU auto-calibration (standing only) |
| SELECT / BACK | Emergency stop while held |
While sitting, the same buttons navigate the on-screen settings menu (L1/R1 up/down, L2/R2 left/right, A enter/save, B back/cancel). Entering emote or puppet mode resets the gait to trot.
Main constants are at the top of Quadruped_Firmwarev1.ino. The ones you're most likely to touch:
| Constant | Default | Meaning |
|---|---|---|
STEP_HEIGHT | menu-adjustable | Foot lift in trot |
WALK_STEP_HEIGHT | 0.010f (1 cm) | Foot lift in walk gait |
LEVEL_GAIN_DEFAULT | 0.6f | Body-leveling strength |
CAL_ZERO_DURATION_S / CAL_BOW_DURATION_S / CAL_HOLD_DURATION_S / CAL_RETURN_DURATION_S | 1.0 / 0.8 / 0.5 / 1.0 | Timing of the IMU auto-calibration "bow" routine |
CAL_TILT_MAGNITUDE_M | 0.025f | Tilt amount used during calibration |
AP_SSID, AP_PASSWORD, UDP_PORT | see above | Wi-Fi settings |
9 emotes: Curious Head Tilt, Cautious Object Tap, The Wiggle, Play Bow, Happy Dance into Sneak, Breathing into Foot Stomp, Matrix Gyro Roll, Push-Ups, Sit & Wave Hello.
Emote mode runs with balance disabled.
| File | Purpose |
|---|---|
Quadruped_Firmwarev1.ino | Main sketch: settings, setup, RTOS tasks |
Robot_Gait_Mechanism.h | Gait engine, IMU/Kalman filter, balance, servo output |
Robot_Emotes.h | Emote animations |
Screen_Settings.h | TFT telemetry screen, settings menu, UDP telemetry |
Robot_Bluetooth.h | Bluepad32 gamepad input |
| Symptom | Check |
|---|---|
Won't compile: TFT_eSPI.h setup errors | Edit User_Setup.h in the TFT_eSPI library folder |
| Won't compile with Bluepad32 / sketch too large | Use the Huge APP partition scheme |
| Blank/white screen | TFT pins, driver (ST7789_DRIVER), backlight pin |
| Servos twitch or the ESP32 resets | Servo supply undersized; add bulk capacitance; check common ground |
| IMU not found | I²C wiring (SDA 21 / SCL 22), address 0x68; run the IMU test sketches |
| Robot leans while standing | Re-run IMU calibration (R3) on a flat surface |
| App can't connect | Phone must be joined to QUADRUPED_ESP32; disable "auto-switch to mobile data" |
| Gamepad won't pair | Bluepad32 board package selected? Put pad in pairing mode; see serial [BT] messages |
Servos can pinch fingers and the robot can fall. Keep the emergency stop (SELECT/BACK) within reach, test on a stand first, and use a properly fused battery.
Licensed under Creative Commons Attribution–NonCommercial 4.0 International (CC BY-NC 4.0).
You are free to remix, adapt and build upon this design for non-commercial purposes, with appropriate credit. Commercial use of any kind is not permitted.