Getting a simulation to behave exactly like real-world hardware is notoriously tough. This project demonstrates the successful zero-shot sim-to-real transfer of a deep reinforcement learning policy to control a highly non-linear, 2-DoF underactuated Furuta pendulum. Executing swing-up maneuvers and maintaining continuous balance for over one hour on physical hardware without a single line of hardware-specific tuning.
Mechanical Design
The physical system was designed entirely from scratch in Fusion 360 and manufactured via FDM 3D printing. The Furuta pendulum configuration consists of a motorized horizontal arm (rotor) and a free-swinging vertical pendulum link. All mechanical components were designed to be lightweight while maintaining the rigid joint geometry required for accurate dynamic modeling.

Exploded assembly view — the full pendulum mechanism with motor mount, rotor arm, and pendulum link.
Simulation Development & Calibration
A high-fidelity MuJoCo physics simulation was the foundation of the entire project. Getting the sim-to-real transfer to work required the simulation to be meticulously calibrated to match the physical system.
Two isolated calibration experiments were designed to independently characterize and tune the key physical parameters before any RL training began.
Pendulum Drop Test
The pendulum link was released from a fixed angle and the free-swing decay was recorded. The simulation’s inertia and damping parameters were iteratively adjusted until the simulated drop response matched the physical hardware response.


Rotor Friction Test
The rotor arm was driven at a fixed command and released, isolating the motor’s friction and back-EMF characteristics. This independently constrained the motor torque model in the simulation.


Reinforcement Learning
Training Methodology
A Proximal Policy Optimization (PPO) agent was trained in a custom Gymnasium environment wrapping the calibrated MuJoCo simulation. Several techniques were combined to maximize real-world robustness:
Curriculum Learning — A multi-phase reward structure progressively taught the agent distinct sub-behaviors in sequence: stabilizing near the top, catching the pendulum from near-upright positions, and finally executing full swing-up maneuvers from rest. Training was gated so the agent could not advance to the next phase until the current one was reliably solved.
Domain Randomization — On every episode reset, physical parameters were sampled randomly within calibrated bounds: pendulum mass, motor torque limits, and joint damping coefficients. This forced the policy to generalize across the range of hardware variation rather than overfit to a single nominal model.
Noise & Latency Injection — Manual velocity noise was added to all observations, and action latency buffers were used to simulate communication delays between the inference engine and the motor controller, directly mimicking the timing characteristics of the embedded system.

TensorBoard training curves showing episode reward progression across curriculum phases. The distinct reward steps correspond to each phase unlock.
Embedded Deployment
Exporting the Policy
After training, the final PyTorch model weights were exported directly as a flat array of floating-point values representing the neural network in a C++ header file.
Dual-Core Architecture on ESP32
The ESP32’s dual-core architecture was leveraged to strictly isolate compute-intensive inference from real-time motor control:
- Reads encoder positions via SPI at 100 Hz
- Runs neural network forward pass
- Calculates target torque command
- Executes Field Oriented Control (FOC)
- Drives BLDC gimbal motor via SimpleFOC
- Handles current sensing & torque execution
Torque Field Oriented Control was used for actuation, providing precise, linear torque commands rather than velocity or position targets. This gave the RL policy direct, low-latency authority over the motor output, which was essential for the fast dynamics of the swing-up maneuver.
Hardware Debugging & Observation Logging
The first deployment onto physical hardware did not work. The pendulum would immediately diverge rather than swing up and balance. Rather than retraining or adjusting reward functions, observation logging was added to both the simulation and the physical hardware to directly compare the two state streams side by side.


The logs immediately revealed the issue: the SPI encoder readings from the physical AS5047P were sign-inverted relative to the simulation’s coordinate convention. The motor was receiving the correct torque magnitude but driving the pendulum in the wrong direction on every step. A single sign flip in the firmware resolved the issue entirely, no retraining, no reward shaping, no parameter adjustment. The policy worked reliably from that point forward.
Results
The trained policy achieved continuous balance for over one hour before powering off the system. I have yet to find the upper bound for the inverted pendulum balance time. The zero-shot transfer was validated not just by the duration of balance, but by the quality of the swing-up: the policy consistently executed the correct energy-pumping maneuver learned entirely in simulation.