Skip to main content
Kinematrix provides three I2C-related modules for device scanning, bus expansion, and I/O expansion capabilities.

Available Modules

I2C Scanner

Detect and identify I2C devices on the bus

I2C Expander

TCA9548A multiplexer for multiple I2C buses

I/O Expander

PCF8574 chip for GPIO expansion

I2C Scanner

The I2C Scanner module detects all connected I2C devices and reports their addresses.

Module Definition

lib/modules/communication/wired/i2c/i2c-scanner.h
class I2CScanner {
public:
    I2CScanner();
    virtual ~I2CScanner();

    void beginTransmission();
    void scanI2CAddress();
    void endTransmission();
private:
    int n_address_[8];
    int n_devices_;
};

Basic Usage

#define ENABLE_MODULE_I2C_SCANNER
#include "Kinematrix.h"

I2CScanner scanner;

void setup() {
  Serial.begin(115200);
  
  // Start scanning process
  scanner.beginTransmission();
  scanner.scanI2CAddress();
  scanner.endTransmission();
}

void loop() {
  // Scanning is typically done once in setup
}

Output Example

==============================
| START SCANNING I2C
==============================
| WIRE BEGIN
| Scanning...
| Done Scanning
| I2C devices found at address : 
| 0x0027
| 0x003C
| Total devices found          : | 2
| WIRE END

Common I2C Addresses

Device TypeTypical Address(es)Notes
PCF8574 I/O Expander0x20-0x27, 0x38-0x3FConfigurable via A0-A2 pins
LCD with I2C0x27, 0x3FCommon LCD backpack addresses
OLED SSD13060x3C, 0x3D128x64 displays
BMP280 Pressure0x76, 0x77Barometric pressure sensor
BME2800x76, 0x77Temperature/humidity/pressure
MPU6050 IMU0x68, 0x69Accelerometer/gyroscope
ADS1115 ADC0x48-0x4B16-bit analog-to-digital
TCA9548A Multiplexer0x70-0x77I2C bus expander
DS3231 RTC0x68Real-time clock
AT24C EEPROM0x50-0x57External EEPROM

Error Detection

The scanner detects and reports unknown errors:
void I2CScanner::scanI2CAddress() {
    byte error;
    for (address = 1; address < 127; address++) {
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
        
        if (error == 0) {
            // Device found
            n_address_[n_devices_] = address;
            n_devices_++;
        } else if (error == 4) {
            Serial.print("Unknown error at address 0x");
            Serial.println(address, HEX);
        }
    }
}

I2C Expander (TCA9548A)

The I2C Expander module provides access to the TCA9548A I2C multiplexer, allowing up to 8 separate I2C buses from a single master.

Module Definition

lib/modules/communication/wired/i2c/i2c-expander.h
#include "TCA9548A.h"

class I2CExpander : public TCA9548A {
private:
    uint8_t addr;
public:
    using TCA9548A::TCA9548A;
};

Why Use an I2C Expander?

Multiple devices with the same I2C address can coexist on different channels:
// Two OLED displays at 0x3C
expander.selectChannel(0);
oled1.display();  // First OLED

expander.selectChannel(1);
oled2.display();  // Second OLED
Reduce total bus capacitance by splitting devices across multiple channels, improving signal integrity and allowing longer cable runs.
Isolate problematic devices that may hang the bus, allowing the rest of the system to continue operating.

Basic Usage

#define ENABLE_MODULE_I2C_EXPANDER
#include "Kinematrix.h"

I2CExpander expander(0x70);  // TCA9548A default address

void setup() {
  Wire.begin();
  
  // Select channel 0
  expander.selectChannel(0);
  
  // Initialize device on channel 0
  sensor1.begin();
  
  // Select channel 1
  expander.selectChannel(1);
  
  // Initialize device on channel 1
  sensor2.begin();
}

void loop() {
  // Read from channel 0
  expander.selectChannel(0);
  float value1 = sensor1.read();
  
  // Read from channel 1
  expander.selectChannel(1);
  float value2 = sensor2.read();
  
  delay(1000);
}

Advanced Channel Management

// Enable multiple channels simultaneously (bitwise OR)
expander.enableChannels(0b00000011);  // Channels 0 and 1

// Disable all channels
expander.disableAllChannels();

// Check which channels are active
uint8_t activeChannels = expander.getActiveChannels();

TCA9548A Specifications

ParameterValueNotes
Supply Voltage1.65V - 5.5VCompatible with 3.3V and 5V systems
Number of Channels8SC0-SC7
I2C Address Range0x70-0x77Configurable via A0-A2 pins
Max Frequency400 kHzFast mode I2C
Channel SelectionRegister-basedWrite to control register

I/O Expander (PCF8574)

The I/O Expander module provides access to the PCF8574 8-bit I/O expander chip.

Module Definition

lib/modules/communication/wired/i2c/io-expander.h
#include "PCF8574.h"

class IOExpander : public PCF8574 {
private:
    uint8_t addr;
public:
    using PCF8574::PCF8574;
};

Basic GPIO Control

#define ENABLE_MODULE_IO_EXPANDER
#include "Kinematrix.h"

IOExpander expander(0x20);  // PCF8574 address

void setup() {
  expander.begin();
  
  // Set pins 0-3 as outputs, 4-7 as inputs
  expander.pinMode(0, OUTPUT);
  expander.pinMode(1, OUTPUT);
  expander.pinMode(2, OUTPUT);
  expander.pinMode(3, OUTPUT);
  expander.pinMode(4, INPUT);
  expander.pinMode(5, INPUT);
  expander.pinMode(6, INPUT);
  expander.pinMode(7, INPUT);
}

void loop() {
  // Control outputs
  expander.digitalWrite(0, HIGH);
  expander.digitalWrite(1, LOW);
  
  // Read inputs
  bool button1 = expander.digitalRead(4);
  bool button2 = expander.digitalRead(5);
  
  delay(100);
}

LED Control Example

IOExpander leds(0x20);

void setup() {
  leds.begin();
  
  // Set all pins as outputs
  for (int i = 0; i < 8; i++) {
    leds.pinMode(i, OUTPUT);
  }
}

void loop() {
  // LED chaser effect
  for (int i = 0; i < 8; i++) {
    leds.digitalWrite(i, HIGH);
    delay(100);
    leds.digitalWrite(i, LOW);
  }
}

Button Matrix Example

IOExpander buttons(0x21);

void setup() {
  Serial.begin(115200);
  buttons.begin();
  
  // Set all pins as inputs with pull-ups
  for (int i = 0; i < 8; i++) {
    buttons.pinMode(i, INPUT_PULLUP);
  }
}

void loop() {
  // Read all button states
  uint8_t buttonStates = buttons.read8();
  
  // Check individual buttons (active LOW)
  for (int i = 0; i < 8; i++) {
    if (!(buttonStates & (1 << i))) {
      Serial.print("Button ");
      Serial.print(i);
      Serial.println(" pressed");
    }
  }
  
  delay(50);  // Debounce delay
}

PCF8574 Specifications

ParameterValueNotes
Supply Voltage2.5V - 6VTypically 5V
I/O Pins8P0-P7
Current per Pin25 mAMaximum sink/source
I2C Address (PCF8574)0x20-0x27A0-A2 configurable
I2C Address (PCF8574A)0x38-0x3FDifferent variant
Interrupt SupportYesINT pin goes LOW on input change

Multiple Expander Configuration

Combine multiple I/O expanders for large systems:
#define ENABLE_MODULE_IO_EXPANDER
#include "Kinematrix.h"

IOExpander outputs1(0x20);  // 8 outputs
IOExpander outputs2(0x21);  // 8 more outputs
IOExpander inputs(0x22);    // 8 inputs

void setup() {
  outputs1.begin();
  outputs2.begin();
  inputs.begin();
  
  // Configure first 16 pins as outputs
  for (int i = 0; i < 8; i++) {
    outputs1.pinMode(i, OUTPUT);
    outputs2.pinMode(i, OUTPUT);
  }
  
  // Configure 8 pins as inputs
  for (int i = 0; i < 8; i++) {
    inputs.pinMode(i, INPUT_PULLUP);
  }
}

void loop() {
  // Control 16 LEDs
  uint8_t inputState = inputs.read8();
  outputs1.write8(inputState);
  outputs2.write8(~inputState);
  delay(10);
}

I2C Best Practices

Pull-up Resistors: I2C requires pull-up resistors on SDA and SCL lines. Typical values are 4.7kΩ for short distances or 2.2kΩ for longer cables.
Bus Speed: Start with 100kHz (standard mode) for debugging, then increase to 400kHz (fast mode) for better performance once the system is stable.
Address Conflicts: Always scan the I2C bus before adding new devices to avoid address conflicts.

Common I2C Issues

Possible causes:
  • Missing or incorrect pull-up resistors
  • Incorrect wiring (SDA/SCL swapped)
  • Power supply issues
  • Wrong I2C pins on ESP32 (use Wire.begin(SDA, SCL))
Solution:
// ESP32: Specify I2C pins
Wire.begin(21, 22);  // SDA=21, SCL=22

// Check pull-ups with multimeter
// SDA and SCL should read VCC when idle
Possible causes:
  • Bus capacitance too high (long wires)
  • Insufficient pull-up current
  • Electrical noise
Solution:
  • Use stronger pull-ups (2.2kΩ instead of 4.7kΩ)
  • Reduce wire length
  • Add capacitors near devices (100nF)
  • Shield cables for long runs
Possible causes:
  • Device holding SDA low
  • Power glitch during communication
Solution:
// Software I2C reset
Wire.end();
delay(100);
Wire.begin();

// Or clock out stuck device
pinMode(SDA_PIN, OUTPUT);
for (int i = 0; i < 9; i++) {
  digitalWrite(SCL_PIN, HIGH);
  delayMicroseconds(5);
  digitalWrite(SCL_PIN, LOW);
  delayMicroseconds(5);
}
Wire.begin();

Platform-Specific Notes

// Default pins: SDA=21, SCL=22
Wire.begin();

// Custom pins
Wire.begin(SDA_PIN, SCL_PIN);

// Clock speed
Wire.setClock(400000);  // 400kHz

Serial Communication

Alternative communication protocols

SPI Interface

Higher-speed serial communication

Build docs developers (and LLMs) love