I released version 0.5 of imxrt-hal around two years ago. The release produced a package interface that didn’t exclusively favor embedded-hal. It also exposes the low-level details of the hardware to the user. Questions about this design decision came up this time last year and again last week.

I’m happy with this design decision to share low-level details of hardware with users. It makes it easier for me to use and maintain the package. I don’t think the decision excludes users from the project, although I understand the project may not immediately support all use-cases. Finally, I think the package’s design lets us support new capabilities, including async drivers, and drivers that are externally maintained.

Package design and usability

I engage with two categories of imxrt-hal users, each having different thoughts on the package API. The first user group expects imxrt-hal to implement embedded-hal traits and nothing more. These users aren’t interested in the low-level details of how their peripherals work. This is the larger, majority user group.

The second user group knows that their system has an i.MX RT MCU, knows how their MCU’s peripherals work, and knows how to build what they want. These users would use the imxrt-ral register access layer if there was nothing more convenient. This group is smaller than the first group. But importantly, this group contains me.

I’ve benefited from a driver package that presents the low-level details of the underlying hardware. For example, I’ve needed to talk with SPI devices with unusual frame sizes (10, 13, or 25 bits). embedded-hal doesn’t help me, nor would a driver that only supports the common SPI traits. On the other hand, the imxrt-hal LPSPI driver exposes its command queue, so I can transact however many bits I need. I’ve also needed DMA- and interrupt-driven I/O without the conveniences of async Rust. With today’s package, I can achieve that, and I can optimize my drivers for my non-async runtime.

Despite my personal preference, I still believe imxrt-hal should support the larger user group. Since we already have the low-level details, we have what we need to implement embedded-hal traits. Those implementations spin the CPU while waiting for I/O to complete. I believe that efficiently realizing blocking I/O requires a runtime, or different driver design to support a runtime. I haven’t figured out this driver design, one that de-couples a blocking driver from its runtime.

Async Rust changes how we can implement drivers and support embedded runtimes. I’m happy to support async drivers in imxrt-hal. Again, we have what we need to implement embedded-hal-async traits. Achieving this goal doesn’t require that we remove the low-level details from the public API.

Users from the first group might be confused that the package presents low-level details of the hardware. We can address the concern by better user documentation and examples, and by package construction or alternate API design. At the extreme, imxrt-hal becomes exactly what the larger group wants. I and the smaller group will adopt imxrt-ral, with mild to moderate inconvenience.

Package maintenance

If imxrt-hal provides a low-level API to its users, that means users can build their own drivers outside the package. That was a design goal. It’s already possible for the ambitious, since imxrt-ral is its own package. Of course, these users have no need to contribute back to the project.

When I provide contracting services, I’m compensated for my familiarity with embedded Rust and my experience with these MCUs. That’s not the same as being compensated to maintain an open source project. Helping users develop their own drivers results in fewer contributions for me to review, by myself, in my free time. That’s OK with me. I would love to change this! That change needs either a different kind of contract with my clients, or additional maintainers and contributors to put in the work.

In my experience, users who want to use an internal API will maintain a private fork. They may also include their system’s details directly into the package, forming a code base that couldn’t be upstreamed. I don’t see how removing low-level details from the public API decreases this possibility.

Package organization

Today’s imxrt-hal implements most of the drivers supported by our project. We have other drivers that are not implemented in imxrt-hal. The package re-exports (or intends to re-export) these externally-managed drivers. I’m not sure this re-exporting part is always correct.

The first type of externally-managed drivers are those like imxrt-usbd. They have a separate interface package (usb-device) that dictates the driver’s compatibility with a larger ecosystem. Other examples of this driver type include the prototype imxrt-enet and imxrt-usdhc drivers (respectively implementing smoltcp and embedded-sdmmc). These are separately maintained so that they can be released when the interface package breaks. Users can depend on these packages without imxrt-hal.

It’s not clear to me that imxrt-hal should re-export these kinds of drivers. It may be better if users took direct dependencies on these drivers; that way, users could upgrade their drivers and interface packages without waiting for imxrt-hal. I have commits in my public imxrt-hal fork that remove imxrt-usbd, and I may propose this for the next breaking imxrt-hal release.

Our package organization could have a one-to-one correspondence of interface package to implementation package. It could look something like this:

Interface package Implementation package
embedded-hal imxrt-hal
embedded-hal-async imxrt-hal-async
usb-device imxrt-usbd
smoltcp imxrt-enet
embedded-sdmmc imxrt-usdhc

Just as embedded-hal-async depends on embedded-hal, the (fictitious) imxrt-hal-async package depends on imxrt-hal. Separating async drivers into their own package means that users who don’t want async don’t need async.

The second type of externally-managed drivers are those that provide a (perceived) improvement over imxrt-ral. The packages imxrt-iomuxc and imxrt-dma are these type. Although they may be used within and re-exported by imxrt-hal, these packages have value without imxrt-hal. Users may use them to create their own drivers. I think it’s OK for imxrt-hal to re-export these packages, particularly when they’re part of the imxrt-hal public interface.

As for drivers that don’t have an interface package: I’m not sure where they belong. They can go within imxrt-hal or their own package. If it’s the latter, let me know if you want to maintain a package within the imxrt-rs GitHub organization. I would be happy to create a repository for you and make you a repo administrator.