BBR Digital Expander Documentation

Heading (IMU)#

Every method on this page throws if the gyro cannot be trusted — see Troubleshooting. That is deliberate, and better than a heading of 0.00 that never changes.

Reading heading#

double deg = exp.getHeading();   // degrees, CCW positive

The board fuses gyro and accelerometer data continuously at high rate, so the heading you read is already filtered. You are not integrating anything yourself.

Zero it at match start#

exp.resetHeading();

Heading is measured from wherever the robot was when you last called this. Call it at match start, once the robot is in its starting position.

resetHeading() is per-match and is not saved to flash — it will not disturb any mounting calibration.

Calibrate the gyro#

exp.calibrateGyro();   // ~1 second, robot must be COMPLETELY still

This measures the gyro’s bias — the small non-zero reading it produces while sitting still, which would otherwise accumulate into heading drift.

The robot must be genuinely still. Not “mostly still”, not “someone is holding it”. If the board detects movement it refuses rather than storing a bad bias, because a wrong bias is worse than an old one.

Calibrating during init, while the robot sits on the field waiting for the match to start, is the natural place for it.

Checking the state#

BBRDigitalExpander.ImuState imu = exp.readImu();

imu.yawDeg;              // heading
imu.pitchDeg;
imu.rollDeg;
imu.gyroDps;             // rates, robot frame
imu.linearAccel;         // m/s^2, gravity removed
imu.isFusionValid();     // is the attitude output usable right now
imu.isBiasValid();       // has it been still long enough to trust the bias
imu.isGyroSaturated();   // did a rate exceed what the gyro can measure

isFusionValid() is the one to check before trusting a heading. It drops briefly after a mounting change or a reset while the filter re-converges — around 100 ms — and comes back on its own.

isGyroSaturated() matters if your robot spins hard. Past the gyro’s maximum rate the heading estimate is no longer trustworthy, and this flag is how you find out that happened rather than wondering why the heading is off afterwards.

Mounting#

The board knows its own IMU orientation — that is handled in firmware and is not your problem.

What it cannot know is how you bolted the board to your robot.

Mount the board square to the robot’s forward direction. The firmware supports a mounting yaw offset (IMU_YAW_OFFSET), and it applies live without a reboot — but the Java driver does not currently expose a method to set it. Until it does, a board mounted at an angle in the horizontal plane will report headings rotated by that angle, with nothing to correct it from your OpMode.

Square and flat is the default and needs no configuration at all.

If you had to mount the board on its edge rather than flat — bolted to a side rail, say — tell it which way is up, so it integrates yaw about the true vertical instead of about whatever axis happens to be pointing sideways:

exp.setImuAxisUp(BBRRegMap.AXIS_POS_Z);   // AXIS_POS_Z is flat, the default
exp.saveConfigToFlash();
exp.resetLocalizerAndCalibrateImu();      // re-calibrate on the new axis

The six AXIS_* values cover ±X, ±Y and ±Z. Re-calibrate after changing it, or the gyro bias will still belong to the old axis.

Heading triggers#

The board can drive a digital output from heading, the same way it does for colours and distances. A “robot is pointing roughly downfield” pin costs you no loop time and no I2C traffic:

exp.triggerWhenFacing(0, 90, 15);   // output 0 high while heading is 90° ±15°

Degrees, not centidegrees, and the same headings getHeading() reports. Tolerance is either side of the target, so that example is a 30° window. Like the other everyday helpers it saves to flash automatically.

The window is allowed to straddle the ±180 seam — triggerWhenFacing(0, 180, 20) covers 160° to −160°. The board does that arithmetic; you do not.

The pin stays low until the gyro has settled after power-on, and goes low again if the IMU faults. A heading trigger never reports a confident answer the board doesn’t have.

If you need an asymmetric window rather than a target-and-tolerance, the advanced tier takes the raw edges in degrees × 100:

BBRDigitalExpander.OutputConfig out = new BBRDigitalExpander.OutputConfig();
out.source = BBRDigitalExpander.OutputSource.IMU_HEADING;
out.threshMin = 4500;    // 45.00°
out.threshMax = 13500;   // 135.00°
exp.configureOutput(0, out);
exp.saveConfigToFlash();

Setting threshMin above threshMax is not an error there either: it is how you ask for a window that wraps through ±180.

Worked example#

BBRImuHeadingExample covers heading, calibration, and failing loudly when the gyro cannot be trusted. BBRHeadingTriggerExample arms a heading trigger from the current heading and shows the pin following it. See Example OpModes.