Install the driver#
The Python driver drives the whole board — encoders, colour, distance, digital outputs, IMU and the localizer — over a Linux I2C bus. It depends on nothing but smbus2, and while a Raspberry Pi is what most people use it on, nothing in it is Pi-specific: anything exposing /dev/i2c-* works.
It has been run end to end on a Raspberry Pi 4 and a Raspberry Pi 5 — every example including the IMU and the localizer — at the stock bus rate, with no config.txt changes.
Get it#
pip install bbr-digital-expander
Install it straight from GitHub:
pip install git+https://github.com/BuildingBlockRobotics/DigitalExpander-Python
On a system-managed Python (Raspberry Pi OS Bookworm and later), either make a virtual environment first or install into your user site with pip install --user.
Do not edit regmap.py. It is generated from the register map specification, and any change you make will be overwritten the next time it is regenerated.
Turn I2C on#
Off by default on a fresh Raspberry Pi OS:
sudo raspi-config nonint do_i2c 0 # or Interface Options -> I2C
sudo usermod -aG i2c "$USER" # then log out and back in
sudo apt install i2c-tools # for i2cdetect, worth having
Wire it up#
| Expander | Raspberry Pi header |
|---|---|
| SDA | pin 3 — GPIO 2 (SDA1) |
| SCL | pin 5 — GPIO 3 (SCL1) |
| GND | pin 6 — GND |
| Power | pin 1 — 3V3, about 55 mA |
Not pin 2 or 4. Those are 5 V. The Expander’s I2C lines, encoder inputs and digital outputs are all 3.3 V and none of them are 5 V tolerant.
Bus pull-ups are the one thing a Control Hub does that most hosts do not — but a Raspberry Pi is the happy exception. It fits 1.8 kΩ pull-ups to 3.3 V on GPIO 2 and GPIO 3, close to the Hub’s 2.49 kΩ, so on a Pi you normally fit nothing at all. The full picture is on Wiring it up.
Check the bus before writing any code#
i2cdetect -y 1
The board should appear at 0x38 (or wherever you jumpered it). If the row is empty, no amount of Python will help — go to Troubleshooting first.
Address and bus#
The board answers at 7-bit address 0x38 by default, and the address jumpers move it to 0x39, 0x3A or 0x3B. That is how you put more than one Expander on the same bus:
first = BBRDigitalExpander() # 0x38 on /dev/i2c-1
second = BBRDigitalExpander(0x39) # jumpered
bus= takes an adapter number, a device path, or an already-open SMBus you want to share:
BBRDigitalExpander(bus=1) # /dev/i2c-1, the Pi header
BBRDigitalExpander(bus="/dev/i2c-3") # a software-I2C overlay, say
Start it#
from bbr_digital_expander import BBRDigitalExpander, BBRError
try:
with BBRDigitalExpander(bus=1) as expander:
expander.print_device_info()
except BBRError as exc:
print(f"Expander not found: {exc}")
The with form calls begin() on the way in and closes the bus on the way out. begin() reads the board’s identity block and checks three things: that the device ID is really an Expander, that the protocol version is one the driver speaks, and that the hardware variant is one it recognises. Any of those failing raises rather than carrying on — a clear stop at setup beats corrupt data an hour later.
If you would rather manage the lifetime yourself, begin() returns the expander, so expander = BBRDigitalExpander().begin() is the one-line form. Call close() when you are done.
Check it before writing anything else#
Run examples/device_info.py. It prints the firmware version, the hardware variant, the capability bits, the status flags and the address it answered at. If that report appears, your wiring, your pull-ups, your logic levels and your address jumpers are all correct, and everything else on this site will work.
If it does not appear, fix that first — see Troubleshooting.