Crate max7301[][src]

Driver library for the Maxim MAX7301 serial I/O expander.

The MAX7301 is a device that provides either 20 or 28 GPIO pins, which are software-configurable as push-pull output, floating input, or input with weak pull-up. The state of each pin can be read and written through an SPI serial bus.

This driver is intended to work on embedded platforms using any implementation of the embedded-hal trait library. It communicates with the expander via any SPI and GPIO device implementing the respective traits, and permits creation of new GPIO devices corresponding to the I/O pins on the device, which themselves implement the HAL traits.


To set up the driver:

  • Use your platform’s embedded-hal implementation to obtain the necessary I/Os where your MAX7301 is connected. For the SPI version (currently the only supported version), you will need an SPI master device, and one GPIO push-pull output pin device for chip select.
  • Construct an ExpanderInterface — the SpiInterface for MAX7301 — which will take ownership of the I/O devices you just obtained.
  • Construct an Expander, which will take ownership of the ExpanderInterface, and which will provide a builder API to configure the device.
let spi = /* construct something implementing embedded_hal::spi::blocking::{Write,Transfer} */
let cs = /* construct something implementing embedded_hal::digital::OutputPin */

let ei = max7301::SpiInterface::new(spi, cs);
let mut expander = max7301::Expander::new(ei);

Device configuration

See Expander::configure and [expander::Configurator].

The configure method will produce a builder that you can use to set up the chip’s configuration registers:

    .ports(4..=31, max7301::PortMode::InputPullup)
    .port(7, max7301::PortMode::Output)

Raw mode

See Expander.

Now with a configured device, you may use it in raw mode, accessing the device’s ports directly:

let four_thru_twelve: u8 = expander.read_ports(4)?;
expander.write_port(7, false)?;

HAL modes

To compose the driver with other embedded-hal drivers that are compatible with embedded_hal::digital::{InputPin,OutputPin}, you can convert the Expander into an I/O adapter that will produce ownable PortPin instances for each GPIO port on the expander. PortPin implments the InputPin and OutputPin traits from embedded-hal, allowing composition with other drivers that need GPIO pins.

You can choose one of two interfaces to construct PortPins:

  • The immediate mode interface, where calling the GPIO trait methods on any PortPin immediately generates a bus transaction to the expander in order to perform the operation on that pin; or
  • The transactional interface, which allows you to control the bus traffic separately from the activity on the PortPins, enabling batching of reads and writes to amortize and/or reduce bus traffic and latency.

Immediate mode

See Expander::into_immediate and ImmediateIO.

expander.configure().ports(4..=6, max7301::PortMode::Output).commit()?;
let imm_io = expander.into_immediate::<max7301::DefaultMutex<_>>();

let red_lamp = imm_io.port_pin(4);
let yellow_lamp = imm_io.port_pin(5);
let green_lamp = imm_io.port_pin(6);
let mut traffic_light = MyTrafficLight::new(red_lamp, yellow_lamp, green_lamp);


In this example, each time MyTrafficLight interacts with an OutputPin trait method, the driver will immediately trigger a bus transaction to set the appropriate level on the expander’s corresponding output pin. Likewise, if an InputPin trait method is called, the driver will perform a bus transaction to read the current state from the expander pin.

Transactional mode

See Expander::into_transactional and TransactionalIO.

    .ports(4..=6, max7301::PortMode::Output)
    .port(7, max7301::PortMode::InputFloating)
let txn_io = expander.into_transactional::<max7301::DefaultMutex<_>>();

let red_lamp = txn_io.port_pin(4);
let yellow_lamp = txn_io.port_pin(5);
let green_lamp = txn_io.port_pin(6);
let sensor_tripped = txn_io.port_pin(7);
let mut traffic_light =
    MyFancyTrafficLight::new(red_lamp, yellow_lamp, green_lamp, sensor_tripped);


In this example, the transactional API adds two extra methods on the I/O adapter: refresh() and write_back().

  • The refresh method instructs the driver to perform bus operations to load the current state of every port for which a PortPin exists into its internal write-back cache.
  • As MyTrafficLight interacts with the PortPin trait methods, they read and mutate the state of the ports in the write-back cache, without interacting with the hardware.
  • Finally, write_back is called to instruct the driver to perform bus operations to write any modified port states back to the expander. The single argument selects the strategy that will be used to batch the write operations into fewer write cycles on the bus (see expander::transactional::Strategy for a description).

Choosing a mode

  • Transactional mode is excellent for applications which use the MAX7301 to obtain a large number of independent GPIOs, for example HMI applications like keypads, switches, encoders, sensors, LEDs, and indicators. In such a case, the states of the GPIOs are often read or updated in a procedure that can be bracketed by refresh and write_back, since the order in which the states are read from or written out to the hardware is not important.
  • Transactional mode is not appropriate, and immediate mode should be used instead, for drivers or applications that use the generated GPIOs in a “bit-banged” manner, where pins must transition states with particular timings or orderings with respect to each other. In transactional mode, the write-back cache delays the hardware transitions until the next write_back call, erasing any sequencing or timing and breaking these use cases.

Mutual exclusion

The HAL adapters require you to provide a mutual exclusion primitive to arbitrate access to the hardware from multiple PortPins. For now, the adapters are parameterized over a type implementing the IOMutex trait, which is a concept borrowed from shared-bus, and which will hopefully be superseded by a standard embedded-hal trait.

In a std environment you may enable the std Cargo feature, and mutex::DefaultMutex<T> will be a type alias to std::sync::Mutex<T> with a provided impl of IOMutex. Similarly, for Cortex-M environments using the cortex-m crate, enabling the cortexm Cargo feature will alias mutex::DefaultMutex<T> to cortex_m::interrupt::Mutex<core::cell::RefCell<T>> with a provided IOMutex impl. This arrangement should allow you to just specify DefaultMutex as in the examples, and have the correct thing happen based on the build environment.


pub use config::PortMode;
pub use expander::immediate::ImmediateIO;
pub use expander::pin::ExpanderIO;
pub use expander::pin::PortPin;
pub use expander::transactional::Strategy;
pub use expander::transactional::TransactionalIO;
pub use expander::Expander;
pub use interface::spi::SpiInterface;
pub use interface::ExpanderInterface;
pub use mutex::DefaultMutex;
pub use mutex::IOMutex;



Abstractions used to configure the MAX7301 hardware.


The port expander device API. This provides the Expander type which is a direct abstraction of the MAX7301. It allows direct use of all operations available on the device.


This module provide shims for the embedded-hal hardware correspoding to the MAX7301’s supported electrical/bus interfaces. It is a shim between embedded-hal implementations and the expander’s registers.


Provides mutual exclusion for various environments.


The register addresses within the MAX7301.