#embedded-hal #embedded-hal-impl #i2c #spi #bus

no-std shared-bus

Abstraction for sharing a bus between multiple devices

14 releases

0.3.1 Oct 31, 2023
0.2.5 Nov 22, 2022
0.2.4 Jun 16, 2022
0.2.3 Mar 5, 2022
0.1.4 Nov 4, 2018

#26 in Embedded development

Download history 1577/week @ 2023-08-16 1372/week @ 2023-08-23 895/week @ 2023-08-30 1203/week @ 2023-09-06 1320/week @ 2023-09-13 1006/week @ 2023-09-20 1161/week @ 2023-09-27 1423/week @ 2023-10-04 1575/week @ 2023-10-11 1415/week @ 2023-10-18 1841/week @ 2023-10-25 1948/week @ 2023-11-01 2009/week @ 2023-11-08 2143/week @ 2023-11-15 1345/week @ 2023-11-22 1725/week @ 2023-11-29

7,470 downloads per month
Used in 28 crates (25 directly)


403 lines

shared-bus crates.io page docs.rs Continuous Integration

shared-bus is a crate to allow sharing bus peripherals safely between multiple devices.

In the embedded-hal ecosystem, it is convention for drivers to "own" the bus peripheral they are operating on. This implies that only one driver can have access to a certain bus. That, of course, poses an issue when multiple devices are connected to a single bus.

shared-bus solves this by giving each driver a bus-proxy to own which internally manages access to the actual bus in a safe manner. For a more in-depth introduction of the problem this crate is trying to solve, take a look at the blog post.

There are different 'bus managers' for different use-cases:

Sharing within a single task/thread

As long as all users of a bus are contained in a single task/thread, bus sharing is very simple. With no concurrency possible, no special synchronization is needed. This is where a BusManagerSimple should be used:

// For example:
let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1);

let bus = shared_bus::BusManagerSimple::new(i2c);

let mut proxy1 = bus.acquire_i2c();
let mut my_device = MyDevice::new(bus.acquire_i2c());

proxy1.write(0x39, &[0xc0, 0xff, 0xee]);

The BusManager::acquire_*() methods can be called as often as needed; each call will yield a new bus-proxy of the requested type.

Sharing across multiple tasks/threads

For sharing across multiple tasks/threads, synchronization is needed to ensure all bus-accesses are strictly serialized and can't race against each other. The synchronization is handled by a platform-specific BusMutex implementation. shared-bus already contains some implementations for common targets. For each one, there is also a macro for easily creating a bus-manager with 'static lifetime, which is almost always a requirement when sharing across task/thread boundaries. As an example:

// For example:
let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1);

// The bus is a 'static reference -> it lives forever and references can be
// shared with other threads.
let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap();

let mut proxy1 = bus.acquire_i2c();
let mut my_device = MyDevice::new(bus.acquire_i2c());

// We can easily move a proxy to another thread:
std::thread::spawn(move || {

Those platform-specific bits are guarded by a feature that needs to be enabled. Here is an overview of what's already available:

Mutex Bus Manager 'static Bus Macro Feature Name
std::sync::Mutex BusManagerStd new_std!() std
cortex_m::interrupt::Mutex BusManagerCortexM new_cortexm!() cortex-m
shared_bus::XtensaMutex (spin::Mutex in critical section) BusManagerXtensa  new_xtensa!() xtensa
NA BusManagerAtomicCheck new_atomic_check!() cortex-m

Supported Busses

Currently, the following busses can be shared with shared-bus:

Bus Proxy Type Acquire Method Comments
I2C I2cProxy .acquire_i2c()
SPI SpiProxy .acquire_spi() SPI can only be shared within a single task (See SpiProxy for details).
ADC AdcProxy .acquire_adc()


shared-bus is licensed under either of

at your option.