Robot PU Balancing
š¤ Tutorial: Make Robot PU Balance on Tiltsā
Using the Robot PU MakeCode Extension
Robot PU already includes an internal IMUābased balancing engine. When you call actions like rest(), walk(), or explore(), PU automatically reads the micro:bit accelerometer and adjusts its servos to stay upright. This tutorial shows you how to:
- Enable PU's builtāin balancing
- Add tiltāreaction behaviors
- Create your own "balance training" program
- Extend it with headātilt feedback or sound reactions
1. š§© Setup Your MakeCode Projectā
- Go to https://makecode.microbit.org
- Create a New Project
- Click Extensions ā Import URL
- Paste the repo URL:
https://github.com/robotgyms/pxt-robotpu - Add any block from
robotPuinon start(this triggers autoāinitialization).
Example:
robotPu.rest()
PU will automatically run
calibrate()the first time anyrobotPuAPI is used.
2. š§ Enable BuiltāIn Balancing Modeā
The simplest balancing behavior is rest():
basic.forever(function () {
robotPu.rest()
})
Why this works:ā
- rest() keeps PU in a stable idle pose
- It continuously uses the micro:bit accelerometer to maintain balance
- It reacts subtly to sound and tilt inputs
This is the best starting point for tiltābalancing practice.
3. šļø Make PU React to Tiltingā
Robot PU exposes two key "tilt bias" commands via runKeyValueCommand:
| Key | Meaning | Range |
|---|---|---|
| #puroll | Side tilt (left/right) | ā90 to 90 degrees |
| #pupitch | Forward/back tilt | ā90 to 90 degrees |
These values normally come from a remote gamepad, but you can generate them directly from the robot's own micro:bit.
Example: Use PU's own accelerometer to adjust balanceā
basic.forever(function () {
let roll = input.rotation(Rotation.Roll)
let pitch = input.rotation(Rotation.Pitch)
// Send tilt values to PU's balancing engine
robotPu.runKeyValueCommand("#puroll", roll)
robotPu.runKeyValueCommand("#pupitch", pitch)
})
What this does:
- PU reads its own tilt
- You feed those tilt angles back into PU's internal stabilizer
- PU adjusts its head/body to counteract the tilt
- This creates a selfābalancing loop
This is the core of "balance on tilts."
4. šļø Balance Training Mode (Full Program)ā
Here's a complete program that:
- Calibrates PU
- Reads tilt continuously
- Applies smoothing
- Balances PU on uneven surfaces
robotPu.calibrate()
basic.forever(function () {
// Read tilt
let roll = input.rotation(Rotation.Roll)
let pitch = input.rotation(Rotation.Pitch)
// Smooth the values (optional)
roll = roll / 2
pitch = pitch / 2
// Feed into PU's stabilizer
robotPu.runKeyValueCommand("#puroll", roll)
robotPu.runKeyValueCommand("#pupitch", pitch)
// Keep PU in rest pose while balancing
robotPu.rest()
})
Why this works:
- rest() maintains base stability
- #puroll / #pupitch add tiltācompensation
- Smoothing prevents jerky servo motion
- PU becomes noticeably better at staying upright on ramps or when nudged
5. š Add Feedback (Optional)ā
A. Sound reaction when PU tilts too muchā
basic.forever(function () {
let tilt = Math.abs(input.rotation(Rotation.Roll))
if (tilt > 40) {
robotPu.talk("I'm falling!")
}
})
B. LED color changes based on tiltā
basic.forever(function () {
let tilt = Math.abs(input.rotation(Rotation.Pitch))
if (tilt < 20) {
robotPu.sing("C5")
} else if (tilt < 40) {
robotPu.sing("A")
} else {
robotPu.sing("F")
}
})
6. š§Ŗ Try These Challengesā
- Make PU lean into the tilt instead of resisting it
- Add a "balance score" that increases the longer PU stays upright
- Make PU walk slowly while balancing on a tilted board
- Use setWalkSpeedRange() to tune stability vs. speed
7. š¦ Summaryā
To make Robot PU balance on tilts:
- Use rest() for builtāin balancing
- Feed tilt values back into PU using #puroll and #pupitch
- Combine both in a continuous loop
- Add smoothing and feedback for better performance
All behaviors are supported directly by the Robot PU MakeCode extension.
A thresholdātriggered balance controller is simple, but it produces binary behavior: nothing happens until the tilt crosses a limit, then the robot suddenly reacts. That leads to:
- Jerky corrections
- Overshoot
- Poor stability on small tilts
- Noisy behavior near the threshold
Robot PU's moveBalance() already has the right inputs (observed roll/pitch vs. expected roll/pitch). What it needs is a continuous control law, not a threshold switch.
Below are several algorithms that are significantly better and still lightweight enough for micro:bitāclass hardware.
ā Recommended Replacement: PD (ProportionalāDerivative) Balance Controllerā
This is the standard approach used in small robots, quadrupeds, and even drones. It is simple, stable, and smooth.
Ideaā
Compute the error:
- e_r = r_expected ā r_observed
- e_p = p_expected ā p_observed
Then compute a control output:
- u_r = Kp Ā· e_r + Kd Ā· (e_r ā e_r_prev)
- u_p = Kp Ā· e_p + Kd Ā· (e_p ā e_p_prev)
Then apply:
robotPu.runKeyValueCommand("#puroll", u_r)
robotPu.runKeyValueCommand("#pupitch", u_p)
Why this is better:
- Smooth, continuous correction
- No sudden jumps
- Automatically scales with tilt magnitude
- Derivative term damps oscillation
Where to put it:
Inside moveBalance(), replace the threshold logic with PD computation.
ā Even Better: PID Controller (ProportionalāIntegralāDerivative)ā
If PU tends to lean slightly even when "balanced," add an integral term:
- u = Kp Ā· e + Ki Ā· Ī£e + Kd Ā· Īe
This removes longāterm bias (e.g., uneven weight distribution).
Pros:
- Most stable
- Eliminates drift
- Smoothest behavior
Cons:
- Slightly more tuning required
ā Lightweight Alternative: Linear Gain Scaling (Proportional Only)ā
If PD feels too complex, use a simple proportional controller:
let rollError = expectedRoll - observedRoll
let pitchError = expectedPitch - observedPitch
let rollControl = rollError * Kp
let pitchControl = pitchError * Kp
robotPu.runKeyValueCommand("#puroll", rollControl)
robotPu.runKeyValueCommand("#pupitch", pitchControl)
Why this is better than threshold:
- Continuous
- No oscillation from threshold crossing
- Very easy to tune
ā Advanced Option: Complementary Filter + PDā
If the robot moves fast or the IMU is noisy, combine accelerometer + gyro (if available) using a complementary filter:
- Fast gyro for shortāterm stability
- Slow accelerometer for longāterm drift correction
Then feed the filtered angles into a PD controller.
This is how many hobby drones stabilize.
ā Most Advanced: Model Predictive Control (MPC)ā
This is overkill for micro:bit, but worth mentioning:
- Predicts future tilt
- Optimizes control vector
- Very stable on dynamic terrain
Not recommended unless you port PU to a more powerful MCU.
š§ Summary of Recommended Upgradeā
| Method | Difficulty | Smoothness | Stability | Notes |
|---|---|---|---|---|
| Threshold (current) | Easy | Poor | Poor | Discrete, jerky |
| Proportional (P) | Easy | Good | Good | Best simple upgrade |
| PD | Medium | Very good | Very good | Best overall choice |
| PID | Medium | Excellent | Excellent | Removes drift |
| Complementary Filter + PD | Medium | Excellent | Excellent | Best for noisy IMUs |
Below is a clean, modernized PIDābased rewrite of moveBalance() that replaces the threshold logic with:
- Lowāpass smoothing on sensor inputs
- Full PID control for roll and pitch
- Derivative filtering to reduce noise
- Integral antiāwindup to prevent runaway accumulation
- Clamped output to keep gait vectors safe
This version is written in MakeCode TypeScript, matching the style of robotpu.ts.
š§ Improved moveBalance() (PID + smoothing + antiāwindup)ā
// --- PID tuning parameters ---
const KP = 0.35
const KI = 0.02
const KD = 0.15
// --- Smoothing filter (0ā1, higher = smoother) ---
const SMOOTH = 0.6
// --- Output limits (protect servos) ---
const MAX_OUT = 40
// --- Internal PID state ---
let prevRollError = 0
let prevPitchError = 0
let rollIntegral = 0
let pitchIntegral = 0
// --- Derivative smoothing ---
const DERIV_SMOOTH = 0.5
let prevRollDeriv = 0
let prevPitchDeriv = 0
export function moveBalance(
observedRoll: number,
observedPitch: number,
expectedRoll: number,
expectedPitch: number
) {
// -----------------------------
// 1. Smooth the sensor readings
// -----------------------------
let roll = observedRoll * (1 - SMOOTH) + expectedRoll * SMOOTH
let pitch = observedPitch * (1 - SMOOTH) + expectedPitch * SMOOTH
// -----------------------------
// 2. Compute errors
// -----------------------------
let rollError = expectedRoll - roll
let pitchError = expectedPitch - pitch
// -----------------------------
// 3. Integral term with antiāwindup
// -----------------------------
rollIntegral += rollError
pitchIntegral += pitchError
// Clamp integrals to prevent runaway
rollIntegral = Math.constrain(rollIntegral, -MAX_OUT, MAX_OUT)
pitchIntegral = Math.constrain(pitchIntegral, -MAX_OUT, MAX_OUT)
// -----------------------------
// 4. Derivative term (with smoothing)
// -----------------------------
let rollDerivRaw = rollError - prevRollError
let pitchDerivRaw = pitchError - prevPitchError
let rollDeriv = prevRollDeriv * DERIV_SMOOTH + rollDerivRaw * (1 - DERIV_SMOOTH)
let pitchDeriv = prevPitchDeriv * DERIV_SMOOTH + pitchDerivRaw * (1 - DERIV_SMOOTH)
prevRollDeriv = rollDeriv
prevPitchDeriv = pitchDeriv
prevRollError = rollError
prevPitchError = pitchError
// -----------------------------
// 5. PID output
// -----------------------------
let rollOut = KP * rollError + KI * rollIntegral + KD * rollDeriv
let pitchOut = KP * pitchError + KI * pitchIntegral + KD * pitchDeriv
// Clamp output to safe range
rollOut = Math.constrain(rollOut, -MAX_OUT, MAX_OUT)
pitchOut = Math.constrain(pitchOut, -MAX_OUT, MAX_OUT)
// -----------------------------
// 6. Apply to gait control
// -----------------------------
robotPu.runKeyValueCommand("#puroll", rollOut)
robotPu.runKeyValueCommand("#pupitch", pitchOut)
}
š§ Why This Version Is Dramatically Betterā
ā Smooth, continuous balancing
The lowāpass filter prevents jitter from the micro:bit accelerometer.
ā PID gives proportional, stable correction
- P corrects tilt proportionally
- I removes longāterm lean (e.g., weight imbalance)
- D damps oscillation and overshoot
ā Antiāwindup prevents instability
Integral terms are clamped so they never ārun away.ā
ā Derivative smoothing reduces noise
Raw derivative from micro:bit IMU is extremely noisy; smoothing makes it usable.
ā Output clamping protects servos
Ensures gait vectors stay within safe limits.