nxp: Add MIMXRT1011 GPIO and time driver

PIT is used for the time driver
This commit is contained in:
i509VCB 2025-07-09 23:08:59 -05:00
parent d656b174ed
commit 1ad5d5a771
No known key found for this signature in database
GPG Key ID: 3E860D038915EF88
16 changed files with 1780 additions and 9 deletions

View File

@ -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",

2
ci.sh
View File

@ -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 \

View File

@ -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"]

136
embassy-nxp/build.rs Normal file
View File

@ -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(&registers.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(&registers.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<Path>) {
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<Self::Item, GetOneError>;
}
impl<T: Iterator> IteratorExt for T {
fn get_one(mut self) -> Result<Self::Item, GetOneError> {
match self.next() {
None => Err(GetOneError::None),
Some(res) => match self.next() {
Some(_) => Err(GetOneError::Multiple),
None => Ok(res),
},
}
}
}

View File

@ -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<String>,
declared: HashSet<String>,
}
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<str>) {
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<str>]) {
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<str>) {
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<str>]) {
for cfg in cfgs.iter() {
self.declare(cfg.as_ref());
}
}
pub fn set(&mut self, cfg: impl Into<String>, 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"));
}

View File

@ -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"));
}

View File

@ -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::*;

View File

@ -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<bool> for Level {
fn from(val: bool) -> Self {
match val {
true => Self::High,
false => Self::Low,
}
}
}
impl From<Level> 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<AnyPin> + 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 youre 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<MuxCtl, RW> {
// 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<Ctl, RW> {
// 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<Self::Output> {
// 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<u32> {
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<u32> {
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<peripherals::$name> 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);
}

View File

@ -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<Self::Item> {
match self.0.trailing_zeros() {
32 => None,
b => {
self.0 &= !(1 << b);
Some(b)
}
}
}
}

View File

@ -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<Cell<u64>>,
queue: Mutex<RefCell<Queue>>,
}
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();
}

View File

@ -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"

View File

@ -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

View File

@ -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");
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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;