How to get 72 I2C busses on a PICO

Published 2026-03-22

There just aren't enough I2C busses on the PICO, right? Especially when you get some devices that have only one address, and you need to access a bunch of them. Sure, you could use an I2C expander, but that costs money and consumes board real-estate.

I have a much simpler solution — bus multiplexing. Here's a matrix that give you an idea of what I mean:

36 I<sup>2</sup>C busses on a PICO
36 I<sup>2</sup>C busses on a PICO
36 I2C busses on a PICO XL

Considering only the first I2C controller, i2c0, there are six pins that can be configured as SDA, and six pins that can be configured as SCL. The key insight is, they can be used in any combination.

In the diagram above, we see the six SDA pins as rows, and the six SCL pins as columns, giving us an intersection of 36 unique bus points!

With some cleverness in the software, you can actually host 36 I2 devices using those 12 pins.

Here's an example board that I made as a proof of concept that can support 9 busses with two devices each:

9 I<sup>2</sup>C busses on a PICO
9 I<sup>2</sup>C busses on a PICO
9 I2C busses on a PICO XL

Yes, there will be issues with bus driving and capacitance. This is a software article :-)

I made this board because the HTU31D temperature and humidity sensor can be selected to have one of two possible addresses. AND, the board designers neglected to bring out the address select pin to the board, so effectively each board had the same address. I cut and jumpered a few of the boards to short the address lines the other way, and was able to run 12 HTU31D boards.

I see only 36 busses...

Yes, there are 36 busses on i2c0, but there are another 36 busses on i2c1, for a total of 72 busses.

The software

Let's take a look at the software that I used for the above HTU31D board. We'll call each intersection of an SDA and SCL line on the same controller a "crosspoint bus".

First, declare the nine crosspoint busses — this is just a convenient way to keep track:

#define	I2C_PORT	i2c1	// the pins we map are all from the I2C1 controller

// we use a 3 x 3 matrix of I2C in order to get 9 busses and 18 devices
#define	SDA_1	2	// SDA 1, pin 4
#define	SCL_1	3	// SCL 1, pin 5
#define	SDA_2	6	// SDA 2, pin 9
#define	SCL_2	7	// SCL 2, pin 10
#define	SDA_3	10	// SDA 3, pin 14
#define	SCL_3	11	// SCL 3, pin 15

typedef struct
{
	int		sda;
	int		scl;
} OneI2C;

OneI2C i2cs [9] =
{
	{SDA_1, SCL_1}, {SDA_1, SCL_2}, {SDA_1, SCL_3},
	{SDA_2, SCL_1}, {SDA_2, SCL_2}, {SDA_2, SCL_3},
	{SDA_3, SCL_1}, {SDA_3, SCL_2}, {SDA_3, SCL_3}
};

You could expand this to all 36 busses, but I stayed with 9 busses for simplicity, and used the i2c1 controller.

First, we need to initialize all the busses. We do this by turning off the I2C controller, and setting the pins as inputs with pullup. This is effectively the "inactive" state for I2C:

static void
pin_as_input (int p)
{
	gpio_set_function (p, GPIO_FUNC_SIO);
	gpio_set_dir (p, GPIO_IN);
	gpio_pull_up (p);
}

static void
crosspoint_as_input (int b)
{
	i2c_deinit (I2C_PORT);
	pin_as_input (i2cs [b].scl);
	pin_as_input (i2cs [b].sda);
}

int main ()
{
	// turn all bus pins into inputs with pullup
	crosspoint_as_input (0);	// does SDA1/SCL1
	crosspoint_as_input (4);	// does SDA2/SCL2
	crosspoint_as_input (8);	// does SDA3/SCL3

	...
}

This is straightfoward code to turn each pin into an input with pullups. (There's a small hack in that I carefully selected three crosspoint busses, 0, 4, and 8 to configure all SDA and SCL pin pairs {SDA_1, SCL_1}, {SDA_2, SCL_2}, and {SDA_3, SCL_3}.)

To demonstrate how to use these crosspoint busses, I have the following detection loop in main() — it scans each crosspoint bus and looks for a device (error checking code removed for clarity):

static void
pin_as_i2c (int p)
{
	gpio_set_function (p, GPIO_FUNC_I2C);
}

static void
crosspoint_as_i2c (int b)
{
	i2c_init (I2C_PORT, 100 * 1000);
	pin_as_i2c (i2cs [b].scl);
	pin_as_i2c (i2cs [b].sda);
}

int main ()
{
	...
	printf ("Inventory:\n");
	for (int bus = 0; bus < NELEMENTS (i2cs); bus++) {
		printf ("%d ", bus);
		crosspoint_as_i2c (bus);
		for (int addr = 0; addr < 2; addr++) {
			if (htu31d_init (I2C_PORT, addr)) {
				uint32_t sn = htu31d_read_serial_number (I2C_PORT, addr);
				printf ("%06X ", sn);
			}
		}
		printf ("\n");

		crosspoint_as_input (bus);
	}

That's the whole "trick" right there — we select a crosspoint bus, call crosspoint_as_i2c(), perform an I2C transaction, and then revert the crosspoint bus back to idle.