MicroPython Driver for TCA9548A
Contents
Introduction
This a MicroPython driver written specifically for the BBC micro:bit that works with the TCA9548A 8-channel I2C multiplexer from TI. The driver implements all functions described in the product datasheet.
Note: This driver is also fully compatible with the older PCA9548A multiplexer chip from NPX Semiconductors.
The TCA9548A multiplexer is discussed in some detail here. Most hobbyists will use this chip from a breakout board.
The TCA9548A multiplexer is an 8-channel switch for I2C slave devices. It provides a mechanism for interfacing up to eight slave devices to a microcontroller. These slave devices can be any device with an I2C interface such as sensors, displays, etc.
Each slave is connected to a channel on the multiplexer. Any combination of channels can be opened or closed at any time. This means that slave devices with the same I2C addresses can be accessed from a single microcontroller's I2C bus.
Each channel on the TCA9548A has two pins; SCx (clock) and SDx (data) which are connected to the respective SCL and SDA pins of a slave. The 'x ' is the channel number (0 through 7).
The TCA9548A is easy to use and very flexible. Each channel is a switch that can be opened or closed independently of the state of the other channels. Any slave device attached to an open channel is connected to the microcontroller's I2C bus. If the channel is closed then the microcontroller can't communicate with the device.
Setting the TCA9548A I2C Slave Address
The TCA9548A communicates with the microcontroller through I2C. Since this device is an I2C multiplexer it is essential that the TCA9548A's own slave address is flexible i.e. not fixed.
The TCA9548A has a bank of eight addresses (0x70 through 0x77) that the user can choose from. These are set with the A0, A1 and A2 pins. Each pin is either connected to ground or 3.3V from the micro:bit.
A2 | A1 | A0 | Address |
---|---|---|---|
GND | GND | GND | 0x70 |
GND | GND | 3.3V | 0x71 |
GND | 3.3V | GND | 0x72 |
GND | 3.3V | 3.3V | 0x73 |
3.3V | GND | GND | 0x74 |
3.3V | GND | 3.3V | 0x75 |
3.3V | 3.3V | GND | 0x76 |
3.3V | 3.3V | 3.3V | 0x77 |
The default address of the breakout board is 0x70. The A0, A1 and A2 pins are all grounded through a 10kΩ pulldown resistor.
Connecting the TCA9548A module
This is a two step process. Firstly, the TCA9548A board must be connected to the micro:bit. Then the I2C slaves must be connected to channels on the TCA9548A.
Connecting the TCA9548A Module to the micro:bit
The TCA9548A module must be connected to the micro:bit as follows:
micro:bit | TCA9548A Module |
---|---|
3.3V | VIN |
GND | GND |
Pin 19 | SCL |
Pin 20 | SDA |
Any available digital pin (optional) |
RST (optional) |
This utilises the standard I2C pins on the micro:bit.
The RST pin is a reset pin. Normally it is held HIGH. When pulled LOW momentarily it causes the the TCA9548A to perform a rapid soft reset. The driver does implement this function but it is purely user optional.
Connecting I2C Slaves to the TCA9548A Module
Up to eight slaves can be connected to the TCA9548A at any time; with each slave being assigned to one of the eight multiplexer's channels.
TCA9548A Module x=channel number |
Slave Device |
---|---|
SCx | SCL |
SDx | SDA |
Any other pins on each slave that are required for normal function must also be connected e.g. VCC and GND.
Driver Overview
The driver code can be:
- Copied from this webpage onto the clipboard then pasted into the MicroPython editor e.g. the Mu Editor. It should be saved as fc_tca9548a.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_tca9548a.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_tca9548a.py file to the computer it should be copied to the small filesystem on the micro:bit. The examples on this page will not work if this step is omitted. Any MicroPython editor that recognises the micro:bit will have an option to do this.
The Mu Editor is recommended for its simplicity. It provides a Files button on the toolbar for just this purpose.
The driver is implemented as a class. The first thing to do is call the constructor. This provides an instance of the class. The instance is constructed with all channels closed.
Syntax:
TCA9548A(addr=0x70, reset_pin=None)
Where:
addr is the TCA9548A I2C address.
reset_pin is any available digital
pin on the micro:bit. This pin must
be physically wired to the RST pin
of the TCA9548A module. The default
(None) indicates that the soft reset
function will not be used.
Examples:
from fc_tca9548a import *
# Use the defaults
tca9548a = TCA9548A()
# Change the default address
# Connect pin A1 to 3.3V -> address = 0x72
tca9548a = TCA9548A(addr=0x72)
# Set the reset pin. Pin2 on the
# micro:bit must be connected to RST
tca9548a = TCA9548A(reset_pin=pin2)
# Change address and reset pin
tca9548a = TCA9548A(0x72, pin2)
This assumes that the file fc_tca9548a.py has been successfully copied to the micro:bit's filesystem as described above.
The driver provides methods that:
- Opens any combination of channels in a single operation
- Closes any combination channels in a single operation
- Closes all channels in a single operation
- Checks whether a given channel is open
- Performs a soft reset of the TCA9548A chip
Following is a full list of methods that are user callable. Each method is described and example use given in the sections below.
OpenChannels() | IsOpen() | CloseChannels() |
CloseAllChannels() | Reset() |
Opening Channels
There is a single method that is called to open any number of channels in a single operation: OpenChannels()
Syntax:
OpenChannels(*channels)
*channels is a comma separated list of channels to open.
NOTE: The channels are opened and the state (open or closed)
of the remaining channels are left unaltered.
Examples:
from fc_tca9548a import *
# Use the defaults
tca9548a = TCA9548A()
# Open channels 0, 2, 4
tca9548a.OpenChannels(0, 2, 4)
Is the Channel Open?
The method IsOpen() returns a boolean indicating whether a given channel is open.
Syntax:
IsOpen(channel)
channel is the number of the channel that is checked
whether it is open or closed. True is returned if the
channel is open otherwise False is returned.
Example:
from fc_tca9548a import *
# Use the defaults
tca9548a = TCA9548A()
# Open channels 0, 2, 4
tca9548a.OpenChannels(0, 2, 4)
# Is channel 2 open?
print('Channel 2 open?', tca9548a.IsOpen(2))
Output:
Channel 2 open? True
Closing Channels
The driver provides two methods to close channels on the TCA9548A:
- CloseChannels() : Closes channels specified by the user.
- CloseAllChannels() : Closes all channels.
Closing Selected Channels
Syntax:
CloseChannels(*channels)
*channels is a comma separated list of channels to close.
NOTE: The channels are closed and the state (open or closed)
of the remaining channels are left unaltered.
Example:
from fc_tca9548a import *
tca9548a = TCA9548A()
# Close channels 0, 2, 4
tca9548a.CloseChannels(0, 2, 4)
Closing All Channels
Syntax:
CloseAllChannels()
All channels are closed.
Example:
from fc_tca9548a import *
tca9548a = TCA9548A()
# Close all channels
tca9548a.CloseAllChannels()
Resetting the TCA9548A
The user can send a soft reset command to the TCA9548A chip. This is done by calling the Reset() method. This is quick with the chip taking only nanoseconds to return to a full state of readiness.
The soft reset causes all channels to be closed.
Syntax:
Reset()
Example:
from fc_tca9548a import *
tca9548a = TCA9548A()
tca9548a.Reset()
Examples
Here are three more example.
Opening and Closing Channels
This is extended example showing how to open and close channels on the TCA9548A. No I2C slaves need to be attached to the channels of the multiplexer while running this program.
A soft reset is also performed, demonstrating that this closes all channels.
# Test the TCA9548A MicroPython driver
# for micro:bit
from fc_tca9548a import *
def PrintOpenChannels(tca9548a):
print('Open channels:', end=' ')
for channel in range(8):
if tca9548a.IsOpen(channel):
print(channel, end=' ')
print()
tca9548a = TCA9548A(reset_pin=pin2)
# Open some channels
print('Opening channels 2, 3, 5, 6, 7')
tca9548a.OpenChannels(2, 6, 5, 3, 7)
# Check which channels are open
PrintOpenChannels(tca9548a)
print('Channel 5 open?',tca9548a.IsOpen(5))
print('Channel 0 open?', tca9548a.IsOpen(0))
print('Channel 7 open?', tca9548a.IsOpen(7))
print()
# Close some channels
print('Closing channels 5, 6')
tca9548a.CloseChannels(5, 6)
PrintOpenChannels(tca9548a)
# Open some more channels
print('Opening channels 0, 1, 7')
tca9548a.OpenChannels(1, 7, 0)
# Check which channels are open
PrintOpenChannels(tca9548a)
# Open channel 4
print('\nOpening Channel 4')
tca9548a.OpenChannels(4)
PrintOpenChannels(tca9548a)
# Close all channels
print('\nClosing all channels')
tca9548a.CloseAllChannels()
PrintOpenChannels(tca9548a)
# Test reset
tca9548a.OpenChannels(0, 1, 2, 3, 7)
print('\nOpen some channels')
PrintOpenChannels(tca9548a)
print('Resetting...')
tca9548a.Reset()
PrintOpenChannels(tca9548a)
Output:
Opening channels 2, 3, 5, 6, 7
Open channels: 2 3 5 6 7
Channel 5 open? True
Channel 0 open? False
Channel 7 open? True
Closing channels 5, 6
Open channels: 2 3 7
Opening channels 0, 1, 7
Open channels: 0 1 2 3 7
Opening Channel 4
Open channels: 0 1 2 3 4 7
Closing all channels
Open channels:
Open some channels
Open channels: 0 1 2 3 7
Resetting...
Open channels:
Simple Error Checking
The driver performs only very rudimentary error checking. Basically, the only checking is on the validity of the arguments passed to the methods. Handling of exceptions raised by any other operations such as I2C read and writes is the responsibility of the user program.
This example passes some 'bogus' arguments to methods and demonstrates how they are handled; basically by ignoring them!
# Test the TCA9548A MicroPython driver
# for micro:bit
# Testing for 'bogus' arguments.
from fc_tca9548a import *
def PrintOpenChannels(tca9548a):
print('Open channels:', end=' ')
for channel in range(8):
if tca9548a.IsOpen(channel):
print(channel, end=' ')
print()
tca9548a = TCA9548A(reset_pin=pin2)
# Open some channels
# Not all channels exist!
tca9548a.OpenChannels(0, 3, 8, 5)
print('Opening channels 0, 3, 5, 8...')
PrintOpenChannels(tca9548a)
# Close some channels.
# Not all channels exist!
tca9548a.CloseChannels(3, 9, 5)
print('Closing channels 3, 5, 9...')
PrintOpenChannels(tca9548a)
# Checking on a non-existent channel!
print('Is Channel 10 open?', tca9548a.IsOpen(10))
Output:
Opening channels 0, 3, 5, 8...
Open channels: 0 3 5
Closing channels 3, 5, 9...
Open channels: 0
Is Channel 10 open? False
Attaching I2 Slaves to the TCA9548A
This is an ambitious example but demonstrates exactly the purpose of using the TCA9548A.
Six SHT4x (humidity and temperature) sensors, all with the same I2C address of 0x44 are connected to channels on the multiplexer. In the graphic below, the three sensors on the left are SHT45's and the three on the right are SHT40's.
Connections
The TCA9548A must be wired to the micro:bit as described above.
Each SHT4x sensor must be connected to a separate channel (SDx, SCx) where'x' is different channel number for each sensor. The VCC and GND pins of each sensor should be connected to the micro:bit's 3.3V pin and GND pin respectively.
SHT4x MicroPython Driver for micro:bit
For the following program to work the SHT4x driver must be downloaded and copied to the micro:bit's file system with the filename fc_sht4x.py . The driver code can be found here.
The Program
The program takes a humidity and temperature reading from each sensor and prints it. To show that all sensors have actually been read, the serial number from each sensor is also read and displayed.
# Test TCA9548A MicroPython driver
# for micro:bit.
# Six SHT4x humidity and temperature
# sensors all with same I2C address
# are connected to the multiplexer.
from fc_tca9548a import *
from fc_sht4x import *
def ReadAllSensors(sensors):
print()
for channel in range(8):
# The sensors on on channels 2 to 7
# on the multiplexer.
if sensors[channel] != None:
tca9548a.OpenChannels(channel)
print('Sensor', channel, ':',
sensors[channel].Read())
tca9548a.CloseAllChannels()
def AllSerialNums(sensors):
print()
# Cycle through the sensor collection.
# Read the serial number and print it.
for channel in range(8):
# The sensors are on channels 2 to 7
# on the multiplexer.
if sensors[channel] != None:
tca9548a.OpenChannels(channel)
print('Sensor', channel, 'Serial Number:',
sensors[channel].Serial())
tca9548a.CloseAllChannels()
# Instantiate the multiplexer
tca9548a = TCA9548A()
# Connected to Channel 2
s2 = SHT4X()
# Connected to Channel 3
s3 = SHT4X()
# Connected to Channel 4
s4 = SHT4X()
# Connected to Channel 5
s5 = SHT4X()
# Connected to Channel 6
s6 = SHT4X()
# Connected to Channel 7
s7 = SHT4X()
# Collection of sensors
sensors = (None,None,s2,s3,s4,s5,s6,s7)
# Read and print temperature and relative humidity.
ReadAllSensors(sensors)
# Read and print sensor chip serial numbers.
AllSerialNums(sensors)
Typical Output:
Sensor 2 : (28.19104, 82.74388, 42.06974, True, True)
Sensor 3 : (28.28184, 82.9073, 42.03159, True, True)
Sensor 4 : (28.35394, 83.03708, 42.43404, True, True)
Sensor 5 : (28.49279, 83.28702, 41.65584, True, True)
Sensor 6 : (28.52751, 83.3495, 41.80461, True, True)
Sensor 7 : (28.50347, 83.30625, 42.78119, True, True)
Sensor 2 Serial Number: 4779-35646
Sensor 3 Serial Number: 4779-31071
Sensor 4 Serial Number: 3412-30430
Sensor 5 Serial Number: 3679-21636
Sensor 6 Serial Number: 3679-32136
Sensor 7 Serial Number: 3679-42636
Since all six SHT4x sensors have the same I2C address all TCA9548A channels must be closed except for the channel of the sensor being read.
The program opens the channel of a sensor, gets the reading then closes all channels. The program then moves onto the next sensor in turn and repeats the process till all sensors have been read.
The SHT4x driver returns five values when the Read() is invoked on a sensor.
- Temperature in °C
- Temperature in°F
- Relative Humidity in %
- Is temperature checksum OK?
- Is relative humidity check sum OK?
The serial numbers are all unique clearly indicating that all sensors were correctly accessed.
Enjoy!
TCA9548A Driver Code for micro:bit
Download as zip file
'''
TCA9548A
I2C 8-channel multiplexer
MicroPython driver for micro:bit
AUTHOR: fredscave.com
DATE : 2024/10
VERSION : 1.00
'''
from microbit import *
ADDR = 0x70
class TCA9548A():
def __init__(self, addr=ADDR, reset_pin=None):
self.addr = addr
self.reset_pin = reset_pin
if reset_pin != None:
reset_pin.write_digital(1)
self.CloseAllChannels()
def getReg(self):
buf = i2c.read(self.addr, 1)
return buf[0]
def setReg(self, mask):
i2c.write(self.addr, bytearray([mask]))
def OpenChannels(self, *channels):
mask = 0
ctrl_reg = self.getReg()
for channel in channels:
if channel in range(8):
channel_bit = 1 << channel
mask = mask | channel_bit
self.setReg(ctrl_reg | mask)
def CloseChannels(self, *channels):
mask = 0b11111111
ctrl_reg = self.getReg()
for channel in channels:
channel_bit = 1 << channel
channel_bit = ~channel_bit & ((1 << 8) - 1)
mask = mask & channel_bit
self.setReg(ctrl_reg & mask)
def CloseAllChannels(self):
mask = 0
self.setReg(mask)
def IsOpen(self, channel):
if channel not in range(8):
return False
mask = 1 << channel
ctrl_reg = self.getReg()
if (ctrl_reg & mask) == mask:
return True
else:
return False
def Reset(self):
if self.reset_pin != None:
self.reset_pin.write_digital(0)
sleep(1)
self.reset_pin.write_digital(1)