Stepper is designed to run pretty much everywhere, including very limited target platforms like microcontrollers. To that end, it doesn't interface with any platform-specific code, instead relying on a set of traits that must be implemented on a given target platform for Stepper to run.
Unfortunately, the set of traits required by Stepper is somewhat non-standard, and not implemented on most target platforms. The remainder of this guide will explain what those traits are, and how they can be implemented.
At the time of writing (April 2021), the only target platform that Stepper is confirmed to work on is the LPC845 by NXP. Porting it to other microcontrollers should be relatively straight-forward, by following the rest of this guide.
If you just want to give Stepper a try and don't have specific hardware in mind, just grab an LPC845-BRK development board, which is fully supported. Stepper Terminal should provide a good starting point.
If you find any problems with this guide, if anything is unclear, incomplete, or incorrect, please open an issue. If you need any help with adding support for Stepper to a given HAL, please feel free to reach out to @hannobraun, for example by pinging him from an issue or pull request in the HAL's repository.
As explained above, Stepper relies on a set of traits to interface with its target platforms. These traits need to be implemented on a given target platform for Stepper to work.
Stepper relies on the following implementations from the embedded-hal
crate:
embedded_hal::digital::OutputPin
- Used for interfacing with driver chips, for example STEP, DIR, and any other digital signals.embedded_hal::timer::CountDown
- Used for any timing-related tasks.
embedded-hal
is widely supported in the Embedded Rust ecosystem, but Stepper depends on its latest version (1.0.0-alpha.4, at the time of writing), which is not widely supported.
In addition, Stepper relies on an implementation of TryFrom<Nanoseconds>
for CountDown::Time
. Let's unpack this:
TryFrom
is a standard trait from the Rust core library, widely used for fallible conversions.Nanoseconds
is a type fromembedded-time
, a library for handling time on embedded systems.CountDown::Time
is an associated type of the aforementionedCountDown
trait. Stepper expects a conversion fromNanoseconds
to this associated type to exist.
In Rust, Hardware Abstraction Layer (HAL) refers to a library that provides a high-level interface a specific microcontroller (or family thereof). An example of such a HAL is LPC8xx HAL. These HAL libraries are the ideal place for the trait implementations required by Stepper.
For most HAL libraries, two main hurdles need to be overcome, before these implementations can be added:
- Adding support for the latest alpha version of
embedded-hal
. - Adding support for
embedded-time
.
Many HALs plan to switch to embedded-hal
1.0 once it comes out, and even have open pull requests (written against an alpha version) to that effect. However, it is possible (and perfectly practical) to use a more gradual approach and support the latest stable version of embedded-hal
and the latest alpha version side by side.
This approach has been implemented in LPC8xx HAL. The following list lines out how to repeat this for other HAL libraries:
- Add a dependency to the latest
embedded-hal
version toCargo.toml
. Use thepackage
attribute to refer to it by another name, to prevent name collision (example). - Import the traits into the module where they should be implemented. Change their name using
as
to prevent name collisions (example). - Implement the traits next to their non-alpha versions (example).
None of this is hard, but some HAL maintainers might prefer not to add a dependency on an alpha version. The main drawback of this approach is that it requires ongoing updates, as new embedded-hal
alpha versions come out.
However, besides providing support for Stepper, the big advantage is that the transition to the new embedded-hal
version can be gradual. It also can result in more testing of the new embedded-hal
version.
Unfortunately embedded-time
isn't widely supported yet in the ecosystem. Most HALs do have duration types though that duplicate what embedded-time
already provides (often in a module called time
). In many cases, adding support for embedded-time
might be as simple as adding the dependency, removing the time
module, and updating any code that no longer compiles.
Please note though that Stepper doesn't require this. All it requires is the single TryFrom<Nanoseconds>
implementation for the Time
associated type of the CountDown
implementation that is provided to Stepper.
Again, this has been implemented in LPC8xx HAL. The following list lines out how to do it for other HALs:
- Add a dependency on the latest version of
embedded-time
(example). - Add a dedicated type to represent time for the implementation of
CountDown
that you want to use (example). Most HALs just use a type likeu32
which is not very descriptive, and quite limiting in what can be done with it. - Set the
CountDown
implementation'sTime
associated type to the dedicated time type you created (example). - Implement the conversion from
Nanoseconds
to timer ticks (example). Unfortunately the only example available is quite simplistic (the timer is hardcoded to one specific frequency, and that specific conversion can't fail). It should be possible to support more complex cases using const generics or other techniques.
This is not that easy (and can in fact get quite complicated, if the timer frequency is configurable), but this has lots of utility beyond supporting Stepper.
If adding the required trait implementations directly in the HAL is not practical for some reason, you can work around this by providing these implementations in your own code. While it is not possible to implement a foreign trait for a foreign type ("foreign" as in "defined in another crate"), you can create your own wrapper types, and implement the required traits for them.
The compat
module in Stepper provides such wrappers.