-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to maintain an SPI transaction while manipulating GPIO pins? #478
Comments
You can use SpiBus instead of SpiDevice, managing CS and DC pins manually. The closure design had some downsides (it's not implementable on Linux and some RTOSs, which require specifying all the operations upfront, allowing running arbitrary code prevented implementations that would enqueue the whole transaction and handle it from another task/thread, and the closure was troublesome for the async traits). It's a tradeoff: the "operation list" design fixes all these but in exchange doesn't allow supporting DC pins. After discussing the pros and cons, we decided to go for the "operation list" design. |
I'm having this same issue with SD cards and unfortunately I need to share the bus with an EPD display (See M5Paper). Passing the entire bus to the driver kinda sucks for sharing. Would you ever consider an additional trait that adds the closure design as well? |
So, is it by design? As @Dominaezzz pointed out, many affordable embedded devices with SPI LCD face the same issue. (I believe there are numerous ESP32 boards with a similar configuration.) When implementing BSPs for these devices, if sharing Would it be difficult to add a variant to the |
Yes, it's forced by design. :(
Rust doesn't like putting closures in data structures. It'd need something like How common is putting an LCD on a shared bus? LCDs are somewhat performance-sensitive things so I'd imagine bus sharing is avoided in the wild for these. It is still possible to implement bus sharing though, it requires manually using the bus with something custom only for the display. Something like this: pub struct RefCellDisplay<'a, BUS, PIN, D> {
bus: &'a RefCell<BUS>,
cs: PIN,
dc: PIN,
delay: D,
}
impl<'a, BUS, PIN, D> RefCellDisplay<'a, BUS, PIN, D> {
pub fn new(bus: &'a RefCell<BUS>, cs: PIN, dc: PIN, delay: D) -> Self {
Self { bus, cs, dc, delay }
}
fn transaction(&mut self, command: &[u8], data: &[u8]) -> Result<(), DeviceError> {
let bus = &mut *self.bus.borrow_mut();
self.dc.set_low().map_err(DeviceError::Pin)?;
self.cs.set_low().map_err(DeviceError::Pin)?;
bus.write(command).map_err(DeviceError::Spi)?;
bus.flush().map_err(DeviceError::Spi)?;
self.dc.set_high().map_err(DeviceError::Pin)?;
bus.write(data).map_err(DeviceError::Spi)?;
bus.flush().map_err(DeviceError::Spi)?;
self.cs.set_high().map_err(DeviceError::Pin)?;
Ok(())
}
} would allow creating on the same bus a |
we intentionally decided not to have two traits since it would lead to fragmentation. BTW, HALs are supposed to implement only SpiBus1, and leave SpiDevice to be implemented on top of SpiBus by crates such as So a driver crate for an SPI display could define its own trait, such as: trait DisplayDevice {
type Error: Debug;
fn transaction(&mut self, command: &[u8], data: &[u8]) -> Result<(), Self::Error>;
} and then provide builtin impls that take an owned Footnotes
|
Sure, it is still possible to achieve this at the end of the day but ideally it should be via a trait rather than a concrete implementation per driver. What if I have two devices on a bus that need the closure approach, both libraries may have bus sharing implementations that don't work with each other (leading to fragmentation) or may just not bother to do it at all since the advice you're currently giving to driver implementers is to simply take ownership of the bus.
I don't really understand what you mean by fragmentation in this case. Either drivers can support closures or they cannot. I can appreciate that v0.2's separate
Also, if "HALs are supposed to implement only SpiBus" then we don't have to worry about fragmentation, since crates like
While this sorta works, I still don't like the fact that users wanting to do something custom now have to know the Having said all this, I have no solution for the async side of things, as I barely understood the problem/complexity there. |
No, drivers would provide their own "bus interface trait" like the
This covers 99% of the use cases, and allows sharing the bus with either This "bus interface trait with DC pin" could be ina shared crate, even (
If we had both On the other hand, not having tldr it is good for interoperability to nudge everyone towards using
If the driver / display-interface crate provides all these builtin impls, "something custom" would only be when you're sharing bus with something that's not RefCell/Mutex/CriticalSection, which should be very very rare.
see this comment and next ones #347 (comment) tldr is it's not possible to make no-alloc async closures that borrow things in today's Rust. |
Oh wow, I was still under the impression that
The trait you proposed isn't in terms of SPI though, it's in terms of the display protocol, which should be abstracted away.
I see your point here about convenience but can't this be easily mitigated with There's also another side to the convenience argument; Without the |
It's somewhat common to do protocol-specific "bus interface traits" like these on drivers. The most common is drivers for chips that can be talked to through either SPI or I2C. The main driver is generic on some "interface" trait that has "read reg, write reg", and provides impls on top for SPI and I2C. In this case it'd be a similar driver-specific trait, that abstracts over how to do the sharing and manage the CS/DC pins. It'd even have other advantages, like it'd allow the user to supply a custom impl that uses hardware CS/DC pin support, for example the nrf52840 has it.
Yes, if we wanted My opinion is we don't want it due to
At least not for now. IMO we should launch 1.0 with just
We can't fix lazy, all we can do is nudge reasonably-non-lazy driver authors to the maximally-interoperable |
Fair enough |
@bugadani I'm not sure why the @Dirbaio Thank you for the explanation. I'll attempt to implement my driver for |
I seem to stand corrected. Can you please also confirm the read does not work if you implement it as two transactins? It is interesting because if display identification is a feature people are widely interested in, the mipidsi and display-interface crates are also probably interested parties, not just embedded-hal. They work still if you only write to them, using the current abstractions, but neither support reading currently, and this might be a first step to figure out what is actually necessary to achieve that. Maybe we can get away with two transactions without the need of touching embedded-hal, maybe we can't. |
i do wonder whether we could provide some abstraction over these in |
I would definitely by happy with a construct like that. I'd be happy with any trait (provided by e-hal) that provides a bus. I've been working on a generic version of the pub trait Share {
type Object;
type Error<'a> where Self: 'a;
type Guard<'a>: DerefMut<Target = Self::Object> where Self: 'a;
fn lock(&self) -> Result<Self::Guard<'_>, Self::Error<'_>>;
}
impl<T> Share for Mutex<T> {
type Object = T;
type Error<'a> = PoisonError<Self::Guard<'a>> where Self: 'a;
type Guard<'a> = MutexGuard<'a, T> where T: 'a;
fn lock(&self) -> Result<Self::Guard<'_>, Self::Error<'_>> {
self.lock()
}
}
impl<T> Share for RefCell<T> {
type Object = T;
type Error<'a> = BorrowMutError;
type Guard<'a> = RefMut<'a, T> where Self: 'a;
fn lock(&self) -> Result<Self::Guard<'_>, Self::Error<'_>> {
self.try_borrow_mut()
}
} Then drivers can ask for a |
It seems a bit silly to me that we would now have an |
Okay, so after chatting more on Matrix, I understand more why there's a need for multiple sharing interfaces including the current more locked down |
I am running into this as well when interfacing with the si4463 chip. Its standard transaction is to assert the CS line, write your command, and keep reading from SPI until you get a specific magic byte. The following byte(s) will have the actual response. As a result, I need to lock the SPI bus and execute some Rust code - a closure would be perfect for this. I am wondering if we could have two different A driver that works with a current |
After needing to solve this problem several times I don't think a closure is the way to go. A mutex/guard style interface would be much simpler, easier to use and works with async. This also means dropping the free flush feature. An |
Makes sense to me. I think the |
If it's just a trait then no, you'd still be able to use two unrelated drivers, it just means you'll need to implement two traits on your locking mechanism. Not ideal but still possible |
Hi.
Recently, the API of the
SpiDevice
trait has been updated to use a list of Operation instances instead of passing a closure.embedded-hal/embedded-hal/src/spi.rs
Line 342 in 9ac61a7
Before the API change, I had written code in the implementation of a driver for the ILI9341/ILI9342 SPI LCD, as shown below, to toggle the DC (Data/Command) pin while the CS is asserted.
After the API was changed, it appears that toggling the DC pin within a transaction has become difficult.
So, my question is, how can I implement a similar function using the new API?
I couldn't find any examples to do it.
The text was updated successfully, but these errors were encountered: