Skip to content

PCA9555 and PCAL9555A

What the two parts have in common, what only the PCAL9555A adds, and how the driver tells them apart.

The PCAL9555A is a pin-to-pin and software-compatible successor of the PCA9555: it has the PCA9555's eight registers at the same command bytes, and a second bank of Agile I/O registers at 0x40 to 0x4F. Code written for the PCA9555 runs unchanged on a PCAL9555A. Two power-up differences matter: the PCAL9555A starts with every interrupt masked, and with 100 kΩ pull-ups enabled.

Features by part

Feature PCA9555 PCAL9555A Driver calls
Direction, output, input, polarity inversion yes yes SetPinDirection(), WritePin(), ReadPin(), SetPinPolarity() and their mask / list forms
INT output on input change yes, every input yes, unmasked inputs HandleInterrupt(), RegisterPinInterrupt()
Interrupt mask per pin yes ConfigureInterrupt(), ConfigureInterrupts(), ConfigureInterruptMask()
Interrupt source register yes GetInterruptStatus()
Pull-up or pull-down per pin (100 kΩ typ.) fixed weak pull-up yes SetPullEnable(), SetPullDirection(), GetPullConfiguration() and list forms
Output drive strength, 4 levels per pin yes SetDriveStrength(), SetDriveStrengths()
Input latch per pin yes EnableInputLatch(), EnableMultipleInputLatches(), EnableInputLatches()
Push-pull or open drain per port push-pull yes SetOutputMode()
Supply range 2.3 V to 5.5 V 1.65 V to 5.5 V

On a PCA9555 every call in the PCAL9555A-only rows returns false (or 0 for GetInterruptStatus()), sets Error::UnsupportedFeature and sends nothing on the bus.

Register map

Command Register Default Part
0x00, 0x01 INPUT_PORT_0, INPUT_PORT_1 (read only) pins both
0x02, 0x03 OUTPUT_PORT_0, OUTPUT_PORT_1 0xFF both
0x04, 0x05 POLARITY_INV_0, POLARITY_INV_1 0x00 both
0x06, 0x07 CONFIG_PORT_0, CONFIG_PORT_1 (1 = input) 0xFF both
0x40 to 0x43 DRIVE_STRENGTH_0 to _3 0xFF PCAL9555A
0x44, 0x45 INPUT_LATCH_0, INPUT_LATCH_1 0x00 PCAL9555A
0x46, 0x47 PULL_ENABLE_0, PULL_ENABLE_1 0xFF PCAL9555A
0x48, 0x49 PULL_SELECT_0, PULL_SELECT_1 (1 = pull-up) 0xFF PCAL9555A
0x4A, 0x4B INT_MASK_0, INT_MASK_1 (1 = masked) 0xFF PCAL9555A
0x4C, 0x4D INT_STATUS_0, INT_STATUS_1 (read only) 0x00 PCAL9555A
0x4F OUTPUT_CONF (ODEN1, ODEN0) 0x00 PCAL9555A

The names are the Pcal95555Reg enumerators. The drive-strength registers hold four pins each: 0x40 pins 0-3, 0x41 pins 4-7, 0x42 pins 8-11, 0x43 pins 12-15, two bits per pin from bit 0 up (00 = 0.25, 11 = full).

How the driver detects the part

flowchart TD
    A[Read INPUT_PORT_0] -->|NACK| U[EnsureInitialized fails: bus or address problem]
    A -->|ACK| B[Read OUTPUT_CONF 0x4F, no retries]
    B -->|ACK| P[PCAL9555A]
    B -->|NACK| C[Read INPUT_PORT_0 again]
    C -->|ACK| S[PCA9555]
    C -->|NACK| U

The detection relies on a PCA9555 not acknowledging command byte 0x4F. The PCA9555 datasheet defines only commands 0x00 to 0x07 and does not say how the chip answers others, so treat the result as a well-tested heuristic, not a guarantee. If your board fixes the part, pass the variant to the constructor and the probe is skipped:

// Skip auto-detection when the board fixes the part.
void make_drivers(MyI2c& bus) {
  using pcal95555::ChipVariant;
  Gpio auto_detect(&bus, uint8_t{0x20});                   // probes 0x4F once
  Gpio known_pcal(&bus, uint8_t{0x21}, ChipVariant::PCAL9555A);
  Gpio known_pca(&bus, true, true, false, ChipVariant::PCA9555);  // 0x23
  (void)auto_detect;
  (void)known_pcal;
  (void)known_pca;
}

Inconclusive detection

When the second read of Input Port 0 also fails, the driver cannot tell the parts apart: EnsureInitialized() (and ChangeAddress()) return false with I2CReadFail set and the variant stays Unknown. The next call probes again. Fix the bus (Troubleshooting) or pass the variant explicitly.

Porting PCA9555 code to a PCAL9555A

  • Interrupts: unmask the pins you want with ConfigureInterrupt(pin, InterruptState::Enabled). A PCA9555 needs nothing; a PCAL9555A raises no interrupt until you do.
  • Pull-ups: the PCAL9555A enables a 100 kΩ pull-up on every pin at power-up. Disable it (SetPullEnable(pin, false)) on pins with an external pull-down or an analogue load.
  • Nothing else changes: same address range, same pins, same eight registers.

Next: Interrupts.