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-0x4B depending on how ADDR is strapped.
  • Second bus. If a device has no address option, put it on a separate software bus with dtoverlay=i2c-gpio on 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 identical 0x76 sensors 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 the UU (driver-claimed) marker.
  • NXP Semiconductors, UM10204 — I2C-bus specification and user manual — bus speed modes and pull-up/level considerations.