Module pca9539::pins

source ·
Expand description

Individual GPIO pins

This crate fully implements the digital::v2 traits of embedded_hal.

Due to the I2C overhead, this module offers two options for state management:

  • Regular access mode: The state is synchronously updated when calling state functions like is_high(), causing 1:1 I2C operations for each individual call.
  • Refresh access mode: Register states are internally cached. Functions like is_high() are just using the cached state. The state is updated explicitly, but for all pins at once. In the best case, the I2C overhead is reduced to one eighth. See below examples for more details.


Individual pins can be fetched using PCA9539 instance. Different concurrency models are supported, see Concurrency section for more details.

use pca9539::example::DummyI2CBus;
use pca9539::expander::Bank::Bank0;
use pca9539::expander::PCA9539;
use pca9539::expander::PinID::Pin1;

let i2c_bus = DummyI2CBus::default();
let mut  expander = PCA9539::new(i2c_bus, 0x74);
let pins = expander.pins();

State management modes

Regular access mode

The following examples demonstrate using the synchronous regular access mode. Regular access mode is used when calling get_pin() method.

let pins = expander.pins();
let pin12 = pins.get_pin(Bank1, Pin2);
let mut  pin04 = pins.get_pin(Bank0, Pin4).into_output_pin(PinState::Low).unwrap();

// Fetching input state of Pin12
let is_high = pin12.is_high().unwrap();

// Setting Pin04 to high output state

Refreshable access mode

The following examples demonstrate using the refreshable access mode. Regular access mode is used when calling get_refreshable_pin() method.

In contrast to the previous method, the state must be explicitly updated/refreshed here. It does not matter which pin is used to call update/refresh. The state is always updated for all pins or pins of the same bank.

As is_high() and is_low() are just acting on cached state, calls of this method can not fail.

Input example
let pins = expander.pins();
let pin00 = pins.get_refreshable_pin(Bank0, Pin0);
let pin10 = pins.get_refreshable_pin(Bank1, Pin0);
let pin11 = pins.get_refreshable_pin(Bank1, Pin1);

// Updates the input state of just Bank1. So input state of Pin10 and Pin11 is now up2date

// Updates the input state of all banks. So all pins are now up2date
Output example
let pins = expander.pins();
let mut pin00 = pins.get_refreshable_pin(Bank0, Pin0).into_output_pin(PinState::Low).unwrap();
let mut pin10 = pins.get_refreshable_pin(Bank1, Pin0).into_output_pin(PinState::Low).unwrap();
let mut pin11 = pins.get_refreshable_pin(Bank1, Pin1).into_output_pin(PinState::Low).unwrap();


// Writes the output state of just Bank1.

// Writes the output state of all banks.


As the pins are using a shared reference, some kind of concurrency management is required. This crate currently offers three different concurrency guards. Which one should be used, depends on the application type:


Returns a pins container without using any locks This is the most efficient way of using individual pins The downside is, that these pins are neither Send or Sync, so can only be used in single-threaded and interrupt-free applications

let pins = expander.pins();

CS Mutex (Cortex-M)

Returns a pins container using Mutex based on critical sections Individual pins can be used across threads and interrupts, as long just running on a single core

Requires activation of cortex-m feature

let pins = expander.pins_cs_mutex();

Spin Mutex

Returns a pins container using a spin mutex This is safe to use across threads and on multi-core applications However, this requires a system supporting spin mutexes, which are generally only available on systems with Atomic CAS

Requires activation of spin feature

let pins = expander.pins_spin_mutex();


  • Input mode
  • Output mode
  • Individual GPIO pin
  • Container for fetching individual pins
  • Working on cached register state. State of all pins is refreshed explicitly.
  • State of the pin is synchronously fetched from I2C bus