mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-26 20:00:27 +00:00
nxp: Add MIMXRT1011 GPIO and time driver
PIT is used for the time driver
This commit is contained in:
parent
d656b174ed
commit
1ad5d5a771
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -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
2
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 \
|
||||
|
@ -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
136
embassy-nxp/build.rs
Normal 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(®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<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),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
94
embassy-nxp/build_common.rs
Normal file
94
embassy-nxp/build_common.rs
Normal 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"));
|
||||
}
|
113
embassy-nxp/src/chips/mimxrt1011.rs
Normal file
113
embassy-nxp/src/chips/mimxrt1011.rs
Normal 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"));
|
||||
}
|
@ -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::*;
|
||||
|
895
embassy-nxp/src/gpio/rt1xxx.rs
Normal file
895
embassy-nxp/src/gpio/rt1xxx.rs
Normal 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 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<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);
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
187
embassy-nxp/src/time_driver/pit.rs
Normal file
187
embassy-nxp/src/time_driver/pit.rs
Normal 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();
|
||||
}
|
8
examples/mimxrt1011/.cargo/config.toml
Normal file
8
examples/mimxrt1011/.cargo/config.toml
Normal 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"
|
29
examples/mimxrt1011/Cargo.toml
Normal file
29
examples/mimxrt1011/Cargo.toml
Normal 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
|
14
examples/mimxrt1011/build.rs
Normal file
14
examples/mimxrt1011/build.rs
Normal 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");
|
||||
}
|
48
examples/mimxrt1011/src/bin/blinky.rs
Normal file
48
examples/mimxrt1011/src/bin/blinky.rs
Normal 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();
|
||||
}
|
||||
}
|
62
examples/mimxrt1011/src/bin/button.rs
Normal file
62
examples/mimxrt1011/src/bin/button.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
75
examples/mimxrt1011/src/lib.rs
Normal file
75
examples/mimxrt1011/src/lib.rs
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user