Arduino API summary#
The everyday tier, which is what most projects need. Every method validates its arguments and, on failure, sets an error you can read rather than returning a plausible-looking wrong number.
Nothing here throws and nothing allocates.
Getting the device#
#include <BBRDigitalExpander.h>
#include <Wire.h>
BBRDigitalExpander expander; // 0x38 on Wire
BBRDigitalExpander expander(0x39); // jumpered address
BBRDigitalExpander expander(BBR_I2C_ADDR_DEFAULT, Wire1); // second bus
Wire.begin();
expander.begin(); // false if it is not there, or not what it should be
Errors#
| Method | Returns |
|---|---|
ok() |
Did the most recent call succeed |
lastStatus() |
A BBRStatus — Ok, Transport, NoImu, BadArgument, … |
lastErrorText() |
A flash-resident sentence, printable straight to Serial |
lastCommandResult() |
The firmware’s error code from the last failed command |
Encoders#
| Method | Returns |
|---|---|
encoderCount(channel) |
Signed accumulated counts |
encoderVelocity(channel) |
Signed counts per second, computed on the board |
pulseWidthUs(channel) |
Pulse width in µs (PULSE_WIDTH mode only) |
resetEncoder(channel) |
bool |
resetAllEncoders() |
bool |
Sensors#
| Method | Returns |
|---|---|
sensorConnected(port) |
bool |
sensorType(port) |
BBRSensorType::Color, Distance, Empty or Unknown |
colorClass(port) |
0 for no match, else the colour slot 1–7 |
seesColor(port, classSlot) |
bool |
distanceMm(port) |
Distance in mm, or INFINITY when nothing is in range |
Teaching and triggers#
All of these save to flash automatically, which is why they are setup calls: run them once, or on a button press, never in a loop. See Set up once.
| Method | Effect |
|---|---|
teachColor(port, classSlot) |
Learn the colour currently in view |
triggerOnColor(output, port, classSlot) |
Output high while that colour is seen |
triggerWhenNear(output, port, maxMm) |
Output high while something is within range |
triggerWhenEncoderPast(output, channel, counts) |
Output high past a threshold |
triggerWhenFacing(output, headingDeg, toleranceDeg) |
Output high while facing that heading |
disableOutput(output) |
Stop driving that output at all |
outputState() / outputLatched() |
Bit per output, read back over I2C |
setEncoderDirection(channel, direction) |
Forward or Reverse for one channel |
clearOutputLatch(output) |
Un-latch one output |
clearAllOutputLatches() |
Un-latch all four |
Heading and pose#
These fail rather than return a heading or pose the board cannot stand behind.
| Method | Returns |
|---|---|
heading() |
Degrees CCW-positive, or NAN if the gyro cannot be trusted |
resetHeading() |
bool |
calibrateGyro() |
bool (robot must be still, ~1 s) |
readImu(BBRImuState&) |
bool |
getPose(BBRPose&) |
bool — mm, radians, +X forward, +Y left |
setPose(xMm, yMm, headingRad) |
bool |
readLocalizer(BBRLocalizerState&) |
bool — fails unless RUNNING |
readLocalizerRaw(BBRLocalizerState&) |
bool — any state, check it yourself |
waitForLocalizerReady(timeoutMs) |
bool |
resetLocalizerAndCalibrateImu() |
bool |
setLocalizerParams(...) / getLocalizerParams(...) |
bool |
Mounting and direction#
Set once when the board is bolted on. Unlike setEncoderDirection() above, nothing here auto-saves — follow with saveConfigToFlash().
| Method | Effect |
|---|---|
getEncoderDirection(channel, out) |
Which way that channel counts |
setEncoderInvertMask(mask) |
All four at once; bit per channel, set = inverted |
getEncoderInvertMask() |
Current mask, or -1 on a failed read |
setImuAxisUp(axis) |
Which board axis points up (BBR_AXIS_*, flat is BBR_AXIS_POS_Z) |
getImuAxisUp() |
Current setting, or -1 on a failed read |
Board identity#
| Method | Returns |
|---|---|
capabilities() / hasCapability(bit) |
Capability bits (BBR_CAP_*) |
isOdometryVariant() |
Is an IMU fitted |
protocolMinor() / hardwareVariant() |
Version numbers |
deviceStatus() |
BBR_STATUS_* bits |
isConfigDirty() |
Are there unsaved config changes |
printDeviceInfo(Serial) |
The whole identity block, human-readable |
Blocks and raw access#
| Method | Returns |
|---|---|
readTelemetry(BBRTelemetry&) |
Everything in one snapshot |
setTelemetryMaxAgeMs(ms) |
Cache window behind the everyday getters (default 10) |
invalidateTelemetry() |
Force the next getter to read the device |
encoderPinState() |
Live A/B input levels — a wiring diagnostic |
readRegisters(reg, buf, len) / writeRegisters(reg, data, len) |
Raw block access |
readRegister(reg) / writeRegister(reg, value) |
One byte; read returns -1 on failure |
runCommand(opcode, arg, maxMs) |
The command protocol, done correctly |
Advanced tier#
The full register map is available underneath: configureOutput(), writeColorClass(), writeDistanceClass(), setChannelMode(), setPwmChannelParams(), setPwmWrapEnabled(), setLocalizerParams(), readTelemetry(), runCommand().
Nothing in the advanced tier saves automatically. Call saveConfigToFlash(), or the configuration is gone at power-off. isConfigDirty() tells you whether you have unsaved changes.
Behaviour worth knowing#
begin() refuses loudly. A wrong device ID, an unsupported protocol version or an unrecognised hardware variant returns false rather than producing corrupt data later.
One transaction per snapshot. The everyday getters share a snapshot refreshed roughly every 10 ms, so four questions in one loop iteration cost one transaction and the answers are mutually consistent.
Big reads survive a small Wire buffer. The 96-byte telemetry block does not fit an Uno’s 32-byte buffer, so it arrives in chunks — but the register pointer is written only once and the chunks continue from it, which is what keeps the whole block inside a single firmware snapshot. Nothing tears across the seam.
Commands are idempotent. Every command carries a token, so a bus retry cannot execute it twice.
Config writes are verified by readback. The board never NAKs for addressing reasons — an out-of-range write is silently discarded — so the library writes, reads back and compares.
IMU access fails when the gyro cannot be trusted rather than returning zeros, because a heading of 0.00 that never changes is indistinguishable from working software.
The localizer block is checked with CRC16. I2C acknowledges bytes without verifying them, so a noise-flipped bit would otherwise arrive looking like a valid pose. A mismatch is re-read, then reported.