Skip to content

Balance

Ahmed Dabak edited this page Jul 25, 2026 · 1 revision

Balance

Introduction

Balance is the MPU-6050 motion sensor -- sold as the GY-521 board -- a 3-axis accelerometer plus a 3-axis gyroscope. It is Otto's inner ear: which way is up, is it level, is it being shaken. It is a facade over the Mpu6050 module.

float pitch = Balance::pitchDegrees();
if (Balance::isUpsideDown()) Voice::play(Sound::OhOoh);

OttoFlow talks to the chip with a small built-in driver, so no extra library is needed.

Enabling the sensor

This is the one sensor that must be switched on, because it speaks I2C and a missing device on a bus is worth knowing about rather than silently reading noise.

Wire it to the Nano's I2C pins -- A4 (SDA) and A5 (SCL) -- then:

OttoConfig cfg;
cfg.mpu6050.enabled = true;
OttoFlow::start(cfg);

If your board has the AD0 pin pulled high, its address is 0x69 instead of the default:

cfg.mpu6050.i2cAddress = 0x69;

While the sensor is disabled or absent, every Balance call returns 0 or false rather than misbehaving -- the same silent no-op contract as the rest of the framework.

Pitch and roll

float pitch = Balance::pitchDegrees();   // nose up or down, 0 = level
float roll  = Balance::rollDegrees();    // leaning left or right, 0 = level

Both are in degrees, and both are computed from gravity: the sensor measures which way down is, and the angles follow from that. Standing square on a table, both read near zero -- a degree or two of offset is normal and reflects how the board is mounted, not an error.

Asking questions instead

As everywhere in OttoFlow, the readable form is a question:

if (!Balance::isLevelWithinDegrees(20)) Mouth::show(Icon::Confused);
if (Balance::isUpsideDown())            Voice::play(Sound::OhOoh);
if (Balance::isShakenHarderThanG(0.6))  Gestures::play(Gesture::Fretful);
  • isLevelWithinDegrees(tolerance) -- true when pitch and roll are both inside the tolerance. The default is 15 degrees, which is forgiving enough to survive a walking gait.
  • isUpsideDown() -- true when gravity points out of the top of the board. It is a straight sign test, so it flips at 90 degrees, not at 180.
  • isShakenHarderThanG(threshold) -- compares total acceleration against the 1 g that gravity provides at rest, and is true when it deviates by more than the threshold in either direction. That covers a shake, a bump, a drop, and being picked up. The default threshold is 0.6 g.

When the numbers are trustworthy

Pitch and roll are derived from gravity, and gravity is only the dominant force when Otto is still. The angles are therefore:

  • Reliable standing, resting, tilting slowly, being held.
  • Noisy mid-walk, and meaningless during a jump -- in free fall there is no gravity vector to measure.

Read tilt between movements rather than during them:

Legs::walkForward(2);
if (!Balance::isLevelWithinDegrees(25)) {
  Mouth::show(Icon::Surprised);
  Legs::center();
}

isShakenHarderThanG() is the opposite: it is about the motion, so it works precisely when the tilt angles do not.

Checking the sensor is alive

if (!Mpu6050::isConnected()) Serial.println(F("no MPU-6050 on the bus"));

isConnected() is the first thing to check when tilt values look frozen or stubbornly zero. It reports whether the chip answered on I2C at startup. A false here means wiring, address, or power -- not code.

The Serial Console gives you the same answer without a sketch:

> tilt
pitch -1.4  roll 0.8
> watch tilt

Tilt that never changes while you physically tip the robot is a sensor that is not talking.

Going lower: the Mpu6050 module

float x, y, z;
Mpu6050::readAccelerationG(x, y, z);     // g, 1.0 = earth gravity
Mpu6050::readRotationDps(x, y, z);       // degrees per second
float tempC = Mpu6050::temperatureC();   // the chip's own thermometer
bool ok     = Mpu6050::isConnected();

The module is where the raw axes live. readRotationDps() in particular has no facade call: the gyroscope measures how fast the robot is turning, which is what you want for detecting a spin or a fall in progress rather than a static pose.

temperatureC() reads the die temperature. It is a genuine thermometer, but it measures the chip, which sits warmer than the room.

Fall detection and get-up behaviours built on this sensor are on the Roadmap.

See also Sensors for the other senses.

Clone this wiki locally