Black Lives Matter - Action and Equality. ... Adafruit is open and shipping.
0

TCA9548A I2C Multiplexer Python3
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

TCA9548A I2C Multiplexer Python3

by bholland on Sun Jul 22, 2018 4:05 pm

Hello all,

To my shock and dismay, the TCA9548A IC2 Multiplexer has very little in the way of python documentation and most of the python documentation I found for it was for python 2.7 and wouldn't work with python3. This is introductory text for getting the TCA9548A I2C working with 2 vl6180x distance breakout boards (though this should work with anything). I am using a Raspberry Pi 3 for my configuration.

The Problem
You have two breakout boards that use I2C to communicate, you want to use the beautifully documented Adafruit python modules, and both boards use the same address.

The Solution
Use the TCA9548A IC2 Multiplexer breakout board and connect appropriately. Any board that communicates over I2C will have a VIN, GND, SCL, and SDA pin. VIN and GND are the same for all boards. I use 5V on the Pi3 for the VIN (voltage in), though 3.3 works fine. Basically, this tells the board what voltage a "1" is. SCL is the clock signal. SDA is the data signal. I2C is a synchronous form of communication and all I2C devices will have at least these 4 pins.

Connections
Technically, the Pi3 has 2 I2C buses but we will only use 1 (bus 1). I assume you have 2 identical breakout boards that I will call board 0 and board 1.

  • Connect the VIN, GND, SCL, and SDA pins on the TCA9548A to the GPIO. I connected VIN to pin 2 (5v), GND to pin 6 (though any GND will work), SCL to BCM 3, and SDA to BCM 2.
  • Connect the VIN and GND pins on the 2 breakout boards to a GPIO power pin (either 5v or 3.3v) and a GND pin.
  • Connect the board 0 SCL to SC0 on the TCA9548A and the board 0 SDA to SD0 on the TCA9548A
  • Connect the board 1 SCL to SC1 on the TCA9548A and the board 1 SDA to SD1 on the TCA9548A

At this point, your breadboard should look like a mess or you should have a bunch of twisted wires. The TCA9548A will have 8 pins attached, the GPIO will have 8 pins attached, and both boards will have 4 attached pins.

Test connections
Once everything is connected, you can use ic2detect and it will find 1 or 2 items. It will always find an object at 0x70, which is the address of the multiplexer. It might or might not find another object (in my case, 29). This is actually expected and I will explain why in a bit.

ic2detect accepts a bus identifier. If you attached the connections like I specified here, the bus id should be 1. Type in this:

Code: Select all | TOGGLE FULL SIZE
i2cdetect -y 1


and it will produce something like this:

Code: Select all | TOGGLE FULL SIZE
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --       


Using the Multiplexer In Code
In the docs for TCA9548A, it mentions how you can pass a single byte to a bus and all subsequent bytes will go to that bus. This is probably the most important and least emphasized sentence in the documentation. It also isn't quite true for python. In short, this is how you switch between CH0 and CH1.

The longer version is that we are essentially adding 8 separate buses to the Raspberry Pi bus (/dev/i2c-1). The multiplexer essentially is 8 entirely separate buses that you turn off and on if you want to access that "channel" or bus. If a bus is not active, you cannot access anything on it. This gets complicated because if you connect identical boards on the SC* pins, you can swtich between the buses and everything will look identical.

Changing the Mutliplexer bus

Remember that the address for the multiplexer is 0x70. You can change this by using the A0, A1, and A2 pins but for simplicity, I will use the default address 0x70. You can use smbus2 (https://smbus2.readthedocs.io/en/latest/) to write directly to a specific I2C address. If you write a very specific byte to the multiplexer on address 0x70, it will change which channel or bus you are able to access.

This has nothing to do with any of the Adafruit board modules! This really is not talked about. If you change your bus using the multiplexer, you can just use the Adafruit board modules as you normally would but the multiplexer directs that interaction to a particular bus or channel. This is absolutely fantastic. It means you can use these magnificent Adafruit modules without having to worry about writing anything directly to the bus or managing the bus of the Pi3 or the bus of the multiplexer. You also can use all of the default Adafruit addresses that are basically baked onto the breakout board. The trick is simply changing the channel that you access.

To emphasize this point, say you had two range finer boards (like I do). I create and use the same python objects to pass data to the I2C bus. smbus2 handles all of the communication and the Adafruit modules handle all of the driver interaction. The Adafruit module is unaware of which specific bus or device it is driving, only that a particular board exists at a particular I2C address. You can change the I2C bus using the multiplexer and therefore change which physical board is on the I2C bus.

In the example below, I have 2 boards located at I2C address 29. I have them hooked up to channel 0 and channel 1 on the multiplexer. I can read from board 0 on channel 0, switch channels, and read from channel 1 on board 1. To switch channels, write one of these Channel Bytes to address 0x70-the address for your multiplexer.

Channel Bytes
Code: Select all | TOGGLE FULL SIZE
I2C_ch_0 = 0B00000001
I2C_ch_1 = 0B00000010
I2C_ch_2 = 0B00000100
I2C_ch_3 = 0B00001000
I2C_ch_4 = 0B00010000
I2C_ch_5 = 0B00100000
I2C_ch_6 = 0B01000000
I2C_ch_7 = 0B10000000


Thanks to https://stackoverflow.com/questions/413 ... c-tca9548a

The 0B (a zero followed by a B) denotes that this a byte value (8 bits where each bit can be 0 or 1)

If you write one of these channel bytes to address 0x70, the multiplexer will write all bytes to that particular channel until you change it.

So in python, the smallest possible example is something like this:
Code: Select all | TOGGLE FULL SIZE
import board
import busio

import adafruit_vl6180x
import smbus2

#the raspberry pi bus number (probably located on /dev/i2c-1)
rpi_bus_number = 1

#address of the multiplexer                                                                                                                                           
multiplexer_address = 0x70

#the channel for the mux board                                                                                                                               
#there are 8 available channels, write a single bit                                                                                                           
#to the specific channel to switch it                                                                                                                         
I2C_ch_0 = 0B00000001                                                                                                                                         
I2C_ch_1 = 0B00000010                                                                                                                                         
I2C_ch_2 = 0B00000100                                                                                                                                         
I2C_ch_3 = 0B00001000                                                                                                                                         
I2C_ch_4 = 0B00010000                                                                                                                                         
I2C_ch_5 = 0B00100000                                                                                                                                         
I2C_ch_6 = 0B01000000                                                                                                                                         
I2C_ch_7 = 0B10000000

bus = smbus2.SMBus(rpi_bus_number)
i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_vl6180x.VL6180X(i2c)

#This switches to channel 0
bus.write_byte(multiplexer_address, I2C_ch_0)

#Using channel 0
#Do something with the sensor or channel 0, like this:
print(sensor.range)

#This switches to channel 1
bus.write_byte(multiplexer_address, I2C_ch_1)

#Using channel 1
#Do something with the sensor or channel 1, like this:
print(sensor.range)



So why might it not find something on a bus? Well, if you write a single byte to channel 3, for example, nothing apart from the multiplexer exists on that particular bus. If you change the channel to 3 and type in i2cdetect -y 1, it will only pick up the multiplexer. If you change it back to channel 1, it will pick up the multiplexer and your additional breakout board. It also appears that there is not a default channel for the multiplexer. You have to initialize it to a starting channel before using it. The 0x70 address will always appear using i2cdetect but until you write a byte to change the channel, nothing else will show up. I tested this using channel 0 and 1 only. If it defaults to a different channel, please let me know if the comments and I will add it.

I really do hope this helps. If you need any additional information or want to see something more, please let me know. I will keep this updated as needed.

bholland
 
Posts: 6
Joined: Sun Jul 22, 2018 12:09 am

Please be positive and constructive with your questions and comments.


cron