Reflections on imxrt-hal (2024)
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.