TCA9548A I2C Multiplexer with ESP32: Multiple Sensors at the Same Address

Connect eight I2C channels to an ESP32 with TCA9548A. Full ESPHome YAML for identical-address AHT20 sensors, Arduino channel scanning, 0x70–0x77 addresses, pull-ups, reset and troubleshooting.

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.

FeaturePractical meaning
Eight downstream channels0–7, each with its own SDA/SCL pair
Default mux address0x70
Selectable mux addresses0x70–0x77 via A0, A1 and A2
Supported chip supply1.65–5.5 V; ESP32 breakout normally at 3.3 V
I²C speedUp to 400 kHz, assuming all connected devices and wiring support it
Channel controlOne-byte bit mask written to the mux address
ResetActive-low RESET deselects channels
Power switchingNo: 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 breakoutClassic ESP32 DevKitPurpose
VIN / VCC3V3Power multiplexer at the ESP32 logic voltage
GNDGNDCommon electrical reference
SDA (upstream)GPIO21Master data line
SCL (upstream)GPIO22Master clock line
RST / RESETLeave pulled high on a breakout, or control with optional GPIOActive-low reset
A0 / A1 / A2Normally left low for 0x70Select 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 #1TCA9548APower
SDASD0Not supplied by the SD0 line
SCLSC0Not supplied by the SC0 line
VCC3.3 V supplyConnect sensor power separately
GNDCommon GNDShared 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.

A2A1A0Mux address
0000x70
0010x71
0100x72
0110x73
1000x74
1010x75
1100x76
1110x77

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 100kHz and 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.

SituationPractical approach
Two identical sensors, spare GPIOConsider two physical I²C buses
Three to eight identical fixed-address sensorsOne TCA9548A with one sensor per channel
Two BME280s, one at 0x76 and one at 0x77Same ordinary I²C bus; no mux needed
Multiple different-address sensorsShare one I²C bus unless wiring/electrical requirements say otherwise
Many relay outputs or contact inputsUse 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

SymptomLikely cause / first check
Only 0x70 appears in upstream scanNormal after power-up; downstream channels are initially deselected
No 0x70/0x71–0x77 at allMux power, SDA/SCL reversed, bad upstream pull-ups or wrong address jumpers
ESPHome compiles but second sensor is unavailableWrong i2c_id / channel number or missing downstream power
Two sensors show identical valuesBoth connected to same channel, copied IDs/names, or you are reading the same physical device
Device at 0x38 detected but AHT20 read failsCheck variant: AHT20, supply and branch signal quality
Works at 100 kHz but not 400 kHzCable capacitance, pull-up strength, edge rise time or slow target
Bus locks when one channel is selectedFaulty target or stuck SDA/SCL; deselect/reset and inspect that branch
One channel works, the next does notSwapped SDn/SCn labels or missing pull-ups/power on one branch
ESP32 resets when sensors power upWeak regulator, wiring short or peak-current issue; mux does not switch power
Random 5 V signal on ESP32 GPIOWrong 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

Official Documentation and Wiring References

Share your love