Build guide: Cart-pole balancing kit
Code for this guide
A stepper motor pulls a cart along a linear rail with a toothed belt; a free pole hangs from the cart. The firmware swings the pole up and balances it upright by moving the cart. Build time: 4–5 hours. Level: intermediate.
1. What's in the box
| Part | Qty | Used for |
|---|---|---|
| Arduino Nano (CH340) | 1 | Controller |
| NEMA 17 stepper motor, 40 mm | 1 | Drives the cart |
| TMC2209 stepper driver | 1 | Drives the motor quietly and smoothly |
| AS5600 magnetic encoder with magnet | 1 | Measures the pole angle |
| MGN12 linear rail, 400 mm, with MGN12H carriage | 1 | Guides the cart |
| 2020 aluminium extrusion, cut: 400 mm beam and two 295 mm legs | 3 | Frame |
| Corner brackets | 4 | Join the legs to the beam |
| M5 T-nuts | 20 | Frame screws (14 used, 6 spare) |
| GT2 belt, 1 m, and two 20-tooth GT2 pulleys | 1 set | Drive |
| 608 ball bearings | 4 | Pole pivot (2) and idler (2) |
| Limit switches | 2 | Homing and end-of-travel safety |
| 12 V 5 A power supply | 1 | Power |
| M3 screw and nut kit | 1 | Assembly |
| Printed parts: cart, pole, magnet cap, motor mount, idler mount, two feet, two belt clamps, idler sleeve | 10 | Structure |
| Wiring harness, power lead with DC jack, encoder and switch cables | 1 set | Wiring |
| M5 × 10 screws (14), M5 × 40 idler bolt and nut, M8 × 50 pivot bolt with washers and nuts, zip ties | 1 set | Frame, idler, pivot |
You also need: a computer, a USB Mini-B cable, the Arduino IDE (free), 2.5 mm and 3 mm hex keys, a small screwdriver, and two small G-clamps (or two wood screws) to fix the feet to a table. Python 3 with numpy is optional (for designing your own controller).
2. Frame
- Legs into the feet. Slide a T-nut into one slot near the bottom of each leg. Push each leg into a foot's socket and fix it with an M5 × 10 screw through the side of the socket into the T-nut.
- Beam onto the legs. Lay the beam across the tops of the legs, centred, with the legs 240 mm apart (centre to centre). Fix each leg with two corner brackets, one on each side of the leg, each with two M5 × 10 screws into T-nuts (slide the T-nuts into the beam's bottom slot and the legs' side slots first).
- Rail onto the beam. Slide eight M3 nuts into the beam's top slot. Lay the rail on top, centred and flush with both ends, and fix it with M3 × 10 screws through every second rail hole. Leave the carriage on the rail: if it slides off, its balls fall out.
- Fix the rig to the table with the two holes in each foot (or G-clamps over the feet). The cart pushes hard while it balances; an unfixed rig walks.
3. Drive
- Motor mount. Screw the motor to the underside of the motor mount's plate, shaft up, with four M3 × 8 screws. Slide two T-nuts into the beam's belt-side slot at the left end and fix the mount's flange with two M5 × 10 screws, with the mount's end web against the end of the beam.
- Idler mount. Press the two 608 bearings into the idler mount's boss from above, one on top of the other, until the top one is flush with the plate. Push the printed sleeve into the bearings. Fix the mount at the right end of the beam like the motor mount, but leave its screws loose.
- Pulleys. Put one pulley on the motor shaft, teeth up, with its grub screw on the flat of the shaft, about 1 mm above the plate. Put the M5 × 40 bolt up through the idler bearings from below, add a washer and the second pulley, teeth up, and the nut. Tighten the nut so the bolt, sleeve and pulley turn together with the bearings' inner rings, then tighten the pulley's grub screws onto the bolt.
- Cart. Fix the cart to the carriage with four M3 × 8 screws (the belt wall faces the motor side of the rail). Put four M3 nuts into the traps inside the belt wall.
- Belt. Clamp one end of the belt to the left end of the cart's wall with a belt clamp (teeth into the clamp's grooves, two M3 × 12 screws). Run the belt round the motor pulley, back along the far side, round the idler pulley and back to the cart. Pull it snug and clamp the second end. Then slide the idler mount outwards to tension the belt (it should twang, not flap) and tighten its screws.
- Check: with the motor unplugged, the cart slides the whole length of the rail smoothly, and both pulleys run level with the belt.
4. Pole and encoder
- Bearings. Press one 608 bearing into each end of the cart's bearing housing until it stops against the lip in the middle.
- Pivot bolt. From the encoder-bracket side, pass the M8 bolt through the bracket opening and both bearings. On the far side, add a washer and one nut and tighten until the bolt turns freely with no play.
- Magnet. Press the magnet cap over the bolt head and glue the round magnet into its pocket, centred, with a drop of superglue.
- Encoder. Screw the AS5600 board to the bracket, chip facing the magnet, with M3 screws and nuts. Centre the chip on the bolt. Gap between chip and magnet: 1–3 mm. Wire the board's DIR pin to GND (the harness does this).
- Pole. Slide the pole's hub onto the bolt after the nut so it hangs straight down, tighten the pinch screw and lock it with the second nut.
- Check: the pole swings freely for 20+ seconds after a push.
5. Limit switches and wiring
Push a limit switch into the pocket on top of each end mount's tower, lever on top and pointing towards the cart, and hold it with a zip tie through the tunnel under it. Push the cart against each tower by hand: the switch must click just before the cart touches the tower.
The harness is pre-wired. Connect it as follows (Nano pin → device):
| Nano | Connects to |
|---|---|
| D2 | TMC2209 STEP |
| D3 | TMC2209 DIR |
| D4 | TMC2209 EN |
| D5 | Left (motor end) switch, other side to GND |
| D6 | Right (idler end) switch, other side to GND |
| 5V, GND | TMC2209 VIO and GND; AS5600 VCC and GND |
| A4 | AS5600 SDA |
| A5 | AS5600 SCL |
- Switches use their common (C) and normally-open (NO) terminals.
- TMC2209 MS1 and MS2 → GND (1/8 microstepping). VM → 12 V, GND → supply GND (the 100 µF capacitor across VM/GND is on the harness: never run the driver without it).
- The Nano is powered from USB while you program it; the motor needs the 12 V supply.
- Never unplug the motor while the 12 V supply is on: it can destroy the driver.
6. Firmware
- Install the Arduino IDE. Download the firmware (
cartpole.ino, linked at the top of this guide) into a folder namedcartpoleand open it. - Tools → Board: Arduino Nano; Processor: ATmega328P (Old Bootloader) for most CH340 boards.
- Upload. Open the Serial Monitor at 115200 baud, line ending Newline.
7. First run
- Plug in the 12 V supply. Type
pand press Enter, then press each switch by hand: the printout showsL1orR1while it's pressed. If a switch shows 1 when it isn't pressed, it's wired to NC instead of NO. - Type
h. The cart drives slowly to the motor end until the switch clicks, then parks in the middle of the rail. If it drives towards the idler end instead, swap the two wires of one motor coil (with the power off). - Let the pole hang still and type
s. The cart makes a small jog (this finds the encoder direction), waits, then swings the pole up and balances it, usually within 3–6 seconds. - Type
xto stop at any time. The firmware also brakes and stops by itself if the cart goes more than 150 mm from the centre or touches a switch.
If it doesn't balance, see Troubleshooting. Most rigs balance with the default gains.
8. Logging and tuning
lstreamsms,x,phi,dx,dphi,u,modeat 100 Hz (copy from the Serial Monitor into a CSV).pprints the current gains, the cart position, the pole angle and the switches.g k1 k2 k3 k4sets the balance (LQR) gains;e ke kc kd smsets the swing-up gains.
Design your own controller. cartpole.py holds the model of this rig. Weigh your pole and put its
mass in Rig, measure the friction from a free swing (time how long the swing takes to halve, then
fit b), and run:
python3 cartpole.py
It prints LQR gains for your rig as a g … line to paste into the console, and simulates a swing-up
and balance (it saves a plot if matplotlib is installed).
9. Experiments
- System identification: fit the pole's natural frequency and damping from a free swing.
- Pole placement vs LQR: design both from the same model and compare how far the cart moves when you tap the pole.
- Swing-up: change
keandsmand see how fast and how violently it swings up within the rail. - Reinforcement learning:
cartpole.py'ssimulateis a ready environment. Train a linear policy in simulation (random search over the four gains is enough), load it withg, and compare it with LQR on the real rig. - Sim-to-real: compare logged data with the simulation from the same starting point.
10. Troubleshooting
| Symptom | Fix |
|---|---|
| "Left switch not found" | Check the switch is on D5 and NO; check the motor turns the right way (step 7.2) |
| "Encoder didn't see the pole move" | Magnet off-centre or gap over 3 mm; check SDA/SCL and 5 V |
| Motor buzzes but doesn't turn | Swap one coil pair; check the 12 V supply is on |
| Cart stutters or skips at speed | Belt too tight or too loose; check the pulley grub screws |
| Swings up but overshoots and falls | Reduce ke (try e 0.15 80 2 6) |
| Balances but the cart drifts to the limit | Make k1 and k3 more negative (stronger centring) |
| Jitters while balancing | Increase the encoder gap slightly, or reduce k4 |
| The rig walks across the table | Fix the feet to the table |
11. Safety
The cart moves fast and stops hard. Keep fingers, hair and cables clear of the rail while it runs,
and stop with x before touching it. Use only the supplied 12 V supply.
© 2026 Voltomni. The firmware and Python model are free to use, change and share for learning and teaching. Questions: [email protected].