You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
351: SPI: split into device/bus, allows sharing buses with automatic CS r=therealprof a=Dirbaio
Because why not?
This separates the concepts of "SPI bus" and "SPI device". I got the idea of having traits for SPI devices from #299. It has an excellent breakdown of the issues of the status quo, so go read that first.
The difference from #299 is that proposes "repurposing" the existing traits to represent devices, while this PR proposes specifying the existing traits represent buses, and introduces a new trait for representing devices on top of that.
- HALs impl the bus traits, just like now. No extra boilerplate.
- Crates like `shared-bus` add the "bus mutexing" logic, to impl `SpiDevice` on top of `SpiBus` (+ `OutputPin` for CS). There's a wide variety of possible impls, all can satisfy the `SpiDevice` requirements:
- Exclusive single-device, that just owns the entire bus
- Single-thread, on top of `RefCell`
- Multithreaded std, using std `Mutex`
- Multithreaded embedded, using some `AtomicBool` taken flag.
- Async embedded, using an async mutex (this'd require an async version of SpiDevice).
- Drivers take `T: SpiDevice` (or `SpiDeviceRead`, `SpiDeviceWrite`), then they start a `.transaction()`, then use it to read/write/transfer data. The transaction ends automatically when returning. Example:
```rust
fn read_register(&mut self, addr: u8) -> Result<u8, Error> {
let mut buf = [0; 1];
self.spi.transaction(|bus| {
bus.write(&[addr])?;
bus.read(&mut buf)?;
})?;
Ok(buf[0])
}
```
## No Transactional needed
Previous proposals boiled down to: for each method call (read/write/transfer), it automatically assets CS before, deasserts it after. This means two `write`s are really two transactions, which might not be what you want. To counter this, `Transactional` was added, so that you can do multiple operations in a single CS assert/deassert.
With this proposal, you have an explicit "Transaction" object, so you can easily do multiple reads/writes on it in a single transaction. This means `Transactional` is no longer needed.
The result is more flexible than `Transactional`, even. Take an imaginary device that responds to "read" operations with a status code, and the data only arrives on success. With this proposal you can easily read data then take conditional action. With `Transactional` there's no way, best you can do is read the data unconditionally, then discard it on error.
It allows for smaller memory usage as well. With `Transactional` you had to allocate buffer space for all operations upfront, with this you can do them as you go.
```rust
fn read_data(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Error> {
self.spi.transaction(|bus| {
bus.write(&[addr])?;
// Read status code, 0x00 indicates success
let mut buf = [0; 1];
bus.read(&mut buf)?;
if buf[0] != 0x00 {
return Err(Error::ErrorCode(buf[0]));
}
// If the operation is successful, actually read the data, still in the same transaction
bus.read(dest)
})
}
```
Co-authored-by: Dario Nieuwenhuis <[email protected]>
0 commit comments