Exercise B: GPIO over I2C
~25 min
Goal
Use the TCA6424A I/O expander on the uFerris board to control GPIO pins over I2C. This replicates what you did with direct GPIO — but through an I2C device.
Background
The TCA6424A is a 24-bit I/O expander connected via I2C. It provides three banks (ports) of 8 GPIO pins each (P0, P1, P2), giving you 24 additional GPIO pins over just two I2C wires. Its address should have appeared when you scanned the bus in Exercise A.
How I2C Communication Works with the TCA6424A
To interact with the I/O expander, communication happens in two cycles:
- First cycle (write): You send the register address you want to access — this tells the device which internal register you want to read from or write to.
- Second cycle (read or write): You either write a value to that register (to configure or control a pin) or read back the current value.
For a write operation, you send the register address followed by the data byte in the same I2C write transaction.
For a read operation, you first write the register address, then perform a separate I2C read to get the data back.
TCA6424A Register Map
| Register | Port 0 | Port 1 | Port 2 | Purpose |
|---|---|---|---|---|
| Input | 0x00 | 0x01 | 0x02 | Read pin levels |
| Output | 0x04 | 0x05 | 0x06 | Set output pin levels |
| Configuration | 0x0C | 0x0D | 0x0E | Set pin direction: 0 = output, 1 = input (default) |
Steps
1. Create a New Project
esp-generate --chip esp32c3 -o unstable-hal -o vscode -o esp-backtrace -o log --headless i2c_expander
cd i2c_expander
2. Configure Pin Direction
To use a pin as output, you need to write to the Configuration register for the appropriate port. Set the corresponding bit to 0 for output. Find the I2C abstraction in esp-hal that lets you write to a device at a specific address.
3. Set a Pin High
To turn on an LED, write to the Output register for the appropriate port. Set the corresponding bit to 1 for high. Use the same I2C write approach.
4. Blink an LED
Combine the above in a loop to blink an LED connected to the I/O expander:
- Configure the port direction as output (once at startup)
- In a loop: set the pin high, delay, set the pin low, delay
5. Build and Flash
cargo build --release
espflash flash target/riscv32imc-unknown-none-elf/release/i2c-expander --monitor
What to Notice
- Compare the complexity of GPIO-over-I2C vs direct GPIO — what's the tradeoff?