BBR Digital Expander Documentation

Your first sketch#

This reads a colour sensor on port 0 and prints what it sees. It is about as small as a useful Expander sketch gets.

#include <BBRDigitalExpander.h>
#include <Wire.h>

BBRDigitalExpander expander;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000);

  if (!expander.begin()) {
    Serial.println(expander.lastErrorText());
    while (true) { delay(1000); }
  }

  Serial.print(F("sensor on port 0: "));
  Serial.println((int)expander.sensorType(0));
}

void loop() {
  Serial.print(F("colour class "));
  Serial.print(expander.colorClass(0));
  Serial.print(F("  encoder 0 "));
  Serial.println(expander.encoderCount(0));
  delay(100);
}

Open the serial monitor at 115200 and you should see what kind of sensor is on port 0 before anything else happens. If it reports Empty, the sensor isn’t detected — check the connection.

Teaching it a colour#

colorClass() returns 0 until you have taught the board what to look for. Teaching takes one call, and the result is saved to the board’s flash automatically:

expander.teachColor(0, 1);   // what the sensor sees right now is "colour 1"

Run that once, with the target held in front of the sensor, and from then on colorClass(0) returns 1 whenever it sees that colour again — including after a power cycle, and including from a completely different sketch or a different host entirely.

There is a full walkthrough in Colour sensors.

Errors, without exceptions#

Arduino builds disable exceptions on most cores, so nothing in this library throws. Calls that can fail return false, or a documented sentinel value, and leave the reason behind for you to read:

float mm = expander.distanceMm(0);
if (!expander.ok()) {
  Serial.println(expander.lastErrorText());    // a sentence, usually with the fix in it
  Serial.println((int)expander.lastStatus());  // a BBRStatus value you can branch on
}

The sentinels are chosen so the obvious code is still correct when there is no answer:

Call When it cannot answer
distanceMm(port) INFINITY, so distanceMm(0) < 300 is simply false
heading() NAN, which compares false against everything
colorClass(port) 0, the same as “none of the colours I know”
anything returning bool false

Error text lives in flash rather than RAM, so carrying all of it costs an Uno nothing it can’t spare.

How reads work#

Every everyday getter — encoders, sensors, colours, distances — is served from a snapshot the library refreshes when it is older than 10 ms.

This matters because it means asking four questions in one loop iteration costs one I2C transaction, not four:

int32_t a  = expander.encoderCount(0);
int32_t b  = expander.encoderCount(1);
bool    red = expander.seesColor(2, 1);
float   mm = expander.distanceMm(3);
// all four answers came from the same snapshot, one transaction

It also means those values are mutually consistent — they were all captured at the same instant by the board, not read one at a time while the robot moved.

You don’t have to manage this. Just call the getters. setTelemetryMaxAgeMs() changes the window if you want a faster or slower refresh, and invalidateTelemetry() forces the next getter to go to the device.

Which loop speed?#

The board samples continuously on its own, so your loop rate does not affect what it measures — only how often you look. A 100 Hz loop reading the whole telemetry block at 400 kHz spends well under a millisecond per pass on I2C.

If you need less than that, use the triggers: the board watches the condition itself and drives a pin, and your loop cost drops to one digitalRead(). See Digital outputs and triggers.

What to do next#