5.8 KiB
title, summary, tags, status, date, repo, cover
| title | summary | tags | status | date | repo | cover | |||
|---|---|---|---|---|---|---|---|---|---|
| Inverted Pendulum on a Recycled 3D Printer | A pendulum balanced upright on a motorised cart, built from the axis of an old 3D printer and controlled by a Teensy 4.1 at 1 kHz. |
|
Completed | 2026-03-22 | https://github.com/tdehaeze/inverted-pendulum | cover.jpg |
Overview
This project is a classic control demonstrator: a rigid pendulum is mounted on a cart and kept upright by moving the cart back and forth. The mechanics come from a recycled 3D printer (Alfawise U20): one of its linear axes, with the stepper motor and belt, carries the pendulum pivot. A quadrature encoder measures the pendulum angle, and a Teensy 4.1 microcontroller runs the feedback loop at 1 kHz. A small PyQt5 application on the computer is used to select the controller, change the gains and setpoints, and record data.
{{< figure src="cover.jpg" alt="Photo of the inverted pendulum setup with its main parts labelled" caption="The setup: cart on a belt-driven rail, stepper motor, pendulum encoder and the electronics (labels in French)." >}}
Hardware
| Component | Details |
|---|---|
| Microcontroller | Teensy 4.1 |
| Stepper motor | DRV8825 driver, 32 microsteps, 5 µm per step |
| Pendulum encoder | Quadrature, 4096 steps per revolution (0.088° per step) |
| Cart travel | ±200 mm hard stop, ±160 mm soft limit |
| Control rate | 1 kHz, driven by a hardware timer (IntervalTimer) |
The cart input is an acceleration command. It is integrated to a velocity inside the 1 kHz interrupt, and converted into step pulses for the motor driver. The pendulum angular velocity is estimated by finite difference of the encoder reading. A soft limit blocks accelerations towards the end of the rail, and a hard stop at ±200 mm cuts the motor.
Software
The repository has three parts:
teensy/: firmware (PlatformIO / Arduino framework). Runs the control loop, drives the motor, reads the encoder and streams telemetry over USB serial.gui/: PyQt5 and pyqtgraph application. Sends text commands to the Teensy (MODE PD,SET KP 12,FB ON, ...), plots the telemetry live and saves it to CSV.model/andtools/: Python scripts for simulation, controller design, model identification and plotting experiments.
Model and identification
With φ = θ − π the angle from the upright position and u the cart acceleration, the linearised dynamics around the upright position are:
D·φ̈ + c·φ̇ − m·g·l·φ = m·l·u
where D is the effective inertia of the pendulum about its pivot. The state is [x, ẋ, φ, φ̇] and the input is the cart acceleration.
The pendulum length and friction coefficient are identified from a free oscillation: the pendulum hangs down, receives a small push, and the decaying oscillation is recorded for 10 to 50 seconds.
A Nelder-Mead fit (tools/fit.py) then adjusts the model to the measurement.
| Parameter | Symbol | Value |
|---|---|---|
| Pendulum mass | m | 0.1 kg |
| Half length (pivot to centre of mass) | l | 0.326 m |
| Friction coefficient | c | 3.4 × 10⁻⁴ N·m·s/rad |
{{< figure src="identification-fit.png" alt="Measured and fitted free oscillation of the pendulum" caption="Free oscillation of the pendulum: measurement and fitted model (figure labels in French)." >}}
{{< video src="identification.mp4" caption="Free oscillation used for the identification." >}}
Controllers
All gains are in SI units, and the controller output is the cart acceleration.
- P: u = −Kp · φ
- PD: u = −Kp · φ − Kd · φ̇
- LQR: u = −K · [x, ẋ, φ, φ̇]
The LQR gain vector K is computed in Python (model/inverted_pendulum.py, using the control library) from weights on the four states and on the input, then entered in the GUI.
PD control
A PD controller can hold the pendulum angle at zero, but it does not see the cart position. In the experiment below the angle is regulated while the cart drifts until it reaches the end of the rail and the pendulum falls. The same controller with other gains acts mostly as a damper: it slows down the pendulum without fully stabilising it.
{{< figure src="pd-simulation.png" alt="Simulated PD response of the pendulum angle" caption="Simulated PD response, from a 5° initial angle (figure labels in French)." >}}
{{< figure src="pd-experiment.png" alt="Measured angle and cart position with a PD controller" caption="Measured PD experiment: the angle is held near zero while the cart drifts to the end stop (figure labels in French)." >}}
{{< video src="pd-control.mp4" caption="PD control: the pendulum is stabilised, the cart slowly drifts." >}}
{{< video src="pd-damping.mp4" caption="PD control used as a damper only: the pendulum dynamics are slowed down." >}}
LQR control
The LQR feeds back the cart position and velocity as well as the pendulum angle and angular velocity. This keeps the cart near its setpoint while the pendulum stays upright. Higher weights on the states give faster corrections, at the cost of more aggressive cart motion.
{{< video src="lqr.mp4" caption="LQR with a higher gain: angle and cart position are controlled together." >}}
{{< video src="lqr-setpoint-step.mp4" caption="LQR following a step in the cart position setpoint while keeping the pendulum upright." >}}
Typical workflow
- Flash the firmware with
pio run -e teensy41 -t uploadfromteensy/. - Start the GUI (
python gui/program.py), enable the motor and zero the cart and encoder. - Record a free oscillation (
tools/capture.py) and fit the model parameters (tools/fit.py). - Pick a controller in the GUI, enter the gains, bring the pendulum close to upright and switch the feedback on.
- Save the data to CSV and plot it with
tools/plot_experiment.py.
The full instructions and serial command list are in the README.