This is the controller I use for my Furuta pendulum: a Teensy 4.1 reads the pendulum encoder, commands an ODrive S1 over UART, and runs the balance and swing-up state machine. A local Web Serial dashboard handles setup, telemetry, logging, and supervised tuning.
The project is still an experimental controls platform, not a finished or safety-rated machine. Automatic swing-up remains disabled in the default configuration while I validate guarded trials on the hardware.
- Controller: Teensy 4.1 at a 200 Hz control rate
- Motor drive: ODrive S1 in torque control over isolated 115200-baud UART
- Pendulum sensor: AS5048A absolute encoder over SPI
- Interface: local browser dashboard over USB Web Serial
- Tooling: PlatformIO, native C++ tests, and Python log-analysis tools
The firmware starts disarmed and requires an explicit zero reference before motion. Motor-active modes also require a focused-page Spacebar dead man. Travel, speed, current, stale-feedback, watchdog, and control-deadline checks can stop a run independently.
- full-state upright feedback with measured mechanism parameters;
- guarded energy-shaping swing-up with automatic arm centering;
- checksummed ODrive UART feedback and AS5048A diagnostic checks;
- live telemetry, CSV capture, runtime gain selection, and a hardware-free demo;
- native tests for control math, protocol parsing, limits, and timing logic;
- a command-line tool for capturing and comparing supervised swing-up trials.
Install PlatformIO, then run:
pio test -e native
pio run -e teensy41Upload to a connected Teensy 4.1 with:
pio run -e teensy41 -t uploadStart the dependency-free local server:
python web/serve.pyOpen http://localhost:8765 in Chrome or Edge and choose
Connect Teensy. Add ?demo=1 to the URL to explore the dashboard without
hardware. The server binds to 127.0.0.1 and the page loads no cloud resources.
Power everything off before changing wiring. Teensy 4.1 pins are not 5 V tolerant.
| Teensy 4.1 | ODrive S1 isolated I/O |
|---|---|
pin 0 / RX1 |
isolated UART TX / G06 |
pin 1 / TX1 |
isolated UART RX / G07 |
3.3V |
isolated I/O supply |
GND |
isolated I/O ground |
| Teensy 4.1 | AS5048A |
|---|---|
pin 10 |
CSn |
pin 11 |
MOSI |
pin 12 |
MISO |
pin 13 |
SCK |
3.3V and GND |
sensor power and ground |
The ODrive must be calibrated separately and configured for UART before the
firmware can communicate with it. Review every hardware value in
include/config.hpp, especially direction, travel,
current, and bus-voltage limits, before allowing motion.
| Path | Purpose |
|---|---|
src/main.cpp |
firmware state machine, control loop, and safety paths |
include/config.hpp |
measured plant values and commissioning limits |
include/control_math.hpp |
testable control and protocol math |
web/ |
Web Serial dashboard and local server |
test/ |
native PlatformIO tests |
tools/ |
LQR reproduction and swing-up log analysis |
The measured mechanism values are recorded in
MECHANISM_DATA.md, and the controller derivation is in
BALANCING_REVIEW.md. CAD assumptions behind the rotary
inertia estimate are in CAD_MASS_PROPERTIES.md.
| Quantity | Value |
|---|---|
| Pendulum mass | 0.159 kg |
| Small-angle period | 0.7327 s |
| Pivot-to-COM estimate | 0.05379 m |
| Pendulum pivot inertia | 0.001141 kg m^2 |
| Motor-axis to pendulum pivot | 0.1815 m |
| Upright yaw inertia estimate | 0.00561 kg m^2 |
| Default balance gain | [-0.0700, 1.6000, -0.0692, 0.0900] |
These values describe my current mechanism and should not be copied to a different build without re-measuring it.