Quickstart¶
Light an LED from a button through a PCA9555 or PCAL9555A: wire five signals, write a small I2C port class, call four driver functions.
1. Wire it¶
| MCU | PCA9555 / PCAL9555A | Notes |
|---|---|---|
| I2C SCL | SCL | Pull-up to the bus voltage (4.7 kΩ is a common value at 400 kHz) |
| I2C SDA | SDA | Pull-up as for SCL |
| GND | VSS | Common ground |
| 3.3 V | VDD | 1.65 V to 5.5 V (PCAL9555A) or 2.3 V to 5.5 V (PCA9555); 100 nF next to the pin |
| GND | A0, A1, A2 | Address 0x20; tie each pin to VDD or VSS, never leave it open |
| GPIO in (optional) | INT | Open drain, active low: pull it up |
For the example, connect an LED with a series resistor from P0_0 (pin 0) to VDD, so the expander sinks its current, and a push button from P1_0 (pin 8) to ground. The Hardware page has the pin numbers per package, the address table and the electrical limits.
2. Add the driver¶
Add the repository as a submodule and link the hf_pcal95555 CMake target (or add inc/ to your include path and
build with C++20). Installation covers CMake and ESP-IDF.
3. Write the port¶
The port is the only platform code. It brings the bus up, writes one register and reads one register. Replace the
platform_* calls with your SDK's.
class MyI2c : public pcal95555::I2cInterface<MyI2c> {
public:
// Bring the bus up once; the driver calls this before its first transfer.
bool EnsureInitialized() noexcept {
if (!ready_) {
ready_ = platform_i2c_init();
}
return ready_;
}
// One register write: [addr+W] [reg] [data...] STOP. Return false on a NACK.
bool Write(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) noexcept {
uint8_t frame[3] = {reg, 0, 0};
if (len > 2) {
return false; // the driver writes one register at a time
}
for (size_t i = 0; i < len; ++i) {
frame[1 + i] = data[i];
}
return platform_i2c_write(addr, frame, 1 + len);
}
// One register read: [addr+W] [reg] repeated START [addr+R] [data...] STOP.
bool Read(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) noexcept {
return platform_i2c_write_read(addr, ®, 1, data, len);
}
// Optional: keep the driver's interrupt handler. Without it, poll instead.
bool RegisterInterruptHandler(std::function<void()> handler) noexcept {
handler_ = std::move(handler);
return true;
}
// Call from a task, not from the ISR, after INT (open drain, active low) falls:
// the handler reads the chip over I2C.
void ServiceInterrupt() {
if (handler_) {
handler_();
}
}
private:
bool ready_ = false;
std::function<void()> handler_;
};
Porting explains each method, the optional ones and the ESP-IDF reference port.
4. Light the LED¶
#include "pcal95555.hpp"
int led_follows_button(MyI2c& bus) {
pcal95555::PCAL95555<MyI2c> gpio(&bus, false, false, false); // A2 A1 A0 low: 0x20
if (!gpio.EnsureInitialized()) { // 1. probe, detect the variant
return -1; // wiring, pull-ups or address
}
gpio.SetPinDirection(0, pcal95555::GPIODir::Output); // 2. P0_0 drives an LED
gpio.SetPinDirection(8, pcal95555::GPIODir::Input); // P1_0 reads a button
const bool pressed = !gpio.ReadPin(8); // 3. button pulls the pin low
if (!gpio.WritePin(0, pressed)) { // 4. LED on while pressed
return -2; // see GetErrorFlags()
}
return 0;
}
What each step does:
EnsureInitialized()brings the bus up through your port, reads Input Port 0 to prove the chip answers at 0x20, and probes register 0x4F to tell a PCAL9555A from a PCA9555. Every other call runs it for you on first use; calling it yourself gives you one place to handle a missing chip.SetPinDirection()writes one bit of the Configuration register:GPIODir::Outputis 0,GPIODir::Inputis 1. All pins are inputs after power-up.ReadPin()reads the Input Port register. The button pulls the pin low, so pressed readsfalse; a PCAL9555A's internal pull-up (on by default) or an external one holds it high otherwise.WritePin()reads the Output Port register, changes one bit and writes it back. Writingtruedrives the pin high, which turns this LED off;pressedtherefore lights it.
This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
EnsureInitialized() returns false |
SDA / SCL swapped, missing pull-ups, A2-A0 levels against the address you gave |
GetChipVariant() says PCA9555 on a PCAL9555A |
Read the part marking; a bus that does not report NACKs correctly (Troubleshooting) |
| The pin reads high with the button pressed | Wrong pin number: pins 8-15 are P1_0 to P1_7 |
| The button reads random values on a PCA9555 | The PCA9555 pull-up is weak (about 100 kΩ); add an external one if the line is long |
Next¶
- API guide: pull resistors, drive strength, interrupts, errors.
- Examples: the ESP32-S3 test suite and LED animation demo.
- PCA9555 and PCAL9555A: what each part has and how the driver tells them apart.