The TCA9548A lets an ESP32 talk to eight separate I²C branches, even when every branch contains a sensor with the same fixed address. It solves a common problem with AHT20 temperature/humidity sensors, SHT4x probes, BH1750 light sensors and many small OLED displays: two identical devices respond to the same address, so connecting them directly in parallel does not give the controller a way to address them separately.
The multiplexer costs very little, uses only the ESP32’s existing SDA/SCL pins and is natively supported by ESPHome. It is useful in multi-room Home Assistant sensor hubs, indoor air-quality monitors and projects with several identical sensors. It also provides switchable I²C branches that can help isolate a faulty downstream sensor, with some important electrical limitations.
Quick answer: connect the ESP32 to the TCA9548A upstream SDA/SCL pair; connect each same-address sensor to a different downstream SCn/SDn pair; power every sensor correctly; then assign each sensor its own virtual bus in ESPHome. You do not need to change the sensors’ addresses.
Why Two Same-Address I²C Sensors Do Not Work on One Bus
An I²C address identifies a device on one electrically connected bus. When two AHT20 modules both respond at 0x38, they both acknowledge a request addressed to 0x38. A temperature conversion or data read may trigger both devices and their replies may conflict; the ESP32 cannot tell which sensor supplied the result.
ESP32 SDA/SCL ── AHT20 #1 (0x38)
└── AHT20 #2 (0x38)
Both respond to 0x38: no way to select just one.
A TCA9548A solves the conflict by keeping the branches electrically disconnected until the controller selects a channel. Each channel can contain its own device at 0x38:
ESP32 GPIO21 SDA ── SDA TCA9548A SD0 ── AHT20 #1 SDA (0x38)
ESP32 GPIO22 SCL ── SCL SC0 ── AHT20 #1 SCL
SD1 ── AHT20 #2 SDA (0x38)
SC1 ── AHT20 #2 SCL
SD2 ── AHT20 #3 SDA (0x38)
SC2 ── AHT20 #3 SCL
The controller selects channel 0 to speak to sensor #1, then channel 1 for sensor #2. The I²C address stays exactly the same on every branch.
What the TCA9548A Actually Does
Texas Instruments describes TCA9548A as an eight-channel bidirectional I²C/SMBus switch. It is a switch rather than eight independent I²C controllers: only the selected branches are electrically connected to the upstream SDA/SCL pair. It passes normal transactions in both directions, so standard sensor libraries and ESPHome components can work through it.
| Feature | Practical meaning |
|---|---|
| Eight downstream channels | 0–7, each with its own SDA/SCL pair |
| Default mux address | 0x70 |
| Selectable mux addresses | 0x70–0x77 via A0, A1 and A2 |
| Supported chip supply | 1.65–5.5 V; ESP32 breakout normally at 3.3 V |
| I²C speed | Up to 400 kHz, assuming all connected devices and wiring support it |
| Channel control | One-byte bit mask written to the mux address |
| Reset | Active-low RESET deselects channels |
| Power switching | No: TCA9548A switches SDA/SCL, not VCC/GND |
The device can technically enable several channels simultaneously, but do not enable two channels that both contain 0x38 sensors at the same time. The address collision would immediately return. ESPHome’s virtual-bus implementation selects the requested channel for you.
TCA9548A Pinout and ESP32 Wiring
On a common bare breakout, the upstream pins are VIN/VCC, GND, SDA and SCL. The downstream pairs are usually labelled SD0/SC0 through SD7/SC7. Some boards print SDA0/SCL0 instead; follow the board silkscreen rather than assuming a photograph from a different manufacturer is identical.
| TCA9548A breakout | Classic ESP32 DevKit | Purpose |
|---|---|---|
| VIN / VCC | 3V3 | Power multiplexer at the ESP32 logic voltage |
| GND | GND | Common electrical reference |
| SDA (upstream) | GPIO21 | Master data line |
| SCL (upstream) | GPIO22 | Master clock line |
| RST / RESET | Leave pulled high on a breakout, or control with optional GPIO | Active-low reset |
| A0 / A1 / A2 | Normally left low for 0x70 | Select mux I²C address |
These are example GPIOs for a classic ESP32. On an ESP32-C3, C6 or S3 use free pins suited to that board. You are not obliged to use GPIO21/22 on every ESP32 variant.
Connect a sensor to channel 0
| AHT20 #1 | TCA9548A | Power |
|---|---|---|
| SDA | SD0 | Not supplied by the SD0 line |
| SCL | SC0 | Not supplied by the SC0 line |
| VCC | 3.3 V supply | Connect sensor power separately |
| GND | Common GND | Shared ground with ESP32 |
Repeat with SD1/SC1 for sensor #2 and so on. The mux does not supply sensor power through its channel pair. Many breakout boards expose only SDA/SCL per channel; others provide extra power pins as a convenience. Check whether those power pins are genuinely connected to a suitable supply.
Voltage and Pull-Ups: Do Not Copy 5 V Arduino Wiring Blindly
The ESP32 is a 3.3 V logic device. For a normal ESP32 sensor hub, run the TCA9548A breakout and 3.3 V-compatible sensor modules from 3.3 V, keep all grounds common, and use upstream and downstream pull-ups to the intended logic rails. That avoids almost every voltage-translation complication.
TI does support bus-level voltage translation with appropriately chosen supply and pull-up voltages, but it is not automatic power regulation. A 5 V target still needs its own power supply and its signal levels must be compatible with the entire connected bus when its channel is selected. A random “5 V sensor” module can also carry pull-ups to 5 V on its own PCB. Inspect the circuit before attaching it to ESP32 GPIOs.
I²C relies on open-drain signals and resistive pull-ups. The upstream bus needs suitable pull-ups; each downstream branch also needs pull-ups to its own chosen voltage. Some sensor breakouts and multiplexer boards already contain them, while other mux designs deliberately leave the downstream channels without pull-ups.
- Typical short 3.3 V branches often work with one suitable pull-up pair per branch, commonly around 4.7 kΩ; choose values against the actual bus capacitance, supply and target limits.
- Too many breakout pull-ups connected in parallel can make the effective resistance too low and force an I²C device to sink excessive current.
- Do not rely on the ESP32’s weak internal pull-ups for a long cable or a heavily loaded bus.
- Keep SDA and SCL together, use a shared ground and start at 100 kHz before attempting 400 kHz.
When only one downstream channel is active, the unselected branches are not adding their pull-up currents and capacitance to the upstream bus. That is part of the practical value of a mux, but it does not make a long, poorly wired active branch immune to noise.
How the 0x70–0x77 Address Pins Work
The multiplexer itself is also an I²C device, normally at 0x70. Its address can be changed with A0, A1 and A2. For the bare IC these inputs must be tied to defined logic levels; many breakout boards provide default pull-downs or address jumpers.
| A2 | A1 | A0 | Mux address |
|---|---|---|---|
| 0 | 0 | 0 | 0x70 |
| 0 | 0 | 1 | 0x71 |
| 0 | 1 | 0 | 0x72 |
| 0 | 1 | 1 | 0x73 |
| 1 | 0 | 0 | 0x74 |
| 1 | 0 | 1 | 0x75 |
| 1 | 1 | 0 | 0x76 |
| 1 | 1 | 1 | 0x77 |
If your upstream bus already has a sensor at 0x70, choose another mux address. In theory up to eight TCA9548A ICs can occupy one upstream bus, each with its own eight channels. In practice you must consider bus capacitance, power, update intervals and the need to select channels one at a time.
The address pins select the mux address; they do not change the address of AHT20, SHT40 or any sensor connected downstream.
ESPHome: Two Identical AHT20 Sensors, Full Working Example
ESPHome’s current tca9548a component creates a virtual I²C bus for each declared channel. Each sensor uses i2c_id to choose that virtual bus. Here are two AHT20 sensors at their normal fixed 0x38 address on channels 0 and 1:
esphome:
name: dual-aht20-hub
friendly_name: Dual AHT20 Hub
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
i2c:
id: main_i2c
sda: GPIO21
scl: GPIO22
frequency: 100kHz
scan: true
tca9548a:
- id: sensor_mux
address: 0x70
i2c_id: main_i2c
channels:
- bus_id: room_a_bus
channel: 0
- bus_id: room_b_bus
channel: 1
sensor:
- platform: aht10
variant: AHT20
i2c_id: room_a_bus
temperature:
name: "Room A Temperature"
humidity:
name: "Room A Humidity"
update_interval: 60s
- platform: aht10
variant: AHT20
i2c_id: room_b_bus
temperature:
name: "Room B Temperature"
humidity:
name: "Room B Humidity"
update_interval: 60s
This uses ESPHome’s aht10 platform, which also supports AHT20 when variant: AHT20 is selected. Add your normal API encryption, OTA authentication and fallback AP settings as appropriate for your installation. The example uses current ESPHome-style esp32: and OTA syntax; it does not require any custom C++ multiplexer code.
Both AHT20 modules still reply at 0x38; room_a_bus and room_b_bus tell ESPHome which channel must be connected first. Home Assistant receives four separate named sensor entities through the ESPHome integration.
More than Two Sensors: Reuse a Fixed Address Eight Times
To add six more fixed-address AHT20s, declare channel 2–7 under the existing mux. Give each a unique bus_id, then add another sensor: entry with the matching i2c_id. Do not duplicate the top-level sensor: key: append items to the one sensor list.
tca9548a:
- id: sensor_mux
address: 0x70
i2c_id: main_i2c
channels:
- { bus_id: room_a_bus, channel: 0 }
- { bus_id: room_b_bus, channel: 1 }
- { bus_id: room_c_bus, channel: 2 }
- { bus_id: room_d_bus, channel: 3 }
An individual channel is also a normal I²C bus in its own right. You can put an AHT20 at 0x38 and a BH1750 at 0x23 on channel 0, for example, because those addresses are different. Only devices sharing the same address must be separated from one another.
ESPHome: Two Identical BME280 Modules
For two BME280 sensors that both use 0x76, keep the same multiplexer declaration and attach one to channel 0 and the other to channel 1. Their ESPHome definitions differ only in i2c_id and entity names:
sensor:
- platform: bme280_i2c
address: 0x76
i2c_id: room_a_bus
temperature:
name: "Room A Temperature"
humidity:
name: "Room A Humidity"
pressure:
name: "Room A Pressure"
update_interval: 60s
- platform: bme280_i2c
address: 0x76
i2c_id: room_b_bus
temperature:
name: "Room B Temperature"
humidity:
name: "Room B Humidity"
pressure:
name: "Room B Pressure"
update_interval: 60s
Use this instead of the AHT20 sensor section for a two-BME280 build, not in addition unless both kinds of sensor are physically connected. A BME280 can normally select 0x76 or 0x77, so an ordinary two-sensor setup may not need a multiplexer at all. The TCA9548A becomes valuable when you have three or more sensors with only two address choices, or you want separated branches.
Why an I²C Scan May Show Only 0x70
A TCA9548A powers up with all channels deselected. A normal upstream I²C scan therefore typically finds the mux at 0x70 and does not find the sensors behind it. This is expected behaviour, not evidence that the downstream modules are dead.
To scan a branch, first select its channel and then run the scanner. ESPHome’s virtual-bus configuration handles channel selection when it communicates with each declared sensor; its upstream scan cannot magically detect every isolated channel at once.
Upstream scan (no channel selected): 0x70
Select channel 0 → scan: 0x38 (AHT20), plus 0x70 upstream
Select channel 1 → scan: 0x38 (second AHT20), plus 0x70 upstream
If the mux does not appear at all, confirm VCC/GND, pull-ups, actual A0–A2 address and upstream SDA/SCL. If the mux responds but no sensors are detected after channel selection, focus on the downstream power, SDn/SCn wiring and channel pull-ups.
Arduino IDE: Select One Channel and Scan It
With Arduino, channel switching is just an I²C write to the mux address. The 1 << channel value sets exactly one channel bit. The following sketch runs on a classic ESP32 and scans each downstream branch in turn; it does not depend on an external multiplexer library:
#include <Wire.h>
constexpr uint8_t MUX_ADDR = 0x70;
bool selectChannel(uint8_t channel) {
if (channel > 7) return false;
Wire.beginTransmission(MUX_ADDR);
Wire.write(uint8_t(1U << channel));
return Wire.endTransmission() == 0;
}
void scanChannel(uint8_t channel) {
if (!selectChannel(channel)) {
Serial.printf("Mux channel %u select failed\n", channel);
return;
}
Serial.printf("Channel %u:\n", channel);
for (uint8_t addr = 0x08; addr < 0x78; ++addr) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0 && addr != MUX_ADDR) {
Serial.printf(" Found 0x%02X\n", addr);
}
}
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
Wire.setClock(100000);
delay(300);
}
void loop() {
for (uint8_t channel = 0; channel < 8; ++channel) {
scanChannel(channel);
}
delay(5000);
}
If you fitted an address jumper, update MUX_ADDR. An address shown on multiple channels may belong to a device on the upstream bus, not a different sensor on each branch; the sketch filters the mux address itself but cannot hide other upstream devices.
Arduino sensor libraries must select the channel before every transaction
Some sensor libraries execute several I²C transactions for a single reading. Select the correct mux channel immediately before calling that sensor’s library function and do not allow another task to change channels halfway through the operation. Create separate sensor objects if a library stores per-device calibration or state.
For a pair of Adafruit BME280 library objects, for instance, select channel 0 before bmeA.begin(0x76, &Wire), select channel 1 before bmeB.begin(0x76, &Wire), and select the matching channel again before each object’s read functions. ESPHome takes care of virtual-bus channel switching for its native I²C components, so this manual sequencing is an Arduino concern.
RESET Can Recover a Stuck Selected Channel
TI provides an active-low RESET input. Pulling it low resets the mux control state and deselects every channel without removing power from the IC. This can release the upstream bus if a downstream device is holding SDA or SCL low while its channel was selected.
That does not repair the misbehaving sensor itself. If you select the faulty branch again, the bus may lock up again. For a custom PCB, consider routing RESET to an ESP32 GPIO via a safe reset circuit or at least providing accessible test pads; on many common breakouts RESET is already pulled high. If you control RESET in firmware, allow the specified low pulse and recovery timing from the TI datasheet.
Do not assume the mux isolates electrical faults when a downstream branch is selected. It only isolates branches that are deselected, and it does not switch the sensors’ power rails. Power-short protection, long-cable transients and waterproofing still need separate consideration.
Pull-Ups, Cable Length and Bus Speed
A TCA9548A makes separate short branches practical, but I²C is still intended for on-board or relatively short wiring. Eight long cables running in parallel through an enclosure may work at 100 kHz but fail at 400 kHz, especially with high capacitance, poor grounding or unsuitable pull-ups.
- Begin at
100kHzand shorten cables while commissioning. - Add the correct pull-up pair on each used downstream channel when the sensor breakout does not already provide it.
- Check for multiple pairs in parallel on the same active branch, which can create an excessive low-state sink-current requirement.
- Keep power and ground alongside each sensor connection; only switching the I²C pair does not remove ground-noise problems.
- For sensors many metres away or in a noisy industrial plant, use RS-485/CAN or a remote microcontroller instead of treating the TCA9548A as a cable-distance extender.
An I²C multiplexer is also not a GPIO expander. If your goal is to add eight button/relay inputs rather than split identical I²C-address sensors, an MCP23017 or PCF8574 may be the relevant part.
TCA9548A vs a Second ESP32 I²C Bus
Many ESP32 variants can use more than one hardware I²C controller or independently configured I²C bus. If you have just two identical sensors and enough spare GPIOs, two independent buses may be simpler than fitting a multiplexer.
| Situation | Practical approach |
|---|---|
| Two identical sensors, spare GPIO | Consider two physical I²C buses |
| Three to eight identical fixed-address sensors | One TCA9548A with one sensor per channel |
| Two BME280s, one at 0x76 and one at 0x77 | Same ordinary I²C bus; no mux needed |
| Multiple different-address sensors | Share one I²C bus unless wiring/electrical requirements say otherwise |
| Many relay outputs or contact inputs | Use I²C GPIO expander, not a mux |
The mux also adds an extra channel-select transaction, so it is not an upgrade when two sensors already coexist correctly at different addresses.
Typical ESPHome and Home Assistant Faults
| Symptom | Likely cause / first check |
|---|---|
| Only 0x70 appears in upstream scan | Normal after power-up; downstream channels are initially deselected |
| No 0x70/0x71–0x77 at all | Mux power, SDA/SCL reversed, bad upstream pull-ups or wrong address jumpers |
| ESPHome compiles but second sensor is unavailable | Wrong i2c_id / channel number or missing downstream power |
| Two sensors show identical values | Both connected to same channel, copied IDs/names, or you are reading the same physical device |
| Device at 0x38 detected but AHT20 read fails | Check variant: AHT20, supply and branch signal quality |
| Works at 100 kHz but not 400 kHz | Cable capacitance, pull-up strength, edge rise time or slow target |
| Bus locks when one channel is selected | Faulty target or stuck SDA/SCL; deselect/reset and inspect that branch |
| One channel works, the next does not | Swapped SDn/SCn labels or missing pull-ups/power on one branch |
| ESP32 resets when sensors power up | Weak regulator, wiring short or peak-current issue; mux does not switch power |
| Random 5 V signal on ESP32 GPIO | Wrong upstream pull-up rail or incompatible sensor breakout |
Practical Multi-Room Design
For a three-room Home Assistant hub, put one AHT20 on each of channels 0, 1 and 2. Give them meaningful entity names, locate the sensing elements away from the warm ESP32/regulator and label the actual cables with their channel numbers. A mux solves address conflicts; it cannot solve room-temperature errors created by a sealed enclosure.
If the rooms are far apart, a single central ESP32 with many long analogue/I²C cables is often less robust than several nearby ESPHome nodes. Use the TCA9548A for a compact local cluster such as several probes in one enclosure, plant cabinet or ventilation unit, rather than assuming it can replace proper fieldbus wiring across a house.
ESPHome sends the resulting temperature/humidity entities to Home Assistant through its Native API. No MQTT broker or special Home Assistant multiplexer integration is needed.
Related esp32.co.uk/ Guides
- AHT20 with ESP32 and ESPHome — wiring, fixed address and calibration for the sensor used in this guide.
- BMP280 vs BME280 — two address options and distinguishing the two sensors.
- ESP32 GPIO Expanders: MCP23017 vs PCF8574 vs PCF8575 — use an expander when you need more digital I/O rather than I²C address isolation.
- ESP32 Multi-Sensor Node for Home Assistant — how multiple different sensors share one controller.
- ESP32 Weather Station with ESPHome — an application for multiple environmental sensors.
Official Documentation and Wiring References
- ESPHome TCA9548A component — channels, virtual
bus_idand sensori2c_idsyntax. - ESPHome I²C bus — multiple bus configuration and diagnostics.
- Texas Instruments TCA9548A product and datasheet — reset, address pins, pull-ups and electrical limits.
- Adafruit TCA9548A Arduino wiring/test — selecting a channel with
Wire.write(1 << channel).