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:
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:
```rust // 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.acquirei2c(); let mut mydevice = MyDevice::new(bus.acquire_i2c());
proxy1.write(0x39, &[0xc0, 0xff, 0xee]); mydevice.dosomethingonthe_bus(); ```
The BusManager::acquire_*()
methods can be called as often as needed; each call will yield
a new bus-proxy of the requested type.
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:
```rust // 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 _ = sharedbus::newstd!(SomeI2cBus = i2c).unwrap();
let mut proxy1 = bus.acquirei2c(); let mut mydevice = MyDevice::new(bus.acquire_i2c());
// We can easily move a proxy to another thread:
std::thread::spawn(move || { mydevice.dosomethingonthe_bus(); });
```
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
|
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.