PMPedro Masi Burgos
← Projects

Real-time Wi-Fi mobile robot

Remote-controlled car with a two-board architecture: an ESP32-S3 creates the Wi-Fi network and forwards TCP commands over UART to an STM32 running FreeRTOS, which drives the motors and the steering servo and stops on obstacles. Operated from a PyQt5 interface.

Individual final project of Microcontrollers and Power Electronics (UNCuyo, 2025).

  • STM32
  • C
  • ESP32
  • ESP-IDF
  • FreeRTOS
  • PyQt5
  • TCP
  • UART
  • PWM
Early assembly of the robot: NUCLEO board on the chassis and a breadboard with the motor driver

The project and where it came from

The goal was a vehicle that receives commands from a graphical interface on a PC and executes them in real time: DC motor control, obstacle detection with an ultrasonic sensor, an OLED screen for status, and wireless control over Wi-Fi.

The idea comes from a similar architecture I worked with at ENIB (Brest). I did not have the same hardware, so I redesigned it around what I had: a NUCLEO-F446RE and an ESP32-S3 WROOM. The ESP32 was new to me, so I had to learn ESP-IDF and PlatformIO along the way. It has no industrial purpose; it is a base for mobile robotics and embedded systems.

Architecture: two boards and a PC

I split the work in two so that real-time control does not compete with networking:

  • PC: PyQt5 interface that sends text commands over Wi-Fi/TCP.
  • ESP32-S3 (ESP-IDF, FreeRTOS): creates its own Wi-Fi access point, runs a TCP server and forwards each command to the STM32 over UART.
  • STM32 NUCLEO-F446RE (FreeRTOS): receives the commands over UART with DMA and drives everything else: TIM3 PWM and GPIO for the DC motors through a DRV8833, TIM12 PWM for the MF90 steering servo, TIM2 to trigger and time the HC-SR04, and I²C for the SSD1306 OLED.
  • Power: two banks of two 18650 cells in series and two XL4016 step-down converters, one at 5 V for the NUCLEO and the ESP32 and one at 6 V for the motor driver and the servo.
Block diagram: PC, ESP32-S3, NUCLEO-F446RE, motor driver, servo, ultrasonic sensor, OLED and the two power branches
Technology diagram: links between modules and the two power branches.

How it was built

I built it in steps and validated each one before the next:

  • Unit tests of each module on its own (ultrasonic sensor, servo, DC motors).
  • Basic motion control: motors and servo driven from the NUCLEO with commands typed over UART2, then a simple interface to send them.
  • ESP32 integration for Wi-Fi control. Its camera and the many board variants forced me to check pin and power configuration carefully.
  • A first camera module with a video panel in the interface and a simple red-object detector that drew a bounding box. I dropped it at the end: it was not tied to the embedded control, so I left it as future work.
  • Physical design: a chassis downloaded from Thingiverse and printed at a larger scale. Mounting the parts was the hard part, especially the servo (see the problems section).
  • Tests on a fixed stand first and then on the floor, still sending commands over UART from the PC.
  • OLED screen, which also put the I²C bus to use.
  • Final power integration and complete tests with remote control over Wi-Fi.
The 3D-printed chassis with wheels and the ultrasonic sensor mounted at the front
Printed chassis with the HC-SR04 at the front.
NUCLEO board wired on top of the chassis, with the ultrasonic sensor and a wheel motor
NUCLEO board wired on the chassis during assembly.

STM32 firmware with FreeRTOS

Each function is a task, and interrupts only release semaphores so they stay short:

  • Commands arrive on UART2/UART4 (115200 baud) with DMA reception. The callback releases a binary semaphore that wakes CmdProcessing_Task (above normal priority).
  • Commands are text lines: vXXX sets speed from −100 to 100 (the sign is the direction) and sXXX sets the servo angle. Anything else is ignored. Valid commands go to the motor or servo queue.
  • Motor_Task (normal priority) takes from the motor queue and applies direction and PWM duty. Servo_Task (low) turns the angle into a 1 to 2 ms pulse.
  • UltraSonic_Task (high priority, so safety wins): TIM2 emits a 10 µs trigger every 100 ms (about 10 Hz) and two input-capture channels time the echo. If the echo is shorter than 1160 µs (about 20 cm) it sends a stop command to the motor queue.
  • OLED_Task (low) shows the distance, or "Obstaculo" when it stops.

The ultrasonic handling is custom, with no external library; the OLED driver is an open-source SSD1306 library.

Flow diagram of the tasks, semaphores and queues between the ESP32 and the NUCLEO
Flow of a command and of the obstacle check. The diagram says < 10 cm; the threshold in the final firmware is about 20 cm.

ESP32 gateway and PC interface

The ESP32 firmware is split in modules: main starts the memory and the UART and launches the Wi-Fi task; wifi_manager sets up the access point; tcp_server listens on port 1234 and starts one receive task per client, which splits the text by line breaks; uart_comm forwards each line to the NUCLEO with a semaphore and a dedicated sender task.

The PC interface is PyQt5 with an internal MVC split (view, controller, model). A serial worker lets me talk to the board directly over UART, which is how I tested before Wi-Fi, and a TCP worker connects to the ESP32 as a client. The interface also has a video panel from the camera attempt.

Demonstration

Teleoperation of the prototype from the PC interface.

Teleoperation and motion test on the prototype.

Figures

Measured:

  • NUCLEO plus peripherals (including OLED and sensor): about 110 mA at 3.3 V with the motors at full speed.
  • ESP32: about 52 mA at 5 V, with higher peaks when Wi-Fi starts.
  • Whole car at rest, without motors or load: about 100 mA.

Estimated:

  • Autonomy: 3 to 4 hours at rest and about 1.5 to 2.5 hours with the motors running.
  • Wi-Fi range: 15 to 20 m indoors.

Other design values: HC-SR04 accuracy of ±1 cm from 2 to 200 cm sampled at about 10 Hz; a 200 Ω and 100 Ω divider brings the 5 V echo signal down to about 3.3 V; 470 µF and 100 nF filtering on the 5 V branch, double on the 6 V one, plus 470 µF at the motor driver and at the servo.

Problems and next steps

  • Servo: its torque was too low for a firm wheel turn. Fixing it with wire left some slip, and I had trouble matching the servo's natural physical angle to the digital angle for straight driving.
  • Power: power problems appeared once I removed the USB cables. I added a second XL4016 and split the circuit into two independent branches, 5 V for logic and 6 V for motors and servo, which made the floor tests stable.
  • TCP: sporadic disconnections when many commands are sent in quick succession. It does not stop the car from working, but it is the main thing to fix.
  • No encoders: speed is open loop, so there is no PID control.

Next: encoders and PID, a better TCP server on the ESP32, a mobile or web interface, the camera with AI on the video stream, and more sensors for autonomous driving. In a hypothetical commercial version I would swap the HC-SR04 for a VL53L0X laser sensor, use a Raspberry Pi for Wi-Fi and vision, 12 V Pololu motors and a 4S LiPo pack.