Merge branch 'embassy-rs:main' into main

This commit is contained in:
Mateusz 2024-10-29 13:27:44 +01:00 committed by GitHub
commit 7350382e70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
290 changed files with 9484 additions and 3777 deletions

View File

@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
mv rust-toolchain-nightly.toml rust-toolchain.toml
cargo test --manifest-path ./embassy-executor/Cargo.toml
cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml

1
.github/ci/test.sh vendored
View File

@ -12,6 +12,7 @@ export CARGO_TARGET_DIR=/ci/cache/target
# used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
cargo test --manifest-path ./embassy-executor/Cargo.toml
cargo test --manifest-path ./embassy-futures/Cargo.toml
cargo test --manifest-path ./embassy-sync/Cargo.toml
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml

9
ci.sh
View File

@ -166,11 +166,13 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5f9zj,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \
--- 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 \
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
@ -214,6 +216,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
--- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7b0 \
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
--- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \
--- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \
@ -227,6 +230,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \
--- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/lpc55s69 \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \
@ -303,11 +307,6 @@ rm out/tests/stm32f207zg/eth
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
rm out/tests/stm32u5a5zj/usart
# flaky, probably due to bad ringbuffered dma code.
rm out/tests/stm32l152re/usart_rx_ringbuffered
rm out/tests/stm32f207zg/usart_rx_ringbuffered
rm out/tests/stm32wl55jc/usart_rx_ringbuffered
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests
exit

View File

@ -9,7 +9,8 @@
pub(crate) mod fmt;
#[cfg(feature = "bluetooth")]
mod bluetooth;
/// Bluetooth module.
pub mod bluetooth;
mod bus;
mod consts;
mod control;

View File

@ -372,3 +372,16 @@ Issues like these while implementing drivers often fall into one of the followin
3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds
4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary
5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time
== How can I prevent the thread-mode executor from going to sleep? ==
In some cases you might want to prevent the thread-mode executor from going to sleep, for example when doing so would result in current spikes that reduce analog performance.
As a workaround, you can spawn a task that yields in a loop, preventing the executor from going to sleep. Note that this may increase power consumption.
[source,rust]
----
#[embassy_executor::task]
async fn idle() {
loop { embassy_futures::yield_now().await; }
}
----

View File

@ -73,16 +73,28 @@ Now that cargo knows what target to compile for (and probe-rs knows what chip to
Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, well only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`.
At the time of writing, the latest version of embassy isnt available on crates.io, so we need to install it straight from the git repository. The recommended way of doing so is as follows:
At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml.
[source,toml]
----
[dependencies]
embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] }
embassy-executor = { version = "0.6.1", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
----
Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published.
The recommended way of doing so is as follows:
* Copy the required `embassy-*` lines from the example `Cargo.toml`
* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32`
* Remove the `path = ""` keys in the `embassy-*` entries
* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit were checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD`
NOTE: When using this method, its necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crates `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. Hopefully this will no longer be necessary once embassy is released on crates.io!
NOTE: When using this method, its necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crates `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed.
At the time of writing, this method produces the following results:
An example Cargo.toml file might look as follows:
[source,toml]
----

View File

@ -52,7 +52,7 @@ link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot]
== What is DMA?
For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bits at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller.
For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bytes at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller.
The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them.
@ -80,4 +80,4 @@ For more reading material on async Rust and Embassy:
Videos:
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]

View File

@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
/// Firmware updater flash configuration holding the two flashes used by the updater
///
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct FirmwareUpdaterConfig<DFU, STATE> {
/// The dfu flash partition

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-executor-macros"
version = "0.5.0"
version = "0.6.1"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"
@ -13,7 +13,7 @@ categories = [
]
[dependencies]
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
syn = { version = "2.0.15", features = ["full", "visit"] }
quote = "1.0.9"
darling = "0.20.1"
proc-macro2 = "1.0.29"

View File

@ -1,28 +1,11 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use darling::ast::NestedMeta;
use proc_macro::TokenStream;
mod macros;
mod util;
use macros::*;
use syn::parse::{Parse, ParseBuffer};
use syn::punctuated::Punctuated;
use syn::Token;
struct Args {
meta: Vec<NestedMeta>,
}
impl Parse for Args {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
Ok(Args {
meta: meta.into_iter().collect(),
})
}
}
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
/// many concurrent tasks can be spawned (default is 1) for the function.
@ -56,17 +39,12 @@ impl Parse for Args {
/// ```
#[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
task::run(args.into(), item.into()).into()
}
#[proc_macro_attribute]
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
}
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
@ -89,9 +67,32 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
}
/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
/// the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// A user-defined entry macro must provided via the `entry` argument
///
/// ## Examples
/// Spawning a task:
/// ``` rust
/// #[embassy_executor::main(entry = "qingke_rt::entry")]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
}
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
@ -124,11 +125,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::riscv(&args.meta))
.unwrap_or_else(|x| x)
.into()
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
}
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
@ -151,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_STD).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
@ -176,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
}

View File

@ -1,125 +1,107 @@
use std::str::FromStr;
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Expr, ReturnType, Type};
use syn::{ReturnType, Type};
use crate::util::ctxt::Ctxt;
use crate::util::*;
#[derive(Debug, FromMeta)]
enum Flavor {
Standard,
Wasm,
}
pub(crate) struct Arch {
default_entry: Option<&'static str>,
flavor: Flavor,
}
pub static ARCH_AVR: Arch = Arch {
default_entry: Some("avr_device::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_RISCV: Arch = Arch {
default_entry: Some("riscv_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_CORTEX_M: Arch = Arch {
default_entry: Some("cortex_m_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_SPIN: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_STD: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_WASM: Arch = Arch {
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
flavor: Flavor::Wasm,
};
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
entry: Option<String>,
}
pub fn avr() -> TokenStream {
quote! {
#[avr_device::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
let mut errors = TokenStream::new();
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn riscv(args: &[NestedMeta]) -> TokenStream {
let maybe_entry = match Args::from_list(args) {
Ok(args) => args.entry,
Err(e) => return e.write_errors(),
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let f: ItemFn = match syn::parse2(item.clone()) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let entry = maybe_entry.unwrap_or("riscv_rt::entry".into());
let entry = match Expr::from_string(&entry) {
Ok(expr) => expr,
Err(e) => return e.write_errors(),
let args = match NestedMeta::parse_meta_list(args) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
quote! {
#[#entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
let args = match Args::from_list(&args) {
Ok(x) => x,
Err(e) => {
errors.extend(e.write_errors());
Args::default()
}
}
}
pub fn cortex_m() -> TokenStream {
quote! {
#[cortex_m_rt::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn wasm() -> TokenStream {
quote! {
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
executor.start(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
});
Ok(())
}
}
}
pub fn std() -> TokenStream {
quote! {
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
#[allow(unused_variables)]
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
};
let fargs = f.sig.inputs.clone();
let ctxt = Ctxt::new();
if f.sig.asyncness.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must be async");
error(&mut errors, &f.sig, "main function must be async");
}
if !f.sig.generics.params.is_empty() {
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
error(&mut errors, &f.sig, "main function must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
error(&mut errors, &f.sig, "main function must not have `where` clauses");
}
if !f.sig.abi.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
error(&mut errors, &f.sig, "main function must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
_ => error(
&mut errors,
&f.sig,
"main function must either not return a value, return `()` or return `!`",
),
@ -127,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
}
if fargs.len() != 1 {
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
}
ctxt.check()?;
let entry = match args.entry.as_deref().or(arch.default_entry) {
None => TokenStream::new(),
Some(x) => match TokenStream::from_str(x) {
Ok(x) => quote!(#[#x]),
Err(e) => {
error(&mut errors, &f.sig, e);
TokenStream::new()
}
},
};
let f_body = f.block;
let f_body = f.body;
let out = &f.sig.output;
let (main_ret, mut main_body) = match arch.flavor {
Flavor::Standard => (
quote!(!),
quote! {
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
::core::mem::transmute(t)
}
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
},
),
Flavor::Wasm => (
quote!(Result<(), wasm_bindgen::JsValue>),
quote! {
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
executor.start(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
});
Ok(())
},
),
};
if !errors.is_empty() {
main_body = quote! {loop{}};
}
let result = quote! {
#[::embassy_executor::task()]
async fn __embassy_main(#fargs) #out {
#f_body
}
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
::core::mem::transmute(t)
#entry
fn main() -> #main_ret {
#main_body
}
#main
#errors
};
Ok(result)
result
}

View File

@ -2,47 +2,68 @@ use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type};
use syn::visit::{self, Visit};
use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
use crate::util::ctxt::Ctxt;
use crate::util::*;
#[derive(Debug, FromMeta)]
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
pool_size: Option<syn::Expr>,
}
pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
let mut errors = TokenStream::new();
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let f: ItemFn = match syn::parse2(item.clone()) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match NestedMeta::parse_meta_list(args) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match Args::from_list(&args) {
Ok(x) => x,
Err(e) => {
errors.extend(e.write_errors());
Args::default()
}
};
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
attrs: vec![],
lit: Lit::Int(LitInt::new("1", Span::call_site())),
}));
let ctxt = Ctxt::new();
if f.sig.asyncness.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must be async");
error(&mut errors, &f.sig, "task functions must be async");
}
if !f.sig.generics.params.is_empty() {
ctxt.error_spanned_by(&f.sig, "task functions must not be generic");
error(&mut errors, &f.sig, "task functions must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses");
error(&mut errors, &f.sig, "task functions must not have `where` clauses");
}
if !f.sig.abi.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier");
error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not be variadic");
error(&mut errors, &f.sig, "task functions must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
_ => error(
&mut errors,
&f.sig,
"task functions must either not return a value, return `()` or return `!`",
),
@ -55,26 +76,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
for arg in fargs.iter_mut() {
match arg {
syn::FnArg::Receiver(_) => {
ctxt.error_spanned_by(arg, "task functions must not have receiver arguments");
error(&mut errors, arg, "task functions must not have `self` arguments");
}
syn::FnArg::Typed(t) => match t.pat.as_mut() {
syn::Pat::Ident(id) => {
id.mutability = None;
args.push((id.clone(), t.attrs.clone()));
syn::FnArg::Typed(t) => {
check_arg_ty(&mut errors, &t.ty);
match t.pat.as_mut() {
syn::Pat::Ident(id) => {
id.mutability = None;
args.push((id.clone(), t.attrs.clone()));
}
_ => {
error(
&mut errors,
arg,
"pattern matching in task arguments is not yet supported",
);
}
}
_ => {
ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
}
},
}
}
}
ctxt.check()?;
let task_ident = f.sig.ident.clone();
let task_inner_ident = format_ident!("__{}_task", task_ident);
let mut task_inner = f;
let mut task_inner = f.clone();
let visibility = task_inner.vis.clone();
task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone();
@ -91,35 +117,43 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
}
#[cfg(feature = "nightly")]
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
trait _EmbassyInternalTaskTrait {
type Fut: ::core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut;
}
impl _EmbassyInternalTaskTrait for () {
type Fut = impl core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut {
#task_inner_ident(#(#full_args,)*)
}
}
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
let mut task_outer_body = quote! {
trait _EmbassyInternalTaskTrait {
type Fut: ::core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut;
}
impl _EmbassyInternalTaskTrait for () {
type Fut = impl core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut {
#task_inner_ident(#(#full_args,)*)
}
}
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
};
#[cfg(not(feature = "nightly"))]
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
}
let mut task_outer_body = quote! {
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
};
task_outer.attrs.append(&mut task_inner.attrs.clone());
let task_outer_attrs = task_inner.attrs.clone();
if !errors.is_empty() {
task_outer_body = quote! {
#![allow(unused_variables, unreachable_code)]
let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!();
_x
};
}
// Copy the generics + where clause to avoid more spurious errors.
let generics = &f.sig.generics;
let where_clause = &f.sig.generics.where_clause;
let result = quote! {
// This is the user's task function, renamed.
@ -129,8 +163,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
#[doc(hidden)]
#task_inner
#task_outer
#(#task_outer_attrs)*
#visibility fn #task_ident #generics (#fargs) -> ::embassy_executor::SpawnToken<impl Sized> #where_clause{
#task_outer_body
}
#errors
};
Ok(result)
result
}
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
struct Visitor<'a> {
errors: &'a mut TokenStream,
}
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
// only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
if i.lifetime.is_none() {
error(
self.errors,
i.and_token,
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
)
}
visit::visit_type_reference(self, i);
}
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
if i.ident.to_string() != "static" {
error(
self.errors,
i,
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
)
}
}
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
}
}
Visit::visit_type(&mut Visitor { errors }, ty);
}

View File

@ -0,0 +1,74 @@
use std::fmt::Display;
use proc_macro2::{TokenStream, TokenTree};
use quote::{ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(error.into_compile_error());
tokens
}
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
}
/// Function signature and body.
///
/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
/// parsing it. This makes the macro not error if there's a syntax error in the body,
/// which helps IDE autocomplete work better.
#[derive(Debug, Clone)]
pub struct ItemFn {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub sig: Signature,
pub brace_token: token::Brace,
pub body: TokenStream,
}
impl Parse for ItemFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
let sig: Signature = input.parse()?;
let content;
let brace_token = braced!(content in input);
while content.peek(Token![#]) && content.peek2(Token![!]) {
let content2;
attrs.push(Attribute {
pound_token: content.parse()?,
style: AttrStyle::Inner(content.parse()?),
bracket_token: bracketed!(content2 in content),
meta: content2.parse()?,
});
}
let mut body = Vec::new();
while !content.is_empty() {
body.push(content.parse::<TokenTree>()?);
}
let body = body.into_iter().collect();
Ok(ItemFn {
attrs,
vis,
sig,
brace_token,
body,
})
}
}
impl ToTokens for ItemFn {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
self.vis.to_tokens(tokens);
self.sig.to_tokens(tokens);
self.brace_token.surround(tokens, |tokens| {
tokens.append_all(self.body.clone());
});
}
}

View File

@ -1,72 +0,0 @@
// nifty utility borrowed from serde :)
// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs
use std::cell::RefCell;
use std::fmt::Display;
use std::thread;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
/// A type to collect errors together and format them.
///
/// Dropping this object will cause a panic. It must be consumed using `check`.
///
/// References can be shared since this type uses run-time exclusive mut checking.
#[derive(Default)]
pub struct Ctxt {
// The contents will be set to `None` during checking. This is so that checking can be
// enforced.
errors: RefCell<Option<Vec<syn::Error>>>,
}
impl Ctxt {
/// Create a new context object.
///
/// This object contains no errors, but will still trigger a panic if it is not `check`ed.
pub fn new() -> Self {
Ctxt {
errors: RefCell::new(Some(Vec::new())),
}
}
/// Add an error to the context object with a tokenenizable object.
///
/// The object is used for spanning in error messages.
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
self.errors
.borrow_mut()
.as_mut()
.unwrap()
// Curb monomorphization from generating too many identical methods.
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
}
/// Add one of Syn's parse errors.
#[allow(unused)]
pub fn syn_error(&self, err: syn::Error) {
self.errors.borrow_mut().as_mut().unwrap().push(err);
}
/// Consume this object, producing a formatted error string if there are errors.
pub fn check(self) -> Result<(), TokenStream> {
let errors = self.errors.borrow_mut().take().unwrap();
match errors.len() {
0 => Ok(()),
_ => Err(to_compile_errors(errors)),
}
}
}
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote!(#(#compile_errors)*)
}
impl Drop for Ctxt {
fn drop(&mut self) {
if !thread::panicking() && self.errors.borrow().is_some() {
panic!("forgot to check for errors");
}
}
}

View File

@ -1 +0,0 @@
pub mod ctxt;

View File

@ -7,12 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
## 0.6.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature.
- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires`nightly-2024-09-06` or newer.
- Improve macro error messages.
## 0.6.0 - 2024-08-05
- Add collapse_debuginfo to fmt.rs macros.
- initial support for avr
- initial support for AVR
- use nightly waker_getters APIs
## 0.5.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
## 0.5.0 - 2024-01-11
- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher.

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-executor"
version = "0.6.0"
version = "0.6.1"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
@ -33,7 +33,7 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
rtos-trace = { version = "0.1.2", optional = true }
embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" }
embassy-executor-macros = { version = "0.6.1", path = "../embassy-executor-macros" }
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true }
embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true }
critical-section = "1.1"
@ -55,7 +55,7 @@ avr-device = { version = "0.5.3", optional = true }
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
trybuild = "1.0"
[features]
@ -82,6 +82,8 @@ arch-riscv32 = ["_arch"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
## AVR
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
## spin (architecture agnostic; never sleeps)
arch-spin = ["_arch"]
#! ### Executor

View File

@ -0,0 +1,58 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-spin`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
pub use embassy_executor_macros::main_spin as main;
use crate::{raw, Spawner};
#[export_name = "__pender"]
fn __pender(_context: *mut ()) {}
/// Spin Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
}
}
}
}

View File

@ -23,7 +23,14 @@ macro_rules! check_at_most_one {
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
};
}
check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",);
check_at_most_one!(
"arch-avr",
"arch-cortex-m",
"arch-riscv32",
"arch-std",
"arch-wasm",
"arch-spin",
);
#[cfg(feature = "_arch")]
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
@ -31,6 +38,7 @@ check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arc
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
mod arch;
#[cfg(feature = "_arch")]

View File

@ -0,0 +1,23 @@
#[cfg(not(miri))]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/abi.rs");
t.compile_fail("tests/ui/bad_return.rs");
t.compile_fail("tests/ui/generics.rs");
t.compile_fail("tests/ui/impl_trait_nested.rs");
t.compile_fail("tests/ui/impl_trait.rs");
t.compile_fail("tests/ui/impl_trait_static.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
t.compile_fail("tests/ui/not_async.rs");
t.compile_fail("tests/ui/self_ref.rs");
t.compile_fail("tests/ui/self.rs");
t.compile_fail("tests/ui/where_clause.rs");
}

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async extern "C" fn task() {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must not have an ABI qualifier
--> tests/ui/abi.rs:6:1
|
6 | async extern "C" fn task() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,10 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task() -> u32 {
5
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must either not return a value, return `()` or return `!`
--> tests/ui/bad_return.rs:6:1
|
6 | async fn task() -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task<T: Sized>(_t: T) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must not be generic
--> tests/ui/generics.rs:6:1
|
6 | async fn task<T: Sized>(_t: T) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: impl Sized) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait.rs:4:18
|
4 | async fn foo(_x: impl Sized) {}
| ^^^^^^^^^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<T>(T);
#[embassy_executor::task]
async fn foo(_x: Foo<impl Sized + 'static>) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait_nested.rs:6:22
|
6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: impl Sized + 'static) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait_static.rs:4:18
|
4 | async fn foo(_x: impl Sized + 'static) {}
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
4 | async fn foo(_x: &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'static &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon_nested.rs:4:28
|
4 | async fn foo(_x: &'static &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
4 | async fn foo(_x: &u32) {}
| ^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo<'a>(_x: &'a u32) {}
fn main() {}

View File

@ -0,0 +1,11 @@
error: task functions must not be generic
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task(_x: Foo<'_>) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_struct_anon.rs:6:23
|
6 | async fn task(_x: Foo<'_>) {}
| ^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task(_x: Foo) {}
fn main() {}

View File

@ -0,0 +1,10 @@
error[E0726]: implicit elided lifetime not allowed here
--> tests/ui/nonstatic_struct_elided.rs:6:19
|
6 | async fn task(_x: Foo) {}
| ^^^ expected lifetime parameter
|
help: indicate the anonymous lifetime
|
6 | async fn task(_x: Foo<'_>) {}
| ++++

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task<'a>(_x: Foo<'a>) {}
fn main() {}

View File

@ -0,0 +1,11 @@
error: task functions must not be generic
--> tests/ui/nonstatic_struct_generic.rs:6:1
|
6 | async fn task<'a>(_x: Foo<'a>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_struct_generic.rs:6:27
|
6 | async fn task<'a>(_x: Foo<'a>) {}
| ^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
fn task() {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must be async
--> tests/ui/not_async.rs:6:1
|
6 | fn task() {}
| ^^^^^^^^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task(self) {}
fn main() {}

View File

@ -0,0 +1,13 @@
error: task functions must not have `self` arguments
--> tests/ui/self.rs:6:15
|
6 | async fn task(self) {}
| ^^^^
error: `self` parameter is only allowed in associated functions
--> tests/ui/self.rs:6:15
|
6 | async fn task(self) {}
| ^^^^ not semantically valid as function parameter
|
= note: associated functions are those in `impl` or `trait` definitions

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task(&mut self) {}
fn main() {}

View File

@ -0,0 +1,13 @@
error: task functions must not have `self` arguments
--> tests/ui/self_ref.rs:6:15
|
6 | async fn task(&mut self) {}
| ^^^^^^^^^
error: `self` parameter is only allowed in associated functions
--> tests/ui/self_ref.rs:6:15
|
6 | async fn task(&mut self) {}
| ^^^^^^^^^ not semantically valid as function parameter
|
= note: associated functions are those in `impl` or `trait` definitions

View File

@ -0,0 +1,12 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task()
where
(): Sized,
{
}
fn main() {}

View File

@ -0,0 +1,7 @@
error: task functions must not have `where` clauses
--> tests/ui/where_clause.rs:6:1
|
6 | / async fn task()
7 | | where
8 | | (): Sized,
| |______________^

View File

@ -188,6 +188,169 @@ where
// ====================================================================
/// Result for [`select5`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Either5<A, B, C, D, E> {
/// First future finished first.
First(A),
/// Second future finished first.
Second(B),
/// Third future finished first.
Third(C),
/// Fourth future finished first.
Fourth(D),
/// Fifth future finished first.
Fifth(E),
}
/// Same as [`select`], but with more futures.
pub fn select5<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E) -> Select5<A, B, C, D, E>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
{
Select5 { a, b, c, d, e }
}
/// Future for the [`select5`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Select5<A, B, C, D, E> {
a: A,
b: B,
c: C,
d: D,
e: E,
}
impl<A, B, C, D, E> Future for Select5<A, B, C, D, E>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
{
type Output = Either5<A::Output, B::Output, C::Output, D::Output, E::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let a = unsafe { Pin::new_unchecked(&mut this.a) };
let b = unsafe { Pin::new_unchecked(&mut this.b) };
let c = unsafe { Pin::new_unchecked(&mut this.c) };
let d = unsafe { Pin::new_unchecked(&mut this.d) };
let e = unsafe { Pin::new_unchecked(&mut this.e) };
if let Poll::Ready(x) = a.poll(cx) {
return Poll::Ready(Either5::First(x));
}
if let Poll::Ready(x) = b.poll(cx) {
return Poll::Ready(Either5::Second(x));
}
if let Poll::Ready(x) = c.poll(cx) {
return Poll::Ready(Either5::Third(x));
}
if let Poll::Ready(x) = d.poll(cx) {
return Poll::Ready(Either5::Fourth(x));
}
if let Poll::Ready(x) = e.poll(cx) {
return Poll::Ready(Either5::Fifth(x));
}
Poll::Pending
}
}
// ====================================================================
/// Result for [`select6`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Either6<A, B, C, D, E, F> {
/// First future finished first.
First(A),
/// Second future finished first.
Second(B),
/// Third future finished first.
Third(C),
/// Fourth future finished first.
Fourth(D),
/// Fifth future finished first.
Fifth(E),
/// Sixth future finished first.
Sixth(F),
}
/// Same as [`select`], but with more futures.
pub fn select6<A, B, C, D, E, F>(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6<A, B, C, D, E, F>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
F: Future,
{
Select6 { a, b, c, d, e, f }
}
/// Future for the [`select6`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Select6<A, B, C, D, E, F> {
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
}
impl<A, B, C, D, E, F> Future for Select6<A, B, C, D, E, F>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
F: Future,
{
type Output = Either6<A::Output, B::Output, C::Output, D::Output, E::Output, F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let a = unsafe { Pin::new_unchecked(&mut this.a) };
let b = unsafe { Pin::new_unchecked(&mut this.b) };
let c = unsafe { Pin::new_unchecked(&mut this.c) };
let d = unsafe { Pin::new_unchecked(&mut this.d) };
let e = unsafe { Pin::new_unchecked(&mut this.e) };
let f = unsafe { Pin::new_unchecked(&mut this.f) };
if let Poll::Ready(x) = a.poll(cx) {
return Poll::Ready(Either6::First(x));
}
if let Poll::Ready(x) = b.poll(cx) {
return Poll::Ready(Either6::Second(x));
}
if let Poll::Ready(x) = c.poll(cx) {
return Poll::Ready(Either6::Third(x));
}
if let Poll::Ready(x) = d.poll(cx) {
return Poll::Ready(Either6::Fourth(x));
}
if let Poll::Ready(x) = e.poll(cx) {
return Poll::Ready(Either6::Fifth(x));
}
if let Poll::Ready(x) = f.poll(cx) {
return Poll::Ready(Either6::Sixth(x));
}
Poll::Pending
}
}
// ====================================================================
/// Future for the [`select_array`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]

View File

@ -326,8 +326,14 @@ pub struct Device<'d, const MTU: usize> {
}
impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ;
type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ;
type RxToken<'a>
= RxToken<'a, MTU>
where
Self: 'a;
type TxToken<'a>
= TxToken<'a, MTU>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() {

View File

@ -83,10 +83,12 @@ pub trait Driver {
}
impl<T: ?Sized + Driver> Driver for &mut T {
type RxToken<'a> = T::RxToken<'a>
type RxToken<'a>
= T::RxToken<'a>
where
Self: 'a;
type TxToken<'a> = T::TxToken<'a>
type TxToken<'a>
= T::TxToken<'a>
where
Self: 'a;

View File

@ -635,11 +635,13 @@ where
S: SpiDevice,
O: OutputPin,
{
type RxToken<'a> = RxToken<'a>
type RxToken<'a>
= RxToken<'a>
where
Self: 'a;
type TxToken<'a> = TxToken<'a, S, O>
type TxToken<'a>
= TxToken<'a, S, O>
where
Self: 'a;

View File

@ -21,6 +21,8 @@ pub struct Config<'a> {
pub auth_prot: AuthProt,
/// Credentials.
pub auth: Option<(&'a [u8], &'a [u8])>,
/// SIM pin
pub pin: Option<&'a [u8]>,
}
/// Authentication protocol.
@ -133,6 +135,16 @@ impl<'a> Control<'a> {
// info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
if let Some(pin) = config.pin {
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CPIN")
.with_string_parameter(pin)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let _ = self.control.at_command(op, &mut buf).await;
// Ignore ERROR which means no pin required
}
Ok(())
}

View File

@ -20,7 +20,7 @@ log = { version = "0.4.14", optional = true }
embedded-io-async = { version = "0.6.1" }
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
ppproto = { version = "0.1.2"}
ppproto = { version = "0.2.0"}
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
[package.metadata.embassy_docs]

View File

@ -152,8 +152,14 @@ impl TunTapDevice {
}
impl Driver for TunTapDevice {
type RxToken<'a> = RxToken where Self: 'a;
type TxToken<'a> = TxToken<'a> where Self: 'a;
type RxToken<'a>
= RxToken
where
Self: 'a;
type TxToken<'a>
= TxToken<'a>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let mut buf = vec![0; self.device.get_ref().mtu];

View File

@ -27,7 +27,7 @@ default = []
std = []
## Enable defmt
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03"]
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"]
## Trace all raw received and transmitted packets using defmt or log.
packet-trace = []
@ -65,10 +65,10 @@ multicast = ["smoltcp/multicast"]
[dependencies]
defmt = { version = "0.3", optional = true }
defmt = { version = "0.3.8", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [
smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="fe0b4d102253465850cd1cf39cd33d4721a4a8d5", default-features = false, features = [
"socket",
"async",
] }
@ -80,5 +80,5 @@ embedded-io-async = { version = "0.6.1" }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
heapless = { version = "0.8", default-features = false }
embedded-nal-async = { version = "0.7.1" }
embedded-nal-async = "0.8.0"
document-features = "0.2.7"

View File

@ -73,8 +73,11 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
&self,
host: &str,
addr_type: embedded_nal_async::AddrType,
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
use embedded_nal_async::{AddrType, IpAddr};
) -> Result<core::net::IpAddr, Self::Error> {
use core::net::IpAddr;
use embedded_nal_async::AddrType;
let (qtype, secondary_qtype) = match addr_type {
AddrType::IPv4 => (DnsQueryType::A, None),
AddrType::IPv6 => (DnsQueryType::Aaaa, None),
@ -98,20 +101,16 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
if let Some(first) = addrs.get(0) {
Ok(match first {
#[cfg(feature = "proto-ipv4")]
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
IpAddress::Ipv4(addr) => IpAddr::V4(*addr),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
IpAddress::Ipv6(addr) => IpAddr::V6(*addr),
})
} else {
Err(Error::Failed)
}
}
async fn get_host_by_address(
&self,
_addr: embedded_nal_async::IpAddr,
_result: &mut [u8],
) -> Result<usize, Self::Error> {
async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result<usize, Self::Error> {
todo!()
}
}

View File

@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
where
T: Driver,
{
type RxToken<'a> = RxTokenAdapter<T::RxToken<'a>> where Self: 'a;
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> where Self: 'a;
type RxToken<'a>
= RxTokenAdapter<T::RxToken<'a>>
where
Self: 'a;
type TxToken<'a>
= TxTokenAdapter<T::TxToken<'a>>
where
Self: 'a;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.inner

View File

@ -108,6 +108,23 @@ impl<'a> RawSocket<'a> {
}
})
}
/// Flush the socket.
///
/// This method will wait until the socket is flushed.
pub async fn flush(&mut self) {
poll_fn(move |cx| {
self.with_mut(|s, _| {
if s.send_queue() == 0 {
Poll::Ready(())
} else {
s.register_send_waker(cx.waker());
Poll::Pending
}
})
})
.await
}
}
impl Drop for RawSocket<'_> {

View File

@ -10,7 +10,7 @@
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use core::task::{Context, Poll};
use embassy_time::Duration;
use smoltcp::iface::{Interface, SocketHandle};
@ -274,6 +274,16 @@ impl<'a> TcpSocket<'a> {
.await
}
/// Wait until the socket becomes readable.
///
/// A socket becomes readable when the receive half of the full-duplex connection is open
/// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer.
///
/// This is the equivalent of [read](#method.read), without buffering any data.
pub async fn wait_read_ready(&self) {
poll_fn(move |cx| self.io.poll_read_ready(cx)).await
}
/// Read data from the socket.
///
/// Returns how many bytes were read, or an error. If no data is available, it waits
@ -285,6 +295,16 @@ impl<'a> TcpSocket<'a> {
self.io.read(buf).await
}
/// Wait until the socket becomes writable.
///
/// A socket becomes writable when the transmit half of the full-duplex connection is open
/// (see [may_send](#method.may_send)), and the transmit buffer is not full.
///
/// This is the equivalent of [write](#method.write), without sending any data.
pub async fn wait_write_ready(&self) {
poll_fn(move |cx| self.io.poll_write_ready(cx)).await
}
/// Write data to the socket.
///
/// Returns how many bytes were written, or an error. If the socket is not ready to
@ -376,11 +396,25 @@ impl<'a> TcpSocket<'a> {
self.io.with_mut(|s, _| s.abort())
}
/// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer.
/// Return whether the transmit half of the full-duplex connection is open.
///
/// This function returns true if it's possible to send data and have it arrive
/// to the remote endpoint. However, it does not make any guarantees about the state
/// of the transmit buffer, and even if it returns true, [write](#method.write) may
/// not be able to enqueue any octets.
///
/// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or
/// `CLOSE-WAIT` state.
pub fn may_send(&self) -> bool {
self.io.with(|s, _| s.may_send())
}
/// Check whether the transmit half of the full-duplex connection is open
/// (see [may_send](#method.may_send)), and the transmit buffer is not full.
pub fn can_send(&self) -> bool {
self.io.with(|s, _| s.can_send())
}
/// return whether the receive half of the full-duplex connection is open.
/// This function returns true if its possible to receive data from the remote endpoint.
/// It will return true while there is data in the receive buffer, and if there isnt,
@ -427,7 +461,7 @@ impl<'d> TcpIo<'d> {
})
}
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
fn with_mut<R>(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
self.stack.with_mut(|i| {
let socket = i.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut i.iface);
@ -436,6 +470,17 @@ impl<'d> TcpIo<'d> {
})
}
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_recv() {
Poll::Ready(())
} else {
s.register_recv_waker(cx.waker());
Poll::Pending
}
})
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
poll_fn(move |cx| {
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
@ -464,6 +509,17 @@ impl<'d> TcpIo<'d> {
.await
}
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_send() {
Poll::Ready(())
} else {
s.register_send_waker(cx.waker());
Poll::Pending
}
})
}
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf) {
@ -675,10 +731,9 @@ mod embedded_io_impls {
pub mod client {
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
use core::net::IpAddr;
use core::ptr::NonNull;
use embedded_nal_async::IpAddr;
use super::*;
/// TCP client connection pool compatible with `embedded-nal-async` traits.
@ -713,19 +768,19 @@ pub mod client {
for TcpClient<'d, N, TX_SZ, RX_SZ>
{
type Error = Error;
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
type Connection<'m>
= TcpConnection<'m, N, TX_SZ, RX_SZ>
where
Self: 'm;
async fn connect<'a>(
&'a self,
remote: embedded_nal_async::SocketAddr,
) -> Result<Self::Connection<'a>, Self::Error> {
async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result<Self::Connection<'a>, Self::Error> {
let addr: crate::IpAddress = match remote.ip() {
#[cfg(feature = "proto-ipv4")]
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr),
#[cfg(not(feature = "proto-ipv4"))]
IpAddr::V4(_) => panic!("ipv4 support not enabled"),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};

View File

@ -103,6 +103,32 @@ impl<'a> UdpSocket<'a> {
})
}
/// Wait until the socket becomes readable.
///
/// A socket is readable when a packet has been received, or when there are queued packets in
/// the buffer.
pub async fn wait_recv_ready(&self) {
poll_fn(move |cx| self.poll_recv_ready(cx)).await
}
/// Wait until a datagram can be read.
///
/// When no datagram is readable, this method will return `Poll::Pending` and
/// register the current task to be notified when a datagram is received.
///
/// When a datagram is received, this method will return `Poll::Ready`.
pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_recv() {
Poll::Ready(())
} else {
// socket buffer is empty wait until at least one byte has arrived
s.register_recv_waker(cx.waker());
Poll::Pending
}
})
}
/// Receive a datagram.
///
/// This method will wait until a datagram is received.
@ -164,6 +190,33 @@ impl<'a> UdpSocket<'a> {
.await
}
/// Wait until the socket becomes writable.
///
/// A socket becomes writable when there is space in the buffer, from initial memory or after
/// dispatching datagrams on a full buffer.
pub async fn wait_send_ready(&self) {
poll_fn(move |cx| self.poll_send_ready(cx)).await
}
/// Wait until a datagram can be sent.
///
/// When no datagram can be sent (i.e. the buffer is full), this method will return
/// `Poll::Pending` and register the current task to be notified when
/// space is freed in the buffer after a datagram has been dispatched.
///
/// When a datagram can be sent, this method will return `Poll::Ready`.
pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_send() {
Poll::Ready(())
} else {
// socket buffer is full wait until a datagram has been dispatched
s.register_send_waker(cx.waker());
Poll::Pending
}
})
}
/// Send a datagram to the specified remote endpoint.
///
/// This method will wait until the datagram has been sent.
@ -241,6 +294,23 @@ impl<'a> UdpSocket<'a> {
.await
}
/// Flush the socket.
///
/// This method will wait until the socket is flushed.
pub async fn flush(&mut self) {
poll_fn(move |cx| {
self.with_mut(|s, _| {
if s.send_queue() == 0 {
Poll::Ready(())
} else {
s.register_send_waker(cx.waker());
Poll::Pending
}
})
})
.await
}
/// Returns the local endpoint of the socket.
pub fn endpoint(&self) -> IpListenEndpoint {
self.with(|s, _| s.endpoint())

View File

@ -177,22 +177,31 @@ mod chip;
// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`.
#[macro_export]
macro_rules! bind_interrupts {
($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
($vis:vis struct $name:ident {
$(
$(#[cfg($cond_irq:meta)])?
$irq:ident => $(
$(#[cfg($cond_handler:meta)])?
$handler:ty
),*;
)*
}) => {
#[derive(Copy, Clone)]
$vis struct $name;
$(
#[allow(non_snake_case)]
#[no_mangle]
$(#[cfg($cond_irq)])?
unsafe extern "C" fn $irq() {
$(
$(#[cfg($cond_handler)])?
<$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt();
$(#[cfg($cond_handler)])?
unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {}
)*
}
$(
unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {}
)*
)*
};
}

View File

@ -4,6 +4,7 @@
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::sync::atomic::compiler_fence;
use core::sync::atomic::Ordering::SeqCst;
use core::task::Poll;
@ -13,11 +14,12 @@ use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(feature = "time")]
use embassy_time::{Duration, Instant};
use embedded_hal_1::i2c::Operation;
use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
use crate::gpio::Pin as GpioPin;
use crate::interrupt::typelevel::Interrupt;
use crate::util::{slice_in_ram, slice_in_ram_or};
use crate::util::slice_in_ram;
use crate::{gpio, interrupt, pac, Peripheral};
/// TWI frequency
@ -103,6 +105,10 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
let r = T::regs();
let s = T::state();
if r.events_suspended.read().bits() != 0 {
s.end_waker.wake();
r.intenclr.write(|w| w.suspended().clear());
}
if r.events_stopped.read().bits() != 0 {
s.end_waker.wake();
r.intenclr.write(|w| w.stopped().clear());
@ -182,8 +188,22 @@ impl<'d, T: Instance> Twim<'d, T> {
}
/// Set TX buffer, checking that it is in RAM and has suitable length.
unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
unsafe fn set_tx_buffer(
&mut self,
buffer: &[u8],
ram_buffer: Option<&mut [MaybeUninit<u8>; FORCE_COPY_BUFFER_SIZE]>,
) -> Result<(), Error> {
let buffer = if slice_in_ram(buffer) {
buffer
} else {
let ram_buffer = ram_buffer.ok_or(Error::BufferNotInRAM)?;
trace!("Copying TWIM tx buffer into RAM for DMA");
let ram_buffer = &mut ram_buffer[..buffer.len()];
// Inline implementation of the nightly API MaybeUninit::copy_from_slice(ram_buffer, buffer)
let uninit_src: &[MaybeUninit<u8>] = unsafe { core::mem::transmute(buffer) };
ram_buffer.copy_from_slice(uninit_src);
unsafe { &*(ram_buffer as *const [MaybeUninit<u8>] as *const [u8]) }
};
if buffer.len() > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong);
@ -290,7 +310,8 @@ impl<'d, T: Instance> Twim<'d, T> {
fn blocking_wait(&mut self) {
let r = T::regs();
loop {
if r.events_stopped.read().bits() != 0 {
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
r.events_suspended.reset();
r.events_stopped.reset();
break;
}
@ -307,7 +328,7 @@ impl<'d, T: Instance> Twim<'d, T> {
let r = T::regs();
let deadline = Instant::now() + timeout;
loop {
if r.events_stopped.read().bits() != 0 {
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
break;
}
@ -331,7 +352,7 @@ impl<'d, T: Instance> Twim<'d, T> {
let s = T::state();
s.end_waker.register(cx.waker());
if r.events_stopped.read().bits() != 0 {
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return Poll::Ready(());
@ -347,168 +368,316 @@ impl<'d, T: Instance> Twim<'d, T> {
})
}
fn setup_write_from_ram(&mut self, address: u8, buffer: &[u8], inten: bool) -> Result<(), Error> {
let r = T::regs();
compiler_fence(SeqCst);
r.address.write(|w| unsafe { w.address().bits(address) });
// Set up the DMA write.
unsafe { self.set_tx_buffer(buffer)? };
// Clear events
r.events_stopped.reset();
r.events_error.reset();
r.events_lasttx.reset();
self.clear_errorsrc();
if inten {
r.intenset.write(|w| w.stopped().set().error().set());
} else {
r.intenclr.write(|w| w.stopped().clear().error().clear());
}
// Start write operation.
r.shorts.write(|w| w.lasttx_stop().enabled());
r.tasks_starttx.write(|w| unsafe { w.bits(1) });
if buffer.is_empty() {
// With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves.
r.tasks_stop.write(|w| unsafe { w.bits(1) });
}
Ok(())
}
fn setup_read(&mut self, address: u8, buffer: &mut [u8], inten: bool) -> Result<(), Error> {
let r = T::regs();
compiler_fence(SeqCst);
r.address.write(|w| unsafe { w.address().bits(address) });
// Set up the DMA read.
unsafe { self.set_rx_buffer(buffer)? };
// Clear events
r.events_stopped.reset();
r.events_error.reset();
self.clear_errorsrc();
if inten {
r.intenset.write(|w| w.stopped().set().error().set());
} else {
r.intenclr.write(|w| w.stopped().clear().error().clear());
}
// Start read operation.
r.shorts.write(|w| w.lastrx_stop().enabled());
r.tasks_startrx.write(|w| unsafe { w.bits(1) });
if buffer.is_empty() {
// With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves.
r.tasks_stop.write(|w| unsafe { w.bits(1) });
}
Ok(())
}
fn setup_write_read_from_ram(
fn setup_operations(
&mut self,
address: u8,
wr_buffer: &[u8],
rd_buffer: &mut [u8],
operations: &mut [Operation<'_>],
tx_ram_buffer: Option<&mut [MaybeUninit<u8>; FORCE_COPY_BUFFER_SIZE]>,
last_op: Option<&Operation<'_>>,
inten: bool,
) -> Result<(), Error> {
) -> Result<usize, Error> {
let r = T::regs();
compiler_fence(SeqCst);
r.address.write(|w| unsafe { w.address().bits(address) });
// Set up DMA buffers.
unsafe {
self.set_tx_buffer(wr_buffer)?;
self.set_rx_buffer(rd_buffer)?;
}
// Clear events
r.events_suspended.reset();
r.events_stopped.reset();
r.events_error.reset();
self.clear_errorsrc();
if inten {
r.intenset.write(|w| w.stopped().set().error().set());
r.intenset.write(|w| w.suspended().set().stopped().set().error().set());
} else {
r.intenclr.write(|w| w.stopped().clear().error().clear());
r.intenclr
.write(|w| w.suspended().clear().stopped().clear().error().clear());
}
// Start write+read operation.
r.shorts.write(|w| {
w.lasttx_startrx().enabled();
w.lastrx_stop().enabled();
w
});
r.tasks_starttx.write(|w| unsafe { w.bits(1) });
if wr_buffer.is_empty() && rd_buffer.is_empty() {
// With a zero-length buffer, LASTRX/LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves.
// TODO handle when only one of the buffers is zero length
r.tasks_stop.write(|w| unsafe { w.bits(1) });
}
assert!(!operations.is_empty());
match operations {
[Operation::Read(_), Operation::Read(_), ..] => {
panic!("Consecutive read operations are not supported!")
}
[Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] => {
let stop = rest.is_empty();
// Set up DMA buffers.
unsafe {
self.set_tx_buffer(wr_buffer, tx_ram_buffer)?;
self.set_rx_buffer(rd_buffer)?;
}
r.shorts.write(|w| {
w.lastrx_starttx().enabled();
if stop {
w.lasttx_stop().enabled();
} else {
w.lasttx_suspend().enabled();
}
w
});
// Start read+write operation.
r.tasks_startrx.write(|w| unsafe { w.bits(1) });
if last_op.is_some() {
r.tasks_resume.write(|w| unsafe { w.bits(1) });
}
// TODO: Handle empty write buffer
if rd_buffer.is_empty() {
// With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STARTTX ourselves.
r.tasks_starttx.write(|w| unsafe { w.bits(1) });
}
Ok(2)
}
[Operation::Read(buffer)] => {
// Set up DMA buffers.
unsafe {
self.set_rx_buffer(buffer)?;
}
r.shorts.write(|w| w.lastrx_stop().enabled());
// Start read operation.
r.tasks_startrx.write(|w| unsafe { w.bits(1) });
if last_op.is_some() {
r.tasks_resume.write(|w| unsafe { w.bits(1) });
}
if buffer.is_empty() {
// With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves.
r.tasks_stop.write(|w| unsafe { w.bits(1) });
}
Ok(1)
}
[Operation::Write(wr_buffer), Operation::Read(rd_buffer)]
if !wr_buffer.is_empty() && !rd_buffer.is_empty() =>
{
// Set up DMA buffers.
unsafe {
self.set_tx_buffer(wr_buffer, tx_ram_buffer)?;
self.set_rx_buffer(rd_buffer)?;
}
// Start write+read operation.
r.shorts.write(|w| {
w.lasttx_startrx().enabled();
w.lastrx_stop().enabled();
w
});
r.tasks_starttx.write(|w| unsafe { w.bits(1) });
if last_op.is_some() {
r.tasks_resume.write(|w| unsafe { w.bits(1) });
}
Ok(2)
}
[Operation::Write(buffer), rest @ ..] => {
let stop = rest.is_empty();
// Set up DMA buffers.
unsafe {
self.set_tx_buffer(buffer, tx_ram_buffer)?;
}
// Start write operation.
r.shorts.write(|w| {
if stop {
w.lasttx_stop().enabled();
} else {
w.lasttx_suspend().enabled();
}
w
});
r.tasks_starttx.write(|w| unsafe { w.bits(1) });
if last_op.is_some() {
r.tasks_resume.write(|w| unsafe { w.bits(1) });
}
if buffer.is_empty() {
// With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves.
if stop {
r.tasks_stop.write(|w| unsafe { w.bits(1) });
} else {
r.tasks_suspend.write(|w| unsafe { w.bits(1) });
}
}
Ok(1)
}
[] => unreachable!(),
}
}
fn check_operations(&mut self, operations: &[Operation<'_>]) -> Result<(), Error> {
compiler_fence(SeqCst);
self.check_errorsrc()?;
assert!(operations.len() == 1 || operations.len() == 2);
match operations {
[Operation::Read(rd_buffer), Operation::Write(wr_buffer)]
| [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] => {
self.check_rx(rd_buffer.len())?;
self.check_tx(wr_buffer.len())?;
}
[Operation::Read(buffer)] => {
self.check_rx(buffer.len())?;
}
[Operation::Write(buffer), ..] => {
self.check_tx(buffer.len())?;
}
_ => unreachable!(),
}
Ok(())
}
fn setup_write_read(
&mut self,
address: u8,
wr_buffer: &[u8],
rd_buffer: &mut [u8],
inten: bool,
) -> Result<(), Error> {
match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) {
Ok(_) => Ok(()),
Err(Error::BufferNotInRAM) => {
trace!("Copying TWIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()];
tx_ram_buf.copy_from_slice(wr_buffer);
self.setup_write_read_from_ram(address, tx_ram_buf, rd_buffer, inten)
}
Err(error) => Err(error),
// ===========================================
/// Execute the provided operations on the I2C bus.
///
/// Each buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
///
/// Consecutive `Operation::Read`s are not supported due to hardware
/// limitations.
///
/// An `Operation::Write` following an `Operation::Read` must have a
/// non-empty buffer.
pub fn blocking_transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> {
let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.blocking_wait();
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> {
match self.setup_write_from_ram(address, wr_buffer, inten) {
Ok(_) => Ok(()),
Err(Error::BufferNotInRAM) => {
trace!("Copying TWIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()];
tx_ram_buf.copy_from_slice(wr_buffer);
self.setup_write_from_ram(address, tx_ram_buf, inten)
}
Err(error) => Err(error),
/// Same as [`blocking_transaction`](Twim::blocking_transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub fn blocking_transaction_from_ram(
&mut self,
address: u8,
mut operations: &mut [Operation<'_>],
) -> Result<(), Error> {
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, None, last_op, false)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.blocking_wait();
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
/// Execute the provided operations on the I2C bus with timeout.
///
/// See [`blocking_transaction`].
#[cfg(feature = "time")]
pub fn blocking_transaction_timeout(
&mut self,
address: u8,
mut operations: &mut [Operation<'_>],
timeout: Duration,
) -> Result<(), Error> {
let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.blocking_wait_timeout(timeout)?;
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
/// Same as [`blocking_transaction_timeout`](Twim::blocking_transaction_timeout) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
#[cfg(feature = "time")]
pub fn blocking_transaction_from_ram_timeout(
&mut self,
address: u8,
mut operations: &mut [Operation<'_>],
timeout: Duration,
) -> Result<(), Error> {
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, None, last_op, false)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.blocking_wait_timeout(timeout)?;
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
/// Execute the provided operations on the I2C bus.
///
/// Each buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
///
/// Consecutive `Operation::Read`s are not supported due to hardware
/// limitations.
///
/// An `Operation::Write` following an `Operation::Read` must have a
/// non-empty buffer.
pub async fn transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> {
let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, true)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.async_wait().await;
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
/// Same as [`transaction`](Twim::transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub async fn transaction_from_ram(
&mut self,
address: u8,
mut operations: &mut [Operation<'_>],
) -> Result<(), Error> {
let mut last_op = None;
while !operations.is_empty() {
let ops = self.setup_operations(address, operations, None, last_op, true)?;
let (in_progress, rest) = operations.split_at_mut(ops);
self.async_wait().await;
self.check_operations(in_progress)?;
last_op = in_progress.last();
operations = rest;
}
Ok(())
}
// ===========================================
/// Write to an I2C slave.
///
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub fn blocking_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> {
self.setup_write(address, buffer, false)?;
self.blocking_wait();
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.blocking_transaction(address, &mut [Operation::Write(buffer)])
}
/// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub fn blocking_write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> {
self.setup_write_from_ram(address, buffer, false)?;
self.blocking_wait();
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)])
}
/// Read from an I2C slave.
@ -516,12 +685,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
self.setup_read(address, buffer, false)?;
self.blocking_wait();
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_rx(buffer.len())?;
Ok(())
self.blocking_transaction(address, &mut [Operation::Read(buffer)])
}
/// Write data to an I2C slave, then read data from the slave without
@ -530,13 +694,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// The buffers must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub fn blocking_write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> {
self.setup_write_read(address, wr_buffer, rd_buffer, false)?;
self.blocking_wait();
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.blocking_transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
}
/// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
@ -546,13 +704,7 @@ impl<'d, T: Instance> Twim<'d, T> {
wr_buffer: &[u8],
rd_buffer: &mut [u8],
) -> Result<(), Error> {
self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?;
self.blocking_wait();
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.blocking_transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
}
// ===========================================
@ -562,12 +714,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// See [`blocking_write`].
#[cfg(feature = "time")]
pub fn blocking_write_timeout(&mut self, address: u8, buffer: &[u8], timeout: Duration) -> Result<(), Error> {
self.setup_write(address, buffer, false)?;
self.blocking_wait_timeout(timeout)?;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], timeout)
}
/// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
@ -578,12 +725,7 @@ impl<'d, T: Instance> Twim<'d, T> {
buffer: &[u8],
timeout: Duration,
) -> Result<(), Error> {
self.setup_write_from_ram(address, buffer, false)?;
self.blocking_wait_timeout(timeout)?;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], timeout)
}
/// Read from an I2C slave.
@ -592,12 +734,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// and at most 65535 bytes on the nRF52840.
#[cfg(feature = "time")]
pub fn blocking_read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> {
self.setup_read(address, buffer, false)?;
self.blocking_wait_timeout(timeout)?;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_rx(buffer.len())?;
Ok(())
self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], timeout)
}
/// Write data to an I2C slave, then read data from the slave without
@ -613,13 +750,11 @@ impl<'d, T: Instance> Twim<'d, T> {
rd_buffer: &mut [u8],
timeout: Duration,
) -> Result<(), Error> {
self.setup_write_read(address, wr_buffer, rd_buffer, false)?;
self.blocking_wait_timeout(timeout)?;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.blocking_transaction_timeout(
address,
&mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)],
timeout,
)
}
/// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
@ -631,13 +766,11 @@ impl<'d, T: Instance> Twim<'d, T> {
rd_buffer: &mut [u8],
timeout: Duration,
) -> Result<(), Error> {
self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?;
self.blocking_wait_timeout(timeout)?;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.blocking_transaction_from_ram_timeout(
address,
&mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)],
timeout,
)
}
// ===========================================
@ -647,12 +780,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
self.setup_read(address, buffer, true)?;
self.async_wait().await;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_rx(buffer.len())?;
Ok(())
self.transaction(address, &mut [Operation::Read(buffer)]).await
}
/// Write to an I2C slave.
@ -660,22 +788,13 @@ impl<'d, T: Instance> Twim<'d, T> {
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> {
self.setup_write(address, buffer, true)?;
self.async_wait().await;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.transaction(address, &mut [Operation::Write(buffer)]).await
}
/// Same as [`write`](Twim::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub async fn write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> {
self.setup_write_from_ram(address, buffer, true)?;
self.async_wait().await;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(buffer.len())?;
Ok(())
self.transaction_from_ram(address, &mut [Operation::Write(buffer)])
.await
}
/// Write data to an I2C slave, then read data from the slave without
@ -684,13 +803,8 @@ impl<'d, T: Instance> Twim<'d, T> {
/// The buffers must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> {
self.setup_write_read(address, wr_buffer, rd_buffer, true)?;
self.async_wait().await;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
.await
}
/// Same as [`write_read`](Twim::write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
@ -700,13 +814,8 @@ impl<'d, T: Instance> Twim<'d, T> {
wr_buffer: &[u8],
rd_buffer: &mut [u8],
) -> Result<(), Error> {
self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, true)?;
self.async_wait().await;
compiler_fence(SeqCst);
self.check_errorsrc()?;
self.check_tx(wr_buffer.len())?;
self.check_rx(rd_buffer.len())?;
Ok(())
self.transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
.await
}
}
@ -777,16 +886,7 @@ mod eh02 {
type Error = Error;
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
if slice_in_ram(bytes) {
self.blocking_write(addr, bytes)
} else {
let buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..];
for chunk in bytes.chunks(FORCE_COPY_BUFFER_SIZE) {
buf[..chunk.len()].copy_from_slice(chunk);
self.blocking_write(addr, &buf[..chunk.len()])?;
}
Ok(())
}
self.blocking_write(addr, bytes)
}
}
@ -832,47 +932,14 @@ impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> {
}
impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> {
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(address, buffer)
}
fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(address, buffer)
}
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_write_read(address, wr_buffer, rd_buffer)
}
fn transaction(
&mut self,
_address: u8,
_operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
todo!();
fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
self.blocking_transaction(address, operations)
}
}
impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> {
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.read(address, read).await
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.write(address, write).await
}
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.write_read(address, write, read).await
}
async fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
todo!()
async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
self.transaction(address, operations).await
}
}

17
embassy-nxp/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "embassy-nxp"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.0"
critical-section = "1.1.2"
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
lpc55-pac = "0.5.0"
defmt = "0.3.8"
[features]
default = ["rt"]
rt = ["lpc55-pac/rt"]

361
embassy-nxp/src/gpio.rs Normal file
View File

@ -0,0 +1,361 @@
use embassy_hal_internal::impl_peripheral;
use crate::pac_utils::*;
use crate::{peripherals, Peripheral, PeripheralRef};
pub(crate) fn init() {
// Enable clocks for GPIO, PINT, and IOCON
syscon_reg()
.ahbclkctrl0
.modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable());
}
/// 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,
}
/// Pull setting for a GPIO input set on "Digital" mode.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Pull {
/// No pull.
None,
/// Internal pull-up resistor.
Up,
/// Internal pull-down resistor.
Down,
}
/// The LPC55 boards have two GPIO banks, each with 32 pins. This enum represents the two banks.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Bank {
Bank0 = 0,
Bank1 = 1,
}
/// GPIO output driver. Internally, this is a specialized [Flex] pin.
pub struct Output<'d> {
pub(crate) pin: Flex<'d>,
}
impl<'d> Output<'d> {
/// Create GPIO output driver for a [Pin] with the provided [initial output](Level).
#[inline]
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level) -> Self {
let mut pin = Flex::new(pin);
pin.set_as_output();
let mut result = Self { pin };
match initial_output {
Level::High => result.set_high(),
Level::Low => result.set_low(),
};
result
}
pub fn set_high(&mut self) {
gpio_reg().set[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
}
pub fn set_low(&mut self) {
gpio_reg().clr[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
}
pub fn toggle(&mut self) {
gpio_reg().not[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
}
/// Get the current output level of the pin. Note that the value returned by this function is
/// the voltage level reported by the pin, not the value set by the output driver.
pub fn level(&self) -> Level {
let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits();
if bits & self.pin.bit() != 0 {
Level::High
} else {
Level::Low
}
}
}
/// GPIO input driver. Internally, this is a specialized [Flex] pin.
pub struct Input<'d> {
pub(crate) pin: Flex<'d>,
}
impl<'d> Input<'d> {
/// Create GPIO output driver for a [Pin] with the provided [Pull].
#[inline]
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, pull: Pull) -> Self {
let mut pin = Flex::new(pin);
pin.set_as_input();
let mut result = Self { pin };
result.set_pull(pull);
result
}
/// Set the pull configuration for the pin. To disable the pull, use [Pull::None].
pub fn set_pull(&mut self, pull: Pull) {
match_iocon!(register, iocon_reg(), self.pin.pin_bank(), self.pin.pin_number(), {
register.modify(|_, w| match pull {
Pull::None => w.mode().inactive(),
Pull::Up => w.mode().pull_up(),
Pull::Down => w.mode().pull_down(),
});
});
}
/// Get the current input level of the pin.
pub fn read(&self) -> Level {
let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits();
if bits & self.pin.bit() != 0 {
Level::High
} else {
Level::Low
}
}
}
/// A flexible GPIO (digital mode) pin whose mode is not yet determined. Under the hood, this is a
/// reference to a type-erased pin called ["AnyPin"](AnyPin).
pub struct Flex<'d> {
pub(crate) pin: PeripheralRef<'d, AnyPin>,
}
impl<'d> Flex<'d> {
/// Wrap the pin in a `Flex`.
///
/// Note: you cannot assume that the pin will be in Digital mode after this call.
#[inline]
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd) -> Self {
Self {
pin: pin.into_ref().map_into(),
}
}
/// Get the bank of this pin. See also [Bank].
///
/// # Example
///
/// ```
/// use embassy_nxp::gpio::{Bank, Flex};
///
/// let p = embassy_nxp::init(Default::default());
/// let pin = Flex::new(p.PIO1_15);
///
/// assert_eq!(pin.pin_bank(), Bank::Bank1);
/// ```
pub fn pin_bank(&self) -> Bank {
self.pin.pin_bank()
}
/// Get the number of this pin within its bank. See also [Bank].
///
/// # Example
///
/// ```
/// use embassy_nxp::gpio::Flex;
///
/// let p = embassy_nxp::init(Default::default());
/// let pin = Flex::new(p.PIO1_15);
///
/// assert_eq!(pin.pin_number(), 15 as u8);
/// ```
pub fn pin_number(&self) -> u8 {
self.pin.pin_number()
}
/// Get the bit mask for this pin. Useful for setting or clearing bits in a register. Note:
/// PIOx_0 is bit 0, PIOx_1 is bit 1, etc.
///
/// # Example
///
/// ```
/// use embassy_nxp::gpio::Flex;
///
/// let p = embassy_nxp::init(Default::default());
/// let pin = Flex::new(p.PIO1_3);
///
/// assert_eq!(pin.bit(), 0b0000_1000);
/// ```
pub fn bit(&self) -> u32 {
1 << self.pin.pin_number()
}
/// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default
/// setting for pins is (usually) non-digital.
fn set_as_digital(&mut self) {
match_iocon!(register, iocon_reg(), self.pin_bank(), self.pin_number(), {
register.modify(|_, w| w.digimode().digital());
});
}
/// Set the pin in output mode. This implies setting the pin to digital mode, which this
/// function handles itself.
pub fn set_as_output(&mut self) {
self.set_as_digital();
gpio_reg().dirset[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirsetp().bits(self.bit()) })
}
pub fn set_as_input(&mut self) {
self.set_as_digital();
gpio_reg().dirclr[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirclrp().bits(self.bit()) })
}
}
/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate.
pub(crate) trait SealedPin: Sized {
fn pin_bank(&self) -> Bank;
fn pin_number(&self) -> u8;
}
/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an
/// [AnyPin]. By default, this trait is sealed and cannot be implemented outside of the
/// `embassy-nxp` crate due to the [SealedPin] trait.
#[allow(private_bounds)]
pub trait Pin: Peripheral<P = Self> + Into<AnyPin> + SealedPin + Sized + 'static {
/// Degrade to a generic pin struct
fn degrade(self) -> AnyPin {
AnyPin {
pin_bank: self.pin_bank(),
pin_number: self.pin_number(),
}
}
/// Returns the pin number within a bank
#[inline]
fn pin(&self) -> u8 {
self.pin_number()
}
/// Returns the bank of this pin
#[inline]
fn bank(&self) -> Bank {
self.pin_bank()
}
}
/// Type-erased GPIO pin.
pub struct AnyPin {
pin_bank: Bank,
pin_number: u8,
}
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(pin_bank: Bank, pin_number: u8) -> Self {
Self { pin_bank, pin_number }
}
}
impl_peripheral!(AnyPin);
impl Pin for AnyPin {}
impl SealedPin for AnyPin {
#[inline]
fn pin_bank(&self) -> Bank {
self.pin_bank
}
#[inline]
fn pin_number(&self) -> u8 {
self.pin_number
}
}
macro_rules! impl_pin {
($name:ident, $bank:expr, $pin_num:expr) => {
impl Pin for peripherals::$name {}
impl SealedPin for peripherals::$name {
#[inline]
fn pin_bank(&self) -> Bank {
$bank
}
#[inline]
fn pin_number(&self) -> u8 {
$pin_num
}
}
impl From<peripherals::$name> for crate::gpio::AnyPin {
fn from(val: peripherals::$name) -> Self {
crate::gpio::Pin::degrade(val)
}
}
};
}
impl_pin!(PIO0_0, Bank::Bank0, 0);
impl_pin!(PIO0_1, Bank::Bank0, 1);
impl_pin!(PIO0_2, Bank::Bank0, 2);
impl_pin!(PIO0_3, Bank::Bank0, 3);
impl_pin!(PIO0_4, Bank::Bank0, 4);
impl_pin!(PIO0_5, Bank::Bank0, 5);
impl_pin!(PIO0_6, Bank::Bank0, 6);
impl_pin!(PIO0_7, Bank::Bank0, 7);
impl_pin!(PIO0_8, Bank::Bank0, 8);
impl_pin!(PIO0_9, Bank::Bank0, 9);
impl_pin!(PIO0_10, Bank::Bank0, 10);
impl_pin!(PIO0_11, Bank::Bank0, 11);
impl_pin!(PIO0_12, Bank::Bank0, 12);
impl_pin!(PIO0_13, Bank::Bank0, 13);
impl_pin!(PIO0_14, Bank::Bank0, 14);
impl_pin!(PIO0_15, Bank::Bank0, 15);
impl_pin!(PIO0_16, Bank::Bank0, 16);
impl_pin!(PIO0_17, Bank::Bank0, 17);
impl_pin!(PIO0_18, Bank::Bank0, 18);
impl_pin!(PIO0_19, Bank::Bank0, 19);
impl_pin!(PIO0_20, Bank::Bank0, 20);
impl_pin!(PIO0_21, Bank::Bank0, 21);
impl_pin!(PIO0_22, Bank::Bank0, 22);
impl_pin!(PIO0_23, Bank::Bank0, 23);
impl_pin!(PIO0_24, Bank::Bank0, 24);
impl_pin!(PIO0_25, Bank::Bank0, 25);
impl_pin!(PIO0_26, Bank::Bank0, 26);
impl_pin!(PIO0_27, Bank::Bank0, 27);
impl_pin!(PIO0_28, Bank::Bank0, 28);
impl_pin!(PIO0_29, Bank::Bank0, 29);
impl_pin!(PIO0_30, Bank::Bank0, 30);
impl_pin!(PIO0_31, Bank::Bank0, 31);
impl_pin!(PIO1_0, Bank::Bank1, 0);
impl_pin!(PIO1_1, Bank::Bank1, 1);
impl_pin!(PIO1_2, Bank::Bank1, 2);
impl_pin!(PIO1_3, Bank::Bank1, 3);
impl_pin!(PIO1_4, Bank::Bank1, 4);
impl_pin!(PIO1_5, Bank::Bank1, 5);
impl_pin!(PIO1_6, Bank::Bank1, 6);
impl_pin!(PIO1_7, Bank::Bank1, 7);
impl_pin!(PIO1_8, Bank::Bank1, 8);
impl_pin!(PIO1_9, Bank::Bank1, 9);
impl_pin!(PIO1_10, Bank::Bank1, 10);
impl_pin!(PIO1_11, Bank::Bank1, 11);
impl_pin!(PIO1_12, Bank::Bank1, 12);
impl_pin!(PIO1_13, Bank::Bank1, 13);
impl_pin!(PIO1_14, Bank::Bank1, 14);
impl_pin!(PIO1_15, Bank::Bank1, 15);
impl_pin!(PIO1_16, Bank::Bank1, 16);
impl_pin!(PIO1_17, Bank::Bank1, 17);
impl_pin!(PIO1_18, Bank::Bank1, 18);
impl_pin!(PIO1_19, Bank::Bank1, 19);
impl_pin!(PIO1_20, Bank::Bank1, 20);
impl_pin!(PIO1_21, Bank::Bank1, 21);
impl_pin!(PIO1_22, Bank::Bank1, 22);
impl_pin!(PIO1_23, Bank::Bank1, 23);
impl_pin!(PIO1_24, Bank::Bank1, 24);
impl_pin!(PIO1_25, Bank::Bank1, 25);
impl_pin!(PIO1_26, Bank::Bank1, 26);
impl_pin!(PIO1_27, Bank::Bank1, 27);
impl_pin!(PIO1_28, Bank::Bank1, 28);
impl_pin!(PIO1_29, Bank::Bank1, 29);
impl_pin!(PIO1_30, Bank::Bank1, 30);
impl_pin!(PIO1_31, Bank::Bank1, 31);

95
embassy-nxp/src/lib.rs Normal file
View File

@ -0,0 +1,95 @@
#![no_std]
pub mod gpio;
mod pac_utils;
pub mod pint;
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
pub use lpc55_pac as pac;
/// Initialize the `embassy-nxp` HAL with the provided configuration.
///
/// This returns the peripheral singletons that can be used for creating drivers.
///
/// This should only be called once and at startup, otherwise it panics.
pub fn init(_config: config::Config) -> Peripherals {
gpio::init();
pint::init();
crate::Peripherals::take()
}
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).
PIO0_0,
PIO0_1,
PIO0_2,
PIO0_3,
PIO0_4,
PIO0_5,
PIO0_6,
PIO0_7,
PIO0_8,
PIO0_9,
PIO0_10,
PIO0_11,
PIO0_12,
PIO0_13,
PIO0_14,
PIO0_15,
PIO0_16,
PIO0_17,
PIO0_18,
PIO0_19,
PIO0_20,
PIO0_21,
PIO0_22,
PIO0_23,
PIO0_24,
PIO0_25,
PIO0_26,
PIO0_27,
PIO0_28,
PIO0_29,
PIO0_30,
PIO0_31,
PIO1_0,
PIO1_1,
PIO1_2,
PIO1_3,
PIO1_4,
PIO1_5,
PIO1_6,
PIO1_7,
PIO1_8,
PIO1_9,
PIO1_10,
PIO1_11,
PIO1_12,
PIO1_13,
PIO1_14,
PIO1_15,
PIO1_16,
PIO1_17,
PIO1_18,
PIO1_19,
PIO1_20,
PIO1_21,
PIO1_22,
PIO1_23,
PIO1_24,
PIO1_25,
PIO1_26,
PIO1_27,
PIO1_28,
PIO1_29,
PIO1_30,
PIO1_31,
}
/// HAL configuration for the NXP board.
pub mod config {
#[derive(Default)]
pub struct Config {}
}

View File

@ -0,0 +1,323 @@
/// Get the GPIO register block. This is used to configure all GPIO pins.
///
/// # Safety
/// Due to the type system of peripherals, access to the settings of a single pin is possible only
/// by a single thread at a time. Read/Write operations on a single registers are NOT atomic. You
/// must ensure that the GPIO registers are not accessed concurrently by multiple threads.
pub(crate) fn gpio_reg() -> &'static lpc55_pac::gpio::RegisterBlock {
unsafe { &*lpc55_pac::GPIO::ptr() }
}
/// Get the IOCON register block.
///
/// # Safety
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
/// registers are not accessed concurrently by multiple threads.
pub(crate) fn iocon_reg() -> &'static lpc55_pac::iocon::RegisterBlock {
unsafe { &*lpc55_pac::IOCON::ptr() }
}
/// Get the INPUTMUX register block.
///
/// # Safety
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
/// registers are not accessed concurrently by multiple threads.
pub(crate) fn inputmux_reg() -> &'static lpc55_pac::inputmux::RegisterBlock {
unsafe { &*lpc55_pac::INPUTMUX::ptr() }
}
/// Get the SYSCON register block.
///
/// # Safety
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
/// registers are not accessed concurrently by multiple threads.
pub(crate) fn syscon_reg() -> &'static lpc55_pac::syscon::RegisterBlock {
unsafe { &*lpc55_pac::SYSCON::ptr() }
}
/// Get the PINT register block.
///
/// # Safety
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
/// registers are not accessed concurrently by multiple threads.
pub(crate) fn pint_reg() -> &'static lpc55_pac::pint::RegisterBlock {
unsafe { &*lpc55_pac::PINT::ptr() }
}
/// Match the pin bank and number of a pin to the corresponding IOCON register.
///
/// # Example
/// ```
/// use embassy_nxp::gpio::Bank;
/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon};
///
/// // Make pin PIO1_6 digital and set it to pull-down mode.
/// match_iocon!(register, iocon_reg(), Bank::Bank1, 6, {
/// register.modify(|_, w| w.mode().pull_down().digimode().digital());
/// });
/// ```
macro_rules! match_iocon {
($register:ident, $iocon_register:expr, $pin_bank:expr, $pin_number:expr, $action:expr) => {
match ($pin_bank, $pin_number) {
(Bank::Bank0, 0) => {
let $register = &($iocon_register).pio0_0;
$action;
}
(Bank::Bank0, 1) => {
let $register = &($iocon_register).pio0_1;
$action;
}
(Bank::Bank0, 2) => {
let $register = &($iocon_register).pio0_2;
$action;
}
(Bank::Bank0, 3) => {
let $register = &($iocon_register).pio0_3;
$action;
}
(Bank::Bank0, 4) => {
let $register = &($iocon_register).pio0_4;
$action;
}
(Bank::Bank0, 5) => {
let $register = &($iocon_register).pio0_5;
$action;
}
(Bank::Bank0, 6) => {
let $register = &($iocon_register).pio0_6;
$action;
}
(Bank::Bank0, 7) => {
let $register = &($iocon_register).pio0_7;
$action;
}
(Bank::Bank0, 8) => {
let $register = &($iocon_register).pio0_8;
$action;
}
(Bank::Bank0, 9) => {
let $register = &($iocon_register).pio0_9;
$action;
}
(Bank::Bank0, 10) => {
let $register = &($iocon_register).pio0_10;
$action;
}
(Bank::Bank0, 11) => {
let $register = &($iocon_register).pio0_11;
$action;
}
(Bank::Bank0, 12) => {
let $register = &($iocon_register).pio0_12;
$action;
}
(Bank::Bank0, 13) => {
let $register = &($iocon_register).pio0_13;
$action;
}
(Bank::Bank0, 14) => {
let $register = &($iocon_register).pio0_14;
$action;
}
(Bank::Bank0, 15) => {
let $register = &($iocon_register).pio0_15;
$action;
}
(Bank::Bank0, 16) => {
let $register = &($iocon_register).pio0_16;
$action;
}
(Bank::Bank0, 17) => {
let $register = &($iocon_register).pio0_17;
$action;
}
(Bank::Bank0, 18) => {
let $register = &($iocon_register).pio0_18;
$action;
}
(Bank::Bank0, 19) => {
let $register = &($iocon_register).pio0_19;
$action;
}
(Bank::Bank0, 20) => {
let $register = &($iocon_register).pio0_20;
$action;
}
(Bank::Bank0, 21) => {
let $register = &($iocon_register).pio0_21;
$action;
}
(Bank::Bank0, 22) => {
let $register = &($iocon_register).pio0_22;
$action;
}
(Bank::Bank0, 23) => {
let $register = &($iocon_register).pio0_23;
$action;
}
(Bank::Bank0, 24) => {
let $register = &($iocon_register).pio0_24;
$action;
}
(Bank::Bank0, 25) => {
let $register = &($iocon_register).pio0_25;
$action;
}
(Bank::Bank0, 26) => {
let $register = &($iocon_register).pio0_26;
$action;
}
(Bank::Bank0, 27) => {
let $register = &($iocon_register).pio0_27;
$action;
}
(Bank::Bank0, 28) => {
let $register = &($iocon_register).pio0_28;
$action;
}
(Bank::Bank0, 29) => {
let $register = &($iocon_register).pio0_29;
$action;
}
(Bank::Bank0, 30) => {
let $register = &($iocon_register).pio0_30;
$action;
}
(Bank::Bank0, 31) => {
let $register = &($iocon_register).pio0_31;
$action;
}
(Bank::Bank1, 0) => {
let $register = &($iocon_register).pio1_0;
$action;
}
(Bank::Bank1, 1) => {
let $register = &($iocon_register).pio1_1;
$action;
}
(Bank::Bank1, 2) => {
let $register = &($iocon_register).pio1_2;
$action;
}
(Bank::Bank1, 3) => {
let $register = &($iocon_register).pio1_3;
$action;
}
(Bank::Bank1, 4) => {
let $register = &($iocon_register).pio1_4;
$action;
}
(Bank::Bank1, 5) => {
let $register = &($iocon_register).pio1_5;
$action;
}
(Bank::Bank1, 6) => {
let $register = &($iocon_register).pio1_6;
$action;
}
(Bank::Bank1, 7) => {
let $register = &($iocon_register).pio1_7;
$action;
}
(Bank::Bank1, 8) => {
let $register = &($iocon_register).pio1_8;
$action;
}
(Bank::Bank1, 9) => {
let $register = &($iocon_register).pio1_9;
$action;
}
(Bank::Bank1, 10) => {
let $register = &($iocon_register).pio1_10;
$action;
}
(Bank::Bank1, 11) => {
let $register = &($iocon_register).pio1_11;
$action;
}
(Bank::Bank1, 12) => {
let $register = &($iocon_register).pio1_12;
$action;
}
(Bank::Bank1, 13) => {
let $register = &($iocon_register).pio1_13;
$action;
}
(Bank::Bank1, 14) => {
let $register = &($iocon_register).pio1_14;
$action;
}
(Bank::Bank1, 15) => {
let $register = &($iocon_register).pio1_15;
$action;
}
(Bank::Bank1, 16) => {
let $register = &($iocon_register).pio1_16;
$action;
}
(Bank::Bank1, 17) => {
let $register = &($iocon_register).pio1_17;
$action;
}
(Bank::Bank1, 18) => {
let $register = &($iocon_register).pio1_18;
$action;
}
(Bank::Bank1, 19) => {
let $register = &($iocon_register).pio1_19;
$action;
}
(Bank::Bank1, 20) => {
let $register = &($iocon_register).pio1_20;
$action;
}
(Bank::Bank1, 21) => {
let $register = &($iocon_register).pio1_21;
$action;
}
(Bank::Bank1, 22) => {
let $register = &($iocon_register).pio1_22;
$action;
}
(Bank::Bank1, 23) => {
let $register = &($iocon_register).pio1_23;
$action;
}
(Bank::Bank1, 24) => {
let $register = &($iocon_register).pio1_24;
$action;
}
(Bank::Bank1, 25) => {
let $register = &($iocon_register).pio1_25;
$action;
}
(Bank::Bank1, 26) => {
let $register = &($iocon_register).pio1_26;
$action;
}
(Bank::Bank1, 27) => {
let $register = &($iocon_register).pio1_27;
$action;
}
(Bank::Bank1, 28) => {
let $register = &($iocon_register).pio1_28;
$action;
}
(Bank::Bank1, 29) => {
let $register = &($iocon_register).pio1_29;
$action;
}
(Bank::Bank1, 30) => {
let $register = &($iocon_register).pio1_30;
$action;
}
(Bank::Bank1, 31) => {
let $register = &($iocon_register).pio1_31;
$action;
}
_ => unreachable!(),
}
};
}
pub(crate) use match_iocon;

440
embassy-nxp/src/pint.rs Normal file
View File

@ -0,0 +1,440 @@
//! Pin Interrupt module.
use core::cell::RefCell;
use core::future::Future;
use core::pin::Pin as FuturePin;
use core::task::{Context, Poll};
use critical_section::Mutex;
use embassy_hal_internal::{Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::{self, AnyPin, Level, SealedPin};
use crate::pac::interrupt;
use crate::pac_utils::*;
struct PinInterrupt {
assigned: bool,
waker: AtomicWaker,
/// If true, the interrupt was triggered due to this PinInterrupt. This is used to determine if
/// an [InputFuture] should return Poll::Ready.
at_fault: bool,
}
impl PinInterrupt {
pub fn interrupt_active(&self) -> bool {
self.assigned
}
/// Mark the interrupt as assigned to a pin.
pub fn enable(&mut self) {
self.assigned = true;
self.at_fault = false;
}
/// Mark the interrupt as available.
pub fn disable(&mut self) {
self.assigned = false;
self.at_fault = false;
}
/// Returns true if the interrupt was triggered due to this PinInterrupt.
///
/// If this function returns true, it will also reset the at_fault flag.
pub fn at_fault(&mut self) -> bool {
let val = self.at_fault;
self.at_fault = false;
val
}
/// Set the at_fault flag to true.
pub fn fault(&mut self) {
self.at_fault = true;
}
}
const NEW_PIN_INTERRUPT: PinInterrupt = PinInterrupt {
assigned: false,
waker: AtomicWaker::new(),
at_fault: false,
};
const INTERUPT_COUNT: usize = 8;
static PIN_INTERRUPTS: Mutex<RefCell<[PinInterrupt; INTERUPT_COUNT]>> =
Mutex::new(RefCell::new([NEW_PIN_INTERRUPT; INTERUPT_COUNT]));
fn next_available_interrupt() -> Option<usize> {
critical_section::with(|cs| {
for (i, pin_interrupt) in PIN_INTERRUPTS.borrow(cs).borrow().iter().enumerate() {
if !pin_interrupt.interrupt_active() {
return Some(i);
}
}
None
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Edge {
Rising,
Falling,
Both,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum InterruptOn {
Level(Level),
Edge(Edge),
}
pub(crate) fn init() {
syscon_reg().ahbclkctrl0.modify(|_, w| w.pint().enable());
// Enable interrupts
unsafe {
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT0);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT1);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT2);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT3);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT4);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT5);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6);
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7);
};
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct InputFuture<'d> {
#[allow(dead_code)]
pin: PeripheralRef<'d, AnyPin>,
interrupt_number: usize,
}
impl<'d> InputFuture<'d> {
/// Create a new input future. Returns None if all interrupts are in use.
fn new(pin: impl Peripheral<P = impl gpio::Pin> + 'd, interrupt_on: InterruptOn) -> Option<Self> {
let pin = pin.into_ref().map_into();
let interrupt_number = next_available_interrupt()?;
// Clear interrupt, just in case
pint_reg()
.rise
.write(|w| unsafe { w.rdet().bits(1 << interrupt_number) });
pint_reg()
.fall
.write(|w| unsafe { w.fdet().bits(1 << interrupt_number) });
// Enable input multiplexing on pin interrupt register 0 for pin (32*bank + pin_number)
inputmux_reg().pintsel[interrupt_number]
.write(|w| unsafe { w.intpin().bits(32 * pin.pin_bank() as u8 + pin.pin_number()) });
match interrupt_on {
InterruptOn::Level(level) => {
// Set pin interrupt register to edge sensitive or level sensitive
// 0 = edge sensitive, 1 = level sensitive
pint_reg()
.isel
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << interrupt_number)) });
// Enable level interrupt.
//
// Note: Level sensitive interrupts are enabled by the same register as rising edge
// is activated.
// 0 = no-op, 1 = enable
pint_reg()
.sienr
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
// Set active level
match level {
Level::Low => {
// 0 = no-op, 1 = select LOW
pint_reg()
.cienf
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
}
Level::High => {
// 0 = no-op, 1 = select HIGH
pint_reg()
.sienf
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
}
}
}
InterruptOn::Edge(edge) => {
// Set pin interrupt register to edge sensitive or level sensitive
// 0 = edge sensitive, 1 = level sensitive
pint_reg()
.isel
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << interrupt_number)) });
// Enable rising/falling edge detection
match edge {
Edge::Rising => {
// 0 = no-op, 1 = enable rising edge
pint_reg()
.sienr
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
// 0 = no-op, 1 = disable falling edge
pint_reg()
.cienf
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
}
Edge::Falling => {
// 0 = no-op, 1 = enable falling edge
pint_reg()
.sienf
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
// 0 = no-op, 1 = disable rising edge
pint_reg()
.cienr
.write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) });
}
Edge::Both => {
// 0 = no-op, 1 = enable
pint_reg()
.sienr
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
pint_reg()
.sienf
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
}
}
}
}
critical_section::with(|cs| {
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
let pin_interrupt = &mut pin_interrupts[interrupt_number];
pin_interrupt.enable();
});
Some(Self { pin, interrupt_number })
}
/// Returns true if the interrupt was triggered for this pin.
fn interrupt_triggered(&self) -> bool {
let interrupt_number = self.interrupt_number;
// Initially, we determine if the interrupt was triggered by this InputFuture by checking
// the flags of the interrupt_number. However, by the time we get to this point, the
// interrupt may have been triggered again, so we needed to clear the cpu flags immediately.
// As a solution, we mark which [PinInterrupt] is responsible for the interrupt ("at fault")
critical_section::with(|cs| {
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
let pin_interrupt = &mut pin_interrupts[interrupt_number];
pin_interrupt.at_fault()
})
}
}
impl<'d> Drop for InputFuture<'d> {
fn drop(&mut self) {
let interrupt_number = self.interrupt_number;
// Disable pin interrupt
// 0 = no-op, 1 = disable
pint_reg()
.cienr
.write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) });
pint_reg()
.cienf
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
critical_section::with(|cs| {
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
let pin_interrupt = &mut pin_interrupts[interrupt_number];
pin_interrupt.disable();
});
}
}
impl<'d> Future for InputFuture<'d> {
type Output = ();
fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let interrupt_number = self.interrupt_number;
critical_section::with(|cs| {
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
let pin_interrupt = &mut pin_interrupts[interrupt_number];
pin_interrupt.waker.register(cx.waker());
});
if self.interrupt_triggered() {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
fn handle_interrupt(interrupt_number: usize) {
pint_reg()
.rise
.write(|w| unsafe { w.rdet().bits(1 << interrupt_number) });
pint_reg()
.fall
.write(|w| unsafe { w.fdet().bits(1 << interrupt_number) });
critical_section::with(|cs| {
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
let pin_interrupt = &mut pin_interrupts[interrupt_number];
pin_interrupt.fault();
pin_interrupt.waker.wake();
});
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT0() {
handle_interrupt(0);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT1() {
handle_interrupt(1);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT2() {
handle_interrupt(2);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT3() {
handle_interrupt(3);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT4() {
handle_interrupt(4);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT5() {
handle_interrupt(5);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT6() {
handle_interrupt(6);
}
#[allow(non_snake_case)]
#[interrupt]
fn PIN_INT7() {
handle_interrupt(7);
}
impl gpio::Flex<'_> {
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
/// try to wait for more than 8 pins, this function will return `None`.
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Both))?.await;
Some(())
}
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Falling))?.await;
Some(())
}
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Rising))?.await;
Some(())
}
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_low(&mut self) -> Option<()> {
InputFuture::new(&mut self.pin, InterruptOn::Level(Level::Low))?.await;
Some(())
}
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_high(&mut self) -> Option<()> {
InputFuture::new(&mut self.pin, InterruptOn::Level(Level::High))?.await;
Some(())
}
}
impl gpio::Input<'_> {
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
/// try to wait for more than 8 pins, this function will return `None`.
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
self.pin.wait_for_any_edge().await
}
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
self.pin.wait_for_falling_edge().await
}
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
self.pin.wait_for_rising_edge().await
}
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_low(&mut self) -> Option<()> {
self.pin.wait_for_low().await
}
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_high(&mut self) -> Option<()> {
self.pin.wait_for_high().await
}
}
impl gpio::Output<'_> {
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
/// try to wait for more than 8 pins, this function will return `None`.
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
self.pin.wait_for_any_edge().await
}
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
self.pin.wait_for_falling_edge().await
}
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
/// for more than 8 pins, this function will return `None`.
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
self.pin.wait_for_rising_edge().await
}
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_low(&mut self) -> Option<()> {
self.pin.wait_for_low().await
}
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
/// more than 8 pins, this function will return `None`.
pub async fn wait_for_high(&mut self) -> Option<()> {
self.pin.wait_for_high().await
}
}

View File

@ -118,18 +118,18 @@ embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" }
atomic-polyfill = "1.0.1"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
nb = "1.0.0"
nb = "1.1.0"
cfg-if = "1.0.0"
cortex-m-rt = ">=0.6.15,<0.8"
cortex-m = "0.7.6"
critical-section = "1.1"
critical-section = "1.2.0"
chrono = { version = "0.4", default-features = false, optional = true }
embedded-io = { version = "0.6.1" }
embedded-io-async = { version = "0.6.1" }
embedded-storage = { version = "0.3" }
embedded-storage-async = { version = "0.4.1" }
rand_core = "0.6.4"
fixed = "1.23.1"
fixed = "1.28.0"
rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] }
@ -141,10 +141,11 @@ embedded-hal-nb = { version = "1.0" }
pio-proc = {version= "0.2" }
pio = {version= "0.2.1" }
rp2040-boot2 = "0.3"
document-features = "0.2.7"
document-features = "0.2.10"
sha2-const-stable = "0.1"
rp-binary-info = { version = "0.1.0", optional = true }
smart-leds = "0.4.0"
[dev-dependencies]
embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
embassy-executor = { version = "0.6.1", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
static_cell = { version = "2" }

View File

@ -1,7 +1,8 @@
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
fn main() {
if env::var("CARGO_FEATURE_RP2040").is_ok() {
@ -16,4 +17,23 @@ fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=link-rp.x.in");
}
// code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs
let mut target = env::var("TARGET").unwrap();
// When using a custom target JSON, `$TARGET` contains the path to that JSON file. By
// convention, these files are named after the actual target triple, eg.
// `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs.
let path = Path::new(&target);
if path.extension() == Some(OsStr::new("json")) {
target = path
.file_stem()
.map_or(target.clone(), |stem| stem.to_str().unwrap().to_string());
}
println!("cargo::rustc-check-cfg=cfg(has_fpu)");
if target.ends_with("-eabihf") {
println!("cargo:rustc-cfg=has_fpu");
}
}

View File

@ -109,7 +109,10 @@ impl ClockConfig {
sys_pll: Some(PllConfig {
refdiv: 1,
fbdiv: 125,
#[cfg(feature = "rp2040")]
post_div1: 6,
#[cfg(feature = "_rp235x")]
post_div1: 5,
post_div2: 2,
}),
usb_pll: Some(PllConfig {

View File

@ -34,6 +34,7 @@ pub mod i2c_slave;
pub mod multicore;
#[cfg(feature = "_rp235x")]
pub mod otp;
pub mod pio_programs;
pub mod pwm;
mod reset;
pub mod rom_data;
@ -165,22 +166,31 @@ embassy_hal_internal::interrupt_mod!(
// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`.
#[macro_export]
macro_rules! bind_interrupts {
($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
($vis:vis struct $name:ident {
$(
$(#[cfg($cond_irq:meta)])?
$irq:ident => $(
$(#[cfg($cond_handler:meta)])?
$handler:ty
),*;
)*
}) => {
#[derive(Copy, Clone)]
$vis struct $name;
$(
#[allow(non_snake_case)]
#[no_mangle]
$(#[cfg($cond_irq)])?
unsafe extern "C" fn $irq() {
$(
$(#[cfg($cond_handler)])?
<$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt();
$(#[cfg($cond_handler)])?
unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {}
)*
}
$(
unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {}
)*
)*
};
}
@ -479,7 +489,7 @@ pub fn install_core0_stack_guard() -> Result<(), ()> {
#[cfg(all(feature = "rp2040", not(feature = "_test")))]
#[inline(always)]
fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
let core = unsafe { cortex_m::Peripherals::steal() };
// Fail if MPU is already configured
@ -507,7 +517,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
#[cfg(all(feature = "_rp235x", not(feature = "_test")))]
#[inline(always)]
fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
let core = unsafe { cortex_m::Peripherals::steal() };
// Fail if MPU is already configured
@ -527,7 +537,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
// so the compile fails when we try to use ARMv8 peripherals.
#[cfg(feature = "_test")]
#[inline(always)]
fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> {
unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> {
Ok(())
}

View File

@ -58,7 +58,7 @@ const RESUME_TOKEN: u32 = !0xDEADBEEF;
static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false);
#[inline(always)]
fn core1_setup(stack_bottom: *mut usize) {
unsafe fn core1_setup(stack_bottom: *mut usize) {
if install_stack_guard(stack_bottom).is_err() {
// currently only happens if the MPU was already set up, which
// would indicate that the core is already in use from outside
@ -148,7 +148,7 @@ where
entry: *mut ManuallyDrop<F>,
stack_bottom: *mut usize,
) -> ! {
core1_setup(stack_bottom);
unsafe { core1_setup(stack_bottom) };
let entry = unsafe { ManuallyDrop::take(&mut *entry) };
@ -169,6 +169,13 @@ where
interrupt::SIO_IRQ_FIFO.enable()
};
// Enable FPU
#[cfg(all(feature = "_rp235x", has_fpu))]
unsafe {
let p = cortex_m::Peripherals::steal();
p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22));
}
entry()
}

View File

@ -0,0 +1,203 @@
//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
.origin 20
loop:
out x, 24
delay:
jmp x--, delay
out pins, 4 side 1
out null, 4 side 0
jmp !osre, loop
irq 0
"#,
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
// many side sets are only there to free up a delay bit!
let prg = pio_proc::pio_asm!(
r#"
.origin 27
.side_set 1
.wrap_target
pull side 0
out x 1 side 0 ; !rs
out y 7 side 0 ; #data - 1
; rs/rw to e: >= 60ns
; e high time: >= 500ns
; e low time: >= 500ns
; read data valid after e falling: ~5ns
; write data hold after e falling: ~10ns
loop:
pull side 0
jmp !x data side 0
command:
set pins 0b00 side 0
jmp shift side 0
data:
set pins 0b01 side 0
shift:
out pins 4 side 1 [9]
nop side 0 [9]
out pins 4 side 1 [9]
mov osr null side 0 [7]
out pindirs 4 side 0
set pins 0b10 side 0
busy:
nop side 1 [9]
jmp pin more side 0 [9]
mov osr ~osr side 1 [9]
nop side 0 [4]
out pindirs 4 side 0
jmp y-- loop side 0
.wrap
more:
nop side 1 [9]
jmp busy side 0 [9]
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed HD44780 driver
pub struct PioHD44780<'l, P: Instance, const S: usize> {
dma: PeripheralRef<'l, AnyChannel>,
sm: StateMachine<'l, P, S>,
buf: [u8; 40],
}
impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
/// Configure the given state machine to first init, then write data to, a HD44780 display.
pub async fn new(
common: &mut Common<'l, P>,
mut sm: StateMachine<'l, P, S>,
mut irq: Irq<'l, P, S>,
dma: impl Peripheral<P = impl Channel> + 'l,
rs: impl PioPin,
rw: impl PioPin,
e: impl PioPin,
db4: impl PioPin,
db5: impl PioPin,
db6: impl PioPin,
db7: impl PioPin,
word_prg: &PioHD44780CommandWordProgram<'l, P>,
seq_prg: &PioHD44780CommandSequenceProgram<'l, P>,
) -> PioHD44780<'l, P, S> {
into_ref!(dma);
let rs = common.make_pio_pin(rs);
let rw = common.make_pio_pin(rw);
let e = common.make_pio_pin(e);
let db4 = common.make_pio_pin(db4);
let db5 = common.make_pio_pin(db5);
let db6 = common.make_pio_pin(db6);
let db7 = common.make_pio_pin(db7);
sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
let mut cfg = Config::default();
cfg.use_program(&word_prg.prg, &[&e]);
cfg.clock_divider = 125u8.into();
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
cfg.shift_out = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Left,
threshold: 32,
};
cfg.fifo_join = FifoJoin::TxOnly;
sm.set_config(&cfg);
sm.set_enable(true);
// init to 8 bit thrice
sm.tx().push((50000 << 8) | 0x30);
sm.tx().push((5000 << 8) | 0x30);
sm.tx().push((200 << 8) | 0x30);
// init 4 bit
sm.tx().push((200 << 8) | 0x20);
// set font and lines
sm.tx().push((50 << 8) | 0x20);
sm.tx().push(0b1100_0000);
irq.wait().await;
sm.set_enable(false);
let mut cfg = Config::default();
cfg.use_program(&seq_prg.prg, &[&e]);
cfg.clock_divider = 8u8.into(); // ~64ns/insn
cfg.set_jmp_pin(&db7);
cfg.set_set_pins(&[&rs, &rw]);
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
cfg.shift_out.direction = ShiftDirection::Left;
cfg.fifo_join = FifoJoin::TxOnly;
sm.set_config(&cfg);
sm.set_enable(true);
// display on and cursor on and blinking, reset display
sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await;
Self {
dma: dma.map_into(),
sm,
buf: [0x20; 40],
}
}
/// Write a line to the display
pub async fn add_line(&mut self, s: &[u8]) {
// move cursor to 0:0, prepare 16 characters
self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]);
// move line 2 up
self.buf.copy_within(22..38, 3);
// move cursor to 1:0, prepare 16 characters
self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]);
// file line 2 with spaces
self.buf[22..38].fill(0x20);
// copy input line
let len = s.len().min(16);
self.buf[22..22 + len].copy_from_slice(&s[0..len]);
// set cursor to 1:15
self.buf[38..].copy_from_slice(&[0x80, 0xcf]);
self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await;
}
}

View File

@ -0,0 +1,95 @@
//! Pio backed I2s output
use fixed::traits::ToFixed;
use crate::dma::{AnyChannel, Channel, Transfer};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents an i2s output driver program
pub struct PioI2sOutProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 2",
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
"left_data:",
" out pins, 1 side 0b00",
" jmp x-- left_data side 0b01",
" out pins 1 side 0b10",
" set x, 14 side 0b11",
"right_data:",
" out pins 1 side 0b10",
" jmp x-- right_data side 0b11",
" out pins 1 side 0b00",
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed I2s output driver
pub struct PioI2sOut<'a, P: Instance, const S: usize> {
dma: PeripheralRef<'a, AnyChannel>,
sm: StateMachine<'a, P, S>,
}
impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
/// Configure a state machine to output I2s
pub fn new(
common: &mut Common<'a, P>,
mut sm: StateMachine<'a, P, S>,
dma: impl Peripheral<P = impl Channel> + 'a,
data_pin: impl PioPin,
bit_clock_pin: impl PioPin,
lr_clock_pin: impl PioPin,
sample_rate: u32,
bit_depth: u32,
channels: u32,
program: &PioI2sOutProgram<'a, P>,
) -> Self {
into_ref!(dma);
let data_pin = common.make_pio_pin(data_pin);
let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
let cfg = {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
cfg.set_out_pins(&[&data_pin]);
let clock_frequency = sample_rate * bit_depth * channels;
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
cfg.shift_out = ShiftConfig {
threshold: 32,
direction: ShiftDirection::Left,
auto_fill: true,
};
// join fifos to have twice the time to start the next dma transfer
cfg.fifo_join = FifoJoin::TxOnly;
cfg
};
sm.set_config(&cfg);
sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
sm.set_enable(true);
Self {
dma: dma.map_into(),
sm,
}
}
/// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
self.sm.tx().dma_push(self.dma.reborrow(), buff)
}
}

View File

@ -0,0 +1,10 @@
//! Pre-built pio programs for common interfaces
pub mod hd44780;
pub mod i2s;
pub mod onewire;
pub mod pwm;
pub mod rotary_encoder;
pub mod stepper;
pub mod uart;
pub mod ws2812;

View File

@ -0,0 +1,109 @@
//! OneWire pio driver
use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine};
/// This struct represents an onewire driver program
pub struct PioOneWireProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.wrap_target
again:
pull block
mov x, osr
jmp !x, read
write:
set pindirs, 1
set pins, 0
loop1:
jmp x--,loop1
set pindirs, 0 [31]
wait 1 pin 0 [31]
pull block
mov x, osr
bytes1:
pull block
set y, 7
set pindirs, 1
bit1:
set pins, 0 [1]
out pins,1 [31]
set pins, 1 [20]
jmp y--,bit1
jmp x--,bytes1
set pindirs, 0 [31]
jmp again
read:
pull block
mov x, osr
bytes2:
set y, 7
bit2:
set pindirs, 1
set pins, 0 [1]
set pindirs, 0 [5]
in pins,1 [10]
jmp y--,bit2
jmp x--,bytes2
.wrap
"#,
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed OneWire driver
pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> {
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
/// Create a new instance the driver
pub fn new(
common: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: impl PioPin,
program: &PioOneWireProgram<'d, PIO>,
) -> Self {
let pin = common.make_pio_pin(pin);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
cfg.set_out_pins(&[&pin]);
cfg.set_in_pins(&[&pin]);
cfg.set_set_pins(&[&pin]);
cfg.shift_in = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Right,
threshold: 8,
};
cfg.clock_divider = 255_u8.into();
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Write bytes over the wire
pub async fn write_bytes(&mut self, bytes: &[u8]) {
self.sm.tx().wait_push(250).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes {
self.sm.tx().wait_push(*b as u32).await;
}
}
/// Read bytes from the wire
pub async fn read_bytes(&mut self, bytes: &mut [u8]) {
self.sm.tx().wait_push(0).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes.iter_mut() {
*b = (self.sm.rx().wait_pull().await >> 24) as u8;
}
}
}

View File

@ -0,0 +1,121 @@
//! PIO backed PWM driver
use core::time::Duration;
use pio::InstructionOperands;
use crate::clocks;
use crate::gpio::Level;
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
/// This struct represents a PWM program loaded into pio instruction memory.
pub struct PioPwmProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed PWM output
pub struct PioPwm<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
pin: Pin<'d, T>,
}
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
/// Configure a state machine as a PWM output
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin: impl PioPin,
program: &PioPwmProgram<'d, T>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&pin]);
sm.set_config(&cfg);
Self { sm, pin }
}
/// Enable's the PIO program, continuing the wave generation from the PIO program.
pub fn start(&mut self) {
self.sm.set_enable(true);
}
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
/// Sets the pwm period, which is the length of time for each pio wave until reset.
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
/// Set the number of pio cycles to set the wave on high to.
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
/// Set the pulse width high time
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
/// Return the state machine and pin.
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
(self.sm, self.pin)
}
}

View File

@ -0,0 +1,73 @@
//! PIO backed quadrature encoder
use fixed::traits::ToFixed;
use crate::gpio::Pull;
use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
/// This struct represents an Encoder program loaded into pio instruction memory.
pub struct PioEncoderProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio Backed quadrature encoder reader
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
/// Configure a state machine with the loaded [PioEncoderProgram]
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin_a: impl PioPin,
pin_b: impl PioPin,
program: &PioEncoderProgram<'d, T>,
) -> Self {
let mut pin_a = pio.make_pio_pin(pin_a);
let mut pin_b = pio.make_pio_pin(pin_b);
pin_a.set_pull(Pull::Up);
pin_b.set_pull(Pull::Up);
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
let mut cfg = Config::default();
cfg.set_in_pins(&[&pin_a, &pin_b]);
cfg.fifo_join = FifoJoin::RxOnly;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.clock_divider = 10_000.to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Read a single count from the encoder
pub async fn read(&mut self) -> Direction {
loop {
match self.sm.rx().wait_pull().await {
0 => return Direction::CounterClockwise,
1 => return Direction::Clockwise,
_ => {}
}
}
}
}
/// Encoder Count Direction
pub enum Direction {
/// Encoder turned clockwise
Clockwise,
/// Encoder turned counter clockwise
CounterClockwise,
}

View File

@ -0,0 +1,147 @@
//! Pio Stepper Driver for 5-wire steppers
use core::mem::{self, MaybeUninit};
use fixed::traits::ToFixed;
use fixed::types::extra::U8;
use fixed::FixedU32;
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
/// This struct represents a Stepper driver program loaded into pio instruction memory.
pub struct PioStepperProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
"pull block",
"mov x, osr",
"pull block",
"mov y, osr",
"jmp !x end",
"loop:",
"jmp !osre step",
"mov osr, y",
"step:",
"out pins, 4 [31]"
"jmp x-- loop",
"end:",
"irq 0 rel"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed Stepper driver
pub struct PioStepper<'d, T: Instance, const SM: usize> {
irq: Irq<'d, T, SM>,
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
/// Configure a state machine to drive a stepper
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
irq: Irq<'d, T, SM>,
pin0: impl PioPin,
pin1: impl PioPin,
pin2: impl PioPin,
pin3: impl PioPin,
program: &PioStepperProgram<'d, T>,
) -> Self {
let pin0 = pio.make_pio_pin(pin0);
let pin1 = pio.make_pio_pin(pin1);
let pin2 = pio.make_pio_pin(pin2);
let pin3 = pio.make_pio_pin(pin3);
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { irq, sm }
}
/// Set pulse frequency
pub fn set_frequency(&mut self, freq: u32) {
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
assert!(clock_divider >= 1, "clkdiv must be >= 1");
self.sm.set_clock_divider(clock_divider);
self.sm.clkdiv_restart();
}
/// Full step, one phase
pub async fn step(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
} else {
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
}
}
/// Full step, two phase
pub async fn step2(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
} else {
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
}
}
/// Half step
pub async fn step_half(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
} else {
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
}
}
async fn run(&mut self, steps: i32, pattern: u32) {
self.sm.tx().wait_push(steps as u32).await;
self.sm.tx().wait_push(pattern).await;
let drop = OnDrop::new(|| {
self.sm.clear_fifos();
unsafe {
self.sm.exec_instr(
pio::InstructionOperands::JMP {
address: 0,
condition: pio::JmpCondition::Always,
}
.encode(),
);
}
});
self.irq.wait().await;
drop.defuse();
}
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
pub fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
pub fn defuse(self) {
mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}

View File

@ -0,0 +1,185 @@
//! Pio backed uart drivers
use core::convert::Infallible;
use embedded_io_async::{ErrorType, Read, Write};
use fixed::traits::ToFixed;
use crate::clocks::clk_sys_freq;
use crate::gpio::Level;
use crate::pio::{
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
};
/// This struct represents a uart tx program loaded into pio instruction memory.
pub struct PioUartTxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
/// Load the uart tx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart transmitter
pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
sm_tx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded tx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_tx: StateMachine<'a, PIO, SM>,
tx_pin: impl PioPin,
program: &PioUartTxProgram<'a, PIO>,
) -> Self {
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&program.prg, &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
Self { sm_tx }
}
/// Write a single u8
pub async fn write_u8(&mut self, data: u8) {
self.sm_tx.tx().wait_push(data as u32).await;
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
for byte in buf {
self.write_u8(*byte).await;
}
Ok(buf.len())
}
}
/// This struct represents a Uart Rx program loaded into pio instruction memory.
pub struct PioUartRxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
/// Load the uart rx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_rx_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_rx_stop: ; No delay before returning to start; a little slack is
in null 24
push ; important in case the TX clock is slightly too fast.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart reciever
pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
sm_rx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded rx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_rx: StateMachine<'a, PIO, SM>,
rx_pin: impl PioPin,
program: &PioUartRxProgram<'a, PIO>,
) -> Self {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
cfg.set_in_pins(&[&rx_pin]);
cfg.set_jmp_pin(&rx_pin);
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
cfg.shift_in.auto_fill = false;
cfg.shift_in.direction = ShiftDirection::Right;
cfg.shift_in.threshold = 32;
cfg.fifo_join = FifoJoin::RxOnly;
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
Self { sm_rx }
}
/// Wait for a single u8
pub async fn read_u8(&mut self) -> u8 {
self.sm_rx.rx().wait_pull().await as u8
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
let mut i = 0;
while i < buf.len() {
buf[i] = self.read_u8().await;
i += 1;
}
Ok(i)
}
}

View File

@ -0,0 +1,118 @@
//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use embassy_time::Timer;
use fixed::types::U24F8;
use smart_leds::RGB8;
use crate::clocks::clk_sys_freq;
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
const T1: u8 = 2; // start bit
const T2: u8 = 5; // data bit
const T3: u8 = 3; // stop bit
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
/// This struct represents a ws2812 program loaded into pio instruction memory.
pub struct PioWs2812Program<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
/// Load the ws2812 program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let side_set = pio::SideSet::new(false, 1, false);
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
let mut wrap_target = a.label();
let mut wrap_source = a.label();
let mut do_zero = a.label();
a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
a.bind(&mut wrap_target);
// Do stop bit
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
// Do start bit
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
// Do data bit = 1
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
a.bind(&mut do_zero);
// Do data bit = 0
a.nop_with_delay_and_side_set(T2 - 1, 0);
a.bind(&mut wrap_source);
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
let prg = common.load_program(&prg);
Self { prg }
}
}
/// Pio backed ws2812 driver
/// Const N is the number of ws2812 leds attached to this pin
pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
dma: PeripheralRef<'d, AnyChannel>,
sm: StateMachine<'d, P, S>,
}
impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
/// Configure a pio state machine to use the loaded ws2812 program.
pub fn new(
pio: &mut Common<'d, P>,
mut sm: StateMachine<'d, P, S>,
dma: impl Peripheral<P = impl Channel> + 'd,
pin: impl PioPin,
program: &PioWs2812Program<'d, P>,
) -> Self {
into_ref!(dma);
// Setup sm0
let mut cfg = Config::default();
// Pin config
let out_pin = pio.make_pio_pin(pin);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
cfg.use_program(&program.prg, &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
let ws2812_freq = U24F8::from_num(800);
let bit_freq = ws2812_freq * CYCLES_PER_BIT;
cfg.clock_divider = clock_freq / bit_freq;
// FIFO config
cfg.fifo_join = FifoJoin::TxOnly;
cfg.shift_out = ShiftConfig {
auto_fill: true,
threshold: 24,
direction: ShiftDirection::Left,
};
sm.set_config(&cfg);
sm.set_enable(true);
Self {
dma: dma.map_into(),
sm,
}
}
/// Write a buffer of [smart_leds::RGB8] to the ws2812 string
pub async fn write(&mut self, colors: &[RGB8; N]) {
// Precompute the word bytes from the colors
let mut words = [0u32; N];
for i in 0..N {
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
words[i] = word;
}
// DMA transfer
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
Timer::after_micros(55).await;
}
}

View File

@ -1,6 +1,8 @@
//! Pulse Width Modulation (PWM)
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
pub use embedded_hal_1::pwm::SetDutyCycle;
use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType};
use fixed::traits::ToFixed;
use fixed::FixedU16;
use pac::pwm::regs::{ChDiv, Intr};
@ -80,6 +82,21 @@ impl From<InputMode> for Divmode {
}
}
/// PWM error.
#[derive(Debug)]
pub enum PwmError {
/// Invalid Duty Cycle.
InvalidDutyCycle,
}
impl Error for PwmError {
fn kind(&self) -> ErrorKind {
match self {
PwmError::InvalidDutyCycle => ErrorKind::Other,
}
}
}
/// PWM driver.
pub struct Pwm<'d> {
pin_a: Option<PeripheralRef<'d, AnyPin>>,
@ -87,6 +104,30 @@ pub struct Pwm<'d> {
slice: usize,
}
impl<'d> ErrorType for Pwm<'d> {
type Error = PwmError;
}
impl<'d> SetDutyCycle for Pwm<'d> {
fn max_duty_cycle(&self) -> u16 {
pac::PWM.ch(self.slice).top().read().top()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
let max_duty = self.max_duty_cycle();
if duty > max_duty {
return Err(PwmError::InvalidDutyCycle);
}
let p = pac::PWM.ch(self.slice);
p.cc().modify(|w| {
w.set_a(duty);
w.set_b(duty);
});
Ok(())
}
}
impl<'d> Pwm<'d> {
fn new_inner(
slice: usize,
@ -303,6 +344,119 @@ impl<'d> Pwm<'d> {
fn bit(&self) -> u32 {
1 << self.slice as usize
}
/// Splits the PWM driver into separate `PwmOutput` instances for channels A and B.
#[inline]
pub fn split(mut self) -> (Option<PwmOutput<'d>>, Option<PwmOutput<'d>>) {
(
self.pin_a
.take()
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)),
self.pin_b
.take()
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)),
)
}
/// Splits the PWM driver by reference to allow for separate duty cycle control
/// of each channel (A and B) without taking ownership of the PWM instance.
#[inline]
pub fn split_by_ref(&mut self) -> (Option<PwmOutput<'_>>, Option<PwmOutput<'_>>) {
(
self.pin_a
.as_mut()
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)),
self.pin_b
.as_mut()
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)),
)
}
}
enum PwmChannelPin<'d> {
A(PeripheralRef<'d, AnyPin>),
B(PeripheralRef<'d, AnyPin>),
}
/// Single channel of Pwm driver.
pub struct PwmOutput<'d> {
//pin that can be ether ChannelAPin or ChannelBPin
channel_pin: PwmChannelPin<'d>,
slice: usize,
is_owned: bool,
}
impl<'d> PwmOutput<'d> {
fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self {
Self {
channel_pin,
slice,
is_owned,
}
}
}
impl<'d> Drop for PwmOutput<'d> {
fn drop(&mut self) {
if self.is_owned {
let p = pac::PWM.ch(self.slice);
match &self.channel_pin {
PwmChannelPin::A(pin) => {
p.cc().modify(|w| {
w.set_a(0);
});
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
//Enable pin PULL-DOWN
pin.pad_ctrl().modify(|w| {
w.set_pde(true);
});
}
PwmChannelPin::B(pin) => {
p.cc().modify(|w| {
w.set_b(0);
});
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
//Enable pin PULL-DOWN
pin.pad_ctrl().modify(|w| {
w.set_pde(true);
});
}
}
}
}
}
impl<'d> ErrorType for PwmOutput<'d> {
type Error = PwmError;
}
impl<'d> SetDutyCycle for PwmOutput<'d> {
fn max_duty_cycle(&self) -> u16 {
pac::PWM.ch(self.slice).top().read().top()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
let max_duty = self.max_duty_cycle();
if duty > max_duty {
return Err(PwmError::InvalidDutyCycle);
}
let p = pac::PWM.ch(self.slice);
match self.channel_pin {
PwmChannelPin::A(_) => {
p.cc().modify(|w| {
w.set_a(duty);
});
}
PwmChannelPin::B(_) => {
p.cc().modify(|w| {
w.set_b(duty);
});
}
}
Ok(())
}
}
/// Batch representation of PWM slices.

View File

@ -359,17 +359,18 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(rx_dma, clk, miso);
into_ref!(tx_dma, rx_dma, clk, miso);
Self::new_inner(
inner,
Some(clk.map_into()),
None,
Some(miso.map_into()),
None,
None,
Some(tx_dma.map_into()),
Some(rx_dma.map_into()),
config,
)

View File

@ -2,6 +2,7 @@
#![allow(async_fn_in_trait)]
#![doc = include_str!("../README.md")]
// #![warn(missing_docs)]
#![allow(static_mut_refs)] // TODO: Fix
// This must go FIRST so that all the other modules see its macros.
mod fmt;

View File

@ -371,7 +371,7 @@ pub struct DataRequest {
}
impl DataRequest {
pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &mut Self {
pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &'a mut Self {
self.msdu_ptr = buf as *const _ as *const u8;
self.msdu_length = buf.len() as u8;

View File

@ -23,8 +23,14 @@ impl<'d> Driver<'d> {
impl<'d> embassy_net_driver::Driver for Driver<'d> {
// type RxToken<'a> = RxToken<'a, 'd> where Self: 'a;
// type TxToken<'a> = TxToken<'a, 'd> where Self: 'a;
type RxToken<'a> = RxToken<'d> where Self: 'a;
type TxToken<'a> = TxToken<'d> where Self: 'a;
type RxToken<'a>
= RxToken<'d>
where
Self: 'a;
type TxToken<'a>
= TxToken<'d>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.runner.rx_channel.poll_ready_to_receive(cx).is_ready()

View File

@ -51,7 +51,7 @@ embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", def
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" }
embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" }
embassy-executor = { version = "0.6.0", path = "../embassy-executor", optional = true }
embassy-executor = { version = "0.6.1", path = "../embassy-executor", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
@ -93,6 +93,8 @@ aligned = "0.4.1"
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
proptest = "1.5.0"
proptest-state-machine = "0.3.0"
[build-dependencies]
proc-macro2 = "1.0.36"

View File

@ -55,7 +55,7 @@ fn main() {
let mut singletons: Vec<String> = Vec::new();
for p in METADATA.peripherals {
if let Some(r) = &p.registers {
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" {
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" || r.kind == "octospi" {
// TODO: should we emit this for all peripherals? if so, we will need a list of all
// possible peripherals across all chips, so that we can declare the configs
// (replacing the hard-coded list of `peri_*` cfgs below)
@ -113,6 +113,7 @@ fn main() {
"peri_ucpd2",
"peri_usb_otg_fs",
"peri_usb_otg_hs",
"peri_octospi2",
]);
cfgs.declare_all(&["mco", "mco1", "mco2"]);
@ -1067,6 +1068,30 @@ fn main() {
(("octospi", "NCS"), quote!(crate::ospi::NSSPin)),
(("octospi", "CLK"), quote!(crate::ospi::SckPin)),
(("octospi", "NCLK"), quote!(crate::ospi::NckPin)),
(("octospim", "P1_IO0"), quote!(crate::ospi::D0Pin)),
(("octospim", "P1_IO1"), quote!(crate::ospi::D1Pin)),
(("octospim", "P1_IO2"), quote!(crate::ospi::D2Pin)),
(("octospim", "P1_IO3"), quote!(crate::ospi::D3Pin)),
(("octospim", "P1_IO4"), quote!(crate::ospi::D4Pin)),
(("octospim", "P1_IO5"), quote!(crate::ospi::D5Pin)),
(("octospim", "P1_IO6"), quote!(crate::ospi::D6Pin)),
(("octospim", "P1_IO7"), quote!(crate::ospi::D7Pin)),
(("octospim", "P1_DQS"), quote!(crate::ospi::DQSPin)),
(("octospim", "P1_NCS"), quote!(crate::ospi::NSSPin)),
(("octospim", "P1_CLK"), quote!(crate::ospi::SckPin)),
(("octospim", "P1_NCLK"), quote!(crate::ospi::NckPin)),
(("octospim", "P2_IO0"), quote!(crate::ospi::D0Pin)),
(("octospim", "P2_IO1"), quote!(crate::ospi::D1Pin)),
(("octospim", "P2_IO2"), quote!(crate::ospi::D2Pin)),
(("octospim", "P2_IO3"), quote!(crate::ospi::D3Pin)),
(("octospim", "P2_IO4"), quote!(crate::ospi::D4Pin)),
(("octospim", "P2_IO5"), quote!(crate::ospi::D5Pin)),
(("octospim", "P2_IO6"), quote!(crate::ospi::D6Pin)),
(("octospim", "P2_IO7"), quote!(crate::ospi::D7Pin)),
(("octospim", "P2_DQS"), quote!(crate::ospi::DQSPin)),
(("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)),
(("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)),
(("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)),
(("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)),
(("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)),
(("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)),
@ -1124,6 +1149,18 @@ fn main() {
peri = format_ident!("{}", pin.signal.replace('_', ""));
}
// OCTOSPIM is special
if p.name == "OCTOSPIM" {
// Some chips have OCTOSPIM but not OCTOSPI2.
if METADATA.peripherals.iter().any(|p| p.name == "OCTOSPI2") {
peri = format_ident!("{}", "OCTOSPI2");
g.extend(quote! {
pin_trait_impl!(#tr, #peri, #pin_name, #af);
});
}
peri = format_ident!("{}", "OCTOSPI1");
}
g.extend(quote! {
pin_trait_impl!(#tr, #peri, #pin_name, #af);
})

View File

@ -6,11 +6,13 @@ use embassy_hal_internal::{into_ref, Peripheral};
use stm32_metapac::adc::vals::SampleTime;
use crate::adc::{Adc, AdcChannel, Instance, RxDma};
use crate::dma::ringbuffer::OverrunError;
use crate::dma::{Priority, ReadableRingBuffer, TransferOptions};
use crate::pac::adc::vals;
use crate::rcc;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct OverrunError;
fn clear_interrupt_flags(r: crate::pac::adc::Adc) {
r.sr().modify(|regs| {
regs.set_eoc(false);
@ -226,9 +228,8 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> {
/// Turns on ADC if it is not already turned on and starts continuous DMA transfer.
pub fn start(&mut self) -> Result<(), OverrunError> {
self.ring_buf.clear();
self.setup_adc();
self.ring_buf.clear();
Ok(())
}
@ -245,7 +246,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> {
/// [`start`]: #method.start
pub fn teardown_adc(&mut self) {
// Stop the DMA transfer
self.ring_buf.request_stop();
self.ring_buf.request_pause();
let r = T::regs();

View File

@ -893,7 +893,7 @@ impl RxMode {
RxFifo::Fifo0 => 0usize,
RxFifo::Fifo1 => 1usize,
};
T::regs().ier().write(|w| {
T::regs().ier().modify(|w| {
w.set_fmpie(fifo_idx, false);
});
waker.wake();
@ -936,18 +936,22 @@ impl RxMode {
Self::NonBuffered(_) => {
let registers = &info.regs;
if let Some(msg) = registers.receive_fifo(RxFifo::Fifo0) {
registers.0.ier().write(|w| {
registers.0.ier().modify(|w| {
w.set_fmpie(0, true);
});
Ok(msg)
} else if let Some(msg) = registers.receive_fifo(RxFifo::Fifo1) {
registers.0.ier().write(|w| {
registers.0.ier().modify(|w| {
w.set_fmpie(1, true);
});
Ok(msg)
} else if let Some(err) = registers.curr_error() {
Err(TryReadError::BusError(err))
} else {
registers.0.ier().modify(|w| {
w.set_fmpie(0, true);
w.set_fmpie(1, true);
});
Err(TryReadError::Empty)
}
}

View File

@ -2,7 +2,7 @@ use core::cmp::Ordering;
use core::convert::Infallible;
pub use embedded_can::{ExtendedId, Id, StandardId};
use stm32_metapac::can::vals::Lec;
use stm32_metapac::can::vals::{Lec, Rtr};
use super::{Mailbox, TransmitStatus};
use crate::can::enums::BusError;
@ -306,6 +306,9 @@ impl Registers {
mb.tir().write(|w| {
w.0 = id.0;
w.set_txrq(true);
if frame.header().rtr() {
w.set_rtr(Rtr::REMOTE);
}
});
}

View File

@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker};
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer};
use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer};
use super::word::{Word, WordSize};
use super::{AnyChannel, Channel, Dir, Request, STATE};
use crate::interrupt::typelevel::Interrupt;
@ -299,7 +299,6 @@ impl AnyChannel {
} else {
return;
}
state.waker.wake();
}
#[cfg(bdma)]
@ -763,10 +762,6 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> {
self.0.get_remaining_transfers() as _
}
fn get_complete_count(&self) -> usize {
STATE[self.0.id as usize].complete_count.load(Ordering::Acquire)
}
fn reset_complete_count(&mut self) -> usize {
let state = &STATE[self.0.id as usize];
#[cfg(not(armv6m))]
@ -832,27 +827,28 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
///
/// You must call this after creating it for it to work.
pub fn start(&mut self) {
self.channel.start()
self.channel.start();
self.clear();
}
/// Clear all data in the ring buffer.
pub fn clear(&mut self) {
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow()));
}
/// Read elements from the ring buffer
/// Return a tuple of the length read and the length remaining in the buffer
/// If not all of the elements were read, then there will be some elements in the buffer remaining
/// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
/// Error is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> {
self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf)
}
/// Read an exact number of elements from the ringbuffer.
///
/// Returns the remaining number of elements available for immediate reading.
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
/// Error is returned if the portion to be read was overwritten by the DMA controller.
///
/// Async/Wake Behavior:
/// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
@ -860,12 +856,17 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
/// ring buffer was created with a buffer of size 'N':
/// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
/// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, OverrunError> {
pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, Error> {
self.ringbuf
.read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer)
.await
}
/// The current length of the ringbuffer
pub fn len(&mut self) -> Result<usize, Error> {
Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?)
}
/// The capacity of the ringbuffer
pub const fn capacity(&self) -> usize {
self.ringbuf.cap()
@ -979,34 +980,40 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> {
///
/// You must call this after creating it for it to work.
pub fn start(&mut self) {
self.channel.start()
self.channel.start();
self.clear();
}
/// Clear all data in the ring buffer.
pub fn clear(&mut self) {
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow()));
}
/// Write elements directly to the raw buffer.
/// This can be used to fill the buffer before starting the DMA transfer.
#[allow(dead_code)]
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
self.ringbuf.write_immediate(buf)
}
/// Write elements from the ring buffer
/// Return a tuple of the length written and the length remaining in the buffer
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf)
}
/// Write an exact number of elements to the ringbuffer.
pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, OverrunError> {
pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, Error> {
self.ringbuf
.write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer)
.await
}
/// The current length of the ringbuffer
pub fn len(&mut self) -> Result<usize, Error> {
Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?)
}
/// The capacity of the ringbuffer
pub const fn capacity(&self) -> usize {
self.ringbuf.cap()

View File

@ -1,668 +0,0 @@
#![cfg_attr(gpdma, allow(unused))]
use core::future::poll_fn;
use core::ops::Range;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::{Poll, Waker};
use super::word::Word;
/// A "read-only" ring-buffer to be used together with the DMA controller which
/// writes in a circular way, "uncontrolled" to the buffer.
///
/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field
/// to the current register value. `ndtr` describes the current position of the DMA
/// write.
///
/// # Buffer layout
///
/// ```text
/// Without wraparound: With wraparound:
///
/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+
/// | | | | | |
/// v v v v v v
/// +-----------------------------------------+ +-----------------------------------------+
/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX|
/// +-----------------------------------------+ +-----------------------------------------+
/// ^ ^ ^ ^ ^ ^
/// | | | | | |
/// +- start --+ | +- end ------+ |
/// | | | |
/// +- end --------------------+ +- start ----------------+
/// ```
pub struct ReadableDmaRingBuffer<'a, W: Word> {
pub(crate) dma_buf: &'a mut [W],
start: usize,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct OverrunError;
pub trait DmaCtrl {
/// Get the NDTR register value, i.e. the space left in the underlying
/// buffer until the dma writer wraps.
fn get_remaining_transfers(&self) -> usize;
/// Get the transfer completed counter.
/// This counter is incremented by the dma controller when NDTR is reloaded,
/// i.e. when the writing wraps.
fn get_complete_count(&self) -> usize;
/// Reset the transfer completed counter to 0 and return the value just prior to the reset.
fn reset_complete_count(&mut self) -> usize;
/// Set the waker for a running poll_fn
fn set_waker(&mut self, waker: &Waker);
}
impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> {
pub fn new(dma_buf: &'a mut [W]) -> Self {
Self { dma_buf, start: 0 }
}
/// Reset the ring buffer to its initial state
pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
self.start = 0;
dma.reset_complete_count();
}
/// The capacity of the ringbuffer
pub const fn cap(&self) -> usize {
self.dma_buf.len()
}
/// The current position of the ringbuffer
fn pos(&self, dma: &mut impl DmaCtrl) -> usize {
self.cap() - dma.get_remaining_transfers()
}
/// Read an exact number of elements from the ringbuffer.
///
/// Returns the remaining number of elements available for immediate reading.
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
///
/// Async/Wake Behavior:
/// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
/// and when it wraps around. This means that when called with a buffer of length 'M', when this
/// ring buffer was created with a buffer of size 'N':
/// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
/// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result<usize, OverrunError> {
let mut read_data = 0;
let buffer_len = buffer.len();
poll_fn(|cx| {
dma.set_waker(cx.waker());
compiler_fence(Ordering::SeqCst);
match self.read(dma, &mut buffer[read_data..buffer_len]) {
Ok((len, remaining)) => {
read_data += len;
if read_data == buffer_len {
Poll::Ready(Ok(remaining))
} else {
Poll::Pending
}
}
Err(e) => Poll::Ready(Err(e)),
}
})
.await
}
/// Read elements from the ring buffer
/// Return a tuple of the length read and the length remaining in the buffer
/// If not all of the elements were read, then there will be some elements in the buffer remaining
/// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
/*
This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check
after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed
to fire in the same clock cycle that a register is read, so checking get_complete_count early does
not yield relevant information.
Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full
buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error
conditions.
After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that
the dma has not overrun within the data we could have copied. We check the data we could have copied
rather than the data we actually copied because it costs nothing and confirms an error condition
earlier.
*/
let end = self.pos(dma);
if self.start == end && dma.get_complete_count() == 0 {
// No elements are available in the buffer
Ok((0, self.cap()))
} else if self.start < end {
// The available, unread portion in the ring buffer DOES NOT wrap
// Copy out the elements from the dma buffer
let len = self.copy_to(buf, self.start..end);
compiler_fence(Ordering::SeqCst);
/*
first, check if the dma has wrapped at all if it's after end
or more than once if it's before start
this is in a critical section to try to reduce mushy behavior.
it's not ideal but it's the best we can do
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count()));
if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 {
Err(OverrunError)
} else {
self.start = (self.start + len) % self.cap();
Ok((len, self.cap() - self.start))
}
} else if self.start + buf.len() < self.cap() {
// The available, unread portion in the ring buffer DOES wrap
// The DMA writer has wrapped since we last read and is currently
// writing (or the next byte added will be) in the beginning of the ring buffer.
// The provided read buffer is not large enough to include all elements from the tail of the dma buffer.
// Copy out from the dma buffer
let len = self.copy_to(buf, self.start..self.cap());
compiler_fence(Ordering::SeqCst);
/*
first, check if the dma has wrapped around more than once
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let pos = self.pos(dma);
if pos > self.start || pos < end || dma.get_complete_count() > 1 {
Err(OverrunError)
} else {
self.start = (self.start + len) % self.cap();
Ok((len, self.start + end))
}
} else {
// The available, unread portion in the ring buffer DOES wrap
// The DMA writer has wrapped since we last read and is currently
// writing (or the next byte added will be) in the beginning of the ring buffer.
// The provided read buffer is large enough to include all elements from the tail of the dma buffer,
// so the next read will not have any unread tail elements in the ring buffer.
// Copy out from the dma buffer
let tail = self.copy_to(buf, self.start..self.cap());
let head = self.copy_to(&mut buf[tail..], 0..end);
compiler_fence(Ordering::SeqCst);
/*
first, check if the dma has wrapped around more than once
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let pos = self.pos(dma);
if pos > self.start || pos < end || dma.reset_complete_count() > 1 {
Err(OverrunError)
} else {
self.start = head;
Ok((tail + head, self.cap() - self.start))
}
}
}
/// Copy from the dma buffer at `data_range` into `buf`
fn copy_to(&mut self, buf: &mut [W], data_range: Range<usize>) -> usize {
// Limit the number of elements that can be copied
let length = usize::min(data_range.len(), buf.len());
// Copy from dma buffer into read buffer
// We need to do it like this instead of a simple copy_from_slice() because
// reading from a part of memory that may be simultaneously written to is unsafe
unsafe {
let dma_buf = self.dma_buf.as_ptr();
for i in 0..length {
buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize));
}
}
length
}
}
pub struct WritableDmaRingBuffer<'a, W: Word> {
pub(crate) dma_buf: &'a mut [W],
end: usize,
}
impl<'a, W: Word> WritableDmaRingBuffer<'a, W> {
pub fn new(dma_buf: &'a mut [W]) -> Self {
Self { dma_buf, end: 0 }
}
/// Reset the ring buffer to its initial state
pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
self.end = 0;
dma.reset_complete_count();
}
/// The capacity of the ringbuffer
pub const fn cap(&self) -> usize {
self.dma_buf.len()
}
/// The current position of the ringbuffer
fn pos(&self, dma: &mut impl DmaCtrl) -> usize {
self.cap() - dma.get_remaining_transfers()
}
/// Write elements directly to the buffer. This must be done before the DMA is started
/// or after the buffer has been cleared using `clear()`.
pub fn write_immediate(&mut self, buffer: &[W]) -> Result<(usize, usize), OverrunError> {
if self.end != 0 {
return Err(OverrunError);
}
let written = self.copy_from(buffer, 0..self.cap());
self.end = written % self.cap();
Ok((written, self.cap() - written))
}
/// Write an exact number of elements to the ringbuffer.
pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result<usize, OverrunError> {
let mut written_data = 0;
let buffer_len = buffer.len();
poll_fn(|cx| {
dma.set_waker(cx.waker());
compiler_fence(Ordering::SeqCst);
match self.write(dma, &buffer[written_data..buffer_len]) {
Ok((len, remaining)) => {
written_data += len;
if written_data == buffer_len {
Poll::Ready(Ok(remaining))
} else {
Poll::Pending
}
}
Err(e) => Poll::Ready(Err(e)),
}
})
.await
}
/// Write elements from the ring buffer
/// Return a tuple of the length written and the capacity remaining to be written in the buffer
pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> {
let start = self.pos(dma);
if start > self.end {
// The occupied portion in the ring buffer DOES wrap
let len = self.copy_from(buf, self.end..start);
compiler_fence(Ordering::SeqCst);
// Confirm that the DMA is not inside data we could have written
let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count()));
if (pos >= self.end && pos < start) || (complete_count > 0 && pos >= start) || complete_count > 1 {
Err(OverrunError)
} else {
self.end = (self.end + len) % self.cap();
Ok((len, self.cap() - (start - self.end)))
}
} else if start == self.end && dma.get_complete_count() == 0 {
Ok((0, 0))
} else if start <= self.end && self.end + buf.len() < self.cap() {
// The occupied portion in the ring buffer DOES NOT wrap
// and copying elements into the buffer WILL NOT cause it to
// Copy into the dma buffer
let len = self.copy_from(buf, self.end..self.cap());
compiler_fence(Ordering::SeqCst);
// Confirm that the DMA is not inside data we could have written
let pos = self.pos(dma);
if pos > self.end || pos < start || dma.get_complete_count() > 1 {
Err(OverrunError)
} else {
self.end = (self.end + len) % self.cap();
Ok((len, self.cap() - (self.end - start)))
}
} else {
// The occupied portion in the ring buffer DOES NOT wrap
// and copying elements into the buffer WILL cause it to
let tail = self.copy_from(buf, self.end..self.cap());
let head = self.copy_from(&buf[tail..], 0..start);
compiler_fence(Ordering::SeqCst);
// Confirm that the DMA is not inside data we could have written
let pos = self.pos(dma);
if pos > self.end || pos < start || dma.reset_complete_count() > 1 {
Err(OverrunError)
} else {
self.end = head;
Ok((tail + head, self.cap() - (start - self.end)))
}
}
}
/// Copy into the dma buffer at `data_range` from `buf`
fn copy_from(&mut self, buf: &[W], data_range: Range<usize>) -> usize {
// Limit the number of elements that can be copied
let length = usize::min(data_range.len(), buf.len());
// Copy into dma buffer from read buffer
// We need to do it like this instead of a simple copy_from_slice() because
// reading from a part of memory that may be simultaneously written to is unsafe
unsafe {
let dma_buf = self.dma_buf.as_mut_ptr();
for i in 0..length {
core::ptr::write_volatile(dma_buf.offset((data_range.start + i) as isize), buf[i]);
}
}
length
}
}
#[cfg(test)]
mod tests {
use core::array;
use std::{cell, vec};
use super::*;
#[allow(dead_code)]
#[derive(PartialEq, Debug)]
enum TestCircularTransferRequest {
GetCompleteCount(usize),
ResetCompleteCount(usize),
PositionRequest(usize),
}
struct TestCircularTransfer {
len: usize,
requests: cell::RefCell<vec::Vec<TestCircularTransferRequest>>,
}
impl DmaCtrl for TestCircularTransfer {
fn get_remaining_transfers(&self) -> usize {
match self.requests.borrow_mut().pop().unwrap() {
TestCircularTransferRequest::PositionRequest(pos) => {
let len = self.len;
assert!(len >= pos);
len - pos
}
_ => unreachable!(),
}
}
fn get_complete_count(&self) -> usize {
match self.requests.borrow_mut().pop().unwrap() {
TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count,
_ => unreachable!(),
}
}
fn reset_complete_count(&mut self) -> usize {
match self.requests.get_mut().pop().unwrap() {
TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count,
_ => unreachable!(),
}
}
fn set_waker(&mut self, waker: &Waker) {}
}
impl TestCircularTransfer {
pub fn new(len: usize) -> Self {
Self {
requests: cell::RefCell::new(vec![]),
len,
}
}
pub fn setup(&self, mut requests: vec::Vec<TestCircularTransferRequest>) {
requests.reverse();
self.requests.replace(requests);
}
}
#[test]
fn empty_and_read_not_started() {
let mut dma_buf = [0u8; 16];
let ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
}
#[test]
fn can_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([0, 1], buf);
assert_eq!(2, ringbuf.start);
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::PositionRequest(12),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([2, 3], buf);
assert_eq!(4, ringbuf.start);
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(12),
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 8];
assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]);
assert_eq!(12, ringbuf.start);
}
#[test]
fn can_read_with_wrap() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
/*
Now, read around the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::ResetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(4, ringbuf.start);
}
#[test]
fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
/*
Now, read to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::ResetCompleteCount(1),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(0, ringbuf.start);
}
#[test]
fn can_read_when_dma_writer_wraps_once_with_same_ndtr() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read to about the middle of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(6, ringbuf.start);
/*
Now, wrap the DMA controller around
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(12, ringbuf.start);
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read a few bytes
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(2),
TestCircularTransferRequest::PositionRequest(2),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 6];
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(2, ringbuf.start);
/*
Now, overtake the reader
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(4),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
/*
Now, overtake the reader
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::ResetCompleteCount(2),
]);
let mut buf = [0; 6];
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
}
}

Some files were not shown because too many files have changed in this diff Show More