Mixed sensor stacks on Raspberry Pi frequently fail because of address collisions and inconsistent voltage assumptions. I now audit every module before wiring.
The first pass is i2cdetect to verify visibility and address map stability across reboots. If devices disappear under load, I inspect pull-up strength and cable routing.
Level shifting is another common issue. Some breakout boards tolerate 5V logic poorly even when documentation is ambiguous.
I keep an integration worksheet with addresses, bus speed, and power notes for each sensor. It saves hours when revisiting projects months later.
The scan I always run first
Before wiring anything into an application, I enumerate the bus with i2cdetect. On a modern Pi the user-facing bus is bus 1:
i2cdetect -y 1
A healthy mixed setup looks something like this — a BME280 at 0x76, an ADS1115 at 0x48, and a PCF8574 I/O expander at 0x20:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 -- -- -- -- -- -- -- -- --
I run this a few times, and again after a reboot. Addresses that appear and disappear between scans almost always point at weak pull-ups or marginal wiring rather than a firmware bug. A UU in a cell means a kernel driver already claims that address, which is expected once a device-tree overlay is loaded.
Slowing the bus down
Long wires, many devices, or cheap breakouts often mean the default 100 kHz clock is too fast to be reliable. Dropping the bus speed is the cheapest fix and frequently makes intermittent dropouts vanish. On current Raspberry Pi OS this lives in /boot/firmware/config.txt (older images use /boot/config.txt):
dtparam=i2c_arm=on
dtparam=i2c_arm_baudrate=50000
50 kHz is my usual starting point for a long or busy bus; I only push back up to 100 kHz once the wiring is clean. A reboot is required for the change to take effect.
Resolving address collisions
The hardest problem in a mixed stack is two modules that ship on the same fixed address. Three approaches, in order of preference:
- Address pins. Many breakouts (ADS1115, PCF8574, some BME280 boards) expose ADDR/A0-A2 pins or a solder jumper. Tie them differently and the collision disappears. The ADS1115, for example, moves between
0x48-0x4Bdepending on how ADDR is strapped. - Second bus. If a device has no address option, put it on a separate software bus with
dtoverlay=i2c-gpioon spare GPIO pins. - A TCA9548A multiplexer. When you genuinely have several identical sensors, the TCA9548A sits at one address (typically
0x70) and gates eight downstream channels. You select a channel by writing a single byte, then talk to the device normally. This is the only clean way to run four identical0x76sensors on one Pi.
A filled-in audit worksheet
This is the small table I keep per project. Filling it in before wiring catches collisions on paper instead of on the bench:
| Address | Device | Bus | Logic | Notes |
|---|---|---|---|---|
0x20 |
PCF8574 I/O expander | 1 | 3.3V | A0-A2 all low; relay board |
0x48 |
ADS1115 ADC | 1 | 3.3V | ADDR->GND; would clash with a second ADC |
0x70 |
TCA9548A mux | 1 | 3.3V | Channels 0-3 each carry a BME280 at 0x76 |
0x76 |
BME280 (x4) | 1 (via mux) | 3.3V | Identical addresses, separated by mux channel |
The "Logic" column is deliberately explicit. Most of the flaky-sensor tickets I have chased came down to a 5V breakout quietly pulling the bus above 3.3V, not the code.
Sources
- Imported from the previous version of the site (gaborl.hu). Original publication around 2024-11-28.
- i2c-tools documentation (
i2cdetect(8)man page) — bus enumeration, address map interpretation, and theUU(driver-claimed) marker. - NXP Semiconductors, UM10204 — I2C-bus specification and user manual — bus speed modes and pull-up/level considerations.
Update history
- — Adopted from previous gaborl.hu (Hugo) site via legacy import.