BBR Digital Expander Documentation

Troubleshooting#

The board isn’t found at all#

“Not a BBR Digital Expander: DEVICE_ID …” — something answered at the address, but it wasn’t an Expander. Check you picked the right I2C bus in the robot configuration, and that nothing else on the bus is at address 0x38.

Device not found in hardwareMap — the name in your code doesn’t match the name in the robot configuration. The examples use expander.

Configuration doesn’t list “BBR Digital Expander” — the driver folder isn’t in your project, or the project didn’t rebuild. Confirm com/buildingblockrobotics/expander/ is under TeamCode/src/main/java/ and rebuild.

If you used this board before it was renamed, your old robot configuration entry will no longer resolve. Delete it and add “BBR Digital Expander” fresh.

Reads work sometimes, then stop#

Almost always electrical rather than software.

  • Pull-ups too weak. Internal microcontroller pull-ups are tens of kilohms — fine on a short jumper at 100 kHz, marginal at 400 kHz, and worse the longer the cable. Fit 2.2 kΩ–4.7 kΩ to 3.3 V.
  • Try 100 kHz. If dropping Wire.setClock() to 100000 fixes it, the problem is signal integrity, not the board.
  • Cable routing. Keep the I2C cable away from motor leads. This is the failure that shows up only when the robot is driving.
  • Shared ground. The board and the host need one, and it needs to carry current back without a long thin wire in the way.

A sensor reads EMPTY#

The board genuinely cannot see a sensor on that port.

  • Check the cable at both ends.
  • Try the sensor on a different port — that separates a bad sensor from a bad port.
  • Dump all four ports at once: BBRSensorDumpTest on FTC, the SensorDump example on Arduino.

Teaching a colour refuses#

Working as intended. It refuses to store a colour that would never fire or always fire — too dark, saturated, or no sensor on the port.

  • Too dark — more light, or hold the target closer.
  • Saturated — less light, or move the target back.
  • Then teach again.

A stored bad colour is much harder to debug than a refused teach, which is why it fails at the point where you’re standing there holding the target.

A digital output never goes high#

  1. Was the colour taught? A colour trigger refers to a slot that teaching has to have filled first.
  2. Is the sensor seeing it? Check the colour class returns the slot number you expect.
  3. Is it saved? The trigger helpers auto-save, but advanced-tier configureOutput() does not. Check isConfigDirty().
  4. Is the input side set up? On FTC the digital input needs its own entry in the robot configuration; on Arduino it needs pinMode(pin, INPUT).
  5. Is it wired to the pin you think? Output 0 is not necessarily the first pin on the connector.

A digital output flickers#

The condition is sitting right on the threshold. Use hysteresis rather than trying to be more precise:

  • Distance: raise hysteresisMm in a distance class.
  • Any output: raise debounceAssert / debounceRelease in an output config.

Both are set to sensible defaults by the everyday helpers, so if you are seeing chatter you are probably on the advanced tier.

Heading or pose fails#

The board refuses to hand you a heading it cannot stand behind, rather than returning a plausible-looking 0.00. Read the message — it names which case you are in:

  • This board has no IMU. It is the base variant. Heading and odometry need the odometry variant.
  • The gyro is fitted but not responding. A hardware fault, or a board that lost power mid-transaction. Power-cycle; if it persists on every boot, the board needs looking at.
  • The board stopped answering entirely. Check the I2C cable, then see The board isn’t found at all.
  • The localizer was never started. Pose calls need the localizer reset first — heading alone does not.

A failure here is the driver working correctly. A heading of 0.00 that never changes is indistinguishable from working software right up until it matters.

Heading drifts#

  • Calibrate the gyro with the robot completely still.
  • Check the bias-valid flag — false means it hasn’t had a good calibration.
  • Check the gyro-saturated flag — if the robot spun faster than the gyro can measure, the estimate is no longer trustworthy and needs a re-zero.

The pose is wrong#

  • Pose calls fail unless the localizer is running — check the status first.
  • Verify pod geometry and ticks-per-mm. The board cannot detect a wrong measurement; it just accumulates error.
  • Check the pods stay in contact with the floor. Skipping loses counts permanently.
  • Calibrate with the robot still.

The loop crawls, roughly one iteration per second#

A setup helper is being called inside your main loop with values that keep changing.

Teaching and the trigger helpers save to flash, and the board accepts only one save per second. Both drivers wait that limit out rather than failing, so each call costs up to a full second.

  • Move the call into a setup routine, or behind a button press with edge detection so holding the button doesn’t re-fire it every loop.
  • If the value genuinely has to change at runtime, use advanced-tier configureOutput(), which changes the trigger in RAM without touching flash, and save only when you want it to persist.

Repeating a call with unchanged values is not the cause — the board recognises there is nothing new to store and skips the write. It is changing values every loop that costs you.

See Set up once for what this also does to your triggers while the board is writing.

Configuration lost after power-off#

Advanced-tier calls don’t auto-save. Call saveConfigToFlash(). isConfigDirty() tells you whether you have unsaved changes.

The everyday teach and trigger helpers save for you.

Platform-specific#

Readings freeze mid-match#

Check isDataFresh(). When a read can’t reach the device the driver serves the last known good value rather than throwing, so a stale reading looks like a working one that stopped changing.

A sustained streak of failures logs a warning to the Robot Controller log and keeps serving the old value, so the OpMode keeps running. Check isDataFresh() before acting on a reading. To make that streak throw instead while you track the fault down, call exp.setReadFailurePolicy(BBRDigitalExpander.ReadFailurePolicy.STOP_OPMODE).

Did the board reset, or did the bus drop out?#

On firmware 1.1.0 and later, readDiagnostics() answers it in one call. Read it when the OpMode starts and again after a dropout:

BBRDigitalExpander.Diagnostics d = exp.readDiagnostics();
telemetry.addData("last reset", d.resetCause);
telemetry.addData("resets since power-on", d.resetsSincePowerOn);
telemetry.addData("bus recoveries", d.i2cRecoveryCount);
telemetry.addData("bus arbitration lost", d.i2cArbitrationLostCount);
  • resetsSincePowerOn above 0 with WATCHDOG: the firmware hung and restarted. The driver also logs expander REBOOTED. On 1.1.0 bus noise could trigger this; update to 1.1.1, which fixes it.
  • POWER_ON mid-match, with the Hub still running: the board lost power briefly. Check the I2C cable’s power pins and connectors.
  • No reset, but the bus counters climb: the board kept running and the I2C link itself was disturbed. i2cArbitrationLostCount going up usually means electrical noise on the cable. Route the I2C cable away from motor and motor-controller wiring, and check whether the errors follow particular encoder cables (unplug them one at a time).

The Hub stops talking to the board until it is power-cycled#

If readings stop and only a robot power cycle brings them back, the Hub’s I2C port has given up after too many corrupted transfers. First update to 1.1.1, so the board itself never holds the bus. Then reduce what corrupts the transfers:

  • Route the I2C cable away from motor and motor-controller wiring, and away from the encoder cables of the noisiest motors.
  • Read less often. Every transaction is another chance to be hit by noise. Read once per loop, and only what you use.
  • Watch the counters. i2cArbitrationLostCount and i2cRecoveryCount from readDiagnostics() climbing while a mechanism runs points at that mechanism’s wiring.

Everything breaks when the OpMode stops#

Expected, and already handled. The SDK tears the I2C device down at stop, so a read already in flight comes back empty. The driver raises BBRTransportException for that case.

If you have a polling loop, catch it and break — the IMU and localizer examples show the pattern. It means “the read never reached the device”, not “something is broken”.