BBR Digital Expander Documentation

Odometry and pose#

These methods throw if the gyro cannot be trusted, rather than reporting a pose they cannot stand behind. See Troubleshooting.

The board can track where your robot is on the field — x, y, and heading — by combining two dead-wheel encoders with the onboard gyro. The integration runs on the board at 1 kHz, and your OpMode reads the answer in a single I2C transaction.

Setting it up#

Tell the board about your odometry pods once:

BBRDigitalExpander.LocalizerParams p = new BBRDigitalExpander.LocalizerParams();
p.portX = 0;              // encoder channel for the X (forward) pod
p.portY = 1;              // encoder channel for the Y (lateral) pod
p.ticksPerMmX = 13.26;    // measure these, see below
p.ticksPerMmY = 13.26;
p.tcpOffsetXMm = 0;       // tracking point offset from robot centre, mm
p.tcpOffsetYMm = 0;
p.imuScalar = 1.0;        // gyro scale correction
exp.setLocalizerParams(p);

Stored parameters take effect on the next resetLocalizerAndCalibrateImu(), not immediately. Set them first, then start the localizer.

Measuring ticksPerMm: push the robot a couple of metres along one axis, then divide the counts by the distance travelled. Do it for each axis separately. This is the number that decides whether your pose is right, so measure it properly rather than calculating it from the wheel diameter on the datasheet.

Measuring imuScalar: spin the robot ten full turns and compare the reported heading change against 3600°. The ratio is your scalar, and it should land near 1.0.

Getting the directions right: the localizer expects forward travel and left travel to both count positive. A pod mounted the other way round counts backwards, and the pose will drift in a way that looks like a ticksPerMm problem but isn’t. Fix it on the board rather than negating numbers in your OpMode:

exp.setEncoderInvertMask(0b0010);   // invert channel 1 only
exp.saveConfigToFlash();

One bit per encoder channel, set means inverted. Push the robot forward and check the counts climb before you trust any pose.

Then start it, which zeroes the pose and calibrates the gyro in one step:

exp.resetLocalizerAndCalibrateImu();

if (!exp.waitForLocalizerReady(5000)) {
    telemetry.addLine("Localizer never reached RUNNING — is the robot moving?");
    telemetry.update();
    return;
}

The robot must be still for this. It takes one to two seconds.

Reading the pose#

BBRDigitalExpander.Pose2D pose = exp.getPose();
pose.xMm;             // millimetres
pose.yMm;             // millimetres
pose.headingRad;      // radians
pose.headingDeg();    // degrees, for telemetry

Conventions: millimetres and radians, +X forward, +Y left, counter-clockwise positive. This is the standard FTC field convention, so it lines up with the rest of your code.

The field names say their units — xMm, headingRad — so a unit mix-up has to survive you typing the wrong name. Use headingDeg() for telemetry rather than converting by hand.

getPose() throws unless the localizer is actually RUNNING — you never get a stale or made-up position.

Setting the pose#

Zero it, or place the robot at a known point:

exp.setPose(new BBRDigitalExpander.Pose2D(0, 0, 0));
exp.setPose(BBRDigitalExpander.Pose2D.fromDegrees(500, -200, 90));

fromDegrees() exists because everything else in your code is in degrees and converting by hand is an easy place to get a factor of 57.3 wrong.

Setting the pose is how you tell the robot where autonomous started, or correct it mid-match from a known feature.

Checking status#

BBRDigitalExpander.LocalizerState s = exp.readLocalizerRaw();
if (s.status == BBRDigitalExpander.LocalizerStatus.RUNNING) {
    // pose is good
}

Worth watching in telemetry during development. If the localizer drops out of RUNNING, you want to know that rather than to keep driving on a pose that stopped updating.

Getting good results#

  • Pod geometry has to be right. The board cannot detect that you measured the wheelbase wrong; it will just accumulate error confidently.
  • Calibrate with the robot still. A bad gyro bias turns into heading drift, and heading drift turns into position error that grows the further you drive.
  • Dead wheels need contact. A pod that skips over a field seam loses counts permanently — odometry has no way to notice or recover.
  • Re-zero when you can. If autonomous ends at a known position, setPose() at the start of teleop beats inheriting accumulated error.

Worked example#

BBRLocalizerExample covers the whole flow: params, calibration, waiting for RUNNING, live pose, re-zeroing, and teleporting. See Example OpModes.