diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f8ac841dc..4815e42495 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,6 +156,7 @@ jobs: - name: Configure rust target (v6, v7) run: | + rustup target add thumbv7em-none-eabihf rustup target add thumbv7m-none-eabi rustup target add thumbv6m-none-eabi rustup component add rust-src diff --git a/examples/teensy4_blinky/.cargo/config.toml b/examples/teensy4_blinky/.cargo/config.toml new file mode 100644 index 0000000000..7ab43ea0af --- /dev/null +++ b/examples/teensy4_blinky/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.thumbv7em-none-eabihf] +runner = "python3 run.py" +rustflags = ["-C", "link-arg=-Tt4link.x"] + +[build] +target = "thumbv7em-none-eabihf" # Teensy 4 diff --git a/examples/teensy4_blinky/Cargo.lock b/examples/teensy4_blinky/Cargo.lock new file mode 100644 index 0000000000..8253a71bbd --- /dev/null +++ b/examples/teensy4_blinky/Cargo.lock @@ -0,0 +1,603 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bbqueue" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3baa8859d1a4c7411039a75c0599a4640ef1c9a8fc811e4325b00e6cfe0a55" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2894bc2f0457b8ca3d6b8ab8aad64d9337583672494013457f86c5a9146c0e22" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "imxrt-blinky" +version = "0.1.0" +dependencies = [ + "embedded-hal 0.2.7", + "imxrt-log", + "log", + "nb 1.1.0", + "rtic", + "rtic-monotonics", + "teensy4-bsp", + "teensy4-panic", +] + +[[package]] +name = "imxrt-boot-gen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e90bcb04a4619e58f8662d6d993b6318d04416936550cd3d721938d254f5fe19" + +[[package]] +name = "imxrt-dma" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be4dc99cfca9c88c3478be3cba16ca3e3b2bde180e1dd69f7d04f470d6412a4" +dependencies = [ + "cortex-m", + "ral-registers", +] + +[[package]] +name = "imxrt-hal" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48e06b1cd447190708b1e25c3bdc9bb34ef35d5930d39a03d36a3e788fffd81" +dependencies = [ + "bitflags", + "cfg-if", + "embedded-hal 0.2.7", + "fugit", + "imxrt-dma", + "imxrt-iomuxc", + "imxrt-ral", + "imxrt-usbd", + "nb 1.1.0", + "void", +] + +[[package]] +name = "imxrt-iomuxc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc265810697eb1ae9003f2c32da7b389b465a8485a934941a9d72be16713f60" + +[[package]] +name = "imxrt-log" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66f5d6331f47afc29af176d337c5556e677549a0dc7aef5692134d81372d69" +dependencies = [ + "bbqueue", + "critical-section", + "defmt", + "imxrt-hal", + "imxrt-usbd", + "log", + "usb-device", + "usbd-serial", +] + +[[package]] +name = "imxrt-ral" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb8b67f5867258a62fcbfa51ae0985d7fa2d5ffdb5b9c4f559b0af110300369" +dependencies = [ + "cortex-m", + "ral-registers", +] + +[[package]] +name = "imxrt-rt" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab95fbf48de9bd0f127ecdae1b482f4250c7c1258ff6dec484c4df92ae293c8" +dependencies = [ + "cfg-if", + "cortex-m-rt", +] + +[[package]] +name = "imxrt-usbd" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1212c31e391faaad032fdc248427e0cfb000c59ddeb00b3f95ca16a63942b83" +dependencies = [ + "bitflags", + "cortex-m", + "ral-registers", + "usb-device", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ral-registers" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46b71a9d9206e8b46714c74255adcaea8b11e0350c1d8456165073c3f75fc81a" + +[[package]] +name = "rtic" +version = "2.0.1" +dependencies = [ + "atomic-polyfill", + "bare-metal 1.0.0", + "cortex-m", + "critical-section", + "rtic-core", + "rtic-macros", +] + +[[package]] +name = "rtic-common" +version = "1.0.1" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "rtic-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42" + +[[package]] +name = "rtic-macros" +version = "2.0.1" +dependencies = [ + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rtic-monotonics" +version = "1.2.1" +dependencies = [ + "atomic-polyfill", + "cfg-if", + "cortex-m", + "embedded-hal 1.0.0-rc.1", + "fugit", + "imxrt-ral", + "log", + "rtic-time", +] + +[[package]] +name = "rtic-time" +version = "1.0.0" +dependencies = [ + "critical-section", + "futures-util", + "rtic-common", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "teensy4-bsp" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a920208238f8ee2d2a4e29fdaf923a4025750a76baab845be96ec1d4ef184be8" +dependencies = [ + "cortex-m", + "imxrt-hal", + "imxrt-log", + "imxrt-ral", + "imxrt-rt", + "teensy4-fcb", + "teensy4-pins", +] + +[[package]] +name = "teensy4-fcb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8007b6d680b853afd7aa5a4b2be509efac0b5063698a87f274206955afbf1d0f" +dependencies = [ + "imxrt-boot-gen", +] + +[[package]] +name = "teensy4-panic" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c733a579f17557d093df6714a57d61841e9cbaa222384ca7ee1fbb334888c20" + +[[package]] +name = "teensy4-pins" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a50cc992551983c0034014d58873af2153921cd22601c2194ae93f763f5ba06" +dependencies = [ + "imxrt-iomuxc", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usbd-serial" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db75519b86287f12dcf0d171c7cf4ecc839149fe9f3b720ac4cfce52959e1dfe" +dependencies = [ + "embedded-hal 0.2.7", + "nb 0.1.3", + "usb-device", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/examples/teensy4_blinky/Cargo.toml b/examples/teensy4_blinky/Cargo.toml new file mode 100644 index 0000000000..800ad58b7f --- /dev/null +++ b/examples/teensy4_blinky/Cargo.toml @@ -0,0 +1,52 @@ +[package] +authors = ["Finomnis "] +edition = "2021" +readme = "README.md" +name = "imxrt-blinky" +version = "0.1.0" + +[workspace] + +[dependencies.rtic] +path = "../../rtic" +version = "2.0.1" +features = ["thumbv7-backend"] + +[dependencies.rtic-monotonics] +path = "../../rtic-monotonics" +version = "1.2.1" +features = ["imxrt_gpt1"] + +[dependencies] +embedded-hal = "0.2.7" +teensy4-panic = { version = "0.2.3", default-features = false } + +[dependencies.teensy4-bsp] +features = ["rt"] +version = "0.4.4" + +[dev-dependencies] +nb = "1.1.0" # Async +imxrt-log = { version = "0.1.1", default-features = false, features = [ + "log", + "lpuart", +] } +log = "0.4.20" + +# this lets you use `cargo fix`! +[[bin]] +name = "imxrt-blinky" +test = false +bench = false + +[profile.dev] +opt-level = 1 +codegen-units = 16 +debug = true +lto = false + +[profile.release] +opt-level = "s" # optimize for size +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size on Flash +lto = true # better optimizations diff --git a/examples/teensy4_blinky/README.md b/examples/teensy4_blinky/README.md new file mode 100644 index 0000000000..778e712996 --- /dev/null +++ b/examples/teensy4_blinky/README.md @@ -0,0 +1,25 @@ +# Teensy4 RTIC Blink example + +Working example of simple LED blinking application for Teensy4. Example uses monotonics API and peripherials access. + +## How-to + +### Prerequisites + +The following hardware is required for the examples: +- A [Teensy 4.0](https://www.pjrc.com/store/teensy40.html)/[Teensy 4.1](https://www.pjrc.com/store/teensy41.html)/[Teensy MicroMod](https://www.sparkfun.com/products/16402) development board + +The following software tools have to be installed: +- Python3 (as `python3`, or modify `run.py` to use the `python` binary) +- [`cargo-binutils`](https://crates.io/crates/cargo-binutils) +- [`teensy_loader_cli`](https://www.pjrc.com/teensy/loader_cli.html) + + +### Run + +- Connect the Teensy to PC via USB cable. +- Press the `Reset`/`Boot` button on the Teensy. +- Run: + ```bash + cargo run --release + ``` diff --git a/examples/teensy4_blinky/examples/common/mod.rs b/examples/teensy4_blinky/examples/common/mod.rs new file mode 100644 index 0000000000..9851feab1c --- /dev/null +++ b/examples/teensy4_blinky/examples/common/mod.rs @@ -0,0 +1,42 @@ +macro_rules! uart_panic_handler { + ($uart: ident, $tx_pin: ident, $rx_pin: ident, $baud: expr) => { + #[panic_handler] + fn panic(info: &::core::panic::PanicInfo) -> ! { + use ::core::fmt::Write as _; + use ::embedded_hal::serial::Write as _; + + let ::teensy4_bsp::board::Resources { + $uart: uart, pins, .. + } = ::teensy4_bsp::board::t40(unsafe { ::teensy4_bsp::ral::Instances::instances() }); + + let uart = ::teensy4_bsp::board::lpuart(uart, pins.$tx_pin, pins.$rx_pin, $baud); + + struct UartWriter { + uart: ::teensy4_bsp::hal::lpuart::Lpuart, + } + impl ::core::fmt::Write for UartWriter { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + for &b in s.as_bytes() { + if b == b'\n' { + let _ = ::nb::block!(self.uart.write(b'\r')); + } + let _ = ::nb::block!(self.uart.write(b)); + } + Ok(()) + } + } + + let mut uart = UartWriter { uart }; + + ::core::writeln!(uart).ok(); + ::core::writeln!(uart, "{}", info).ok(); + ::core::writeln!(uart).ok(); + + let _ = ::nb::block!(uart.uart.flush()); + + ::teensy4_panic::sos() + } + }; +} + +pub(crate) use uart_panic_handler; diff --git a/examples/teensy4_blinky/examples/with_logs.rs b/examples/teensy4_blinky/examples/with_logs.rs new file mode 100644 index 0000000000..dd4fef0b7d --- /dev/null +++ b/examples/teensy4_blinky/examples/with_logs.rs @@ -0,0 +1,113 @@ +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +mod common; +common::uart_panic_handler!(lpuart6, p1, p0, 115200); + +use teensy4_bsp as bsp; + +use bsp::board; +use bsp::hal; +use bsp::logging; + +use embedded_hal::serial::Write; + +use rtic_monotonics::imxrt::Gpt1 as Mono; +use rtic_monotonics::imxrt::*; +use rtic_monotonics::Monotonic; + +#[rtic::app(device = teensy4_bsp, dispatchers = [LPSPI1])] +mod app { + use super::*; + + const LOG_POLL_INTERVAL: u32 = board::PERCLK_FREQUENCY / 100; + const LOG_DMA_CHANNEL: usize = 0; + + #[shared] + struct Shared {} + + #[local] + struct Local { + led: board::Led, + poll_log: hal::pit::Pit<3>, + log_poller: logging::Poller, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let board::Resources { + mut dma, + pit: (_, _, _, mut poll_log), + pins, + lpuart6, + mut gpio2, + mut gpt1, + .. + } = board::t40(cx.device); + + // Logging + let log_dma = dma[LOG_DMA_CHANNEL].take().unwrap(); + let mut log_uart = board::lpuart(lpuart6, pins.p1, pins.p0, 115200); + for &ch in "\r\n===== Teensy4 Rtic Blinky =====\r\n\r\n".as_bytes() { + nb::block!(log_uart.write(ch)).unwrap(); + } + nb::block!(log_uart.flush()).unwrap(); + let log_poller = + logging::log::lpuart(log_uart, log_dma, logging::Interrupts::Enabled).unwrap(); + poll_log.set_interrupt_enable(true); + poll_log.set_load_timer_value(LOG_POLL_INTERVAL); + poll_log.enable(); + + // Initialize the systick interrupt & obtain the token to prove that we did + gpt1.set_clock_source(hal::gpt::ClockSource::PeripheralClock); + let gpt1_mono_token = rtic_monotonics::create_imxrt_gpt1_token!(); + Mono::start(board::PERCLK_FREQUENCY, gpt1.release(), gpt1_mono_token); + + // Setup LED + let led = board::led(&mut gpio2, pins.p13); + led.set(); + + // Schedule the blinking task + blink::spawn().ok(); + + ( + Shared {}, + Local { + log_poller, + poll_log, + led, + }, + ) + } + + #[task(local = [led])] + async fn blink(cx: blink::Context) { + let blink::LocalResources { led, .. } = cx.local; + + let mut next_update = Mono::now(); + + loop { + led.toggle(); + log::info!("Time: {}", Mono::now()); + next_update += 1000.millis(); + Mono::delay_until(next_update).await; + } + } + + #[task(binds = PIT, priority = 1, local = [poll_log, log_poller])] + fn logger(cx: logger::Context) { + let logger::LocalResources { + poll_log, + log_poller, + .. + } = cx.local; + + if poll_log.is_elapsed() { + poll_log.clear_elapsed(); + + log_poller.poll(); + } + } +} diff --git a/examples/teensy4_blinky/run.py b/examples/teensy4_blinky/run.py new file mode 100644 index 0000000000..1e2fbf3925 --- /dev/null +++ b/examples/teensy4_blinky/run.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +from pathlib import Path +from tempfile import TemporaryDirectory + +import subprocess +import sys + + +def main(): + if len(sys.argv) < 2: + print("Please provide the binary as first argument!") + exit(1) + + binary = sys.argv[1] + print(f"Flashing {binary} ...") + + with TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + hexfile = tmpdir / "firmware.hex" + + subprocess.run(["rust-objcopy", "-O", "ihex", binary, hexfile], check=True) + subprocess.run(["teensy_loader_cli", "--mcu=imxrt1062", "-wv", hexfile], check=True) + + print("Teensy successfully flashed.") + + +if __name__ == "__main__": + main() diff --git a/examples/teensy4_blinky/src/main.rs b/examples/teensy4_blinky/src/main.rs new file mode 100644 index 0000000000..1b95e7b082 --- /dev/null +++ b/examples/teensy4_blinky/src/main.rs @@ -0,0 +1,62 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +#[panic_handler] +fn panic(_: &::core::panic::PanicInfo) -> ! { + ::teensy4_panic::sos() +} + +use teensy4_bsp::{board, hal}; + +use rtic_monotonics::imxrt::Gpt1 as Mono; +use rtic_monotonics::imxrt::*; + +#[rtic::app(device = teensy4_bsp, dispatchers = [LPSPI1])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + led: board::Led, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let board::Resources { + pins, + mut gpio2, + mut gpt1, + .. + } = board::t40(cx.device); + + // Initialize the systick interrupt & obtain the token to prove that we did + gpt1.set_clock_source(hal::gpt::ClockSource::PeripheralClock); + let gpt1_mono_token = rtic_monotonics::create_imxrt_gpt1_token!(); + Mono::start(board::PERCLK_FREQUENCY, gpt1.release(), gpt1_mono_token); + + // Setup LED + let led = board::led(&mut gpio2, pins.p13); + led.set(); + + // Schedule the blinking task + blink::spawn().ok(); + + (Shared {}, Local { led }) + } + + #[task(local = [led])] + async fn blink(cx: blink::Context) { + let blink::LocalResources { led, .. } = cx.local; + + loop { + led.toggle(); + Mono::delay(1000.millis()).await; + } + } +} diff --git a/rtic-monotonics/CHANGELOG.md b/rtic-monotonics/CHANGELOG.md index 327448c4ee..cd8817dfbb 100644 --- a/rtic-monotonics/CHANGELOG.md +++ b/rtic-monotonics/CHANGELOG.md @@ -7,6 +7,10 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top! ## Unreleased +### Added + +- i.MX RT support + ### Fixed - Fix STM32 rollover race condition diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml index 1d9138d3e1..7470ed9a6d 100644 --- a/rtic-monotonics/Cargo.toml +++ b/rtic-monotonics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rtic-monotonics" -version = "1.2.0" +version = "1.2.1" edition = "2021" authors = [ @@ -45,6 +45,9 @@ nrf9160-pac = { version = "0.12.2", optional = true } # STM32 stm32-metapac = { version = "14.0.0", optional = true } +# i.MX RT +imxrt-ral = { version = "0.5.3", optional = true } + [build-dependencies] proc-macro2 = { version = "1.0.36", optional = true } quote = { version = "1.0.15", optional = true } @@ -74,6 +77,12 @@ nrf5340-app = ["dep:cortex-m", "dep:nrf5340-app-pac", "dep:critical-section"] nrf5340-net = ["dep:cortex-m", "dep:nrf5340-net-pac", "dep:critical-section"] nrf9160 = ["dep:cortex-m", "dep:nrf9160-pac", "dep:critical-section"] +# i.MX RT Timers +# Use as `features = ["imxrt_gpt1"]` +imxrt = ["dep:cortex-m", "dep:imxrt-ral"] +imxrt_gpt1 = ["imxrt"] +imxrt_gpt2 = ["imxrt"] + # STM32 timers # Use as `features = ["stm32g081kb", "stm32_tim15"]` stm32_tim2 = [] diff --git a/rtic-monotonics/src/imxrt.rs b/rtic-monotonics/src/imxrt.rs new file mode 100644 index 0000000000..ee7e2ffed8 --- /dev/null +++ b/rtic-monotonics/src/imxrt.rs @@ -0,0 +1,300 @@ +//! [`Monotonic`] impl for the i.MX RT. +//! +//! # Example +//! +//! ``` +//! use rtic_monotonics::imxrt::*; +//! use rtic_monotonics::imxrt::Gpt1 as Mono; +//! +//! fn init() { +//! // Obtain ownership of the timer register block +//! let gpt1 = unsafe { imxrt_ral::gpt::GPT1::instance() }; +//! +//! // Configure the timer clock source and determine its tick rate +//! let timer_tickrate_hz = 1_000_000; +//! +//! // Generate timer token to ensure correct timer interrupt handler is used +//! let token = rtic_monotonics::create_imxrt_gpt1_token!(); +//! +//! // Start the monotonic +//! Mono::start(timer_tickrate_hz, gpt1, token); +//! } +//! +//! async fn usage() { +//! loop { +//! // Use the monotonic +//! let timestamp = Mono::now().ticks(); +//! Mono::delay(100.millis()).await; +//! } +//! } +//! ``` + +use crate::{Monotonic, TimeoutError, TimerQueue}; +use atomic_polyfill::{compiler_fence, AtomicU32, Ordering}; +pub use fugit::{self, ExtU64}; + +use imxrt_ral as ral; + +const TIMER_HZ: u32 = 1_000_000; + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_imxrt_timer_interrupt { + ($mono_timer:ident, $timer:ident, $timer_token:ident) => {{ + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn $timer() { + $crate::imxrt::$mono_timer::__tq().on_monotonic_interrupt(); + } + + pub struct $timer_token; + + unsafe impl $crate::InterruptToken<$crate::imxrt::$mono_timer> for $timer_token {} + + $timer_token + }}; +} + +/// Register GPT1 interrupt for the monotonic. +#[cfg(feature = "imxrt_gpt1")] +#[macro_export] +macro_rules! create_imxrt_gpt1_token { + () => {{ + $crate::__internal_create_imxrt_timer_interrupt!(Gpt1, GPT1, Gpt1Token) + }}; +} + +/// Register GPT2 interrupt for the monotonic. +#[cfg(feature = "imxrt_gpt2")] +#[macro_export] +macro_rules! create_imxrt_gpt2_token { + () => {{ + $crate::__internal_create_imxrt_timer_interrupt!(Gpt2, GPT2, Gpt2Token) + }}; +} + +// Credits to the `time-driver` of `embassy-stm32`. +// +// Clock timekeeping works with something we call "periods", which are time intervals +// of 2^31 ticks. The Clock counter value is 32 bits, so one "overflow cycle" is 2 periods. +// +// A `period` count is maintained in parallel to the Timer hardware `counter`, like this: +// - `period` and `counter` start at 0 +// - `period` is incremented on overflow (at counter value 0) +// - `period` is incremented "midway" between overflows (at counter value 0x8000_0000) +// +// Therefore, when `period` is even, counter is in 0..0x7FFF_FFFF. When odd, counter is in 0x8000_0000..0xFFFF_FFFF +// This allows for now() to return the correct value even if it races an overflow. +// +// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +// the expected range for the `period` parity, we're done. If it doesn't, this means that +// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +// corresponds to the next period. +// +// `period` is a 32bit integer, so it overflows on 2^32 * 2^31 / 1_000_000 seconds of uptime, which is 292471 years. +fn calc_now(period: u32, counter: u32) -> u64 { + (u64::from(period) << 31) + u64::from(counter ^ ((period & 1) << 31)) +} + +macro_rules! make_timer { + ($mono_name:ident, $timer:ident, $period:ident, $tq:ident$(, doc: ($($doc:tt)*))?) => { + /// Monotonic timer queue implementation. + $( + #[cfg_attr(docsrs, doc(cfg($($doc)*)))] + )? + + pub struct $mono_name; + + use ral::gpt::$timer; + + /// Number of 2^31 periods elapsed since boot. + static $period: AtomicU32 = AtomicU32::new(0); + static $tq: TimerQueue<$mono_name> = TimerQueue::new(); + + impl $mono_name { + /// Starts the monotonic timer. + /// - `tick_freq_hz`: The tick frequency of the given timer. + /// - `gpt`: The GPT timer register block instance. + /// - `_interrupt_token`: Required for correct timer interrupt handling. + /// This method must be called only once. + pub fn start(tick_freq_hz: u32, gpt: $timer, _interrupt_token: impl crate::InterruptToken) { + // Find a prescaler that creates our desired tick frequency + let previous_prescaler = ral::read_reg!(ral::gpt, gpt, PR, PRESCALER) + 1; + let previous_clock_freq = tick_freq_hz * previous_prescaler; + assert!((previous_clock_freq % TIMER_HZ) == 0, + "Unable to find a fitting prescaler value!\n Input: {}/{}\n Desired: {}", + previous_clock_freq, previous_prescaler, TIMER_HZ); + let prescaler = previous_clock_freq / TIMER_HZ; + assert!(prescaler > 0); + assert!(prescaler <= 4096); + + // Disable the timer. + ral::modify_reg!(ral::gpt, gpt, CR, EN: 0); + // Clear all status registers. + ral::write_reg!(ral::gpt, gpt, SR, 0b11_1111); + + // Base configuration + ral::modify_reg!(ral::gpt, gpt, CR, + ENMOD: 1, // Clear timer state + FRR: 1, // Free-Run mode + ); + + // Reset period + $period.store(0, Ordering::Relaxed); + + // Prescaler + ral::modify_reg!(ral::gpt, gpt, PR, + PRESCALER: (prescaler - 1), // Scale to our desired clock rate + ); + + // Enable interrupts + ral::write_reg!(ral::gpt, gpt, IR, + ROVIE: 1, // Rollover interrupt + OF1IE: 1, // Timer compare 1 interrupt (for half-periods) + OF2IE: 1, // Timer compare 2 interrupt (for dynamic wakeup) + ); + + // Configure half-period interrupt + ral::write_reg!(ral::gpt, gpt, OCR[0], 0x8000_0000); + + // Dynamic interrupt register; for now initialize to zero + // so it gets combined with rollover interrupt + ral::write_reg!(ral::gpt, gpt, OCR[1], 0x0000_0000); + + // Enable the timer + ral::modify_reg!(ral::gpt, gpt, CR, EN: 1); + ral::modify_reg!(ral::gpt, gpt, CR, + ENMOD: 0, // Keep state when disabled + ); + + $tq.initialize(Self {}); + + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + crate::set_monotonic_prio(ral::NVIC_PRIO_BITS, ral::Interrupt::$timer); + cortex_m::peripheral::NVIC::unmask(ral::Interrupt::$timer); + } + } + + /// Used to access the underlying timer queue + #[doc(hidden)] + pub fn __tq() -> &'static TimerQueue<$mono_name> { + &$tq + } + + /// Delay for some duration of time. + #[inline] + pub async fn delay(duration: ::Duration) { + $tq.delay(duration).await; + } + + /// Timeout at a specific time. + pub async fn timeout_at( + instant: ::Instant, + future: F, + ) -> Result { + $tq.timeout_at(instant, future).await + } + + /// Timeout after a specific duration. + #[inline] + pub async fn timeout_after( + duration: ::Duration, + future: F, + ) -> Result { + $tq.timeout_after(duration, future).await + } + + /// Delay to some specific time instant. + #[inline] + pub async fn delay_until(instant: ::Instant) { + $tq.delay_until(instant).await; + } + } + + #[cfg(feature = "embedded-hal-async")] + impl embedded_hal_async::delay::DelayUs for $mono_name { + #[inline] + async fn delay_us(&mut self, us: u32) { + Self::delay((us as u64).micros()).await; + } + + #[inline] + async fn delay_ms(&mut self, ms: u32) { + Self::delay((ms as u64).millis()).await; + } + } + + impl embedded_hal::delay::DelayUs for $mono_name { + fn delay_us(&mut self, us: u32) { + let done = Self::now() + (us as u64).micros(); + while Self::now() < done {} + } + } + + impl Monotonic for $mono_name { + type Instant = fugit::TimerInstantU64; + type Duration = fugit::TimerDurationU64; + + const ZERO: Self::Instant = Self::Instant::from_ticks(0); + + fn now() -> Self::Instant { + let gpt = unsafe{ $timer::instance() }; + + // Important: period **must** be read first. + let period = $period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = ral::read_reg!(ral::gpt, gpt, CNT); + + Self::Instant::from_ticks(calc_now(period, counter)) + } + + fn set_compare(instant: Self::Instant) { + let gpt = unsafe{ $timer::instance() }; + + // Set the timer regardless of whether it is multiple periods in the future, + // or even already in the past. + // The worst thing that can happen is a spurious wakeup, and with a timer + // period of half an hour, this is hardly a problem. + + let ticks = instant.duration_since_epoch().ticks(); + let ticks_wrapped = ticks as u32; + + ral::write_reg!(ral::gpt, gpt, OCR[1], ticks_wrapped); + } + + fn clear_compare_flag() { + let gpt = unsafe{ $timer::instance() }; + ral::write_reg!(ral::gpt, gpt, SR, OF2: 1); + } + + fn pend_interrupt() { + cortex_m::peripheral::NVIC::pend(ral::Interrupt::$timer); + } + + fn on_interrupt() { + let gpt = unsafe{ $timer::instance() }; + + let (rollover, half_rollover) = ral::read_reg!(ral::gpt, gpt, SR, ROV, OF1); + + if rollover != 0 { + $period.fetch_add(1, Ordering::Relaxed); + ral::write_reg!(ral::gpt, gpt, SR, ROV: 1); + } + + if half_rollover != 0 { + $period.fetch_add(1, Ordering::Relaxed); + ral::write_reg!(ral::gpt, gpt, SR, OF1: 1); + } + } + } + }; +} + +#[cfg(feature = "imxrt_gpt1")] +make_timer!(Gpt1, GPT1, GPT1_HALFPERIODS, GPT1_TQ); + +#[cfg(feature = "imxrt_gpt2")] +make_timer!(Gpt2, GPT2, GPT2_HALFPERIODS, GPT2_TQ); diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index 8757eb8595..69925cba11 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -33,6 +33,9 @@ pub mod systick; #[cfg(feature = "rp2040")] pub mod rp2040; +#[cfg(feature = "imxrt")] +pub mod imxrt; + #[cfg(any( feature = "nrf52810", feature = "nrf52811", @@ -64,6 +67,7 @@ pub(crate) const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { feature = "nrf5340-app", feature = "nrf5340-net", feature = "nrf9160", + feature = "imxrt", stm32, ))] pub(crate) unsafe fn set_monotonic_prio(