From 1ad5d5a771d5109a763361454fb724b85ae25fdd Mon Sep 17 00:00:00 2001 From: i509VCB Date: Wed, 9 Jul 2025 23:08:59 -0500 Subject: [PATCH] nxp: Add MIMXRT1011 GPIO and time driver PIT is used for the time driver --- .vscode/settings.json | 1 + ci.sh | 2 + embassy-nxp/Cargo.toml | 36 +- embassy-nxp/build.rs | 136 ++++ embassy-nxp/build_common.rs | 94 +++ embassy-nxp/src/chips/mimxrt1011.rs | 113 ++++ embassy-nxp/src/gpio.rs | 2 + embassy-nxp/src/gpio/rt1xxx.rs | 895 +++++++++++++++++++++++++ embassy-nxp/src/lib.rs | 87 ++- embassy-nxp/src/time_driver/pit.rs | 187 ++++++ examples/mimxrt1011/.cargo/config.toml | 8 + examples/mimxrt1011/Cargo.toml | 29 + examples/mimxrt1011/build.rs | 14 + examples/mimxrt1011/src/bin/blinky.rs | 48 ++ examples/mimxrt1011/src/bin/button.rs | 62 ++ examples/mimxrt1011/src/lib.rs | 75 +++ 16 files changed, 1780 insertions(+), 9 deletions(-) create mode 100644 embassy-nxp/build.rs create mode 100644 embassy-nxp/build_common.rs create mode 100644 embassy-nxp/src/chips/mimxrt1011.rs create mode 100644 embassy-nxp/src/gpio/rt1xxx.rs create mode 100644 embassy-nxp/src/time_driver/pit.rs create mode 100644 examples/mimxrt1011/.cargo/config.toml create mode 100644 examples/mimxrt1011/Cargo.toml create mode 100644 examples/mimxrt1011/build.rs create mode 100644 examples/mimxrt1011/src/bin/blinky.rs create mode 100644 examples/mimxrt1011/src/bin/button.rs create mode 100644 examples/mimxrt1011/src/lib.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index e4814ff27..070e8fbd3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ // "examples/nrf52840-rtic/Cargo.toml", // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", + // "examples/mimxrt1011/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", // "examples/stm32c0/Cargo.toml", diff --git a/ci.sh b/ci.sh index 9d3e47a41..e225bc7c9 100755 --- a/ci.sh +++ b/ci.sh @@ -181,6 +181,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf --features lpc55,defmt \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv7em-none-eabihf --features mimxrt1011,defmt,time-driver-pit \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0c1104dgs20,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507pm,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3519pz,defmt,time-driver-any \ @@ -264,6 +265,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ + --- build --release --manifest-path examples/mimxrt1011/Cargo.toml --target thumbv7em-none-eabihf --artifact-dir out/examples/mimxrt1011 \ --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 56d00bfb2..625906183 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -11,22 +11,50 @@ embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", fe embassy-sync = { version = "0.7.0", path = "../embassy-sync" } defmt = { version = "1", optional = true } log = { version = "0.4.27", optional = true } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } +nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e" } + +imxrt-rt = { version = "0.1.7", optional = true, features = ["device"] } + +[build-dependencies] +cfg_aliases = "0.2.1" +nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e", features = ["metadata"], optional = true } +proc-macro2 = "1.0.95" +quote = "1.0.15" [features] default = ["rt"] -# Enable PACs as optional dependencies, since some chip families will use different pac crates. -rt = ["lpc55-pac?/rt"] +# Enable PACs as optional dependencies, since some chip families will use different pac crates (temporarily). +rt = ["lpc55-pac?/rt", "nxp-pac?/rt"] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt"] + +log = ["dep:log"] + +## Use Periodic Interrupt Timer (PIT) as the time driver for `embassy-time`, with a tick rate of 1 MHz +time-driver-pit = ["_time_driver", "embassy-time?/tick-hz-1_000_000"] + ## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable) unstable-pac = [] -# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. +# This is unstable because semver-minor (non-breaking) releases of embassy-nxp may major-bump (breaking) the PAC version. # If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. # There are no plans to make this stable. +## internal use only +# +# This feature is unfortunately a hack around the fact that cfg_aliases cannot apply to the buildscript +# that creates the aliases. +_rt1xxx = [] + +# A timer driver is enabled. +_time_driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils"] + #! ### Chip selection features -lpc55 = ["lpc55-pac"] +lpc55 = ["dep:lpc55-pac"] +mimxrt1011 = ["nxp-pac/mimxrt1011", "_rt1xxx", "dep:imxrt-rt"] diff --git a/embassy-nxp/build.rs b/embassy-nxp/build.rs new file mode 100644 index 000000000..6c10d0e69 --- /dev/null +++ b/embassy-nxp/build.rs @@ -0,0 +1,136 @@ +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::{env, fs}; + +use cfg_aliases::cfg_aliases; +#[cfg(feature = "_rt1xxx")] +use nxp_pac::metadata; +#[allow(unused)] +use proc_macro2::TokenStream; +#[allow(unused)] +use quote::quote; + +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); + + let chip_name = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_MIMXRT") || x.starts_with("CARGO_FEATURE_LPC")) + .get_one() + { + Ok(x) => x, + Err(GetOneError::None) => panic!("No mimxrt/lpc Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple mimxrt/lpc Cargo features enabled"), + } + .strip_prefix("CARGO_FEATURE_") + .unwrap() + .to_ascii_lowercase(); + + cfg_aliases! { + rt1xxx: { feature = "mimxrt1011" }, + gpio1: { feature = "mimxrt1011" }, + gpio2: { feature = "mimxrt1011" }, + gpio5: { feature = "mimxrt1011" }, + } + + eprintln!("chip: {chip_name}"); + + generate_code(); +} + +#[cfg(feature = "_rt1xxx")] +fn generate_iomuxc() -> TokenStream { + use proc_macro2::{Ident, Span}; + + let pads = metadata::iomuxc::IOMUXC_REGISTERS.iter().map(|registers| { + let name = Ident::new(®isters.name, Span::call_site()); + let address = registers.pad_ctl; + + quote! { + pub const #name: u32 = #address; + } + }); + + let muxes = metadata::iomuxc::IOMUXC_REGISTERS.iter().map(|registers| { + let name = Ident::new(®isters.name, Span::call_site()); + let address = registers.mux_ctl; + + quote! { + pub const #name: u32 = #address; + } + }); + + quote! { + pub mod iomuxc { + pub mod pads { + #(#pads)* + } + + pub mod muxes { + #(#muxes)* + } + } + } +} + +fn generate_code() { + #[allow(unused)] + use std::fmt::Write; + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + #[allow(unused_mut)] + let mut output = String::new(); + + #[cfg(feature = "_rt1xxx")] + writeln!(&mut output, "{}", generate_iomuxc()).unwrap(); + + let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); + fs::write(&out_file, output).unwrap(); + rustfmt(&out_file); +} + +/// rustfmt a given path. +/// Failures are logged to stderr and ignored. +fn rustfmt(path: impl AsRef) { + let path = path.as_ref(); + match Command::new("rustfmt").args([path]).output() { + Err(e) => { + eprintln!("failed to exec rustfmt {:?}: {:?}", path, e); + } + Ok(out) => { + if !out.status.success() { + eprintln!("rustfmt {:?} failed:", path); + eprintln!("=== STDOUT:"); + std::io::stderr().write_all(&out.stdout).unwrap(); + eprintln!("=== STDERR:"); + std::io::stderr().write_all(&out.stderr).unwrap(); + } + } + } +} + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} diff --git a/embassy-nxp/build_common.rs b/embassy-nxp/build_common.rs new file mode 100644 index 000000000..4f24e6d37 --- /dev/null +++ b/embassy-nxp/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy-nxp/src/chips/mimxrt1011.rs b/embassy-nxp/src/chips/mimxrt1011.rs new file mode 100644 index 000000000..a74d953fc --- /dev/null +++ b/embassy-nxp/src/chips/mimxrt1011.rs @@ -0,0 +1,113 @@ +// This must be imported so that __preinit is defined. +use imxrt_rt as _; +pub use nxp_pac as pac; + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + GPIO_00, + GPIO_01, + GPIO_02, + GPIO_03, + GPIO_04, + GPIO_05, + GPIO_06, + GPIO_07, + GPIO_08, + GPIO_09, + GPIO_10, + GPIO_11, + GPIO_12, + GPIO_13, + GPIO_AD_00, + GPIO_AD_01, + GPIO_AD_02, + GPIO_AD_03, + GPIO_AD_04, + GPIO_AD_05, + GPIO_AD_06, + GPIO_AD_07, + GPIO_AD_08, + GPIO_AD_09, + GPIO_AD_10, + GPIO_AD_11, + GPIO_AD_12, + GPIO_AD_13, + GPIO_AD_14, + GPIO_SD_00, + GPIO_SD_01, + GPIO_SD_02, + GPIO_SD_03, + GPIO_SD_04, + GPIO_SD_05, + GPIO_SD_06, + GPIO_SD_07, + GPIO_SD_08, + GPIO_SD_09, + GPIO_SD_10, + GPIO_SD_11, + GPIO_SD_12, + GPIO_SD_13, + PMIC_ON_REQ, +} + +impl_gpio! { + // GPIO Bank 1 + GPIO_00(Gpio1, 0); + GPIO_01(Gpio1, 1); + GPIO_02(Gpio1, 2); + GPIO_03(Gpio1, 3); + GPIO_04(Gpio1, 4); + GPIO_05(Gpio1, 5); + GPIO_06(Gpio1, 6); + GPIO_07(Gpio1, 7); + GPIO_08(Gpio1, 8); + GPIO_09(Gpio1, 9); + GPIO_10(Gpio1, 10); + GPIO_11(Gpio1, 11); + GPIO_12(Gpio1, 12); + GPIO_13(Gpio1, 13); + GPIO_AD_00(Gpio1, 14); + GPIO_AD_01(Gpio1, 15); + GPIO_AD_02(Gpio1, 16); + GPIO_AD_03(Gpio1, 17); + GPIO_AD_04(Gpio1, 18); + GPIO_AD_05(Gpio1, 19); + GPIO_AD_06(Gpio1, 20); + GPIO_AD_07(Gpio1, 21); + GPIO_AD_08(Gpio1, 22); + GPIO_AD_09(Gpio1, 23); + GPIO_AD_10(Gpio1, 24); + GPIO_AD_11(Gpio1, 25); + GPIO_AD_12(Gpio1, 26); + GPIO_AD_13(Gpio1, 27); + GPIO_AD_14(Gpio1, 28); + + // GPIO Bank 2 + GPIO_SD_00(Gpio2, 0); + GPIO_SD_01(Gpio2, 1); + GPIO_SD_02(Gpio2, 2); + GPIO_SD_03(Gpio2, 3); + GPIO_SD_04(Gpio2, 4); + GPIO_SD_05(Gpio2, 5); + GPIO_SD_06(Gpio2, 6); + GPIO_SD_07(Gpio2, 7); + GPIO_SD_08(Gpio2, 8); + GPIO_SD_09(Gpio2, 9); + GPIO_SD_10(Gpio2, 10); + GPIO_SD_11(Gpio2, 11); + GPIO_SD_12(Gpio2, 12); + GPIO_SD_13(Gpio2, 13); + + // GPIO Bank 5 + PMIC_ON_REQ(Gpio5, 0); +} + +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} diff --git a/embassy-nxp/src/gpio.rs b/embassy-nxp/src/gpio.rs index 809903d97..3049cc12d 100644 --- a/embassy-nxp/src/gpio.rs +++ b/embassy-nxp/src/gpio.rs @@ -1,5 +1,7 @@ //! General purpose input/output (GPIO) driver. +#![macro_use] #[cfg_attr(feature = "lpc55", path = "./gpio/lpc55.rs")] +#[cfg_attr(rt1xxx, path = "./gpio/rt1xxx.rs")] mod inner; pub use inner::*; diff --git a/embassy-nxp/src/gpio/rt1xxx.rs b/embassy-nxp/src/gpio/rt1xxx.rs new file mode 100644 index 000000000..9c58e8a7d --- /dev/null +++ b/embassy-nxp/src/gpio/rt1xxx.rs @@ -0,0 +1,895 @@ +#![macro_use] + +use core::future::Future; +use core::ops::Not; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use nxp_pac::gpio::vals::Icr; +use nxp_pac::iomuxc::vals::Pus; + +use crate::chip::{mux_address, pad_address}; +use crate::pac::common::{Reg, RW}; +#[cfg(feature = "rt")] +use crate::pac::interrupt; +use crate::pac::iomuxc::regs::{Ctl, MuxCtl}; +#[cfg(gpio5)] +use crate::pac::{self, gpio::Gpio}; + +/// The GPIO pin level for pins set on "Digital" mode. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. Corresponds to 0V. + Low, + /// Logical high. Corresponds to VDD. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +impl Not for Level { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Level::Low => Level::High, + Level::High => Level::Low, + } + } +} + +/// Pull setting for a GPIO input set on "Digital" mode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + + // TODO: What Does PUE::KEEPER mean here? + + // 22 kOhm pull-up resistor. + Up22K, + + // 47 kOhm pull-up resistor. + Up47K, + + // 100 kOhm pull-up resistor. + Up100K, + + // 100 kOhm pull-down resistor. + Down100K, +} + +/// Drive strength of an output +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Drive { + Disabled, + _150R, + _75R, + _50R, + _37R, + _30R, + _25R, + _20R, +} + +/// Slew rate of an output +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SlewRate { + Slow, + + Fast, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Bank { + /// Bank 1 + #[cfg(gpio1)] + Gpio1, + + /// Bank 2 + #[cfg(gpio2)] + Gpio2, + + /// Bank 5 + #[cfg(gpio5)] + Gpio5, +} + +/// GPIO flexible pin. +/// +/// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +pub struct Flex<'d> { + pub(crate) pin: Peri<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>) -> Self { + Self { pin: pin.into() } + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + let (pke, pue, pus) = match pull { + Pull::None => (false, true, Pus::PUS_0_100K_OHM_PULL_DOWN), + Pull::Up22K => (true, true, Pus::PUS_3_22K_OHM_PULL_UP), + Pull::Up47K => (true, true, Pus::PUS_1_47K_OHM_PULL_UP), + Pull::Up100K => (true, true, Pus::PUS_2_100K_OHM_PULL_UP), + Pull::Down100K => (true, true, Pus::PUS_0_100K_OHM_PULL_DOWN), + }; + + self.pin.pad().modify(|w| { + w.set_pke(pke); + w.set_pue(pue); + w.set_pus(pus); + }); + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slewrate(&mut self, rate: SlewRate) { + self.pin.pad().modify(|w| { + w.set_sre(match rate { + SlewRate::Slow => false, + SlewRate::Fast => true, + }); + }); + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad().modify(|w| { + w.set_hys(enable); + }); + } + + /// Put the pin into input mode. + /// + /// The pull setting is left unchanged. + #[inline] + pub fn set_as_input(&mut self) { + self.pin.mux().modify(|w| { + w.set_mux_mode(GPIO_MUX_MODE); + }); + + // Setting direction is RMW + critical_section::with(|_cs| { + self.pin.block().gdir().modify(|w| { + w.set_gdir(self.pin.pin_number() as usize, false); + }); + }) + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self) { + self.pin.mux().modify(|w| { + w.set_mux_mode(GPIO_MUX_MODE); + }); + + // Setting direction is RMW + critical_section::with(|_cs| { + self.pin.block().gdir().modify(|w| { + w.set_gdir(self.pin.pin_number() as usize, true); + }); + }) + } + + /// Put the pin into input + open-drain output mode. + /// + /// The hardware will drive the line low if you set it to low, and will leave it floating if you set + /// it to high, in which case you can read the input to figure out whether another device + /// is driving the line low. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + /// + /// The internal weak pull-up and pull-down resistors will be disabled. + #[inline] + pub fn set_as_input_output(&mut self) { + self.pin.pad().modify(|w| { + w.set_ode(true); + }); + } + + /// Set the pin as "disconnected", ie doing nothing and consuming the lowest + /// amount of power possible. + /// + /// This is currently the same as [`Self::set_as_analog()`] but is semantically different + /// really. Drivers should `set_as_disconnected()` pins when dropped. + /// + /// Note that this also disables the pull-up and pull-down resistors. + #[inline] + pub fn set_as_disconnected(&mut self) { + self.pin.pad().modify(|w| { + w.set_ode(false); + w.set_pke(false); + w.set_pue(false); + w.set_pus(Pus::PUS_0_100K_OHM_PULL_DOWN); + }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.block().psr().read().psr(self.pin.pin_number() as usize) + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.block().dr_set().write(|w| { + w.set_dr_set(self.pin.pin_number() as usize, true); + }); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.block().dr_clear().write(|w| { + w.set_dr_clear(self.pin.pin_number() as usize, true); + }); + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.block().dr_toggle().write(|w| { + w.set_dr_toggle(self.pin.pin_number() as usize, true); + }); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Get the current pin output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Is the output level high? + /// + /// If the [`Flex`] is set as an input, then this is equivalent to [`Flex::is_high`]. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.block().dr().read().dr(self.pin.pin_number() as usize) + } + + /// Is the output level low? + /// + /// If the [`Flex`] is set as an input, then this is equivalent to [`Flex::is_low`]. + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::High).await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::Low).await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::RisingEdge).await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::FallingEdge).await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::AnyEdge).await + } +} + +impl<'d> Drop for Flex<'d> { + fn drop(&mut self) { + self.set_as_disconnected(); + } +} + +/// GPIO input driver. +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + pin.set_pull(pull); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +/// GPIO output driver. +/// +/// Note that pins will **return to their floating state** when `Output` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `Output`, or pass it to [`core::mem::forget`]. +pub struct Output<'d> { + pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + pin.set_level(initial_output); + Self { pin } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle(); + } +} + +/// GPIO output open-drain driver. +/// +/// Note that pins will **return to their floating state** when `OutputOpenDrain` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `OutputOpenDrain`, or pass it to [`core::mem::forget`]. +pub struct OutputOpenDrain<'d> { + pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_level(initial_output); + pin.set_as_input_output(); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.pin.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level); + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +#[allow(private_bounds)] +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self.pin_number() + } + + #[inline] + fn bank(&self) -> Bank { + self._bank() + } +} + +/// Type-erased GPIO pin. +pub struct AnyPin { + pub(crate) pin_number: u8, + pub(crate) bank: Bank, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(bank: Bank, pin_number: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_number, bank }) + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_number(&self) -> u8 { + self.pin_number + } + + #[inline] + fn _bank(&self) -> Bank { + self.bank + } +} + +// Impl details + +/// Mux mode for GPIO pins. This is constant across all RT1xxx parts. +const GPIO_MUX_MODE: u8 = 0b101; + +// FIXME: These don't always need to be 32 entries. GPIO5 on RT1101 contains a single pin and GPIO2 only 14. +#[cfg(gpio1)] +static GPIO1_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio2)] +static GPIO2_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio5)] +static GPIO5_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; + +/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate. +pub(crate) trait SealedPin: Sized { + fn pin_number(&self) -> u8; + + fn _bank(&self) -> Bank; + + #[inline] + fn block(&self) -> Gpio { + match self._bank() { + #[cfg(gpio1)] + Bank::Gpio1 => pac::GPIO1, + #[cfg(gpio2)] + Bank::Gpio2 => pac::GPIO2, + #[cfg(gpio5)] + Bank::Gpio5 => pac::GPIO5, + } + } + + #[inline] + fn mux(&self) -> Reg { + // SAFETY: The generated mux address table is valid since it is generated from the SVD files. + let address = unsafe { mux_address(self._bank(), self.pin_number()).unwrap_unchecked() }; + + // SAFETY: The register at the address is an instance of MuxCtl. + unsafe { Reg::from_ptr(address as *mut _) } + } + + #[inline] + fn pad(&self) -> Reg { + // SAFETY: The generated pad address table is valid since it is generated from the SVD files. + let address = unsafe { pad_address(self._bank(), self.pin_number()).unwrap_unchecked() }; + + // SAFETY: The register at the address is an instance of Ctl. + unsafe { Reg::from_ptr(address as *mut _) } + } + + fn waker(&self) -> &AtomicWaker { + match self._bank() { + #[cfg(gpio1)] + Bank::Gpio1 => &GPIO1_WAKERS[self.pin_number() as usize], + #[cfg(gpio2)] + Bank::Gpio2 => &GPIO2_WAKERS[self.pin_number() as usize], + #[cfg(gpio5)] + Bank::Gpio5 => &GPIO5_WAKERS[self.pin_number() as usize], + } + } +} + +/// This enum matches the layout of Icr. +enum InterruptConfiguration { + Low, + High, + RisingEdge, + FallingEdge, + AnyEdge, +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: Peri<'d, AnyPin>, config: InterruptConfiguration) -> Self { + let block = pin.block(); + + let (icr, edge_sel) = match config { + InterruptConfiguration::Low => (Icr::LOW_LEVEL, false), + InterruptConfiguration::High => (Icr::HIGH_LEVEL, false), + InterruptConfiguration::RisingEdge => (Icr::RISING_EDGE, false), + InterruptConfiguration::FallingEdge => (Icr::FALLING_EDGE, false), + InterruptConfiguration::AnyEdge => (Icr::FALLING_EDGE, true), + }; + + let index = if pin.pin_number() > 15 { 1 } else { 0 }; + + // Interrupt configuration performs RMW + critical_section::with(|_cs| { + // Disable interrupt so a level/edge detection change does not cause ISR to be set. + block.imr().modify(|w| { + w.set_imr(pin.pin_number() as usize, false); + }); + + block.icr(index).modify(|w| { + w.set_pin(pin.pin_number() as usize, icr); + }); + + block.edge_sel().modify(|w| { + w.set_edge_sel(pin.pin_number() as usize, edge_sel); + }); + + // Clear the previous interrupt. + block.isr().modify(|w| { + // "Status flags are cleared by writing a 1 to the corresponding bit position." + w.set_isr(pin.pin_number() as usize, true); + }); + }); + + Self { pin } + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + let waker = self.pin.waker(); + waker.register(cx.waker()); + + // Enabling interrupt is RMW + critical_section::with(|_cs| { + self.pin.block().imr().modify(|w| { + w.set_imr(self.pin.pin_number() as usize, true); + }); + }); + + let isr = self.pin.block().isr().read(); + + if isr.isr(self.pin.pin_number() as usize) { + return Poll::Ready(()); + } + + Poll::Pending + } +} + +/// A macro to generate all GPIO pins. +/// +/// This generates a lookup table for IOMUX register addresses. +macro_rules! impl_gpio { + ( + $($name: ident($bank: ident, $pin_number: expr);)* + ) => { + #[inline] + pub(crate) const fn pad_address(bank: crate::gpio::Bank, pin: u8) -> Option { + match (bank, pin) { + $( + (crate::gpio::Bank::$bank, $pin_number) => Some(crate::chip::_generated::iomuxc::pads::$name), + )* + _ => None + } + } + + #[inline] + pub(crate) const fn mux_address(bank: crate::gpio::Bank, pin: u8) -> Option { + match (bank, pin) { + $( + (crate::gpio::Bank::$bank, $pin_number) => Some(crate::chip::_generated::iomuxc::muxes::$name), + )* + _ => None + } + } + + $( + impl_pin!($name, $bank, $pin_number); + )* + }; +} + +macro_rules! impl_pin { + ($name: ident, $bank: ident, $pin_num: expr) => { + impl crate::gpio::Pin for crate::peripherals::$name {} + impl crate::gpio::SealedPin for crate::peripherals::$name { + #[inline] + fn pin_number(&self) -> u8 { + $pin_num + } + + #[inline] + fn _bank(&self) -> crate::gpio::Bank { + crate::gpio::Bank::$bank + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + use crate::gpio::SealedPin; + + Self { + pin_number: val.pin_number(), + bank: val._bank(), + } + } + } + }; +} + +pub(crate) fn init() { + #[cfg(feature = "rt")] + unsafe { + use embassy_hal_internal::interrupt::InterruptExt; + + pac::Interrupt::GPIO1_COMBINED_0_15.enable(); + pac::Interrupt::GPIO1_COMBINED_16_31.enable(); + pac::Interrupt::GPIO2_COMBINED_0_15.enable(); + pac::Interrupt::GPIO5_COMBINED_0_15.enable(); + } +} + +/// IRQ handler for GPIO pins. +/// +/// If `high_bits` is false, then the interrupt is for pins 0 through 15. If true, then the interrupt +/// is for pins 16 through 31 +#[cfg(feature = "rt")] +fn irq_handler(block: Gpio, wakers: &[AtomicWaker; 32], high_bits: bool) { + use crate::BitIter; + + let isr = block.isr().read().0; + let imr = block.imr().read().0; + let mask = if high_bits { 0xFFFF_0000 } else { 0x0000_FFFF }; + let bits = isr & imr & mask; + + for bit in BitIter(bits) { + wakers[bit as usize].wake(); + + // Disable further interrupts for this pin. The input future will check ISR (which is kept + // until reset). + block.imr().modify(|w| { + w.set_imr(bit as usize, false); + }); + } +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO1_COMBINED_0_15() { + irq_handler(pac::GPIO1, &GPIO1_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO1_COMBINED_16_31() { + irq_handler(pac::GPIO1, &GPIO1_WAKERS, true); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO2_COMBINED_0_15() { + irq_handler(pac::GPIO2, &GPIO2_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO5_COMBINED_0_15() { + irq_handler(pac::GPIO5, &GPIO5_WAKERS, false); +} diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 1abaca708..a715770c4 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -1,12 +1,19 @@ #![no_std] -pub mod fmt; +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + pub mod gpio; #[cfg(feature = "lpc55")] pub mod pint; +#[cfg(feature = "_time_driver")] +#[cfg_attr(feature = "time-driver-pit", path = "time_driver/pit.rs")] +mod time_driver; + // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "lpc55", path = "chips/lpc55.rs")] +#[cfg_attr(feature = "mimxrt1011", path = "chips/mimxrt1011.rs")] mod chip; #[cfg(feature = "unstable-pac")] @@ -22,13 +29,66 @@ pub use embassy_hal_internal::{Peri, PeripheralType}; /// /// This should only be called once and at startup, otherwise it panics. pub fn init(_config: config::Config) -> Peripherals { - #[cfg(feature = "lpc55")] + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + #[cfg(feature = "mimxrt1011")] { - gpio::init(); - pint::init(); + // The RT1010 Reference manual states that core clock root must be switched before + // reprogramming PLL2. + pac::CCM.cbcdr().modify(|w| { + w.set_periph_clk_sel(pac::ccm::vals::PeriphClkSel::PERIPH_CLK_SEL_1); + }); + + while matches!( + pac::CCM.cdhipr().read().periph_clk_sel_busy(), + pac::ccm::vals::PeriphClkSelBusy::PERIPH_CLK_SEL_BUSY_1 + ) {} + + info!("Core clock root switched"); + + // 480 * 18 / 24 = 360 + pac::CCM_ANALOG.pfd_480().modify(|x| x.set_pfd2_frac(12)); + + //480*18/24(pfd0)/4 + pac::CCM_ANALOG.pfd_480().modify(|x| x.set_pfd0_frac(24)); + pac::CCM.cscmr1().modify(|x| x.set_flexspi_podf(3.into())); + + // CPU Core + pac::CCM_ANALOG.pfd_528().modify(|x| x.set_pfd3_frac(18)); + cortex_m::asm::delay(500_000); + + // Clock core clock with PLL 2. + pac::CCM + .cbcdr() + .modify(|x| x.set_periph_clk_sel(pac::ccm::vals::PeriphClkSel::PERIPH_CLK_SEL_0)); // false + + while matches!( + pac::CCM.cdhipr().read().periph_clk_sel_busy(), + pac::ccm::vals::PeriphClkSelBusy::PERIPH_CLK_SEL_BUSY_1 + ) {} + + pac::CCM + .cbcmr() + .write(|v| v.set_pre_periph_clk_sel(pac::ccm::vals::PrePeriphClkSel::PRE_PERIPH_CLK_SEL_0)); + + // TODO: Some for USB PLLs + + // DCDC clock? + pac::CCM.ccgr6().modify(|v| v.set_cg0(1)); } - crate::Peripherals::take() + #[cfg(any(feature = "lpc55", rt1xxx))] + gpio::init(); + + #[cfg(feature = "lpc55")] + pint::init(); + + #[cfg(feature = "_time_driver")] + time_driver::init(); + + peripherals } /// HAL configuration for the NXP board. @@ -36,3 +96,20 @@ pub mod config { #[derive(Default)] pub struct Config {} } + +#[allow(unused)] +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} diff --git a/embassy-nxp/src/time_driver/pit.rs b/embassy-nxp/src/time_driver/pit.rs new file mode 100644 index 000000000..985e5e815 --- /dev/null +++ b/embassy-nxp/src/time_driver/pit.rs @@ -0,0 +1,187 @@ +//! Time driver using Periodic Interrupt Timer (PIT) +//! +//! This driver is used with the iMXRT1xxx parts. +//! +//! The PIT is run in lifetime mode. Timer 1 is chained to timer 0 to provide a free-running 64-bit timer. +//! The 64-bit timer is used to track how many ticks since boot. +//! +//! Timer 2 counts how many ticks there are within the current u32::MAX tick period. Timer 2 is restarted when +//! a new alarm is set (or every u32::MAX ticks). One caveat is that an alarm could be a few ticks late due to +//! restart. However the Cortex-M7 cores run at 500 MHz easily and the PIT will generally run at 1 MHz or lower. +//! Along with the fact that scheduling an alarm takes a critical section worst case an alarm may be a few +//! microseconds late. +//! +//! All PIT timers are clocked in lockstep, so the late start will not cause the now() count to drift. + +use core::cell::{Cell, RefCell}; +use core::task::Waker; + +use critical_section::{CriticalSection, Mutex}; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_time_driver::Driver as _; +use embassy_time_queue_utils::Queue; + +use crate::pac::{self, interrupt}; + +struct Driver { + alarm: Mutex>, + queue: Mutex>, +} + +impl embassy_time_driver::Driver for Driver { + fn now(&self) -> u64 { + loop { + // Even though reading LTMR64H will latch LTMR64L if another thread preempts between any of the + // three reads and calls now() then the value in LTMR64L will be wrong when execution returns to + // thread which was preempted. + let hi = pac::PIT.ltmr64h().read().lth(); + let lo = pac::PIT.ltmr64l().read().ltl(); + let hi2 = pac::PIT.ltmr64h().read().lth(); + + if hi == hi2 { + // PIT timers always count down. + return u64::MAX - ((hi as u64) << 32 | (lo as u64)); + } + } + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +impl Driver { + fn init(&'static self) { + // Disable PIT clock during mux configuration. + pac::CCM.ccgr1().modify(|r| r.set_cg6(0b00)); + + // TODO: This forces the PIT to be driven by the oscillator. However that isn't the only option as you + // could divide the clock root by up to 64. + pac::CCM.cscmr1().modify(|r| { + // 1 MHz + r.set_perclk_podf(pac::ccm::vals::PerclkPodf::DIVIDE_24); + r.set_perclk_clk_sel(pac::ccm::vals::PerclkClkSel::PERCLK_CLK_SEL_1); + }); + + pac::CCM.ccgr1().modify(|r| r.set_cg6(0b11)); + + // Disable clock during init. + // + // It is important that the PIT clock is prepared to not exceed limit (50 MHz on RT1011), or else + // you will need to recover the device with boot mode switches when using any PIT registers. + pac::PIT.mcr().modify(|w| { + w.set_mdis(true); + }); + + pac::PIT.timer(0).ldval().write_value(u32::MAX); + pac::PIT.timer(1).ldval().write_value(u32::MAX); + pac::PIT.timer(2).ldval().write_value(0); + pac::PIT.timer(3).ldval().write_value(0); + + pac::PIT.timer(1).tctrl().write(|w| { + // In lifetime mode, timer 1 is chained to timer 0 to form a 64-bit timer. + w.set_chn(true); + w.set_ten(true); + w.set_tie(false); + }); + + pac::PIT.timer(0).tctrl().write(|w| { + w.set_chn(false); + w.set_ten(true); + w.set_tie(false); + }); + + pac::PIT.timer(2).tctrl().write(|w| { + w.set_tie(true); + }); + + unsafe { interrupt::PIT.enable() }; + + pac::PIT.mcr().write(|w| { + w.set_mdis(false); + }); + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarm.borrow(cs); + alarm.set(timestamp); + + let timer = pac::PIT.timer(2); + let now = self.now(); + + if timestamp <= now { + alarm.set(u64::MAX); + + return false; + } + + timer.tctrl().modify(|x| x.set_ten(false)); + timer.tflg().modify(|x| x.set_tif(true)); + + // If the next alarm happens in more than u32::MAX cycles then the alarm will be restarted later. + timer.ldval().write_value((timestamp - now) as u32); + timer.tctrl().modify(|x| x.set_ten(true)); + + true + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow_ref_mut(cs).next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = self.queue.borrow_ref_mut(cs).next_expiration(self.now()); + } + } + + fn on_interrupt(&self) { + critical_section::with(|cs| { + let timer = pac::PIT.timer(2); + let alarm = self.alarm.borrow(cs); + let interrupted = timer.tflg().read().tif(); + timer.tflg().write(|r| r.set_tif(true)); + + if interrupted { + // A new load value will not apply until the next timer expiration. + // + // The expiry may be up to u32::MAX cycles away, so the timer must be restarted. + timer.tctrl().modify(|r| r.set_ten(false)); + + let now = self.now(); + let timestamp = alarm.get(); + + if timestamp <= now { + self.trigger_alarm(cs); + } else { + // The alarm is not ready. Wait for u32::MAX cycles and check again or set the next alarm. + timer.ldval().write_value((timestamp - now) as u32); + timer.tctrl().modify(|r| r.set_ten(true)); + } + } + }); + } +} + +embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver { + alarm: Mutex::new(Cell::new(0)), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +pub(crate) fn init() { + DRIVER.init(); +} + +#[cfg(feature = "rt")] +#[interrupt] +fn PIT() { + DRIVER.on_interrupt(); +} diff --git a/examples/mimxrt1011/.cargo/config.toml b/examples/mimxrt1011/.cargo/config.toml new file mode 100644 index 000000000..12f4b27b2 --- /dev/null +++ b/examples/mimxrt1011/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip MIMXRT1010' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M7 + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mimxrt1011/Cargo.toml b/examples/mimxrt1011/Cargo.toml new file mode 100644 index 000000000..cf4e4c163 --- /dev/null +++ b/examples/mimxrt1011/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embassy-imxrt1011-examples" +version = "0.1.0" +edition = "2021" +license = "MIT or Apache-2.0" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.3" +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["defmt", "mimxrt1011", "unstable-pac", "time-driver-pit"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", ] } # "defmt-timestamp-uptime" # RT1011 hard faults currently with this enabled. +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0.0" + +imxrt-boot-gen = { version = "0.3.4", features = ["imxrt1010"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[build-dependencies] +imxrt-rt = { version = "0.1.7", features = ["device"] } + +[profile.release] +debug = 2 diff --git a/examples/mimxrt1011/build.rs b/examples/mimxrt1011/build.rs new file mode 100644 index 000000000..99e172aba --- /dev/null +++ b/examples/mimxrt1011/build.rs @@ -0,0 +1,14 @@ +use imxrt_rt::{Family, RuntimeBuilder}; + +fn main() { + // The IMXRT1010-EVK technically has 128M of flash, but we only ever use 8MB so that the examples + // will build fine on the Adafruit Metro M7 boards. + RuntimeBuilder::from_flexspi(Family::Imxrt1010, 8 * 1024 * 1024) + .build() + .unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + // Not link.x, as imxrt-rt needs to do some special things + println!("cargo:rustc-link-arg-bins=-Timxrt-link.x"); +} diff --git a/examples/mimxrt1011/src/bin/blinky.rs b/examples/mimxrt1011/src/bin/blinky.rs new file mode 100644 index 000000000..a5d5de6b3 --- /dev/null +++ b/examples/mimxrt1011/src/bin/blinky.rs @@ -0,0 +1,48 @@ +//! This example works on the following boards: +//! - IMXRT1010-EVK +//! - Adafruit Metro M7 (with microSD or with AirLift), requires an external button +//! - Makerdiary iMX RT1011 Nano Kit (TODO: currently untested, please change this) +//! +//! Although beware you will need to change the GPIO pins being used (scroll down). + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use embassy_time::Timer; +// Must include `embassy_imxrt1011_examples` to ensure the FCB gets linked. +use {defmt_rtt as _, embassy_imxrt1011_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + /* Pick the pins to use depending on your board. */ + + // IMXRT1010-EVK + // + // LED (D25) + let led = p.GPIO_11; + + // Adafruit Metro M7 (both microSD and AirLift variants) + // + // The LED is connected to D13 on the board. + // let led = p.GPIO_03; + + // Makerdiary iMX RT1011 Nano Kit + // + // LED0 + // let led = p.GPIO_SD_04; + + let mut led = Output::new(led, Level::Low); + + loop { + Timer::after_millis(500).await; + + info!("Toggle"); + led.toggle(); + } +} diff --git a/examples/mimxrt1011/src/bin/button.rs b/examples/mimxrt1011/src/bin/button.rs new file mode 100644 index 000000000..e63d7171d --- /dev/null +++ b/examples/mimxrt1011/src/bin/button.rs @@ -0,0 +1,62 @@ +//! This example works on the following boards: +//! - IMXRT1010-EVK +//! - Adafruit Metro M7 (with microSD or with AirLift), requires an external button +//! - Makerdiary iMX RT1011 Nano Kit (TODO: currently untested, please change this) +//! +//! Although beware you will need to change the GPIO pins being used (scroll down). + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +// Must include `embassy_imxrt1011_examples` to ensure the FCB gets linked. +use {defmt_rtt as _, embassy_imxrt1011_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + /* Pick the pins to use depending on your board. */ + + // IMXRT1010-EVK + // + // LED (D25) and user button (SW4) + let (led, button) = (p.GPIO_11, p.GPIO_SD_05); + + // Adafruit Metro M7 (both microSD and AirLift variants) + // + // The LED is connected to D13 on the board. + // + // In particular the Metro M7 has no board user buttons, so you will need to connect a button. + // Any other GPIO pin can be used. GPIO_04 is used for example since it is on pin D12. + // let (led, button) = (p.GPIO_03, p.GPIO_04); + + // Makerdiary iMX RT1011 Nano Kit + // + // LED0 and user button. + // let (led, button) = (p.GPIO_SD_04, p.GPIO_SD_03); + + let mut button = Input::new(button, Pull::Up100K); + let mut led = Output::new(led, Level::Low); + led.set_high(); + + loop { + button.wait_for_falling_edge().await; + + info!("Toggled"); + led.toggle(); + + // The RT1010EVK has a 100 nF debouncing capacitor which results in false positive events + // when listening for a falling edge in a loop, wait for the rising edge and then wait for + // stabilization. + button.wait_for_rising_edge().await; + + // Stabilization. + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + } +} diff --git a/examples/mimxrt1011/src/lib.rs b/examples/mimxrt1011/src/lib.rs new file mode 100644 index 000000000..f0391ef57 --- /dev/null +++ b/examples/mimxrt1011/src/lib.rs @@ -0,0 +1,75 @@ +//! FlexSPI configuration block (FCB) for iMXRT1011 boards. +//! +//! This is a generic FCB that should work with most QSPI flash. + +#![no_std] + +use imxrt_boot_gen::flexspi; +use imxrt_boot_gen::flexspi::opcodes::sdr::*; +use imxrt_boot_gen::flexspi::{ + ColumnAddressWidth, Command, DeviceModeConfiguration, FlashPadType, Instr, LookupTable, Pads, + ReadSampleClockSource, Sequence, SequenceBuilder, SerialClockFrequency, SerialFlashRegion, + WaitTimeConfigurationCommands, +}; +use imxrt_boot_gen::serial_flash::nor; + +/// While the IMXRT1010-EVK and Makerdiary iMX RT1011 Nano Kit have 128MBit of flash we limit to 64Mbit +/// to allow the Metro M7 boards to use the same FCB configuration. +const DENSITY_BITS: u32 = 64 * 1024 * 1024; +const DENSITY_BYTES: u32 = DENSITY_BITS / 8; + +const SEQ_READ: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0xEB)) + .instr(Instr::new(RADDR, Pads::Four, 0x18)) + .instr(Instr::new(DUMMY, Pads::Four, 0x06)) + .instr(Instr::new(READ, Pads::Four, 0x04)) + .build(); + +const SEQ_READ_STATUS: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x05)) + .instr(Instr::new(READ, Pads::One, 0x01)) + .build(); + +const SEQ_WRITE_ENABLE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x06)).build(); + +const SEQ_ERASE_SECTOR: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x20)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .build(); + +const SEQ_PAGE_PROGRAM: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x02)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .instr(Instr::new(WRITE, Pads::One, 0x04)) + .build(); + +const SEQ_CHIP_ERASE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x60)).build(); + +const LUT: LookupTable = LookupTable::new() + .command(Command::Read, SEQ_READ) + .command(Command::ReadStatus, SEQ_READ_STATUS) + .command(Command::WriteEnable, SEQ_WRITE_ENABLE) + .command(Command::EraseSector, SEQ_ERASE_SECTOR) + .command(Command::PageProgram, SEQ_PAGE_PROGRAM) + .command(Command::ChipErase, SEQ_CHIP_ERASE); + +const COMMON_CONFIGURATION_BLOCK: flexspi::ConfigurationBlock = flexspi::ConfigurationBlock::new(LUT) + .read_sample_clk_src(ReadSampleClockSource::LoopbackFromDQSPad) + .cs_hold_time(0x03) + .cs_setup_time(0x03) + .column_address_width(ColumnAddressWidth::OtherDevices) + .device_mode_configuration(DeviceModeConfiguration::Disabled) + .wait_time_cfg_commands(WaitTimeConfigurationCommands::disable()) + .flash_size(SerialFlashRegion::A1, DENSITY_BYTES) + .serial_clk_freq(SerialClockFrequency::MHz120) + .serial_flash_pad_type(FlashPadType::Quad); + +pub const SERIAL_NOR_CONFIGURATION_BLOCK: nor::ConfigurationBlock = + nor::ConfigurationBlock::new(COMMON_CONFIGURATION_BLOCK) + .page_size(256) + .sector_size(4096) + .ip_cmd_serial_clk_freq(nor::SerialClockFrequency::MHz30); + +#[unsafe(no_mangle)] +#[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".fcb")] +pub static FLEXSPI_CONFIGURATION_BLOCK: nor::ConfigurationBlock = SERIAL_NOR_CONFIGURATION_BLOCK;