Skip to content

Firmware Guide

Complete guide for building, flashing, configuring, and debugging the ESP32-S3 firmware.

Prerequisites

ESP-IDF Installation

Install ESP-IDF v5.x following the official guide:

mkdir -p ~/esp
cd ~/esp
git clone -b v5.2 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32s3
source export.sh

micro-ROS Component

The firmware depends on micro-ROS for ESP32. It will be fetched automatically by the ESP-IDF component manager on first build.

USB Driver

  • Linux: No driver needed (CP2102 supported by kernel)
  • macOS: Install Silicon Labs CP210x driver
  • Windows: Install CP210x Universal Driver from Silicon Labs

Build

# Source ESP-IDF environment
. $HOME/esp/esp-idf/export.sh

cd firmware/esp32

# Set target chip
idf.py set-target esp32s3

# (Optional) Configure options
idf.py menuconfig

# Build (default SKU: standard)
idf.py build

SKU Variants

Available: basic, standard, industrial

# Build specific SKU variant (from firmware/esp32/ directory)
cp sdkconfig.defaults.industrial sdkconfig.defaults
idf.py fullclean && idf.py build

Host-Side Unit Tests (no hardware needed)

cd firmware/tests
make clean && make
./build/test_runner

Flash

Connection

Connect USB-C cable between your computer and the ESP32-S3 programming port.

If using UART header instead: - TX -> USB-UART adapter RX - RX -> USB-UART adapter TX - GND -> GND - Hold BOOT button during reset to enter download mode

Flash Command

idf.py -p /dev/ttyUSB0 flash

Port names by platform: - Linux: /dev/ttyUSB0 or /dev/ttyACM0 - macOS: /dev/cu.usbserial-* or /dev/cu.SLAB_USBtoUART - Windows: COM3 (check Device Manager)

Monitor

idf.py -p /dev/ttyUSB0 monitor

Expected boot messages:

I (xxx) main: Robot Platform Firmware starting...
I (xxx) ext_wdt: External watchdog feeder started (500ms period)
I (xxx) main: Wi-Fi connected
I (xxx) safety: Safety system initialized (E-stop GPIO=41, speed_limit=1.00 m/s)
I (xxx) safety: Relay self-test PASSED (CH1=1, CH2=1, relay energized)
I (xxx) motor: Motor control initialized (20000 Hz PWM, 8-bit)
I (xxx) encoder: Encoders initialized (PCNT, 1440 CPR)
I (xxx) ultrasonic: Ultrasonic sensors initialized (trig=12)
I (xxx) i2c_bus: I2C bus initialized (SDA=8, SCL=9, freq=400000 Hz)
I (xxx) imu: BNO055 initialized (addr=0x28, NDOF mode)
I (xxx) battery: Battery ADC initialized (2S, divider=3.0)
I (xxx) heartbeat: Heartbeat monitor initialized (timeout=5000ms)
I (xxx) thermal: Thermal monitor initialized
I (xxx) payload: Payload hot-plug initialized
I (xxx) uros: micro-ROS initialized (UART 921600 baud)
I (xxx) udp_xport: UDP transport initialized (cmd:5685, telem:5686)
I (xxx) dtls: DTLS transport initialized (port 5684, max_sessions=4)
I (xxx) main: All systems initialized. Robot ready.

Exit monitor: Ctrl+]

Filtering Logs

idf.py monitor --filter "safety"
idf.py monitor --filter "uros"

Configuration (menuconfig)

Key options in Robot Platform Configuration:

Option Default Description
Robot SKU Basic Enables/disables SKU-specific features
Motor PID Kp 1.20 Proportional gain
Motor PID Ki 0.80 Integral gain
Motor PID Kd 0.01 Derivative gain
micro-ROS baud 921600 UART communication speed
cmd_vel timeout 500ms Motor stop timeout
Safety distance 15cm Ultrasonic E-stop threshold
Display enabled Yes OLED status display (SH1106 128x64)
Display refresh 200ms Display update interval

micro-ROS Agent Setup (Raspberry Pi)

# Install (one-time)
sudo apt install ros-humble-micro-ros-agent

# Run agent (serial transport)
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyAMA0 -b 921600

# Or UDP transport (if configured in firmware)
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888

Verify Communication

ros2 topic list

# Expected topics:
#   /odom
#   /ultrasonic/front, /back, /left, /right
#   /battery_state
#   /wheel_speeds
#   /cmd_vel

ros2 topic echo /odom

ros2 topic pub /cmd_vel geometry_msgs/Twist \
  "{linear: {x: 0.1, y: 0.0, z: 0.0}, angular: {z: 0.0}}"

OTA Update

For fleet-wide rollout strategy, staged deployments, and monitoring, see fleet_ota_strategy.md.

Over-the-air firmware updates via ESP-IDF's native OTA mechanism:

  1. Build firmware binary: idf.py build
  2. Sign with ECDSA P-256 key (see firmware/esp32/main/ota_signing.h for format)
  3. Upload signed binary to OTA server
  4. Trigger update via ROS2 service or HTTP endpoint

OTA requires two OTA partitions and secure boot. See fleet_ota_strategy.md for production deployment.

Pre-flight Checks

Before an OTA update is accepted, the firmware runs an automated pre-flight validation (ota_preflight.c):

Check Threshold Rationale
Battery ≥ 50% Prevent power loss during flash
Wi-Fi RSSI ≥ -70 dBm Ensure stable download
Safety state NORMAL E-stop must be cleared
Motors Idle No active motion commands
Thermal Not CRITICAL or SHUTDOWN Prevent overtemp during heavy CPU use

If any check fails, the OTA is rejected with a fail_reason string.

Validation Window

After a successful OTA boot, a 30-second validation window starts. If the firmware does not confirm itself within this period (or if 3 consecutive boot failures occur), the bootloader automatically rolls back to the previous partition.

Compatibility Matrix

The OTA compatibility module (ota_compat.c) enforces protocol version gating. A new firmware image declares a minimum and maximum compatible firmware version, preventing downgrades below the protocol break point.


Heartbeat Monitor

The heartbeat monitor (heartbeat_monitor.c) expects periodic messages from the companion computer (Raspberry Pi). If no heartbeat arrives within 5 seconds:

  1. Motors are commanded to zero
  2. After 3 consecutive resets within 30 minutes, the safety relay is pulsed off for 3 seconds (hardware-level stop)

This ensures the robot stops if the companion computer crashes or the serial link is severed.


External Watchdog

An external TPS3813 hardware watchdog IC provides a last-resort reset if the ESP32 firmware hangs. The external_wdt.c module toggles GPIO 46 every 200ms (providing ~8× margin against the TPS3813's 1.6s timeout). If toggling stops, the TPS3813 triggers a hardware reset via the RST/EN line.


Charging Contact Detection

The charging_detect.c module monitors a pogo-pin voltage on ADC1_CH8 (GPIO 9). It uses the ESP32-S3 curve-fitting calibration scheme for accurate millivolt conversion.

Parameter Value
ADC channel ADC1_CH8 (GPIO 9)
Attenuation 11 dB (0–3.1V range, ESP-IDF 5.x)
Contact threshold 2000 mV
Calibration Curve fitting (ESP32-S3)

charging_detect_is_connected() returns true when the voltage exceeds the threshold, indicating the robot is seated on a charging dock.


Troubleshooting

Error Cause Solution
No serial port found USB not connected or driver missing Check cable, install driver
Failed to connect ESP32 not in download mode Hold BOOT, press RESET, release BOOT
micro-ROS agent: no subscriber UART wiring issue Check TX/RX crossover, verify baud rate
BNO055 not found I2C connection issue Check SDA/SCL wiring, pull-ups
Watchdog timeout Control loop stalled Check for infinite loops, stack overflow