Closed ghost closed 5 years ago
I'm entirely of your way of thinking. As a comparison, ChibiOS has blanket implementations for variants of STM32 peripherals.
If you look under os/hal/ports/STM32
, you'll see there's an LLD
folder containing low-level driver implementations for various peripherals -- for instance, USARTv1
. For these "common" peripherals, the individual STM32 devices select the appropriate variants from the common code.
I present this not as a potential organizational model, but instead as a vindication of the idea that driver implementations can be profitably shared between many of the STM32 variants.
Perhaps it would make sense to implement a number of crates (like stm32-usart-variant1-hal
) which the individual MCU crates could depend on and re-export. For more unique peripherals, or MCU families which don't have the same level of hardware peripheral sharing, the MCU crate itself could just directly implement the drivers.
This is a tricky topic. Whereas other C implementations "profit" from being able to abstract over slight implementation differences, with Rust this is not as easy as the prototype implementation has to be abstract over all of them so every slightly different implementation will typically require a change in the prototype and all of the HAL implementations.
I'd love to see this but I'm highly sceptical it can be done. Bring in the wizards! 😉
I'm not sure I understand the issue here -- but I could easily be missing some Rust subtlety. My embedded experience is almost entirely in C.
In the case of the STM32 family, these are identical individual hardware peripherals -- there's no slight implementation difference required for interfacing with them. I definitely think a hardware peripheral with similar but not identical functionality should be a separate driver. But I also shudder at the idea of maintaining identical driver code across multiple crates.
I know that many of the drivers (for instance, stm32f30x_hal::spi
) put the meat of their implementation in a macro. Perhaps these macros could live in shared driver crates, and the MCU crates import them and instantiate the appropriate driver code for their specific SPI instances.
I'd also really like to see common API/HALs for STM32s and think the ChibiOS implementations are a good example of what could be done.
Sadly there end up being a lot of small differences even between apparently identical peripherals. In C this is less of an issue (you just use the registers you want, by name, and they'd better exist) but the svd2rust-generated APIs will differ both in terms of their actual type not being the same object and in terms of them containing different registers or registers with different fields.
I found there were surprisingly few STM32 peripherals which were exactly identical even inside families, mostly only the 3rd party ones like CAN and USB. I had a big table of what matched up with what somewhere, I'll try and find it...
@adamgreig Yes, that's exactly what I'm talking about. Would love to see your table, though I somewhat doubt there'd be a lot of surprises for me. 😉
Y'all might be interested in svd2rust#96 which is an ongoing discussion around extraction of common features from processor families and how we can generate / compose them into devices.
@ryankurte thanks, that is an interesting discussion! I'll have a look over it.
I found the tables I made, https://agg.io/u/stm32/stm32f/index.html is the STM32F family overview (go up one level for L/H families), click a family header to get the detail breakdown inside that family. Each unique peripheral (grouped by register names+addresses) is listed on the left, with a green tick against devices that implement it. Click the peripheral to see the registers (grouped by fields) etc.
I'm looking for the stm32_common_hal
that was proposed in http://blog.japaric.io/brave-new-io/ as well. So I gather from the discussion here that still everyone is copying the basics from the f3
reference implementation?
@bergus Uhm, I've written a couple of these by now but they're not copied from f3
at all. I think one of the farthest HAL implementations is in stm32f103xx-hal
. The problem here is not so much the implementation of the HAL traits, they're almost but not quite identical so one might gloss over that (or adjust the register definition to match the register splitting and naming as I think @adamgreig is planning to do with his stm32
crate), the real problems are:
@therealprof OK, I haven't looked into the more advanced peripherals yet. I just was comparing the basics - timers, gpio - and was looking into stm32f30x-hal
and stm32l4x6
. Their delay.rs
files [1] [2] are virtually identical (though one seems to do busy waiting for timer values larger than 24 bits while the other panics). Their rcc.rs
files [1] [2] share the same structure and implement the same methods. OK, the l4 has a few more registers, but for example it implements the constrain
method in a generic trait Constrain<Rcc>
while the f3 does it in a RccExt
trait. Their CFGR
implementations with freeze
have exactly the same functionality (apart from using different constants) but differ vastly in their coding style - and in l4, the Clocks
return value is a struct with public fields while in f3 it is a struct with private fields and getters. Their gpio.rs
files [1] [2] are the most different. Sure, I guess they have different numbers of pins and everything, but why does l4 use hal::gpio::A::new()
while f3 uses GPIOA::split()
for the same thing? And (probably because of their different module structure) they have different macro patterns. At the same time, they share a lot of (typelevel) constants.
I would think that all hal crates should use the same patterns for the same things. And possibly even implement the same traits, imported from something like stm32_common_hal
(similar to how the device creates use bare-metal
). And possibly even share the same code parts, imported as individual reusable function snippets or through macros.
I am really looking forward to @adamgreig's automatic code generation that knows about the detailed differences (or even can automatically extract them from the device description files), but until that is viable I think we should manually identify the common parts at least for the basic peripherals.
OK, enough of telling you what I think you should do. I guess what I am really looking for is a guideline on how to implement a HAL. Specifically, I am using a STM32L432, for which none exists yet. So what is the recommended approach? I have not written much Rust yet, or any MCU low-level stuff, so I am mostly programming by example for now. Which of the patterns that can be found in the existing crates should I follow? Where exactly do I need to watch out for differences, and is the only way to confirm them by comparing the respective programming manuals? (Or: how do I know which parts I can copy&paste?)
@bergus It's unfortunately not quite so simple. The reason so many different implementation "styles" exist is that there's no one which ultimately fits all, e.g. @japaric seems to prefer to move "ownership" of registers into HAL implementations wherever possible, "split" resources whenever they're shared and only in very circumstances use unsafe APIs to access registers directly. In contrast I prefer to only split resources which are really shared all over the map, move ownership of registers only when there's exactly one single legitimate user of the register, and use unsafe register access everywhere else. Also the HAL implementation depends on the vendor implementation of the peripherals which are dramatically different, so an approach that works nicely for STMicro will absolutely not work for most other manufacturer so it's a bit tricky to provide a general step-by-step documentation.
Specifically, I am using a STM32L432, for which none exists yet.
The STM32L432 is quite similar to all STM32L4 or STM32F4 MCUs so I'd start with one of those. If I was to start one, I'd use a crate, change the peripheral API and all references to it in the code and simply try compiling it. If there're compile errors in the code, fix them; if the compiler is complaining about non-available registers; open the reference manual and adjust accordingly.
I have not written much Rust yet, or any MCU low-level stuff, so I am mostly programming by example for now.
Hm, the new book is very early WIP at the moment, I guess the next best thing is to check out https://japaric.github.io/discovery/README.html about some general information and to get acquainted with Rust then continue on with examples in existing implementations. (Shameless plug) I have cut back on the direct reqister fiddling examples but there're still 2 left in https://github.com/therealprof/stm32f042.git.
Where exactly do I need to watch out for differences, and is the only way to confirm them by comparing the respective programming manuals? (Or: how do I know which parts I can copy&paste?)
The good thing with is: If it compiles you're most likely on the right path or even fully there. Of course the vendor supplied SVD files are full of errors but most of the stuff is correct and the compiler will watch over the correct use of register fields and names which is a huge thing.
This repository is focusing on the generic HAL traits rather than generic HAL implementations, hence I'm going to close this now.
If you're interested in this (very valid) topic about universal STM32 drivers you're more than welcome to checkout our bundled STM32 efforts in the STM32-rs org.
Hello, everyone,
for one of my boards containing a STM32F405 I'm creating a BSP-Crate and corresponding HAL-Crate and note that the STM32F4 will be able to share some drivers of the HAL with the STM32F3, but some of them are very different, like I2C.
Are there any plans for a better representation of the common features of the STM32 family? It would be quite unpleasant to always have to copy from the stm32f30x-hal. I'm really not a friend of copying code. At this point I would like to offer my help.
Greetings,
Ingo