Illustration by Hanna Dyrcz
Technical

How to Generate Unique I2C Addresses on Daisy-Chained, Identical PCBAs

Inter-Integrated Circuit (I2C) is a communications protocol that requires each component on the bus to have a unique address. This is normally fine because there are at least 2^7=127 I2C address options available. Some I2C components design around this issue and give the opportunity to set up to three address pins with external circuitry, allowing the same IC to present eight different addresses on the bus. Adafruit put together a list of some known I2C addresses and what components might be conflicting on those addresses.

I recently ran into a fun engineering problem trying to work around an I2C addressing situation and I feel it is worth sharing.

In certain scenarios, potentially for I2C temperature sensing, ADC, or IO Expander applications, you may want to daisy chain multiple “dumb” sensing PCBAs to a master “smart” PCBA. The master PCBA sits at the front of the chain, and “dumb” PCBAs are connected to the chain in any order. Each sensing PCBA needs at minimum a 4-pin connector, for power, ground, and the two I2C lines.

Let’s say we want to sense the temperature on eight MCP9808 I2C temperature sensors spread across four boards (two sensors per board). We limit ourselves to eight because the MCP9808 temp sensor has three address pins, giving us the option for eight unique I2C addresses (000, 001, 010, etc…).

Typically when a board is daisy-chained with the same sensor IC being repeated on the bus, there are a couple of ways to solve that problem.

  • Option 1: Turn the bus into multiple buses! Something like an I2C bus switch/multiplexer (mux) takes a single clock and data input pair and splits it into four discrete output channels. By putting the same address on different output channels, up to four of the same address can be used on the same “first” bus.

Figure 1: TCA9548A I2C 1-to-8 Bi-directional Channel Switch

  • There are some drawbacks to the mux circuit. It requires a lot of pins if you would like to truly daisy chain the boards. If you do not want to daisy chain the boards, the cable routing can become unwieldy. A positive aspect though, if we continue to follow our temp sensing example, is that the mux allows for each board to have eight unique sensors on it!
  • Option 2: Spin assembly variants stuffing pull up or pull down resistors on the address pins to give each sensor a unique address. In this approach, you have a single board fabricated with landing spots for pull up or pull down resistors on the address lines of the temp sensor then have four different assembly variants. One for each of the boards in the chain:
    • First board: with addresses 000, 001
    • Second board: with addresses 010, 011
    • Third board: with addresses 100, 101
    • And fourth board: with addresses 110, 111
  • This is probably the simplest solution from a design standpoint but is the scariest solution from a production standpoint. Having four assembly part numbers and revisions to keep track of can be a pain for supply chain and engineering. To change something on the board, you need to update all four of the unique design files. Keeping bins organized and separated can be a bit annoying as well.
  • Option 3: The simplest solution is to have a single board with a single part number, whose addresses could somehow be determined by its position in the chain. This is what we set out to do, and our journey to find the solution provided a lot of interesting puzzles to solve!

The goal is to have a single PCBA, with two temperature sensors per board, and have that PCBA be connected in a daisy chain of four boards so that the eight I2C addresses are unique.
The solution to this problem could be some sort of position-based I2C addressing technique. The generic boards do not hold the I2C addresses, it is the board’s position in the chain that defines the address.

The project started with a single board (easy). Then mechanical engineers asked if we could make two boards to tell temperature in two locations (harder). Then they asked for three boards, before finally settling on four temperature sensing locations (hardest).
As the problem evolved, the solution had to evolve with it.

Keep in mind, for the multi-board problems, we want them to be the exact same board! Not assembly variants of each other.

For the one board problem, we can set the unique addresses on the board using resistors, and not worry about a chain. Pretty straightforward.

Two Boards: Here we want to design a generic board, such that it can be placed in both locations and not have a repeated address.

To solve this, we used the idea of a left or right neighbor. We added two lines to the original 4-pin cable harness. Each board had a pull-up on a pin going to the next board in the chain, saying “hello, left neighbor, I am your right neighbor and I am here!” It also had a pull up on a pin going to the previous board in the chain to say, “Howdy neighbor, I am your left neighbor and I am here!”

The diagram shows the logic of the PCBA being used in both locations of the chain.

 

The A1 address pin represents the presence of a left neighbor. If there is a left neighbor, that bit is pulled high, otherwise the pull-down on each of the boards pulls it low. Notice the addresses for the temp sensors on the PCBA on the left have no left neighbor, therefore their A1 bit is low.

The A0 address pin is associated with the right neighbor which will pull that pin high, otherwise the bit is low. Notice the board on the left has the A0 pin pulled high whereas the A0 bit on the right board has no pull-up, therefore it is low.

Bits:
A2 A1 A0

Addresses:
First Board (Far right):
1 1 0 : 6
0 1 0 : 2

Second Board:
1 0 1 : 5
0 0 1 : 1

Success! The addresses are unique! The board in each location is exactly the same so it can be unplugged, swapped, replaced, etc. and the addresses will remain unique.

The solution is fun, simple, and generic!

But, what if (hypothetically) the mechanical engineer comes to you saying, “Sam! Cool two board system, now we want three boards?”

We scratch our heads for a moment and then decide to plug a third board into the chain and just see what happens:

The system still works! There is one board with only a right neighbor (far left board), one board (middle board) with both a left and right neighbor, and one board with only a left neighbor (far right board).

Bits:
A2 A1 A0

Addresses:
First Board (Far Right):
1 1 0 : 6
0 1 0 : 2

Second Board:
1 1 1 : 7
0 1 1 : 3

Third Board:
1 0 1 : 5
0 0 1 : 1

Again, each board has a unique address, each board is able to be swapped with another board and the addresses are maintained. One single board assembly part number, three “unique” boards in a chain.

We are on fire. We are on top of the world. We are engineering!

“Sam, we want four boards now.”

This is where the system breaks down.

Let’s look at the addresses:

First Board (Far Right):
1 1 0 : 6
0 1 0 : 2

Second Board:
1 1 1 : 7
0 1 1 : 3

Third Board:
1 1 1 : 7
0 1 1 : 3

Fourth Board:
1 0 1 : 5
0 0 1 : 1

Oops, we have two of the same addresses (7 and 3)! The two middle boards both have left and right neighbors, giving their temperature sensors the same address. How do we solve this issue?

To get to an answer, let’s look at the binary layout of the bits and see if we can find a fun way to split the binary tree to give us eight unique addresses for our four board chain.

Separating the “top” and “bottom” temp sensors with pull-up and pull-downs is a simple way to cut the tree in half.

If we continue to look at the binary tree, we need to split the two groups again. This time we will split the A1 bit of the addresses. Looking at the middle boards vs the outside boards seems like a logical way to separate them. And as we saw in the four board chain, the middle boards shared an address. Maybe there is something to that…

And finally, if we find a way to alternate the last bit we should have the tree filled out.

What better way than to simply alternate?

It looks like the fun part is complete! We found a way to distinguish the boards into eight unique addresses by separating by Top vs Bottom; Inside vs Outside; and simply alternating the last bit.

Now that the hard part is over, it is time to think of a generic implementation technique that will give us a single part number for a PCBA while accomplishing our architecture.

The A2 bit is already done, having it pulled up for “top” sensors and pulled down for “bottom” sensors. This is the same as it was in the two and three board implementation.

A0 is also trivial. Having an inverter on each board that passes the signal through to the next board will force A0 to alternate on each board.

The final design step is the A1 bit. How to discern the middle boards in the chain versus the outside? It looks like we have a hint from our “botched” attempt of using our three board system with a four board chain. The two middle boards had both left and right neighbors, whereas the outside boards only have one neighbor…

For A1 bit, let’s say the boards send a left and right neighbor signal as they did before, but this time we pass those signals through an AND Gate. This way, it will only pass a “1” if both inputs are “1” aka if it is a “middle” board!

The architecture per board would look a little something like this:

The A0 bit is pulled down at the start of the chain and passed through an inverter on the board, so the first board in the chain will have the A0 bit as a 1. The second board will have the A0 bit as a 0, and so on.

The input to the A1 bit is the Right Neighbor and Left Neighbor signal as we had before, but now pulled down and passed through an AND Gate. In this way, if a right and left neighbor are not both present, the output of the AND Gate will be low. Therefore the outside boards will have A1 be “0” while the middle boards whose inputs to the AND Gate are both “1” will have their A1 bit be “1.”

And lastly, old reliable: the A2 bit is pulled up on the upper sensor while A2 is pulled down on the lower sensor, completing the binary tree.

The final chain of boards looks like this:

Where the addresses are:

First Board (far right):
0 0 1 : 1
1 0 1 : 5

Second Board:
1 1 0 : 6
0 1 0 : 2

Third Board:
1 1 1 : 7
0 1 1 : 3

And Fourth Board:
1 0 0 : 4
0 0 0 : 0

Perfect!

With a single board we are able to achieve the eight unique addresses in a daisy chain architecture! Again, the one board design cuts down the impact on production significantly when you start to think about designing, changing, building, and testing four unique part numbers. Which saves time, complexity, and, in the end, money.

As you can see the actual logic here is not complicated, we are talking about an AND Gate and an Inverter. The fun part is looking at the problem (we need 8 unique I2C addresses spread across 4 boards), and constraints (try to design a single board that can set address based on position in chain), to come up with an interesting engineering solution!

Of course, I imagine there are a lot of other interesting ideas for how a single board can satisfy our requirements! I know when I posed the problem to a firmware engineer here, Robert, he came up with a binary adder to simply increment on every board… Well played Robert.

If you have your own fun, clever solutions, we would love to hear from you!