-
Notifications
You must be signed in to change notification settings - Fork 101
Abstract support for peripheral drivers for communication protocols #8
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
Comments
I think it may be possible to leverage traits for this. The trait provides an API over e.g. SPI and then one can write a generic driver for a SPI device (e.g. an accelerometer) that exposes an API that takes e.g.
I think we should start experimenting with a blocking API. Async is going to require settling on some async solution first. I think I've heard @posborne mention this idea of traits as peripheral interfaces once before. Perhaps they have experience with this approach in the RPi sub-ecosystem and can share it? |
@japaric I agree that we shoul
I agree with this statement, I just wanted to point out that in general I would never want a framework for embedded which didn't have a good story for async, even if that is just callback functions. |
Traits as peripheral interfaces is a great end goal. I want to advise against formalizing this too early; Firmata has many edge cases that (as a generic protocol) prevents it from compatibility with every device. It may be more practical to start with a generic crate with multiple feature-enabled backends (like Johnny-Five's IO adapters) before going straight to traits. With Tessel (an OpenWRT system with a custom GPIO driver), we've prototyped I2C and Gpio ports, with SPI and UART in the pipeline: https://docs.rs/tessel/0.3.1/tessel/ We've prototyped an async API built on Tokio: https://github.com/Dr-Emann/tessel-rust/tree/new_api I'd love to experiment with drivers like the MMA8452Q driver written for Tessel and for rust-embedded, probably with an adapter class inbetween. |
Here's something that I started to put together to come up with a standard trait for accessing GPIOs: posborne/rust-gpio#1 |
@posborne , that looks like a good start. I'm with @tcr and @cwoodall about the risks of over constraining ourselves too early, but also the need for async. It's hard to define a universal interface/trait that will satisfy all hardware. However, maybe there can be a base type, that only takes in u8s, and emits u8s, and doesn't expose any buffering, and then more complicated interfaces can implement additional traits? Another part of generic peripheral drivers worth considering is coupling physical GPIO pins. To avoid getting way into the weeds right away, I'll only pose a few few questions: Are GPIO pins configured separately from the peripheral? Are they passed in when initializing a peripheral? Does a UART know how many pins it needs, and in what configuration? |
@igutekunst and then to get a little more into the weeds, how are pins as a resource handled? Pins can have multiple configurations... May even have multiple configurations throughout a program. A major improvement of the current state would be if there was a concept of ownership applied to pin configurations. Not sure how you could do this, but it seems like rust might actually be able to solve this problem with its built in system of lifetimes and borrowing (or I might also be crazy) |
The lifetimes and borrowing idea brings a smile to my face! I'd love to see peripherals actually owning pins in a compiler enforced manner. Maybe it's time to actually building a few projects for a few different MCUs (let's say an AVR/Arduino and a STM32F4), and see what emerges. A concern is how to do this in a zero cost way. I'm too new to Rust to have an intuition on whether it's possible to do so without runtime/codesize effects. I don't want to get into the trap I've seen happen with C, where you and up with a struct GPIOpin, with all kinds of members and function pointers. Ideally this abstraction would boil away after compilation leaving almost direct manipulation of registers. A thing to consider with pin ownership is configuration management/board revisions. Let's say you move stdio to a different UART on a new board spin. Where does this configuration live? My intuition is telling me it lives in a board definition file or crate, that exposes the right pins to the UART, which in turn exposes the UART to the application layer. |
relevant: zinc has this "platform tree" feature (example), which is just a syntax extension, that assigns pins to peripherals (e.g. PA0 -> UART1) in a declarative manner and validates the assignments (no pin can be shared between two peripherals) at compile time. The syntax extension expands to rust code, so the compile time validation is actually done by rustc borrow checker. This feature also constrains what can be accessed in the At least, that's my understanding of what it does. I haven't actually used myself. 😄 |
That's quite cool. I had only briefly looked at it, but will look at it more. |
Not sure if anyone here has seen this: Zinc mini post-mortem
This matches up with my experience looking at Zinc, admittedly more than a year ago. There are a lot of ambitious ideas which unfortunately can lead to a big learning curve, and it is tough having basically everything in a single crate. f3 seems to be a better approach, at least starting out - there are a lot fewer abstractions to learn, and while I'm sure there are ways that you can shoot yourself in the foot, sometimes it's easier to debug straightforward errors than to work through a heavier API. Also, lighter-weight abstractions can make it a lot easier to translate between the Rust code, vendor hardware documentation, and any C-based sample code that you might have. That's important right now because there's very little Rust embedded code to look at for inspiration. Right now I'd prefer to see people experimenting with varied approaches and applying them to larger projects to see what really works. It's still a bit early to be standardizing. |
@jcsoo that us a really awesome piece of info I did not see that post mortem! It all makes a lot of sense to me! Ideally we want a world where we have a bunch of interoperable crates, maybe built up from some core abstractions (like how many concurrency libraries use the future/promises crates and then build up easier to use semantics from there) I have rethought some of my thinking I think those base projects are key but setting a goal to have an arduino like setup where you can easily do hobby projects as a first big stop would really help us establish ourselves as a community draw in a variety of makers and start getting projects posted to hackaday.com, Hacker news, etc. |
Perhaps there's something to be said for the UNIX character device / ioctl approach. Implementing the Read and Write traits would work for a range of I/O devices, and you can specify peripheral specific setup in the new method. I think it would be limiting to say this is a UART and this is an SPI device when all I actually need to know is that it's a u8 sink of some sort. |
@thejpster yes definitely in some (maybe most) respects the UNIX way makes sense, but there are probably a number of peripherals that would end up being served oddly by this approach. particularly I would much rather be able to send CAN frames directly rather than serialising them to bytes or something? Similarly I would rather send the struct that wraps my structured messages over serial than have to mush it into bytes manually every time? Also wrt SPI devices that /aren't/ merely byte source/sinks, it makes more sense to sit them on top of a specific SPI interface? |
@fbstj I think the trick is building an ioctl interface that's type safe (and it may have to be interface specific to achieve that) but I'd argue that I/O device payloads (to be clear, I mean the data you wish to transfer to the remote system, as opposed to the metadata like the destination address or the baud rate) should fundamentally be expressed in octets. If you need to send structured data, perhaps something like serde-rs should perform the marshalling for you rather than relying on the alignment and packing of your particular platform to match your target. I would be really interested to know more about any SPI devices that are not merely full-duplex u8 source/sinks. Do you have any examples? |
I think implementing the Take, for instance, the interface exposed by one library I have worked on, spidev. Although we can (and do) implement the read/write traits for SPI devices, I also implement a mechanism for doing full-duplex transfer operations as well. Those APIs match the kernel APIs pretty closely (in terms of capabilities), but they hide the ugliness of actually dealing with ioctls (which are pretty terrible in terms of usability/correctness). For I2C, I decided it did not really make sense to implement the Read/Write traits directly (at least for now) since I could not think of many cases where composition using these traits would make sense (but maybe I should revisit that decision). In my dream world, I could write a driver for a device using traits and have this device be supported on multiple vastly different platforms. For instance, consider a few examples from my work on I2C: Today, these drivers are not suitable for use across platforms, but I have tried to at least move things in that direction by making the drivers only depend on traits. In turn, some of these devices expose a trait interface. |
I believe that this issue essentially describes Marking this for a cleanup sweep. If we would like this to stay open, please provide an update to what this issue should be focused on. |
I am closing this issue, please feel free to open another issue if you would like this discussed further. |
The following list of protocols are often found as drivers in many embedded systems:
Each application often needs to use these in slightly different ways, and need support for: interrupts, asyncio of some form, and blocking operation typically. It would be great as a community, especially for the
microcontroller
/arduino
sub-ecosystem to standardize on some sort of interface for these and standard libraries.An easy and standard API to these peripherals will help produce. The ARM provided CMSIS layer tends to build up some of this standardization, at least trying to make what might be disparate peripherals feel very similar to the application programmer.
The text was updated successfully, but these errors were encountered: