# Contributing Thanks for helping us build embedded Rust support for NXP's i.MX RT processors! Please open an issue if - you find a bug - you have an idea for a feature - something isn't clear in our documentation ## Development The steps below are useful for developers who want to build and modify `imxrt-hal`. All steps assume that you've cloned the repository. You should be familiar with the API documentation and understand how an end user integrates `imxrt-hal` into their project. You should also install the dependencies listed in the top-level README, and you should know how to build examples for a board. ### Building To build a HAL that works for any i.MX RT chip, enable any `imxrt-ral` feature. For instance, ``` cargo build --features=imxrt-ral/imxrt1062 ``` builds the HAL for an i.MX RT 1062 chip. Replacing `imxrt1062` with `imxrt1011` is expected to work, too. Notice how `--target=thumbv7em-none-eabihf` is not required. The HAL should build for your target system so that unit and integration tests can execute. Of course, adding `--target=thumbv7em-none-eabihf` will work as well. To build a HAL with chip features, enable that feature along with the RAL feature: ``` cargo build --features=imxrt-ral/imxrt1062,imxrt1060 ``` `imxrt-hal` is the root of a non-virtual Cargo workspace. To build all packages in the workspace, append `--workspace`. Since the `board` crate is part of the workspace, you can specify a `board` feature to influence the entire build. Prefer this approach for building and testing code throughout this repository. ``` cargo build --features=board/teensy4 --workspace ``` Again, all packages should build for your host to support testing. When you build examples, you must supply `--target`, because the examples are all expected to run on hardware. ``` cargo build --features=board/teensy4 --examples --target=thumbv7em-none-eabihf ``` With version 2 of the Cargo feature resolver, it's trickier to build a specific package in the workspace. It's typically achieved by 1. always including `--package=imxrt-hal`. 2. including the package of interest with another `--package=...`. 3. enabling a qualified `imxrt-ral` feature, an _unqualified_ `imxrt-hal` feature, and any other features needed for the package of interest. The snippet below shows how to build `imxrt-log`. No `imxrt-log`-specific features are enabled in this build. If they were needed, they could go after the `imxrt1060` unqualified `imxrt-hal` feature. ``` cargo build \ --package=imxrt-hal \ --package=imxrt-log \ --features=imxrt-ral/imxrt1062,imxrt1060 ``` ### Running automated tests To run tests, you need to exclude examples from the build. One way to do that is to explicitly select `--tests` and `--doc` so that the build only builds and runs the necessary artifacts. ``` cargo test --features=board/teensy4 --workspace --tests cargo test --features=board/teensy4 --workspace --doc ``` ### Generating documentation It's just like building, but change `build` to `doc`. The command below builds `imxrt-hal` documentation. ``` cargo doc --features=imxrt1060,imxrt-ral/imxrt1062 ``` If you only need `imxrt-hal` documentation, include `--no-deps` in the command. The next command builds the common HAL for a 1011 chip. Note that this may have documentation warnings, since intra-doc links may assume that a imxrt-hal chip feature is also enabled. ``` cargo doc --features=imxrt-ral/imxrt1011 ``` To build documentation for all packages in the workspace, include `--workspace`, and use a `board` feature for convenience. ``` cargo doc --workspace --features=board/teensy4 ``` ### Chip features We support one feature per i.MX RT processor chip. A "chip" is described by an NXP datasheet and reference manual. For example, the `imxrt1060` feature is associated with the [i.MX RT1060 Crossover Processors](https://www.nxp.com/docs/en/nxp/data-sheets/IMXRT1060CEC.pdf), which includes the following processors: - i.MX RT 1061 - i.MX RT 1062 We try to use the term "family" to describe related chips across RMs. The 10xx family is for all i.MX RT 10xx MCUs, and the 11xx family is for the bigger, faster MCUs. ## Running hardware tests Our CI system ensures that the HAL build for all processor variants. But, we can't automatically test against hardware! To test your changes on hardware, use the examples maintained in the repo. See [the documentation](board/README.md) to get started. ### Adding a new board Adding a new board lets you easily develop and test i.MX RT hardware peripherals, and makes it easier for others to contribute. If you run into issues, reach out to the imxrt-rs team. If the HAL doesn't yet support your chip (family), you'll first need to add support for it. See the design section of this document for guidance. Essentially, you'll define a new chip configuration module under `chip`. Use the existing configuration modules as your guide. Once the HAL has a feature for a chip (family), you're ready to add a board. Here's the `board` files of interest: - `board/build.rs` will need a new mapping to a runtime configuration. See the existing examples for help, and also consult the `imxrt-rt` documentation. - `board/Cargo.toml` will need a new feature to describe your board. Use the existing features as an example. - `board/src/[your_board_name].rs` is a module that you'll add to specify the hardware configuration. Use the existing board modules as an example. Integrate this module into `board/src/lib.rs`. You shouldn't need to support all `imxrt-hal` hardware examples right away; the design does its best to allow incremental board support. Start with the `hal_led` example, adding all the code necessary to turn on the LED. Then, pick another example, and add more code to your board module. Keep going until your board supports all hardware examples. The board uses a build-time configuration library to automatically add startup and runtime support. However, you're still required to supply the firmware configuration block (FCB) in your board module. If you're having trouble defining your FCB, reach out to the imxrt-rs team. ## Tips and tricks If you're using `rust-analyzer` with VSCode, you only need to supply a board feature. Add this to your `.vscode/settings.json` at the repo root: ```json { "rust-analyzer.cargo.features": [ "board/teensy4", ], } ``` Change the board feature when you're ready to work on a different chip (family). If there's no board for your chip (family), replace that feature with a RAL feature. ## Resources For more resources, consult the resources section in [the imxrt-rs book] (https://imxrt-rs.github.io). ## Design Here's a brief design overview of `imxrt-hal` (HAL). For more details, see comments in the source files. This section might help if you're adding new driver or chip family support. The HAL tries to provide a consistent API for drivers across all i.MX RT chips. The best way to do that is to write a common driver. These drivers are available under `common` and exposed directly to the end user. When contributing new peripherals, try your best to fit them exclusively in `common`. The criteria is that they build and behave consistently across _all_ chips supported by `imxrt-ral` (RAL). There's restrictions: things under `common` will not require a HAL chip feature; they only require a RAL chip feature. If this isn't possible, split your modules across `common` and `chip`, and tie them together in the crate root. Modules under `chip` require both a RAL and a HAL feature. These modules implement chip and family features, which could be a single function or an entire driver. Modules under `chip` are allowed to use conditional compilation. Modules under `chip` are also allowed to reference the special chip configuration modules. Modules under `chip` that start with `imxrt` are chip configuration modules. These modules configure the rest of the chip modules. They use path attributes and aliases to export shared behaviors. There's a few reasons for this madness (which we're looking to assess with this design): - The approach maximizes internal code sharing while minimizing the number of `#[cfg(...)]` you, a human, need to parse. Ideally, there's only one `#[cfg(...)]` to include a given chip configuration module. Then, separate modules -- possibly shared across different families -- are linked into this configuration module. - The approach consolidates the minimum set of behaviors needed for any chip (family). To bring up a new chip (family), implement its configuration module. There's no hard spec of what goes here, so what's expected of that configuration module is demonstrated by the existing configuration modules. This may end up as only an aesthetic preference. Other HALs have shown that using various `#[cfg(...)]`s throughout their modules works. We could go that route when we consider split HALs. The approach represents a future of split HALs. This structure makes it easy to understand what would populate `imxrt-hal-common`, and what features would be specific to an individual HAL package. The current design expresses this in a single package with conditionally-compiled modules only for prototyping convenience. This HAL makes the interesting decision to depend on a dependency's feature without explicitly controlling that feature. Since the RAL is part of the HAL's public API, there didn't seem to be any value in adding a HAL chip specific feature that simply enabled a RAL chip specific feature. The end user can simply pick their chip through the RAL, making the choice explicitly in their build. This approach has some repercussions; namely, when you build the HAL for the 1060 chips, you don't know if you're building for a 1061 and 1062 chip. There's no need to handle this with today's drivers, so we're punting this problem. Split HALs with their own optional features could solve this. In lieu of split HALs, we could build these drivers as their own crates, and the user could depend on them as needed.