Furuta Pendulum
A rotary inverted pendulum balanced with LQR control on an Arduino Uno.
Introduction
ELECTENG291 covered second-order circuits, Laplace transforms and frequency response, and I realised these are the foundations of control theory. I hadn't taken a controls paper yet, so I decided to learn it hands-on by combining the ATmega328P fundamentals from COMPSYS201 with self-taught state-space control.
The result is a rotary inverted pendulum: a quadrature encoder measures the pendulum angle while a stepper motor swings the arm to keep the rod balanced upright.
This write-up is based on independent self-study rather than formal controls coursework, so some explanations may be simplified.
Design and Mechanical Assembly
I kept the mechanical side simple so I could focus on the control system. Each component was chosen to make the build reliable and quick to iterate on.
| Component | Design choice | Reasoning |
|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | Familiar from COMPSYS201, with hardware interrupts for reading the encoder. |
| Actuator | NEMA17 stepper motor | Holds its commanded position, which simplifies the arm dynamics. |
| Motor driver | TMC2209 SilentStepStick | Adjustable current limit and quiet microstepping, after the A4988 and DRV8825 failed. |
| Sensor | Quadrature encoder | Measures the pendulum angle precisely at 1600 steps per revolution. |
| Pendulum rod | Found rod with a 90° bend | Already the right shape and height for a rotary pendulum. |
| Frame | 3D printed tower (sourced files) | Rigid support that matched the rod size, without designing it from scratch. |
| Shaft adapter | Custom part, Fusion 360 | Joins the 6 mm encoder shaft to the 6.1 to 6.2 mm rod. |
The adapter was the one part I designed myself. The first version was too short and let the rod wobble, which threw off encoder alignment, so I remodelled it taller with a more rigid fit.
Mathematical Modelling and Control
I began with cascaded PID, but tuning it by trial and error never produced stable balancing, which is what led me to LQR. That pushed me to teach myself state-space modelling and LQR, mostly through MATLAB's YouTube lectures and other controls resources. I modelled the pendulum with a linearised state-space equation:
ẋ = Ax + Bu
with the state vector
x = [θ, θ̇, α, α̇]
where θ is the motor shaft angle and α is the pendulum angle from upright. The measured arm length, pendulum length and pendulum mass gave the A and B matrices.
Choosing the gains
LQR picks the feedback gains by minimising a cost that trades off state error (weighted by Q) against motor effort (weighted by R). I penalised the pendulum angle most heavily, since keeping the rod upright was the main goal:
Q = diag([1, 0.1, 200, 20]);
R = 0.05;
MATLAB solved the Riccati equation for the gain matrix K, one gain per state:
K = [-4.47, -4.40, 824.2, 97.9]
The arm gains carried over to the firmware unchanged. The pendulum gains were converted from radians to encoder steps (1600 steps per revolution):
Kα = 824.2 × 1600/360 = 3663.2
Kα̇ = 97.9 × 1600/360 = 435.0
Every control cycle, the controller combines all four states into one command:
e = r − x, u = Ke
Tuning Q and R was iterative. Stronger pendulum gains corrected faster but made the arm jittery, while softer gains were smoother but let the rod lean further before reacting.
Assumptions
- The rod is a rigid body with fixed length and mass distribution.
- Friction at the encoder pivot is negligible.
- The encoder never misses a count, and its upright zero reference never drifts.
- The stepper holds position regardless of the rod, so the arm is treated as an independent input rather than coupled to the pendulum.
- The model is linearised about upright, so it is only valid near vertical.
Hardware and Signal Implementation
The hardest problem was noisy encoder readings. I probed the signals on an oscilloscope to rule out wiring, pull-ups, power and code. The fault turned out to be a bad USB cable to the Arduino, and replacing it made the controller far more consistent.
To keep the signals clean, I added pull-up resistors on the encoder lines so they never float between logic thresholds, and a 100 µF capacitor across the motor supply to absorb the voltage dips the stepper causes when it starts, stops or changes speed. ELECTENG209 framed this well: real signals are never the clean lines they are on paper, so the design has to work in the noise.
Setting the driver current
The TMC2209 sets its coil current from VREF, which I adjusted with its potentiometer. I targeted about 90% of the motor's rating (1.33 A RMS) to prevent overheating, using the relationship from the SilentStepStick datasheet with its 0.11 Ω sense resistor:
VREF = Irms × 2.5 × √2 × Rsense
VREF = 1.33 × 2.5 × √2 × 0.11 ≈ 0.5 V
Results
Once tuned, the pendulum stays upright indefinitely, settling within about 1 to 2° of vertical. It recovers from light taps up to the 45° give-up angle, past which the controller disengages. A small steady wobble remains from the stepper's finite step size.
Drawbacks and Hardware Iteration
The build took several iterations. I killed my first A4988 and DRV8825 drivers, which I put down to having no capacitor on the supply (though disconnecting the motor while powered is another common cause), before moving to the TMC2209. The first shaft adapter wobbled and had to be redesigned, and the PID controller was replaced by LQR.
The main limitation is that LQR only works near upright, so the pendulum has to be placed vertically by hand. My next goal is swing-up control: training a reinforcement learning policy in MuJoCo, MATLAB and Simulink, then handing over to the existing LQR controller near the top.
The biggest lesson was that a controller is only as good as the signals feeding it. Debugging the physical system mattered just as much as the code.