diff --git a/.github/ci/test-nightly.sh b/.github/ci/test-nightly.sh index 1724ffe89..a03b55e8d 100755 --- a/.github/ci/test-nightly.sh +++ b/.github/ci/test-nightly.sh @@ -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 diff --git a/.github/ci/test.sh b/.github/ci/test.sh index 0de265049..0fe088bfe 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -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 diff --git a/ci.sh b/ci.sh index 50331bdba..9f7a7a037 100755 --- a/ci.sh +++ b/ci.sh @@ -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 diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index 6b71c18e6..3cd0e4988 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -9,7 +9,8 @@ pub(crate) mod fmt; #[cfg(feature = "bluetooth")] -mod bluetooth; +/// Bluetooth module. +pub mod bluetooth; mod bus; mod consts; mod control; diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index 8eb947b5e..d4f49bfc1 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -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; } +} +---- diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc index 821bcbd27..38cea044b 100644 --- a/docs/pages/new_project.adoc +++ b/docs/pages/new_project.adoc @@ -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, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`. -At the time of writing, the latest version of embassy isn‘t 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 we’re 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, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `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, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `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] ---- diff --git a/docs/pages/overview.adoc b/docs/pages/overview.adoc index 2ebc85f6d..8f97d937f 100644 --- a/docs/pages/overview.adoc +++ b/docs/pages/overview.adoc @@ -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] \ No newline at end of file +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs index 4c4f4f10b..4814786bf 100644 --- a/embassy-boot/src/firmware_updater/mod.rs +++ b/embassy-boot/src/firmware_updater/mod.rs @@ -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 { /// The dfu flash partition diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml index 218e820ce..306cf8352 100644 --- a/embassy-executor-macros/Cargo.toml +++ b/embassy-executor-macros/Cargo.toml @@ -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" diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5461fe04c..5f2182f10 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -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, -} - -impl Parse for Args { - fn parse(input: &ParseBuffer) -> syn::Result { - let meta = Punctuated::::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() } diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 26dfa2397..14b1d9de2 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -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, } -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 { - #[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 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: &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: &mut T) -> &'static mut T { - ::core::mem::transmute(t) + #entry + fn main() -> #main_ret { + #main_body } - #main + #errors }; - Ok(result) + result } diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 96c6267b2..2f2aeda76 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -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, } -pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - 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 { - 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 ::embassy_executor::SpawnToken { - 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 { - 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 ::embassy_executor::SpawnToken #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); } diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs new file mode 100644 index 000000000..ebd032a62 --- /dev/null +++ b/embassy-executor-macros/src/util.rs @@ -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(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, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + 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::()?); + } + 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()); + }); + } +} diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs deleted file mode 100644 index 9c78cda01..000000000 --- a/embassy-executor-macros/src/util/ctxt.rs +++ /dev/null @@ -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>>, -} - -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(&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) -> 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"); - } - } -} diff --git a/embassy-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs deleted file mode 100644 index 28702809e..000000000 --- a/embassy-executor-macros/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ctxt; diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 5582b56ec..3d8c48676 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -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. diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 01fa28b88..22a176621 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -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 diff --git a/embassy-executor/src/arch/spin.rs b/embassy-executor/src/arch/spin.rs new file mode 100644 index 000000000..340023620 --- /dev/null +++ b/embassy-executor/src/arch/spin.rs @@ -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() }; + } + } + } +} diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 6a2e493a2..d816539ac 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -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")] diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs new file mode 100644 index 000000000..be4679485 --- /dev/null +++ b/embassy-executor/tests/ui.rs @@ -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"); +} diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs new file mode 100644 index 000000000..fd52f7e41 --- /dev/null +++ b/embassy-executor/tests/ui/abi.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 000000000..e264e371a --- /dev/null +++ b/embassy-executor/tests/ui/abi.stderr @@ -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() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 000000000..f09a5205b --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 000000000..e9d94dff8 --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.stderr @@ -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 { + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs new file mode 100644 index 000000000..b83123bb1 --- /dev/null +++ b/embassy-executor/tests/ui/generics.rs @@ -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: T) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 000000000..197719a7b --- /dev/null +++ b/embassy-executor/tests/ui/generics.stderr @@ -0,0 +1,5 @@ +error: task functions must not be generic + --> tests/ui/generics.rs:6:1 + | +6 | async fn task(_t: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 000000000..a21402aa0 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 000000000..099b1828f --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.stderr @@ -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) {} + | ^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 000000000..07442b8fa --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo(T); + +#[embassy_executor::task] +async fn foo(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 000000000..39592f958 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.stderr @@ -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) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 000000000..272470f98 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 000000000..0032a20c9 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.stderr @@ -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) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 000000000..417c360a1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 000000000..0544de843 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr @@ -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) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 000000000..175ebccc1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 000000000..79f262e6b --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr @@ -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) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 000000000..cf49ad709 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 000000000..7e2b9eb7c --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr @@ -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) {} + | ^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 000000000..3f8a26cf8 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 000000000..af8491ad7 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr @@ -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) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 000000000..ba95d1459 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 000000000..5df2a6e06 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr @@ -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<'_>) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 000000000..4cfe2966a --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 000000000..099ef8b4e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr @@ -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<'_>) {} + | ++++ diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 000000000..ec3d908f6 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 000000000..61d5231bc --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr @@ -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>) {} + | ^^ diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 000000000..f3f7e9bd2 --- /dev/null +++ b/embassy-executor/tests/ui/not_async.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 000000000..27f040d9c --- /dev/null +++ b/embassy-executor/tests/ui/not_async.stderr @@ -0,0 +1,5 @@ +error: task functions must be async + --> tests/ui/not_async.rs:6:1 + | +6 | fn task() {} + | ^^^^^^^^^ diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs new file mode 100644 index 000000000..f83a962d1 --- /dev/null +++ b/embassy-executor/tests/ui/self.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr new file mode 100644 index 000000000..aaf031573 --- /dev/null +++ b/embassy-executor/tests/ui/self.stderr @@ -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 diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 000000000..5e49bba5e --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 000000000..dd2052977 --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.stderr @@ -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 diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 000000000..848d78149 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.rs @@ -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() {} diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 000000000..eba45af40 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.stderr @@ -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, + | |______________^ diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 57f0cb41f..bb175253b 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -188,6 +188,169 @@ where // ==================================================================== +/// Result for [`select5`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either5 { + /// 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: A, b: B, c: C, d: D, e: E) -> Select5 +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: A, + b: B, + c: C, + d: D, + e: E, +} + +impl Future for Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + type Output = Either5; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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 { + /// 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: A, b: B, c: C, d: D, e: E, f: F) -> Select6 +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: A, + b: B, + c: C, + d: D, + e: E, + f: F, +} + +impl Future for Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + type Output = Either6; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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"] diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index 7ad4d449e..6390502a8 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -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() { diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs index 87f9f6ed1..4c847718d 100644 --- a/embassy-net-driver/src/lib.rs +++ b/embassy-net-driver/src/lib.rs @@ -83,10 +83,12 @@ pub trait Driver { } impl 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; diff --git a/embassy-net-enc28j60/src/lib.rs b/embassy-net-enc28j60/src/lib.rs index dda35f498..c1f32719a 100644 --- a/embassy-net-enc28j60/src/lib.rs +++ b/embassy-net-enc28j60/src/lib.rs @@ -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; diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 8b45919ef..2dda615c1 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -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(()) } diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml index f6371f955..d2f43ef45 100644 --- a/embassy-net-ppp/Cargo.toml +++ b/embassy-net-ppp/Cargo.toml @@ -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] diff --git a/embassy-net-tuntap/src/lib.rs b/embassy-net-tuntap/src/lib.rs index 56f55fba1..2ff23f462 100644 --- a/embassy-net-tuntap/src/lib.rs +++ b/embassy-net-tuntap/src/lib.rs @@ -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]; diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 2e21b4231..1090892e0 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -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" diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 1fbaea4f0..dbe73776c 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -73,8 +73,11 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { &self, host: &str, addr_type: embedded_nal_async::AddrType, - ) -> Result { - use embedded_nal_async::{AddrType, IpAddr}; + ) -> Result { + 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 { + async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result { todo!() } } diff --git a/embassy-net/src/driver_util.rs b/embassy-net/src/driver_util.rs index b2af1d499..f51641425 100644 --- a/embassy-net/src/driver_util.rs +++ b/embassy-net/src/driver_util.rs @@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> where T: Driver, { - type RxToken<'a> = RxTokenAdapter> where Self: 'a; - type TxToken<'a> = TxTokenAdapter> where Self: 'a; + type RxToken<'a> + = RxTokenAdapter> + where + Self: 'a; + type TxToken<'a> + = TxTokenAdapter> + where + Self: 'a; fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { self.inner diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs index 1098dc208..ace325a46 100644 --- a/embassy-net/src/raw.rs +++ b/embassy-net/src/raw.rs @@ -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<'_> { diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index bcddbc95b..a00ced8f4 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -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 it’s possible to receive data from the remote endpoint. /// It will return true while there is data in the receive buffer, and if there isn’t, @@ -427,7 +461,7 @@ impl<'d> TcpIo<'d> { }) } - fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + fn with_mut(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { self.stack.with_mut(|i| { let socket = i.sockets.get_mut::(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 { 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 { 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::Error> { + async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, 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"), }; diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 3eb6e2f83..76602edc2 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -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()) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 13623dd1c..bd53664a2 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -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 {} - )* )* }; } diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index c64743ecc..187fce021 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -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 interrupt::typelevel::Handler 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; 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] = unsafe { core::mem::transmute(buffer) }; + ram_buffer.copy_from_slice(uninit_src); + unsafe { &*(ram_buffer as *const [MaybeUninit] 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; FORCE_COPY_BUFFER_SIZE]>, + last_op: Option<&Operation<'_>>, inten: bool, - ) -> Result<(), Error> { + ) -> Result { 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 } } diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml new file mode 100644 index 000000000..df329be66 --- /dev/null +++ b/embassy-nxp/Cargo.toml @@ -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"] \ No newline at end of file diff --git a/embassy-nxp/src/gpio.rs b/embassy-nxp/src/gpio.rs new file mode 100644 index 000000000..d5d04ee69 --- /dev/null +++ b/embassy-nxp/src/gpio.rs @@ -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

+ '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

+ '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

+ '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

+ Into + 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 you’re 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 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); diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs new file mode 100644 index 000000000..80fdecb2e --- /dev/null +++ b/embassy-nxp/src/lib.rs @@ -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 {} +} diff --git a/embassy-nxp/src/pac_utils.rs b/embassy-nxp/src/pac_utils.rs new file mode 100644 index 000000000..86a807f6c --- /dev/null +++ b/embassy-nxp/src/pac_utils.rs @@ -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; diff --git a/embassy-nxp/src/pint.rs b/embassy-nxp/src/pint.rs new file mode 100644 index 000000000..3313f91a6 --- /dev/null +++ b/embassy-nxp/src/pint.rs @@ -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> = + Mutex::new(RefCell::new([NEW_PIN_INTERRUPT; INTERUPT_COUNT])); + +fn next_available_interrupt() -> Option { + 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

+ 'd, interrupt_on: InterruptOn) -> Option { + 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 { + 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 + } +} diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 29a8a3c53..3e61641ff 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -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" } diff --git a/embassy-rp/build.rs b/embassy-rp/build.rs index 3216a3826..a8d387611 100644 --- a/embassy-rp/build.rs +++ b/embassy-rp/build.rs @@ -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"); + } } diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index f229a5acd..e82beb0f1 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -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 { diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index d402cf793..51f6ed7f8 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -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(()) } diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 9f7d77bf5..ea0a29a36 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -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, 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() } diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs new file mode 100644 index 000000000..9bbf44fc4 --- /dev/null +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -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 ( <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 ( , 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

+ '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; + } +} diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs new file mode 100644 index 000000000..e3f1f89d4 --- /dev/null +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -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

+ '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) + } +} diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs new file mode 100644 index 000000000..74537825b --- /dev/null +++ b/embassy-rp/src/pio_programs/mod.rs @@ -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; diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs new file mode 100644 index 000000000..f3bc5fcd7 --- /dev/null +++ b/embassy-rp/src/pio_programs/onewire.rs @@ -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; + } + } +} diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs new file mode 100644 index 000000000..c6502387a --- /dev/null +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -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) + } +} diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs new file mode 100644 index 000000000..86423fd31 --- /dev/null +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -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, +} diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs new file mode 100644 index 000000000..0d58c754c --- /dev/null +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -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 = (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: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs new file mode 100644 index 000000000..c643f1063 --- /dev/null +++ b/embassy-rp/src/pio_programs/uart.rs @@ -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 ErrorType for PioUartTx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Write for PioUartTx<'_, PIO, SM> { + async fn write(&mut self, buf: &[u8]) -> Result { + 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 ErrorType for PioUartRx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Read for PioUartRx<'_, PIO, SM> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() { + buf[i] = self.read_u8().await; + i += 1; + } + Ok(i) + } +} diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs new file mode 100644 index 000000000..875f0209f --- /dev/null +++ b/embassy-rp/src/pio_programs/ws2812.rs @@ -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

+ '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; + } +} diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 027f5504e..4fb8ade12 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -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 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>, @@ -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>, Option>) { + ( + 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>, Option>) { + ( + 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. diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index b89df74a2..a8f4e72c7 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -359,17 +359,18 @@ impl<'d, T: Instance> Spi<'d, T, Async> { inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, miso: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, rx_dma: impl Peripheral

+ '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, ) diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs index f9560d235..fb34d4ba0 100644 --- a/embassy-stm32-wpan/src/lib.rs +++ b/embassy-stm32-wpan/src/lib.rs @@ -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; diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs index c97c609c3..82b9d2772 100644 --- a/embassy-stm32-wpan/src/mac/commands.rs +++ b/embassy-stm32-wpan/src/mac/commands.rs @@ -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; diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs index 5b9d5daf4..41cca09e3 100644 --- a/embassy-stm32-wpan/src/mac/driver.rs +++ b/embassy-stm32-wpan/src/mac/driver.rs @@ -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() diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 8fc8da006..9fdc799d8 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -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" diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 96bac2bd5..cbe2d093b 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -55,7 +55,7 @@ fn main() { let mut singletons: Vec = 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); }) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 3b064044e..3f0c1a57a 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -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(); diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index baa4bee79..19f1cea29 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -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) } } diff --git a/embassy-stm32/src/can/bxcan/registers.rs b/embassy-stm32/src/can/bxcan/registers.rs index c5de1c683..9798a058b 100644 --- a/embassy-stm32/src/can/bxcan/registers.rs +++ b/embassy-stm32/src/can/bxcan/registers.rs @@ -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); + } }); } diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index d10b5554f..cdc603e2c 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -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 { + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { self.ringbuf .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + 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 { + pub async fn write_exact(&mut self, buffer: &[W]) -> Result { self.ringbuf .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + /// The capacity of the ringbuffer pub const fn capacity(&self) -> usize { self.ringbuf.cap() diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs deleted file mode 100644 index 23f1d67d5..000000000 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ /dev/null @@ -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 { - 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 { - // 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 { - 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 { - // 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>, - } - - 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) { - 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()); - } -} diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs new file mode 100644 index 000000000..12d418414 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -0,0 +1,305 @@ +#![cfg_attr(gpdma, allow(unused))] + +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use crate::dma::word::Word; + +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; + + /// 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); +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Overrun, + DmaUnsynced, +} + +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DmaIndex { + complete_count: usize, + pos: usize, +} + +impl DmaIndex { + fn reset(&mut self) { + self.pos = 0; + self.complete_count = 0; + } + + fn as_index(&self, cap: usize, offset: usize) -> usize { + (self.pos + offset) % cap + } + + fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { + // Important! + // The ordering of the first two lines matters! + // If changed, the code will detect a wrong +capacity + // jump at wrap-around. + let count_diff = dma.reset_complete_count(); + let pos = cap - dma.get_remaining_transfers(); + self.pos = if pos < self.pos && count_diff == 0 { + cap - 1 + } else { + pos + }; + + self.complete_count += count_diff; + } + + fn advance(&mut self, cap: usize, steps: usize) { + let next = self.pos + steps; + self.complete_count += next / cap; + self.pos = next % cap; + } + + fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) { + let min_count = lhs.complete_count.min(rhs.complete_count); + lhs.complete_count -= min_count; + rhs.complete_count -= min_count; + } + + fn diff(&self, cap: usize, rhs: &DmaIndex) -> isize { + (self.complete_count * cap + self.pos) as isize - (rhs.complete_count * cap + rhs.pos) as isize + } +} + +pub struct ReadableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + write_index: DmaIndex, + read_index: DmaIndex, +} + +impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { + /// Construct an empty buffer. + pub fn new(dma_buf: &'a mut [W]) -> Self { + Self { + dma_buf, + write_index: Default::default(), + read_index: Default::default(), + } + } + + /// Reset the ring buffer to its initial state. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.write_index.reset(); + self.write_index.dma_sync(self.cap(), dma); + self.read_index = self.write_index; + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Get the available readable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.write_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.write_index, &mut self.read_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::DmaUnsynced) + } else if diff > self.cap() as isize { + Err(Error::Overrun) + } else { + Ok(diff as usize) + } + } + + /// 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 + /// Error is returned if the portion to be read was overwritten by the DMA controller, + /// in which case the rinbuffer will automatically reset itself. + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + self.read_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// 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, + /// 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 { + let mut read_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + 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 + } + + fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + let readable = self.len(dma)?.min(buf.len()); + for i in 0..readable { + buf[i] = self.read_buf(i); + } + let available = self.len(dma)?; + self.read_index.advance(self.cap(), readable); + Ok((readable, available - readable)) + } + + fn read_buf(&self, offset: usize) -> W { + unsafe { + core::ptr::read_volatile( + self.dma_buf + .as_ptr() + .offset(self.read_index.as_index(self.cap(), offset) as isize), + ) + } + } +} + +pub struct WritableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + read_index: DmaIndex, + write_index: DmaIndex, +} + +impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { + /// Construct a ringbuffer filled with the given buffer data. + pub fn new(dma_buf: &'a mut [W]) -> Self { + let len = dma_buf.len(); + Self { + dma_buf, + read_index: Default::default(), + write_index: DmaIndex { + complete_count: 0, + pos: len, + }, + } + } + + /// Reset the ring buffer to its initial state. The buffer after the reset will be full. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.read_index.reset(); + self.read_index.dma_sync(self.cap(), dma); + self.write_index = self.read_index; + self.write_index.advance(self.cap(), self.cap()); + } + + /// Get the remaining writable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.read_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.read_index, &mut self.write_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::Overrun) + } else if diff > self.cap() as isize { + Err(Error::DmaUnsynced) + } else { + Ok(self.cap().saturating_sub(diff as usize)) + } + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Append data to the ring buffer. + /// Returns a tuple of the data written and the remaining write capacity in the buffer. + /// Error is returned if the portion to be written was previously read by the DMA controller. + /// In this case, the ringbuffer will automatically reset itself, giving a full buffer worth of + /// leeway between the write index and the DMA. + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + self.write_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Write elements directly to the buffer. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + for (i, data) in buf.iter().enumerate() { + self.write_buf(i, *data) + } + let written = buf.len().min(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 { + let mut written_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + 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 + } + + fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + let writable = self.len(dma)?.min(buf.len()); + for i in 0..writable { + self.write_buf(i, buf[i]); + } + let available = self.len(dma)?; + self.write_index.advance(self.cap(), writable); + Ok((writable, available - writable)) + } + + fn write_buf(&mut self, offset: usize, value: W) { + unsafe { + core::ptr::write_volatile( + self.dma_buf + .as_mut_ptr() + .offset(self.write_index.as_index(self.cap(), offset) as isize), + value, + ) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs new file mode 100644 index 000000000..6fabedb83 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -0,0 +1,90 @@ +use std::{cell, vec}; + +use super::*; + +#[allow(unused)] +#[derive(PartialEq, Debug)] +enum TestCircularTransferRequest { + ResetCompleteCount(usize), + PositionRequest(usize), +} + +#[allow(unused)] +struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, +} + +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 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 { + #[allow(unused)] + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len, + } + } + + #[allow(unused)] + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); + } +} + +const CAP: usize = 16; + +#[test] +fn dma_index_as_index_returns_index_mod_cap_by_default() { + let index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + assert_eq!(index.as_index(CAP, 1), 1); + assert_eq!(index.as_index(CAP, 2), 2); + assert_eq!(index.as_index(CAP, 3), 3); + assert_eq!(index.as_index(CAP, 4), 4); + assert_eq!(index.as_index(CAP, CAP), 0); + assert_eq!(index.as_index(CAP, CAP + 1), 1); +} + +#[test] +fn dma_index_advancing_increases_as_index() { + let mut index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 2); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 3); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 4); + index.advance(CAP, CAP - 4); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); +} + +mod prop_test; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs new file mode 100644 index 000000000..661fb1728 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs @@ -0,0 +1,50 @@ +use std::task::Waker; + +use proptest::prop_oneof; +use proptest::strategy::{self, BoxedStrategy, Strategy as _}; +use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest}; + +use super::*; + +const CAP: usize = 128; + +#[derive(Debug, Default)] +struct DmaMock { + pos: usize, + wraps: usize, +} + +impl DmaMock { + pub fn advance(&mut self, steps: usize) { + let next = self.pos + steps; + self.pos = next % CAP; + self.wraps += next / CAP; + } +} + +impl DmaCtrl for DmaMock { + fn get_remaining_transfers(&self) -> usize { + CAP - self.pos + } + + fn reset_complete_count(&mut self) -> usize { + core::mem::replace(&mut self.wraps, 0) + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +#[derive(Debug, Clone)] +enum Status { + Available(usize), + Failed, +} + +impl Status { + pub fn new(capacity: usize) -> Self { + Self::Available(capacity) + } +} + +mod reader; +mod writer; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs new file mode 100644 index 000000000..4f3957a68 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -0,0 +1,123 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum ReaderTransition { + Write(usize), + Reset, + ReadUpTo(usize), +} + +struct ReaderSM; + +impl ReferenceStateMachine for ReaderSM { + type State = Status; + type Transition = ReaderTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(0)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(ReaderTransition::Write), + (1..50_usize).prop_map(ReaderTransition::ReadUpTo), + strategy::Just(ReaderTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, ReaderTransition::Reset) => Status::Available(0), + (Status::Available(x), ReaderTransition::Write(y)) => { + if x + y > CAP { + Status::Failed + } else { + Status::Available(x + y) + } + } + (Status::Failed, ReaderTransition::Write(_)) => Status::Failed, + (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)), + (Status::Failed, ReaderTransition::ReadUpTo(_)) => Status::Available(0), + } + } +} + +struct ReaderSut { + status: Status, + buffer: *mut [u8], + producer: DmaMock, + consumer: ReadableDmaRingBuffer<'static, u8>, +} + +impl Debug for ReaderSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.producer, f) + } +} + +struct ReaderTest; + +impl StateMachineTest for ReaderTest { + type SystemUnderTest = ReaderSut; + type Reference = ReaderSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + ReaderSut { + status: ref_status.clone(), + buffer, + producer: DmaMock::default(), + consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + ReaderTransition::Write(x) => sut.producer.advance(x), + ReaderTransition::Reset => { + sut.consumer.reset(&mut sut.producer); + } + ReaderTransition::ReadUpTo(x) => { + let status = sut.status; + let ReaderSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = consumer.read(producer, &mut buf); + match status { + Status::Available(n) => { + let readable = x.min(n); + + assert_eq!(res.unwrap().0, readable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + ReaderSut { + status: ref_state.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn reader_state_test(sequential 1..20 => ReaderTest); +} diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs new file mode 100644 index 000000000..15433c0ee --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -0,0 +1,122 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum WriterTransition { + Read(usize), + WriteUpTo(usize), + Reset, +} + +struct WriterSM; + +impl ReferenceStateMachine for WriterSM { + type State = Status; + type Transition = WriterTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(CAP)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(WriterTransition::Read), + (1..50_usize).prop_map(WriterTransition::WriteUpTo), + strategy::Just(WriterTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, WriterTransition::Reset) => Status::Available(CAP), + (Status::Available(x), WriterTransition::Read(y)) => { + if x < *y { + Status::Failed + } else { + Status::Available(x - y) + } + } + (Status::Failed, WriterTransition::Read(_)) => Status::Failed, + (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)), + (Status::Failed, WriterTransition::WriteUpTo(_)) => Status::Available(CAP), + } + } +} + +struct WriterSut { + status: Status, + buffer: *mut [u8], + producer: WritableDmaRingBuffer<'static, u8>, + consumer: DmaMock, +} + +impl Debug for WriterSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.consumer, f) + } +} + +struct WriterTest; + +impl StateMachineTest for WriterTest { + type SystemUnderTest = WriterSut; + type Reference = WriterSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + WriterSut { + status: ref_status.clone(), + buffer, + producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }), + consumer: DmaMock::default(), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_status: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + WriterTransition::Read(x) => sut.consumer.advance(x), + WriterTransition::Reset => { + sut.producer.reset(&mut sut.consumer); + } + WriterTransition::WriteUpTo(x) => { + let status = sut.status; + let WriterSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = producer.write(consumer, &mut buf); + match status { + Status::Available(n) => { + let writable = x.min(CAP - n.min(CAP)); + assert_eq!(res.unwrap().0, writable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + WriterSut { + status: ref_status.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn writer_state_test(sequential 1..20 => WriterTest); +} diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 6442176da..1b875b71a 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -74,8 +74,14 @@ impl PacketQueue { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { - type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; - type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + type RxToken<'a> + = RxToken<'a, 'd> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, 'd> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { WAKER.register(cx.waker()); diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 224d51b84..87512c92d 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -41,9 +41,6 @@ fn exticr_regs() -> pac::afio::Afio { } unsafe fn on_irq() { - #[cfg(feature = "low-power")] - crate::low_power::on_wakeup_irq(); - #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] let bits = EXTI.pr(0).read().0; #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] @@ -68,6 +65,9 @@ unsafe fn on_irq() { EXTI.rpr(0).write_value(Lines(bits)); EXTI.fpr(0).write_value(Lines(bits)); } + + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); } struct BitIter(u32); diff --git a/embassy-stm32/src/flash/h5.rs b/embassy-stm32/src/flash/h5.rs new file mode 100644 index 000000000..9e131ca2b --- /dev/null +++ b/embassy-stm32/src/flash/h5.rs @@ -0,0 +1,177 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +// const fn is_dual_bank() -> bool { +// FLASH_REGIONS.len() >= 2 +// } + +pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + if !pac::FLASH.nscr().read().lock() { + pac::FLASH.nscr().modify(|r| { + r.set_lock(true); + }); + } +} + +pub(crate) unsafe fn unlock() { + // TODO: check locked first + while pac::FLASH.nssr().read().bsy() { + #[cfg(feature = "defmt")] + defmt::trace!("busy") + } + + // only unlock if locked to begin with + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // // We cannot have the write setup sequence in begin_write as it depends on the address + // let bank = if start_address < BANK1_REGION.end() { + // pac::FLASH.bank(0) + // } else { + // pac::FLASH.bank(1) + // }; + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + clear_all_err(); + + pac::FLASH.nscr().write(|w| { + w.set_pg(true); + // w.set_psize(2); // 32 bits at once + }); + + let mut res = None; + let mut address = start_address; + // TODO: see write size + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(blocking_wait_ready().map_err(|e| { + error!("write err"); + e + })); + pac::FLASH.nssr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + pac::FLASH.nscr().write(|w| w.set_pg(false)); + + unwrap!(res) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + // pac::FLASH.wrp2r_cur().read().wrpsg() + // TODO: write protection check + if pac::FLASH.nscr().read().lock() == true { + error!("flash locked"); + } + + loop { + let sr = pac::FLASH.nssr().read(); + if !sr.bsy() && !sr.dbne() { + break; + } + } + clear_all_err(); + + pac::FLASH.nscr().modify(|r| { + // TODO: later check bank swap + r.set_bksel(match sector.bank { + crate::flash::FlashBank::Bank1 => stm32_metapac::flash::vals::NscrBksel::B_0X0, + crate::flash::FlashBank::Bank2 => stm32_metapac::flash::vals::NscrBksel::B_0X1, + }); + r.set_snb(sector.index_in_bank); + r.set_ser(true); + }); + + pac::FLASH.nscr().modify(|r| { + r.set_strt(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = blocking_wait_ready().map_err(|e| { + error!("erase err"); + e + }); + + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nssr().modify(|_| {}) +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr.bsy() { + if sr.optchangeerr() { + error!("optchangeerr"); + return Err(Error::Prog); + } + if sr.obkwerr() { + error!("obkwerr"); + return Err(Error::Seq); + } + if sr.obkerr() { + error!("obkerr"); + return Err(Error::Seq); + } + if sr.incerr() { + error!("incerr"); + return Err(Error::Unaligned); + } + if sr.strberr() { + error!("strberr"); + return Err(Error::Parallelism); + } + if sr.wrperr() { + error!("protected"); + return Err(Error::Protected); + } + + return Ok(()); + } + } +} diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index a0bfeb395..ea00bf499 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -23,6 +23,9 @@ pub(crate) unsafe fn lock() { w.set_prglock(true); w.set_pelock(true); }); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nslock(true)); } pub(crate) unsafe fn unlock() { @@ -46,6 +49,14 @@ pub(crate) unsafe fn unlock() { pac::FLASH.prgkeyr().write_value(0x1314_1516); } } + + #[cfg(any(flash_l5))] + { + if pac::FLASH.nscr().read().nslock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } + } } pub(crate) unsafe fn enable_blocking_write() { @@ -53,11 +64,17 @@ pub(crate) unsafe fn enable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(true)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(true)); } pub(crate) unsafe fn disable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(false)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(false)); } pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { @@ -84,13 +101,25 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E write_volatile(sector.start as *mut u32, 0xFFFFFFFF); } - #[cfg(any(flash_wl, flash_wb, flash_l4))] + #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l5))] { let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; #[cfg(flash_l4)] let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + #[cfg(flash_l5)] + let (idx, bank) = if pac::FLASH.optr().read().dbank() { + if idx > 255 { + (idx - 256, Some(true)) + } else { + (idx, Some(false)) + } + } else { + (idx, None) + }; + + #[cfg(not(flash_l5))] pac::FLASH.cr().modify(|w| { w.set_per(true); w.set_pnb(idx as u8); @@ -101,6 +130,16 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_l4))] w.set_bker(bank); }); + + #[cfg(flash_l5)] + pac::FLASH.nscr().modify(|w| { + w.set_nsper(true); + w.set_nspnb(idx as u8); + if let Some(bank) = bank { + w.set_nsbker(bank); + } + w.set_nsstrt(true); + }); } let ret: Result<(), Error> = wait_ready_blocking(); @@ -108,6 +147,9 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_per(false)); + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nsper(false)); + #[cfg(any(flash_l0, flash_l1))] pac::FLASH.pecr().modify(|w| { w.set_erase(false); @@ -121,42 +163,78 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E pub(crate) unsafe fn clear_all_err() { // read and write back the same value. // This clears all "write 1 to clear" bits. + #[cfg(not(flash_l5))] pac::FLASH.sr().modify(|_| {}); + + #[cfg(flash_l5)] + pac::FLASH.nssr().modify(|_| {}); } unsafe fn wait_ready_blocking() -> Result<(), Error> { loop { - let sr = pac::FLASH.sr().read(); + #[cfg(not(flash_l5))] + { + let sr = pac::FLASH.sr().read(); - if !sr.bsy() { - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.progerr() { - return Err(Error::Prog); + if !sr.bsy() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.miserr() { + return Err(Error::Miss); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.pgserr() { + return Err(Error::Seq); + } + + return Ok(()); } + } - if sr.wrperr() { - return Err(Error::Protected); + #[cfg(flash_l5)] + { + let nssr = pac::FLASH.nssr().read(); + + if !nssr.nsbsy() { + if nssr.nsprogerr() { + return Err(Error::Prog); + } + + if nssr.nswrperr() { + return Err(Error::Protected); + } + + if nssr.nspgaerr() { + return Err(Error::Unaligned); + } + + if nssr.nssizerr() { + return Err(Error::Size); + } + + if nssr.nspgserr() { + return Err(Error::Seq); + } + + return Ok(()); } - - if sr.pgaerr() { - return Err(Error::Unaligned); - } - - if sr.sizerr() { - return Err(Error::Size); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.miserr() { - return Err(Error::Miss); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.pgserr() { - return Err(Error::Seq); - } - - return Ok(()); } } } diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index bce638db0..88fe6a291 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -91,7 +91,7 @@ pub enum FlashBank { Bank2 = 1, } -#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] +#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] #[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] #[cfg_attr(flash_f2, path = "f2.rs")] @@ -101,12 +101,14 @@ pub enum FlashBank { #[cfg_attr(flash_h7, path = "h7.rs")] #[cfg_attr(flash_h7ab, path = "h7.rs")] #[cfg_attr(flash_u5, path = "u5.rs")] +#[cfg_attr(flash_h5, path = "h5.rs")] #[cfg_attr(flash_h50, path = "h50.rs")] #[cfg_attr(flash_u0, path = "u0.rs")] #[cfg_attr( not(any( - flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, flash_f7, - flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 + flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, + flash_f7, flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0, + flash_h5, )), path = "other.rs" )] diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 739e960b9..1fc91f1ef 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -88,7 +88,7 @@ impl Config { Speed::Medium, match self.scl_pullup { true => Pull::Up, - false => Pull::Down, + false => Pull::None, }, ); } @@ -102,7 +102,7 @@ impl Config { Speed::Medium, match self.sda_pullup { true => Pull::Up, - false => Pull::Down, + false => Pull::None, }, ); } diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs index 094de2461..f11371f98 100644 --- a/embassy-stm32/src/i2s.rs +++ b/embassy-stm32/src/i2s.rs @@ -1,12 +1,13 @@ //! Inter-IC Sound (I2S) +use embassy_futures::join::join; use embassy_hal_internal::into_ref; +use stm32_metapac::spi::vals; -use crate::dma::ChannelAndRequest; +use crate::dma::{ringbuffer, ChannelAndRequest, ReadableRingBuffer, TransferOptions, WritableRingBuffer}; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; use crate::mode::Async; -use crate::pac::spi::vals; -use crate::spi::{Config as SpiConfig, *}; +use crate::spi::{Config as SpiConfig, RegsExt as _, *}; use crate::time::Hertz; use crate::{Peripheral, PeripheralRef}; @@ -19,6 +20,19 @@ pub enum Mode { Slave, } +/// I2S function +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum Function { + /// Transmit audio data + Transmit, + /// Receive audio data + Receive, + #[cfg(spi_v3)] + /// Transmit and Receive audio data + FullDuplex, +} + /// I2C standard #[derive(Copy, Clone)] pub enum Standard { @@ -34,6 +48,30 @@ pub enum Standard { PcmShortSync, } +/// SAI error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// `write` called on a SAI in receive mode. + NotATransmitter, + /// `read` called on a SAI in transmit mode. + NotAReceiver, + /// Overrun + Overrun, +} + +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } + Self::Overrun + } +} + impl Standard { #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn i2sstd(&self) -> vals::I2sstd { @@ -142,31 +180,62 @@ impl Default for Config { } } +/// I2S driver writer. Useful for moving write functionality across tasks. +pub struct Writer<'s, 'd, W: Word>(&'s mut WritableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Writer<'s, 'd, W> { + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfre’s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + self.0.write_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + +/// I2S driver reader. Useful for moving read functionality across tasks. +pub struct Reader<'s, 'd, W: Word>(&'s mut ReadableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Reader<'s, 'd, W> { + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If there’s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + self.0.read_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to prevent overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + /// I2S driver. -pub struct I2S<'d> { - _peri: Spi<'d, Async>, +pub struct I2S<'d, W: Word> { + #[allow(dead_code)] + mode: Mode, + spi: Spi<'d, Async>, txsd: Option>, rxsd: Option>, ws: Option>, ck: Option>, mck: Option>, + tx_ring_buffer: Option>, + rx_ring_buffer: Option>, } -/// I2S function -#[derive(Copy, Clone)] -#[allow(dead_code)] -enum Function { - /// Transmit audio data - Transmit, - /// Receive audio data - Receive, - #[cfg(spi_v3)] - /// Transmit and Receive audio data - FullDuplex, -} - -impl<'d> I2S<'d> { - /// Create a transmitter driver +impl<'d, W: Word> I2S<'d, W> { + /// Create a transmitter driver. pub fn new_txonly( peri: impl Peripheral

+ 'd, sd: impl Peripheral

> + 'd, @@ -174,18 +243,18 @@ impl<'d> I2S<'d> { ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, ws, ck, - mck, - new_dma!(txdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), None, freq, config, @@ -193,42 +262,61 @@ impl<'d> I2S<'d> { ) } - /// Create a receiver driver + /// Create a transmitter driver without a master clock pin. + pub fn new_txonly_nomck( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + ws, + ck, + None, + new_dma!(txdma).map(|d| (d, txdma_buf)), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a receiver driver. pub fn new_rxonly( peri: impl Peripheral

+ 'd, sd: impl Peripheral

> + 'd, ws: impl Peripheral

> + 'd, ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, - #[cfg(not(spi_v3))] txdma: impl Peripheral

> + 'd, rxdma: impl Peripheral

> + 'd, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, None, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - #[cfg(not(spi_v3))] - new_dma!(txdma), - #[cfg(spi_v3)] + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, - new_dma!(rxdma), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, - #[cfg(not(spi_v3))] - Function::Transmit, - #[cfg(spi_v3)] Function::Receive, ) } #[cfg(spi_v3)] - /// Create a full duplex transmitter driver + /// Create a full duplex driver. pub fn new_full_duplex( peri: impl Peripheral

+ 'd, txsd: impl Peripheral

> + 'd, @@ -237,44 +325,144 @@ impl<'d> I2S<'d> { ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], rxdma: impl Peripheral

> + 'd, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(txsd, rxsd); Self::new_inner( peri, new_pin!(txsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), new_pin!(rxsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - new_dma!(txdma), - new_dma!(rxdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, Function::FullDuplex, ) } - /// Write audio data. - pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.read(data).await + /// Start I2S driver. + pub fn start(&mut self) { + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + self.spi.set_word_size(W::CONFIG); + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.start(); + + set_txdmaen(self.spi.info.regs, true); + } + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.start(); + // SPIv3 clears rxfifo on SPE=0 + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + flush_rx_fifo(self.spi.info.regs); + + set_rxdmaen(self.spi.info.regs, true); + } + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.spi.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); } - /// Write audio data. - pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { - self._peri.write(data).await + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + pub fn clear(&mut self) { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.clear(); + } + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.clear(); + } } - /// Transfer audio data. - pub async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { - self._peri.transfer(read, write).await + /// Stop I2S driver. + pub async fn stop(&mut self) { + let regs = self.spi.info.regs; + + let tx_f = async { + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.stop().await; + + set_txdmaen(regs, false); + } + }; + + let rx_f = async { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.stop().await; + + set_rxdmaen(regs, false); + } + }; + + join(rx_f, tx_f).await; + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + if let Mode::Master = self.mode { + regs.cr1().modify(|w| { + w.set_csusp(true); + }); + + while regs.cr1().read().cstart() {} + } + } + + regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.clear(); } - /// Transfer audio data in place. - pub async fn transfer_in_place(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.transfer_in_place(data).await + /// Split the driver into a Reader/Writer pair. + /// Useful for splitting the reader/writer functionality across tasks or + /// for calling the read/write methods in parallel. + pub fn split<'s>(&'s mut self) -> Result<(Reader<'s, 'd, W>, Writer<'s, 'd, W>), Error> { + match (&mut self.rx_ring_buffer, &mut self.tx_ring_buffer) { + (None, _) => Err(Error::NotAReceiver), + (_, None) => Err(Error::NotATransmitter), + (Some(rx_ring), Some(tx_ring)) => Ok((Reader(rx_ring), Writer(tx_ring))), + } + } + + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If there’s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + match &mut self.rx_ring_buffer { + Some(ring) => Reader(ring).read(data).await, + _ => Err(Error::NotAReceiver), + } + } + + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfre’s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Writer(ring).write(data).await, + _ => Err(Error::NotATransmitter), + } + } + + /// Write data directly to the raw I2S ringbuffer. + /// This can be used to fill the buffer before starting the DMA transfer. + pub async fn write_immediate(&mut self, data: &mut [W]) -> Result<(usize, usize), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Ok(ring.write_immediate(data)?), + _ => return Err(Error::NotATransmitter), + } } fn new_inner( @@ -283,23 +471,23 @@ impl<'d> I2S<'d> { rxsd: Option>, ws: impl Peripheral

> + 'd, ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - txdma: Option>, - rxdma: Option>, + mck: Option>, + txdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, + rxdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, freq: Hertz, config: Config, function: Function, ) -> Self { - into_ref!(ws, ck, mck); + into_ref!(ws, ck); ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - mck.set_as_af(mck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - let mut spi_cfg = SpiConfig::default(); - spi_cfg.frequency = freq; - - let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg); + let spi = Spi::new_internal(peri, None, None, { + let mut config = SpiConfig::default(); + config.frequency = freq; + config + }); let regs = T::info().regs; @@ -390,22 +578,29 @@ impl<'d> I2S<'d> { w.set_i2se(true); }); - #[cfg(spi_v3)] - regs.cr1().modify(|w| w.set_spe(true)); - } + let mut opts = TransferOptions::default(); + opts.half_transfer_ir = true; - Self { - _peri: spi, - txsd: txsd.map(|w| w.map_into()), - rxsd: rxsd.map(|w| w.map_into()), - ws: Some(ws.map_into()), - ck: Some(ck.map_into()), - mck: Some(mck.map_into()), + Self { + mode: config.mode, + spi, + txsd: txsd.map(|w| w.map_into()), + rxsd: rxsd.map(|w| w.map_into()), + ws: Some(ws.map_into()), + ck: Some(ck.map_into()), + mck: mck.map(|w| w.map_into()), + tx_ring_buffer: txdma.map(|(ch, buf)| unsafe { + WritableRingBuffer::new(ch.channel, ch.request, regs.tx_ptr(), buf, opts) + }), + rx_ring_buffer: rxdma.map(|(ch, buf)| unsafe { + ReadableRingBuffer::new(ch.channel, ch.request, regs.rx_ptr(), buf, opts) + }), + } } } } -impl<'d> Drop for I2S<'d> { +impl<'d, W: Word> Drop for I2S<'d, W> { fn drop(&mut self) { self.txsd.as_ref().map(|x| x.set_as_disconnected()); self.rxsd.as_ref().map(|x| x.set_as_disconnected()); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 451f595e0..1e6185bc1 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -165,22 +165,31 @@ pub use crate::_generated::interrupt; // 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 {} - )* )* }; } @@ -296,6 +305,9 @@ mod dual_core { /// It cannot be initialized by the user. The intended use is: /// /// ``` + /// use core::mem::MaybeUninit; + /// use embassy_stm32::{init_secondary, SharedData}; + /// /// #[link_section = ".ram_d3"] /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); /// diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index f3e4c6994..2be1a42b7 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -51,6 +51,9 @@ //! } //! ``` +// TODO: Usage of `static mut` here is unsound. Fix then remove this `allow`.` +#![allow(static_mut_refs)] + use core::arch::asm; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 4c5239971..e25c4f3fb 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -395,7 +395,10 @@ impl<'d, T: Instance> Ltdc<'d, T> { // framebuffer pitch and line length layer.cfblr().modify(|w| { w.set_cfbp(width * bytes_per_pixel); + #[cfg(not(stm32u5))] w.set_cfbll(width * bytes_per_pixel + 7); + #[cfg(stm32u5)] + w.set_cfbll(width * bytes_per_pixel + 3); }); // framebuffer line number diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 289bfa672..f8ef66216 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -16,6 +16,8 @@ use crate::dma::{word, ChannelAndRequest}; use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::pac::octospi::{vals, Octospi as Regs}; +#[cfg(octospim_v1)] +use crate::pac::octospim::Octospim; use crate::rcc::{self, RccPeripheral}; use crate::{peripherals, Peripheral}; @@ -177,6 +179,71 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { } impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), OspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + let reg = T::REGS; + while reg.sr().read().busy() {} + + reg.ccr().modify(|r| { + r.set_dqse(false); + r.set_sioo(true); + }); + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(SizeInBits::from_bits(write_config.isize.into())); + + w.set_admode(PhaseMode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.idtr); + w.set_adsize(SizeInBits::from_bits(write_config.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(PhaseMode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORYMAPPED); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { + r.set_en(true); + }); + } + fn new_inner( peri: impl Peripheral

+ 'd, d0: Option>, @@ -197,6 +264,83 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { ) -> Self { into_ref!(peri); + #[cfg(octospim_v1)] + { + // RCC for octospim should be enabled before writing register + #[cfg(stm32l4)] + crate::pac::RCC.ahb2smenr().modify(|w| w.set_octospimsmen(true)); + #[cfg(stm32u5)] + crate::pac::RCC.ahb2enr1().modify(|w| w.set_octospimen(true)); + #[cfg(not(any(stm32l4, stm32u5)))] + crate::pac::RCC.ahb3enr().modify(|w| w.set_iomngren(true)); + + // Disable OctoSPI peripheral first + T::REGS.cr().modify(|w| { + w.set_en(false); + }); + + // OctoSPI IO Manager has been enabled before + T::OCTOSPIM_REGS.cr().modify(|w| { + w.set_muxen(false); + w.set_req2ack_time(0xff); + }); + + // Clear config + T::OCTOSPIM_REGS.p1cr().modify(|w| { + w.set_clksrc(false); + w.set_dqssrc(false); + w.set_ncssrc(false); + w.set_clken(false); + w.set_dqsen(false); + w.set_ncsen(false); + w.set_iolsrc(0); + w.set_iohsrc(0); + }); + + T::OCTOSPIM_REGS.p1cr().modify(|w| { + let octospi_src = if T::OCTOSPI_IDX == 1 { false } else { true }; + w.set_ncsen(true); + w.set_ncssrc(octospi_src); + w.set_clken(true); + w.set_clksrc(octospi_src); + if dqs.is_some() { + w.set_dqsen(true); + w.set_dqssrc(octospi_src); + } + + // Set OCTOSPIM IOL and IOH according to the index of OCTOSPI instance + if T::OCTOSPI_IDX == 1 { + w.set_iolen(true); + w.set_iolsrc(0); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b01); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b00); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } else { + w.set_iolen(true); + w.set_iolsrc(0b10); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b11); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b10); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } + }); + } + // System configuration rcc::enable_and_reset::(); while T::REGS.sr().read().busy() {} @@ -1056,11 +1200,25 @@ fn finish_dma(regs: Regs) { }); } +#[cfg(octospim_v1)] +/// OctoSPI I/O manager instance trait. +pub(crate) trait SealedOctospimInstance { + const OCTOSPIM_REGS: Octospim; + const OCTOSPI_IDX: u8; +} + +/// OctoSPI instance trait. pub(crate) trait SealedInstance { const REGS: Regs; } /// OSPI instance trait. +#[cfg(octospim_v1)] +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral + SealedOctospimInstance {} + +/// OSPI instance trait. +#[cfg(not(octospim_v1))] #[allow(private_bounds)] pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} @@ -1078,6 +1236,31 @@ pin_trait!(DQSPin, Instance); pin_trait!(NSSPin, Instance); dma_trait!(OctoDma, Instance); +// Hard-coded the octospi index, for OCTOSPIM +#[cfg(octospim_v1)] +impl SealedOctospimInstance for peripherals::OCTOSPI1 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 1; +} + +#[cfg(all(octospim_v1, peri_octospi2))] +impl SealedOctospimInstance for peripherals::OCTOSPI2 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 2; +} + +#[cfg(octospim_v1)] +foreach_peripheral!( + (octospi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +#[cfg(not(octospim_v1))] foreach_peripheral!( (octospi, $inst:ident) => { impl SealedInstance for peripherals::$inst { diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs index 308947e99..1dfd0e918 100644 --- a/embassy-stm32/src/qspi/mod.rs +++ b/embassy-stm32/src/qspi/mod.rs @@ -18,7 +18,7 @@ use crate::{peripherals, Peripheral}; /// QSPI transfer configuration. pub struct TransferConfig { - /// Instraction width (IMODE) + /// Instruction width (IMODE) pub iwidth: QspiWidth, /// Address width (ADMODE) pub awidth: QspiWidth, diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 8022a35a4..4f43d3748 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -86,7 +86,7 @@ pub(crate) unsafe fn set_freqs(freqs: Clocks) { #[cfg(not(feature = "_dual-core"))] /// Safety: Reads a mutable global. pub(crate) unsafe fn get_freqs() -> &'static Clocks { - CLOCK_FREQS.assume_init_ref() + (*core::ptr::addr_of_mut!(CLOCK_FREQS)).assume_init_ref() } #[cfg(feature = "_dual-core")] @@ -171,7 +171,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount += 1; if *refcount > 1 { return; @@ -235,7 +237,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount -= 1; if *refcount > 0 { return; diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index 63f48ace0..7d2f071de 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -27,8 +27,14 @@ pub enum Error { } #[cfg(not(gpdma))] -impl From for Error { - fn from(_: ringbuffer::OverrunError) -> Self { +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } Self::Overrun } } diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index d034c028e..a65b0cc64 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -311,8 +311,7 @@ impl<'d, M: PeriMode> Spi<'d, M> { } } - /// Set SPI word size. Disables SPI if needed, you have to enable it back yourself. - fn set_word_size(&mut self, word_size: word_impl::Config) { + pub(crate) fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } @@ -895,7 +894,7 @@ fn compute_frequency(kernel_clock: Hertz, br: Br) -> Hertz { kernel_clock / div } -trait RegsExt { +pub(crate) trait RegsExt { fn tx_ptr(&self) -> *mut W; fn rx_ptr(&self) -> *mut W; } @@ -983,7 +982,7 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { } } -fn flush_rx_fifo(regs: Regs) { +pub(crate) fn flush_rx_fifo(regs: Regs) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] while regs.sr().read().rxne() { #[cfg(not(spi_v2))] @@ -997,7 +996,7 @@ fn flush_rx_fifo(regs: Regs) { } } -fn set_txdmaen(regs: Regs, val: bool) { +pub(crate) fn set_txdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_txdmaen(val); @@ -1008,7 +1007,7 @@ fn set_txdmaen(regs: Regs, val: bool) { }); } -fn set_rxdmaen(regs: Regs, val: bool) { +pub(crate) fn set_rxdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_rxdmaen(val); @@ -1169,7 +1168,7 @@ impl<'d, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, Async> { } } -trait SealedWord { +pub(crate) trait SealedWord { const CONFIG: word_impl::Config; } diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index e643722aa..3136ea4e9 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -6,6 +6,8 @@ //! //! The available functionality depends on the timer type. +use core::mem::ManuallyDrop; + use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; // Re-export useful enums pub use stm32_metapac::timer::vals::{FilterValue, Sms as SlaveMode, Ts as TriggerSource}; @@ -198,6 +200,11 @@ impl<'d, T: CoreInstance> Timer<'d, T> { Self { tim } } + pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop { + let tim = unsafe { self.tim.clone_unchecked() }; + ManuallyDrop::new(Self { tim }) + } + /// Get access to the virutal core 16bit timer registers. /// /// Note: This works even if the timer is more capable, because registers diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 25782ee13..aa9dd91d9 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -2,6 +2,7 @@ use core::marker::PhantomData; +use embassy_hal_internal::Peripheral; use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(stm32l0))] @@ -66,7 +67,7 @@ impl State { } } -trait SealedInstance: RccPeripheral { +trait SealedInstance: RccPeripheral + Peripheral

{ /// Async state for this timer fn state() -> &'static State; } diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index b7771bd64..9e4a09095 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -1,6 +1,7 @@ //! Simple PWM driver. use core::marker::PhantomData; +use core::mem::ManuallyDrop; use embassy_hal_internal::{into_ref, PeripheralRef}; @@ -51,6 +52,111 @@ channel_impl!(new_ch2, Ch2, Channel2Pin); channel_impl!(new_ch3, Ch3, Channel3Pin); channel_impl!(new_ch4, Ch4, Channel4Pin); +/// A single channel of a pwm, obtained from [`SimplePwm::split`], +/// [`SimplePwm::channel`], [`SimplePwm::ch1`], etc. +/// +/// It is not possible to change the pwm frequency because +/// the frequency configuration is shared with all four channels. +pub struct SimplePwmChannel<'d, T: GeneralInstance4Channel> { + timer: ManuallyDrop>, + channel: Channel, +} + +// TODO: check for RMW races +impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn set_duty_cycle(&mut self, duty: u16) { + assert!(duty <= (*self).max_duty_cycle()); + self.timer.set_compare_value(self.channel, duty.into()) + } + + /// Set the duty cycle to 0%, or always inactive. + pub fn set_duty_cycle_fully_off(&mut self) { + self.set_duty_cycle(0); + } + + /// Set the duty cycle to 100%, or always active. + pub fn set_duty_cycle_fully_on(&mut self) { + self.set_duty_cycle((*self).max_duty_cycle()); + } + + /// Set the duty cycle to `num / denom`. + /// + /// The caller is responsible for ensuring that `num` is less than or equal to `denom`, + /// and that `denom` is not zero. + pub fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { + assert!(denom != 0); + assert!(num <= denom); + let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom); + + // This is safe because we know that `num <= denom`, so `duty <= self.max_duty_cycle()` (u16) + #[allow(clippy::cast_possible_truncation)] + self.set_duty_cycle(duty as u16); + } + + /// Set the duty cycle to `percent / 100` + /// + /// The caller is responsible for ensuring that `percent` is less than or equal to 100. + pub fn set_duty_cycle_percent(&mut self, percent: u8) { + self.set_duty_cycle_fraction(u16::from(percent), 100) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn current_duty_cycle(&self) -> u16 { + unwrap!(self.timer.get_compare_value(self.channel).try_into()) + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + +/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. +pub struct SimplePwmChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: SimplePwmChannel<'d, T>, + /// Channel 2 + pub ch2: SimplePwmChannel<'d, T>, + /// Channel 3 + pub ch3: SimplePwmChannel<'d, T>, + /// Channel 4 + pub ch4: SimplePwmChannel<'d, T>, +} + /// Simple PWM driver. pub struct SimplePwm<'d, T: GeneralInstance4Channel> { inner: Timer<'d, T>, @@ -89,19 +195,76 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this } - /// Enable the given channel. - pub fn enable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, true); + /// Get a single channel + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> { + SimplePwmChannel { + timer: unsafe { self.inner.clone_unchecked() }, + channel, + } } - /// Disable the given channel. - pub fn disable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, false); + /// Channel 1 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch1(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch1) } - /// Check whether given channel is enabled - pub fn is_enabled(&self, channel: Channel) -> bool { - self.inner.get_channel_enable_state(channel) + /// Channel 2 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch2(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch2) + } + + /// Channel 3 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch3(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch3) + } + + /// Channel 4 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch4(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch4) + } + + /// Splits a [`SimplePwm`] into four pwm channels. + /// + /// This returns all four channels, including channels that + /// aren't configured with a [`PwmPin`]. + // TODO: I hate the name "split" + pub fn split(self) -> SimplePwmChannels<'static, T> + where + // must be static because the timer will never be dropped/disabled + 'd: 'static, + { + // without this, the timer would be disabled at the end of this function + let timer = ManuallyDrop::new(self.inner); + + let ch = |channel| SimplePwmChannel { + timer: unsafe { timer.clone_unchecked() }, + channel, + }; + + SimplePwmChannels { + ch1: ch(Channel::Ch1), + ch2: ch(Channel::Ch2), + ch3: ch(Channel::Ch3), + ch4: ch(Channel::Ch4), + } } /// Set PWM frequency. @@ -109,6 +272,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Note: when you call this, the max duty value changes, so you will have to /// call `set_duty` on all channels with the duty calculated based on the new max duty. pub fn set_frequency(&mut self, freq: Hertz) { + // TODO: prevent ARR = u16::MAX? let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { @@ -120,33 +284,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Get max duty value. /// /// This value depends on the configured frequency and the timer's clock rate from RCC. - pub fn get_max_duty(&self) -> u32 { - self.inner.get_max_compare_value() + 1 - } - - /// Set the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn set_duty(&mut self, channel: Channel, duty: u32) { - assert!(duty <= self.get_max_duty()); - self.inner.set_compare_value(channel, duty) - } - - /// Get the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn get_duty(&self, channel: Channel) -> u32 { - self.inner.get_compare_value(channel) - } - - /// Set the output polarity for a given channel. - pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { - self.inner.set_output_polarity(channel, polarity); - } - - /// Set the output compare mode for a given channel. - pub fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) { - self.inner.set_output_compare_mode(channel, mode); + pub fn max_duty_cycle(&self) -> u16 { + let max = self.inner.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 } /// Generate a sequence of PWM waveform @@ -164,8 +305,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_duty_state = self.get_duty(channel); - let original_enable_state = self.is_enabled(channel); + let original_duty_state = self.channel(channel).current_duty_cycle(); + let original_enable_state = self.channel(channel).is_enabled(); let original_update_dma_state = self.inner.get_update_dma_state(); if !original_update_dma_state { @@ -173,7 +314,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { } if !original_enable_state { - self.enable(channel); + self.channel(channel).enable(); } unsafe { @@ -201,10 +342,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { // restore output compare state if !original_enable_state { - self.disable(channel); + self.channel(channel).disable(); } - self.set_duty(channel, original_duty_state); + self.channel(channel).set_duty_cycle(original_duty_state); // Since DMA is closed before timer update event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -234,8 +375,8 @@ macro_rules! impl_waveform_chx { let cc_channel = Channel::$cc_ch; - let original_duty_state = self.get_duty(cc_channel); - let original_enable_state = self.is_enabled(cc_channel); + let original_duty_state = self.channel(cc_channel).current_duty_cycle(); + let original_enable_state = self.channel(cc_channel).is_enabled(); let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); @@ -249,7 +390,7 @@ macro_rules! impl_waveform_chx { } if !original_enable_state { - self.enable(cc_channel); + self.channel(cc_channel).enable(); } unsafe { @@ -277,10 +418,10 @@ macro_rules! impl_waveform_chx { // restore output compare state if !original_enable_state { - self.disable(cc_channel); + self.channel(cc_channel).disable(); } - self.set_duty(cc_channel, original_duty_state); + self.channel(cc_channel).set_duty_cycle(original_duty_state); // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -304,6 +445,41 @@ impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { + type Error = core::convert::Infallible; +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { + fn max_duty_cycle(&self) -> u16 { + self.max_duty_cycle() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty_cycle(duty); + Ok(()) + } + + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_off(); + Ok(()) + } + + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_on(); + Ok(()) + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + self.set_duty_cycle_fraction(num, denom); + Ok(()) + } + + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + self.set_duty_cycle_percent(percent); + Ok(()) + } +} + impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { type Channel = Channel; type Time = Hertz; @@ -330,7 +506,7 @@ impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { } fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { - assert!(duty <= self.get_max_duty()); + assert!(duty <= self.max_duty_cycle() as u32); self.inner.set_compare_value(channel, duty) } diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 86f56eb7c..7ba209063 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -210,7 +210,7 @@ impl<'d> BufferedUart<'d> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), None, None, @@ -246,6 +246,54 @@ impl<'d> BufferedUart<'d> { ) } + /// Create a new bidirectional buffered UART driver with only the RTS pin as the DE pin + pub fn new_with_rts_as_de( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + new_pin!(rts, AfType::input(Pull::None)), // RTS mapped used as DE + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with only the request-to-send pin + pub fn new_with_rts( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rts, AfType::input(Pull::None)), + None, // no CTS + None, // no DE + tx_buffer, + rx_buffer, + config, + ) + } + /// Create a new bidirectional buffered UART driver with a driver-enable pin #[cfg(not(any(usart_v1, usart_v2)))] pub fn new_with_de( @@ -260,7 +308,7 @@ impl<'d> BufferedUart<'d> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), None, None, diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index e7f2f890a..333e01e36 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -69,6 +69,12 @@ unsafe fn on_interrupt(r: Regs, s: &'static State) { // disable idle line detection w.set_idleie(false); }); + } else if cr1.tcie() && sr.tc() { + // Transmission complete detected + r.cr1().modify(|w| { + // disable Transmission complete interrupt + w.set_tcie(false); + }); } else if cr1.rxneie() { // We cannot check the RXNE flag as it is auto-cleared by the DMA controller @@ -167,6 +173,9 @@ pub struct Config { #[cfg(any(usart_v3, usart_v4))] pub invert_rx: bool, + /// Set the pull configuration for the RX pin. + pub rx_pull: Pull, + // private: set by new_half_duplex, not by the user. half_duplex: bool, } @@ -175,7 +184,7 @@ impl Config { fn tx_af(&self) -> AfType { #[cfg(any(usart_v3, usart_v4))] if self.swap_rx_tx { - return AfType::input(Pull::None); + return AfType::input(self.rx_pull); }; AfType::output(OutputType::PushPull, Speed::Medium) } @@ -185,7 +194,7 @@ impl Config { if self.swap_rx_tx { return AfType::output(OutputType::PushPull, Speed::Medium); }; - AfType::input(Pull::None) + AfType::input(self.rx_pull) } } @@ -206,6 +215,7 @@ impl Default for Config { invert_tx: false, #[cfg(any(usart_v3, usart_v4))] invert_rx: false, + rx_pull: Pull::None, half_duplex: false, } } @@ -416,7 +426,7 @@ impl<'d> UartTx<'d, Async> { /// Wait until transmission complete pub async fn flush(&mut self) -> Result<(), Error> { - self.blocking_flush() + flush(&self.info, &self.state).await } } @@ -448,7 +458,7 @@ impl<'d> UartTx<'d, Blocking> { Self::new_inner( peri, new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(cts, AfType::input(config.rx_pull)), None, config, ) @@ -527,13 +537,40 @@ impl<'d, M: Mode> UartTx<'d, M> { } } +/// Wait until transmission complete +async fn flush(info: &Info, state: &State) -> Result<(), Error> { + let r = info.regs; + if r.cr1().read().te() && !sr(r).read().tc() { + r.cr1().modify(|w| { + // enable Transmission Complete interrupt + w.set_tcie(true); + }); + + compiler_fence(Ordering::SeqCst); + + // future which completes when Transmission complete is detected + let abort = poll_fn(move |cx| { + state.rx_waker.register(cx.waker()); + + let sr = sr(r).read(); + if sr.tc() { + // Transmission complete detected + return Poll::Ready(()); + } + + Poll::Pending + }); + + abort.await; + } + + Ok(()) +} + fn blocking_flush(info: &Info) -> Result<(), Error> { let r = info.regs; - while !sr(r).read().tc() {} - - // Enable Receiver after transmission complete for Half-Duplex mode - if r.cr3().read().hdsel() { - r.cr1().modify(|reg| reg.set_re(true)); + if r.cr1().read().te() { + while !sr(r).read().tc() {} } Ok(()) @@ -567,7 +604,7 @@ impl<'d> UartRx<'d, Async> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), None, new_dma!(rx_dma), config, @@ -585,7 +622,7 @@ impl<'d> UartRx<'d, Async> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), new_dma!(rx_dma), config, @@ -614,7 +651,13 @@ impl<'d> UartRx<'d, Async> { // Call flush for Half-Duplex mode if some bytes were written and flush was not called. // It prevents reading of bytes which have just been written. if r.cr3().read().hdsel() && r.cr1().read().te() { - blocking_flush(self.info)?; + flush(&self.info, &self.state).await?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } // make sure USART state is restored to neutral state when this future is dropped @@ -815,7 +858,7 @@ impl<'d> UartRx<'d, Blocking> { rx: impl Peripheral

> + 'd, config: Config, ) -> Result { - Self::new_inner(peri, new_pin!(rx, AfType::input(Pull::None)), None, None, config) + Self::new_inner(peri, new_pin!(rx, AfType::input(config.rx_pull)), None, None, config) } /// Create a new rx-only UART with a request-to-send pin @@ -827,7 +870,7 @@ impl<'d> UartRx<'d, Blocking> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), None, config, @@ -953,6 +996,12 @@ impl<'d, M: Mode> UartRx<'d, M> { // It prevents reading of bytes which have just been written. if r.cr3().read().hdsel() && r.cr1().read().te() { blocking_flush(self.info)?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } for b in buffer { @@ -1148,6 +1197,11 @@ impl<'d> Uart<'d, Async> { self.tx.write(buffer).await } + /// Wait until transmission complete + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } + /// Perform an asynchronous read into `buffer` pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await @@ -1726,7 +1780,7 @@ impl embedded_io_async::Write for Uart<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } @@ -1737,7 +1791,7 @@ impl embedded_io_async::Write for UartTx<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 75834bf37..eb2399d9c 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -83,7 +83,6 @@ impl<'d> RingBufferedUartRx<'d> { // Clear the buffer so that it is ready to receive data compiler_fence(Ordering::SeqCst); self.ring_buf.start(); - self.ring_buf.clear(); let r = self.info.regs; // clear all interrupts and DMA Rx Request diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 8f2d26fe0..a7dd6f66e 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Add LazyLock sync primitive. +- Add `LazyLock` sync primitive. +- Add `Watch` sync primitive. +- Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. ## 0.6.0 - 2024-05-29 @@ -16,20 +20,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PubSubChannel`. - Made `PubSubBehavior` sealed - If you called `.publish_immediate(...)` on the queue directly before, then now call `.immediate_publisher().publish_immediate(...)` -- Add OnceLock sync primitive. -- Add constructor for DynamicChannel -- Add ready_to_receive functions to Channel and Receiver. +- Add `OnceLock` sync primitive. +- Add constructor for `DynamicChannel` +- Add ready_to_receive functions to `Channel` and `Receiver`. ## 0.5.0 - 2023-12-04 -- Add a PriorityChannel. -- Remove nightly and unstable-traits features in preparation for 1.75. -- Upgrade heapless to 0.8. -- Upgrade static-cell to 2.0. +- Add a `PriorityChannel`. +- Remove `nightly` and `unstable-traits` features in preparation for 1.75. +- Upgrade `heapless` to 0.8. +- Upgrade `static-cell` to 2.0. ## 0.4.0 - 2023-10-31 -- Re-add impl_trait_projections +- Re-add `impl_trait_projections` - switch to `embedded-io 0.6` ## 0.3.0 - 2023-09-14 diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml index 7b7d2bf8e..2f049b6bc 100644 --- a/embassy-sync/Cargo.toml +++ b/embassy-sync/Cargo.toml @@ -27,6 +27,7 @@ turbowakers = [] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } +futures-sink = { version = "0.3", default-features = false, features = [] } futures-util = { version = "0.3.17", default-features = false } critical-section = "1.1" heapless = "0.8" @@ -37,7 +38,7 @@ embedded-io-async = { version = "0.6.1" } futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } futures-test = "0.3.17" futures-timer = "3.0.2" -futures-util = { version = "0.3.17", features = [ "channel" ] } +futures-util = { version = "0.3.17", features = [ "channel", "sink" ] } # Enable critical-section implementation for std, for tests critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 97a663d6d..6871bcabc 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -5,9 +5,10 @@ An [Embassy](https://embassy.dev) project. Synchronization primitives and data structures with async support: - [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. -- [`PriorityChannel`](channel::priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PriorityChannel`](priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. +- [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. - [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. - [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. - [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index 8a4a4c642..beafdb43d 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -104,6 +104,7 @@ impl Mutex { impl Mutex { /// Borrows the data + #[allow(clippy::should_implement_trait)] pub fn borrow(&self) -> &T { let ptr = self.data.get() as *const T; unsafe { &*ptr } diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 55ac5fb66..18b053111 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -72,6 +72,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Send-only access to a [`Channel`] without knowing channel size. @@ -179,6 +221,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Receive-only access to a [`Channel`] without knowing channel size. diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 014bf1d06..df0f5e815 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -21,4 +21,5 @@ pub mod pubsub; pub mod semaphore; pub mod signal; pub mod waitqueue; +pub mod watch; pub mod zerocopy_channel; diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 8c3a3af9f..08f66e374 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -138,7 +138,7 @@ impl From for Mutex { impl Default for Mutex where M: RawMutex, - T: ?Sized + Default, + T: Default, { fn default() -> Self { Self::new(Default::default()) diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 24c6c5a7f..1f4d8667c 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -71,6 +71,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> @@ -146,6 +188,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 812302e2b..a2360a1d8 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -27,8 +27,8 @@ pub use subscriber::{DynSubscriber, Subscriber}; /// /// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. /// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message -/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive -/// an error to indicate that it has lagged. +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. /// /// ## Example /// @@ -755,4 +755,30 @@ mod tests { assert_eq!(1, sub0.try_next_message_pure().unwrap().0); assert_eq!(0, sub1.try_next_message_pure().unwrap().0); } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } } diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index e66b3b1db..7a1ab66de 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -74,6 +74,12 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { pub fn is_full(&self) -> bool { self.channel.is_full() } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { @@ -221,6 +227,67 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } } +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + /// Future for the publisher wait action #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs new file mode 100644 index 000000000..59798d04f --- /dev/null +++ b/embassy-sync/src/watch.rs @@ -0,0 +1,1109 @@ +//! A synchronization primitive for passing the latest value to **multiple** receivers. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. +/// +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. +/// +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain receivers and sender +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); +/// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_none()); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(10); +/// +/// // Receive the new value (async or try) +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(20); +/// +/// // Using `get` marks the value as seen +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +trait SealedWatchBehavior { + /// Poll the `Watch` for the current value, making it as seen. + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn try_changed(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); +} + +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + s.data.clone() + } + false => None, + } + }) + } + + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } +} + +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +impl Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new [`Sender`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynSender`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) + } + + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(Receiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(DynReceiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_get(&self) -> Option { + self.watch.try_get(None) + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(None, &mut f) + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } + + /// Modify the value of the `Watch` using a closure. + pub fn send_modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) + } +} + +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get(&mut self) -> T { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); + } +} + +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::Watch; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_sends() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + + // No update + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.send_modify(|opt| { + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); + }; + block_on(f); + } + + #[test] + fn max_receivers_drop() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_some()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); + let snd = WATCH.sender(); + + // No update for both + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + // Send a new value + snd.send(0); + + // Both receivers receive the new value + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + + #[test] + fn use_dynamics() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn dynamic_receiver_count() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); + }; + block_on(f); + } +} diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index cfce9a571..fabb69bf6 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -53,7 +53,7 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { buf: buf.as_mut_ptr(), phantom: PhantomData, state: Mutex::new(RefCell::new(State { - len, + capacity: len, front: 0, back: 0, full: false, @@ -70,6 +70,28 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { (Sender { channel: self }, Receiver { channel: self }) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } } /// Send-only access to a [`Channel`]. @@ -130,6 +152,28 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { pub fn send_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().push_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } /// Receive-only access to a [`Channel`]. @@ -190,10 +234,33 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { pub fn receive_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().pop_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } struct State { - len: usize, + /// Maximum number of elements the channel can hold. + capacity: usize, /// Front index. Always 0..=(N-1) front: usize, @@ -210,13 +277,31 @@ struct State { impl State { fn increment(&self, i: usize) -> usize { - if i + 1 == self.len { + if i + 1 == self.capacity { 0 } else { i + 1 } } + fn clear(&mut self) { + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.capacity + self.back - self.front + } + } else { + self.capacity + } + } + fn is_full(&self) -> bool { self.full } diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c3b4e4e3a..dc3561145 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -431,4 +431,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } -embassy-executor = { version = "0.6.0", path = "../embassy-executor" } +embassy-executor = { version = "0.6.1", path = "../embassy-executor" } diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index 93e49faef..3815ac196 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 8bb8afdfe..86ea9fc2a 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 1c2934298..29049e58c 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 09e34c7df..ed102b073 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index 5e7f4d5e7..cc705b1d6 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 60fdcfafb..17fc1d7d0 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index fe3ab2c04..fd2c19f99 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index 169856358..3852261ac 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 7cef8fe0d..2d89f6d42 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 860a835a9..e07d97d86 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/lpc55s69/.cargo/config.toml b/examples/lpc55s69/.cargo/config.toml new file mode 100644 index 000000000..9556de72f --- /dev/null +++ b/examples/lpc55s69/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip LPC55S69JBD100" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml new file mode 100644 index 000000000..a69007a2c --- /dev/null +++ b/examples/lpc55s69/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-nxp-lpc55s69-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "0.2.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = {version = "0.7.0"} +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/lpc55s69/build.rs b/examples/lpc55s69/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/lpc55s69/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/lpc55s69/memory.x b/examples/lpc55s69/memory.x new file mode 100644 index 000000000..1483b2fad --- /dev/null +++ b/examples/lpc55s69/memory.x @@ -0,0 +1,28 @@ +/* File originally from lpc55-hal repo: https://github.com/lpc55/lpc55-hal/blob/main/memory.x */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + + /* for use with standard link.x */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* would be used with proper link.x */ + /* needs changes to r0 (initialization code) */ + /* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */ + /* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */ + /* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */ + /* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */ + + /* CASPER SRAM regions */ + /* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */ + /* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */ + + /* USB1 SRAM regin */ + /* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */ + + /* To define our own USB RAM section in one regular */ + /* RAM, probably easiest to shorten length of RAM */ + /* above, and use this freed RAM section */ + +} + diff --git a/examples/lpc55s69/src/bin/blinky_nop.rs b/examples/lpc55s69/src/bin/blinky_nop.rs new file mode 100644 index 000000000..58e2d9808 --- /dev/null +++ b/examples/lpc55s69/src/bin/blinky_nop.rs @@ -0,0 +1,33 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on PIO1_6. + +#![no_std] +#![no_main] + +use cortex_m::asm::nop; +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + + loop { + info!("led off!"); + led.set_high(); + + for _ in 0..200_000 { + nop(); + } + + info!("led on!"); + led.set_low(); + + for _ in 0..200_000 { + nop(); + } + } +} diff --git a/examples/lpc55s69/src/bin/button_executor.rs b/examples/lpc55s69/src/bin/button_executor.rs new file mode 100644 index 000000000..836b1c9eb --- /dev/null +++ b/examples/lpc55s69/src/bin/button_executor.rs @@ -0,0 +1,25 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on +//! PIO1_6 and a button (labeled "user") on PIO1_9. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + let mut button = Input::new(p.PIO1_9, Pull::Up); + + info!("Entered main loop"); + loop { + button.wait_for_rising_edge().await; + info!("Button pressed"); + led.toggle(); + } +} diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 98a678815..2358ced56 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -16,7 +16,7 @@ log = [ [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time" } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } diff --git a/examples/nrf51/Cargo.toml b/examples/nrf51/Cargo.toml index 93a19bea7..f94026158 100644 --- a/examples/nrf51/Cargo.toml +++ b/examples/nrf51/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } diff --git a/examples/nrf52810/Cargo.toml b/examples/nrf52810/Cargo.toml index 0e3e81c3f..867ead6d8 100644 --- a/examples/nrf52810/Cargo.toml +++ b/examples/nrf52810/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 17fa6234d..cb05b7204 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 0da85be07..2387eaa58 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/nrf9151/ns/Cargo.toml b/examples/nrf9151/ns/Cargo.toml index 17fe27b67..78260dae3 100644 --- a/examples/nrf9151/ns/Cargo.toml +++ b/examples/nrf9151/ns/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf9151/s/Cargo.toml b/examples/nrf9151/s/Cargo.toml index 7253fc4be..17ead4ab4 100644 --- a/examples/nrf9151/s/Cargo.toml +++ b/examples/nrf9151/s/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index 9aeb99317..2572f3353 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 929883884..067ec4276 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -9,7 +9,7 @@ use core::str::FromStr; use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; -use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Ipv4Cidr, Stack, StackResources}; use embassy_net_nrf91::context::Status; use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; @@ -70,18 +70,16 @@ fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { let Some(IpAddr::V4(addr)) = status.ip else { panic!("Unexpected IP address"); }; - let addr = Ipv4Address(addr.octets()); - let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { - Some(Ipv4Address(addr.octets())) - } else { - None + let gateway = match status.gateway { + Some(IpAddr::V4(addr)) => Some(addr), + _ => None, }; let mut dns_servers = Vec::new(); for dns in status.dns.iter() { if let IpAddr::V4(ip) = dns { - unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + unwrap!(dns_servers.push(*ip)); } } @@ -163,6 +161,7 @@ async fn main(spawner: Spawner) { apn: b"iot.nat.es", auth_prot: context::AuthProt::Pap, auth: Some((b"orange", b"orange")), + pin: None, }, stack ))); diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 04b4c6317..6a2c99716 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } @@ -25,7 +25,7 @@ fixed = "1.23.1" fixed-macro = "1.2" # for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} +reqwless = { version = "0.13.0", features = ["defmt"] } serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" @@ -42,7 +42,7 @@ embedded-graphics = "0.7.1" st7789 = "0.6.1" display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } -smart-leds = "0.3.0" +smart-leds = "0.4.0" heapless = "0.8" usbd-hid = "0.8.1" diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs index 6c02630e0..164e6f8d3 100644 --- a/examples/rp/src/bin/pio_hd44780.rs +++ b/examples/rp/src/bin/pio_hd44780.rs @@ -7,13 +7,11 @@ use core::fmt::Write; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -43,8 +41,27 @@ async fn main(_spawner: Spawner) { c }); - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, ) .await; @@ -68,173 +85,3 @@ async fn main(_spawner: Spawner) { Timer::after_secs(1).await; } } - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

+ 'l, - irq: Irqs, - dma: impl Peripheral

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - 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 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); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&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; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // 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 mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&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; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - 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; - } -} diff --git a/examples/rp/src/bin/pio_i2s.rs b/examples/rp/src/bin/pio_i2s.rs index cf60e5b30..447100ddf 100644 --- a/examples/rp/src/bin/pio_i2s.rs +++ b/examples/rp/src/bin/pio_i2s.rs @@ -13,10 +13,10 @@ use core::mem; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; -use fixed::traits::ToFixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -25,61 +25,32 @@ bind_interrupts!(struct Irqs { }); const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut p = embassy_rp::init(Default::default()); // Setup pio state machine for i2s output - let mut pio = Pio::new(p.PIO0, Irqs); - - #[rustfmt::skip] - let pio_program = 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 Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); let bit_clock_pin = p.PIN_18; let left_right_clock_pin = p.PIN_19; let data_pin = p.PIN_20; - let data_pin = pio.common.make_pio_pin(data_pin); - let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); - let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); - - let cfg = { - let mut cfg = Config::default(); - cfg.use_program( - &pio.common.load_program(&pio_program.program), - &[&bit_clock_pin, &left_right_clock_pin], - ); - cfg.set_out_pins(&[&data_pin]); - const BIT_DEPTH: u32 = 16; - const CHANNELS: u32 = 2; - 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 - }; - pio.sm0.set_config(&cfg); - pio.sm0.set_pin_dirs( - embassy_rp::pio::Direction::Out, - &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, ); // create two audio buffers (back and front) which will take turns being @@ -90,17 +61,13 @@ async fn main(_spawner: Spawner) { let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); // start pio state machine - pio.sm0.set_enable(true); - let tx = pio.sm0.tx(); - let mut dma_ref = p.DMA_CH0.into_ref(); - let mut fade_value: i32 = 0; let mut phase: i32 = 0; loop { // trigger transfer of front buffer data to the pio fifo // but don't await the returned future, yet - let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + let dma_future = i2s.write(front_buffer); // fade in audio when bootsel is pressed let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs index 5076101ec..991510851 100644 --- a/examples/rp/src/bin/pio_onewire.rs +++ b/examples/rp/src/bin/pio_onewire.rs @@ -6,7 +6,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -18,7 +19,11 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let mut pio = Pio::new(p.PIO0, Irqs); - let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); loop { sensor.start().await; // Start a new measurement @@ -33,89 +38,12 @@ async fn main(_spawner: Spawner) { /// DS18B20 temperature sensor driver pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { - sm: StateMachine<'d, PIO, SM>, + wire: PioOneWire<'d, PIO, SM>, } impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'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) -> 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 pin = common.make_pio_pin(pin); - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[]); - 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 - 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 - 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; - } + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } } /// Calculate CRC8 of the data @@ -139,14 +67,14 @@ impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { /// Start a new measurement. Allow at least 1000ms before getting `temperature`. pub async fn start(&mut self) { - self.write_bytes(&[0xCC, 0x44]).await; + self.wire.write_bytes(&[0xCC, 0x44]).await; } /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. pub async fn temperature(&mut self) -> Result { - self.write_bytes(&[0xCC, 0xBE]).await; + self.wire.write_bytes(&[0xCC, 0xBE]).await; let mut data = [0; 9]; - self.read_bytes(&mut data).await; + self.wire.read_bytes(&mut data).await; match Self::crc8(&data) == 0 { true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), false => Err(()), diff --git a/examples/rp/src/bin/pio_pwm.rs b/examples/rp/src/bin/pio_pwm.rs index 23d63d435..7eabb2289 100644 --- a/examples/rp/src/bin/pio_pwm.rs +++ b/examples/rp/src/bin/pio_pwm.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const REFRESH_INTERVAL: u64 = 20000; @@ -19,93 +18,14 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub 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 -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> 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" - ); - - pio.load_program(&prg.program); - 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(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - 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 - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); pwm_pio.start(); diff --git a/examples/rp/src/bin/pio_rotary_encoder.rs b/examples/rp/src/bin/pio_rotary_encoder.rs index 58bdadbc0..2750f61ae 100644 --- a/examples/rp/src/bin/pio_rotary_encoder.rs +++ b/examples/rp/src/bin/pio_rotary_encoder.rs @@ -5,70 +5,18 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_rp::gpio::Pull; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; -use fixed::traits::ToFixed; -use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -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> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, - ) -> 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 prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); - - 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(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } - } -} - -pub enum Direction { - Clockwise, - CounterClockwise, -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { let mut count = 0; loop { info!("Count: {}", count); @@ -78,3 +26,30 @@ async fn main(_spawner: Spawner) { }; } } + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp/src/bin/pio_servo.rs b/examples/rp/src/bin/pio_servo.rs index a79540479..c52ee7492 100644 --- a/examples/rp/src/bin/pio_servo.rs +++ b/examples/rp/src/bin/pio_servo.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo @@ -22,88 +21,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub 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 -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> 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" - ); - - pio.load_program(&prg.program); - 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(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - 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 - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -111,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -153,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -190,7 +109,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp/src/bin/pio_stepper.rs b/examples/rp/src/bin/pio_stepper.rs index 4952f4fbd..3862c248b 100644 --- a/examples/rp/src/bin/pio_stepper.rs +++ b/examples/rp/src/bin/pio_stepper.rs @@ -3,143 +3,20 @@ #![no_std] #![no_main] -use core::mem::{self, MaybeUninit}; use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -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> { - 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, - ) -> 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 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(&pio.load_program(&prg.program), &[]); - 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 = (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: MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { f: MaybeUninit::new(f) } - } - - pub fn defuse(self) { - mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -147,14 +24,18 @@ async fn main(_spawner: Spawner) { mut common, irq0, sm0, .. } = Pio::new(p.PIO0, Irqs); - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); stepper.set_frequency(120); loop { info!("CW full steps"); stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index 53b696309..aaf2a524f 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -13,10 +13,10 @@ use defmt::{info, panic, trace}; use embassy_executor::Spawner; use embassy_futures::join::{join, join3}; -use embassy_rp::bind_interrupts; use embassy_rp::peripherals::{PIO0, USB}; -use embassy_rp::pio::InterruptHandler as PioInterruptHandler; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pipe::Pipe; use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; @@ -25,13 +25,11 @@ use embassy_usb::{Builder, Config}; use embedded_io_async::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; -use crate::uart::PioUart; -use crate::uart_rx::PioUartRx; -use crate::uart_tx::PioUartTx; +//use crate::uart::PioUart; bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; - PIO0_IRQ_0 => PioInterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; }); #[embassy_executor::main] @@ -85,8 +83,15 @@ async fn main(_spawner: Spawner) { let usb_fut = usb.run(); // PIO UART setup - let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5); - let (mut uart_tx, mut uart_rx) = uart.split(); + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); // Pipe setup let mut usb_pipe: Pipe = Pipe::new(); @@ -163,8 +168,8 @@ async fn usb_write<'d, T: Instance + 'd>( } /// Read from the UART and write it to the USB TX pipe -async fn uart_read( - uart_rx: &mut PioUartRx<'_>, +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -180,8 +185,8 @@ async fn uart_read( } /// Read from the UART TX pipe and write it to the UART -async fn uart_write( - uart_tx: &mut PioUartTx<'_>, +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -192,197 +197,3 @@ async fn uart_write( let _ = uart_tx.write(&data).await; } } - -mod uart { - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Pio, PioPin}; - use embassy_rp::Peripheral; - - use crate::uart_rx::PioUartRx; - use crate::uart_tx::PioUartTx; - use crate::Irqs; - - pub struct PioUart<'a> { - tx: PioUartTx<'a>, - rx: PioUartRx<'a>, - } - - impl<'a> PioUart<'a> { - pub fn new( - baud: u64, - pio: impl Peripheral

+ 'a, - tx_pin: impl PioPin, - rx_pin: impl PioPin, - ) -> PioUart<'a> { - let Pio { - mut common, sm0, sm1, .. - } = Pio::new(pio, Irqs); - - let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud); - let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud); - - PioUart { tx, rx } - } - - pub fn split(self) -> (PioUartTx<'a>, PioUartRx<'a>) { - (self.tx, self.rx) - } - } -} - -mod uart_tx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Write}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartTx<'a> { - sm_tx: StateMachine<'a, PIO0, 0>, - } - - impl<'a> PioUartTx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_tx: StateMachine<'a, PIO0, 0>, - tx_pin: impl PioPin, - baud: u64, - ) -> 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 tx_pin = common.make_pio_pin(tx_pin); - sm_tx.set_pins(Level::High, &[&tx_pin]); - sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]); - - let mut cfg = Config::default(); - - cfg.set_out_pins(&[&tx_pin]); - cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]); - cfg.shift_out.auto_fill = false; - cfg.shift_out.direction = ShiftDirection::Right; - cfg.fifo_join = FifoJoin::TxOnly; - cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); - sm_tx.set_config(&cfg); - sm_tx.set_enable(true); - - Self { sm_tx } - } - - pub async fn write_u8(&mut self, data: u8) { - self.sm_tx.tx().wait_push(data as u32).await; - } - } - - impl ErrorType for PioUartTx<'_> { - type Error = Infallible; - } - - impl Write for PioUartTx<'_> { - async fn write(&mut self, buf: &[u8]) -> Result { - for byte in buf { - self.write_u8(*byte).await; - } - Ok(buf.len()) - } - } -} - -mod uart_rx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Read}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartRx<'a> { - sm_rx: StateMachine<'a, PIO0, 1>, - } - - impl<'a> PioUartRx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_rx: StateMachine<'a, PIO0, 1>, - rx_pin: impl PioPin, - baud: u64, - ) -> 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 mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[]); - - 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(Direction::In, &[&rx_pin]); - - cfg.clock_divider = (U56F8!(125_000_000) / (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 } - } - - pub async fn read_u8(&mut self) -> u8 { - self.sm_rx.rx().wait_pull().await as u8 - } - } - - impl ErrorType for PioUartRx<'_> { - type Error = Infallible; - } - - impl Read for PioUartRx<'_> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - let mut i = 0; - while i < buf.len() { - buf[i] = self.read_u8().await; - i += 1; - } - Ok(i) - } - } -} diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs index ac145933c..d1fcfc471 100644 --- a/examples/rp/src/bin/pio_ws2812.rs +++ b/examples/rp/src/bin/pio_ws2812.rs @@ -6,15 +6,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; @@ -22,96 +18,6 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct Ws2812<'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> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - 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; - - 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 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(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - 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, - } - } - - 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; - } -} - /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { @@ -142,7 +48,8 @@ async fn main(_spawner: Spawner) { // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); // Loop forever making RGB values and pushing them out to the WS2812. let mut ticker = Ticker::every(Duration::from_millis(10)); diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs index 26e233260..791b88b5b 100644 --- a/examples/rp/src/bin/pwm.rs +++ b/examples/rp/src/bin/pwm.rs @@ -1,24 +1,36 @@ //! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. //! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::pwm::{Config, Pwm}; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} - let mut c: Config = Default::default(); - c.top = 0x8000; +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); loop { info!("current LED duty cycle: {}/32768", c.compare_b); @@ -27,3 +39,37 @@ async fn main(_spawner: Spawner) { pwm.set_config(&c); } } + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let mut c = Config::default(); + let pwm_freq = 25_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + c.top = (clock_freq / pwm_freq) as u16 - 1; + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 04a61bbd5..0107a2326 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -33,8 +33,8 @@ async fn main(spawner: Spawner) { // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: - // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 - // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml index 087f6fd69..5527a1e0a 100644 --- a/examples/rp23/Cargo.toml +++ b/examples/rp23/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -24,14 +24,15 @@ defmt-rtt = "0.4" fixed = "1.23.1" fixed-macro = "1.2" -# for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" # for assign resources example assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } +# for TB6612FNG example +tb6612fng = "1.0.0" + #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m-rt = "0.7.0" diff --git a/examples/rp23/memory.x b/examples/rp23/memory.x index 777492062..c803896f6 100644 --- a/examples/rp23/memory.x +++ b/examples/rp23/memory.x @@ -31,6 +31,7 @@ SECTIONS { { __start_block_addr = .; KEEP(*(.start_block)); + KEEP(*(.boot_info)); } > FLASH } INSERT AFTER .vector_table; diff --git a/examples/rp23/src/bin/adc.rs b/examples/rp23/src/bin/adc.rs index d1f053d39..f7db9653a 100644 --- a/examples/rp23/src/bin/adc.rs +++ b/examples/rp23/src/bin/adc.rs @@ -1,4 +1,4 @@ -//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! This example test the ADC (Analog to Digital Conversion) of the RP2350A pins 26, 27 and 28. //! It also reads the temperature sensor in the chip. #![no_std] @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); diff --git a/examples/rp23/src/bin/adc_dma.rs b/examples/rp23/src/bin/adc_dma.rs index 5046e5530..a6814c23a 100644 --- a/examples/rp23/src/bin/adc_dma.rs +++ b/examples/rp23/src/bin/adc_dma.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); diff --git a/examples/rp23/src/bin/assign_resources.rs b/examples/rp23/src/bin/assign_resources.rs index 2f9783917..0d4ad8dc3 100644 --- a/examples/rp23/src/bin/assign_resources.rs +++ b/examples/rp23/src/bin/assign_resources.rs @@ -24,16 +24,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(spawner: Spawner) { // initialize the peripherals diff --git a/examples/rp23/src/bin/blinky.rs b/examples/rp23/src/bin/blinky.rs index 9e45679c8..c1ddbb7d2 100644 --- a/examples/rp23/src/bin/blinky.rs +++ b/examples/rp23/src/bin/blinky.rs @@ -17,20 +17,23 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` +// Program metadata for `picotool info`. +// This isn't needed, but it's recomended to have these minimal entries. #[link_section = ".bi_entries"] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico on board LED, connected to gpio 25" + ), embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), embassy_rp::binary_info::rp_program_build_attribute!(), ]; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let mut led = Output::new(p.PIN_2, Level::Low); + let mut led = Output::new(p.PIN_25, Level::Low); loop { info!("led on!"); diff --git a/examples/rp23/src/bin/blinky_two_channels.rs b/examples/rp23/src/bin/blinky_two_channels.rs index 87fc58bbc..ce482858e 100644 --- a/examples/rp23/src/bin/blinky_two_channels.rs +++ b/examples/rp23/src/bin/blinky_two_channels.rs @@ -19,16 +19,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - enum LedState { Toggle, } diff --git a/examples/rp23/src/bin/blinky_two_tasks.rs b/examples/rp23/src/bin/blinky_two_tasks.rs index 40236c53b..5dc62245d 100644 --- a/examples/rp23/src/bin/blinky_two_tasks.rs +++ b/examples/rp23/src/bin/blinky_two_tasks.rs @@ -19,16 +19,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type LedType = Mutex>>; static LED: LedType = Mutex::new(None); diff --git a/examples/rp23/src/bin/button.rs b/examples/rp23/src/bin/button.rs index fb067a370..85f1bcae3 100644 --- a/examples/rp23/src/bin/button.rs +++ b/examples/rp23/src/bin/button.rs @@ -14,16 +14,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/debounce.rs b/examples/rp23/src/bin/debounce.rs index e672521ec..4c8b80d92 100644 --- a/examples/rp23/src/bin/debounce.rs +++ b/examples/rp23/src/bin/debounce.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - pub struct Debouncer<'a> { input: Input<'a>, debounce: Duration, diff --git a/examples/rp23/src/bin/flash.rs b/examples/rp23/src/bin/flash.rs index 84011e394..28dec24c9 100644 --- a/examples/rp23/src/bin/flash.rs +++ b/examples/rp23/src/bin/flash.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Flash"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const ADDR_OFFSET: u32 = 0x100000; const FLASH_SIZE: usize = 2 * 1024 * 1024; diff --git a/examples/rp23/src/bin/gpio_async.rs b/examples/rp23/src/bin/gpio_async.rs index ff12367bf..bfb9a3f95 100644 --- a/examples/rp23/src/bin/gpio_async.rs +++ b/examples/rp23/src/bin/gpio_async.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - /// It requires an external signal to be manually triggered on PIN 16. For /// example, this could be accomplished using an external power source with a /// button so that it is possible to toggle the signal from low to high. diff --git a/examples/rp23/src/bin/gpout.rs b/examples/rp23/src/bin/gpout.rs index d2ee55197..3cc2ea938 100644 --- a/examples/rp23/src/bin/gpout.rs +++ b/examples/rp23/src/bin/gpout.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/i2c_async.rs b/examples/rp23/src/bin/i2c_async.rs index c8d918b56..b30088bae 100644 --- a/examples/rp23/src/bin/i2c_async.rs +++ b/examples/rp23/src/bin/i2c_async.rs @@ -20,16 +20,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C1_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/i2c_async_embassy.rs b/examples/rp23/src/bin/i2c_async_embassy.rs index cce0abcde..c783a80c5 100644 --- a/examples/rp23/src/bin/i2c_async_embassy.rs +++ b/examples/rp23/src/bin/i2c_async_embassy.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - // Our anonymous hypotetical temperature sensor could be: // a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C // It requires no configuration or calibration, works with all i2c bus speeds, diff --git a/examples/rp23/src/bin/i2c_blocking.rs b/examples/rp23/src/bin/i2c_blocking.rs index 85c33bf0d..a68677311 100644 --- a/examples/rp23/src/bin/i2c_blocking.rs +++ b/examples/rp23/src/bin/i2c_blocking.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[allow(dead_code)] mod mcp23017 { pub const ADDR: u8 = 0x20; // default addr diff --git a/examples/rp23/src/bin/i2c_slave.rs b/examples/rp23/src/bin/i2c_slave.rs index fb5f3cda1..8817538c0 100644 --- a/examples/rp23/src/bin/i2c_slave.rs +++ b/examples/rp23/src/bin/i2c_slave.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C0_IRQ => i2c::InterruptHandler; I2C1_IRQ => i2c::InterruptHandler; diff --git a/examples/rp23/src/bin/interrupt.rs b/examples/rp23/src/bin/interrupt.rs index ee3d9bfe7..d9b662253 100644 --- a/examples/rp23/src/bin/interrupt.rs +++ b/examples/rp23/src/bin/interrupt.rs @@ -29,16 +29,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - static COUNTER: AtomicU32 = AtomicU32::new(0); static PWM: Mutex>> = Mutex::new(RefCell::new(None)); static ADC: Mutex, adc::Channel)>>> = diff --git a/examples/rp23/src/bin/multicore.rs b/examples/rp23/src/bin/multicore.rs index 9ab43d7a5..d4d470fa2 100644 --- a/examples/rp23/src/bin/multicore.rs +++ b/examples/rp23/src/bin/multicore.rs @@ -20,16 +20,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - static mut CORE1_STACK: Stack<4096> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); diff --git a/examples/rp23/src/bin/multiprio.rs b/examples/rp23/src/bin/multiprio.rs index 27cd3656e..787854aa9 100644 --- a/examples/rp23/src/bin/multiprio.rs +++ b/examples/rp23/src/bin/multiprio.rs @@ -70,16 +70,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::task] async fn run_high() { loop { diff --git a/examples/rp23/src/bin/otp.rs b/examples/rp23/src/bin/otp.rs index 106e514ca..c67c9821a 100644 --- a/examples/rp23/src/bin/otp.rs +++ b/examples/rp23/src/bin/otp.rs @@ -14,16 +14,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"OTP Read Example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"OTP Read Example"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let _ = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/pio_async.rs b/examples/rp23/src/bin/pio_async.rs index 231afc80e..896447e28 100644 --- a/examples/rp23/src/bin/pio_async.rs +++ b/examples/rp23/src/bin/pio_async.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_dma.rs b/examples/rp23/src/bin/pio_dma.rs index 60fbcb83a..b5f754798 100644 --- a/examples/rp23/src/bin/pio_dma.rs +++ b/examples/rp23/src/bin/pio_dma.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_hd44780.rs b/examples/rp23/src/bin/pio_hd44780.rs index 92aa858f9..c6f5f6db0 100644 --- a/examples/rp23/src/bin/pio_hd44780.rs +++ b/examples/rp23/src/bin/pio_hd44780.rs @@ -7,14 +7,12 @@ use core::fmt::Write; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -22,16 +20,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(pub struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); @@ -58,8 +46,27 @@ async fn main(_spawner: Spawner) { c }); - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, ) .await; @@ -83,173 +90,3 @@ async fn main(_spawner: Spawner) { Timer::after_secs(1).await; } } - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

+ 'l, - irq: Irqs, - dma: impl Peripheral

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - 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 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); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&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; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // 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 mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&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; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - 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; - } -} diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs index d6d2d0ade..1fd34357b 100644 --- a/examples/rp23/src/bin/pio_i2s.rs +++ b/examples/rp23/src/bin/pio_i2s.rs @@ -13,11 +13,12 @@ use core::mem; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Pull}; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; -use fixed::traits::ToFixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -25,78 +26,41 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); // Setup pio state machine for i2s output - let mut pio = Pio::new(p.PIO0, Irqs); - - #[rustfmt::skip] - let pio_program = 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 Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); let bit_clock_pin = p.PIN_18; let left_right_clock_pin = p.PIN_19; let data_pin = p.PIN_20; - let data_pin = pio.common.make_pio_pin(data_pin); - let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); - let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); - - let cfg = { - let mut cfg = Config::default(); - cfg.use_program( - &pio.common.load_program(&pio_program.program), - &[&bit_clock_pin, &left_right_clock_pin], - ); - cfg.set_out_pins(&[&data_pin]); - const BIT_DEPTH: u32 = 16; - const CHANNELS: u32 = 2; - 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 - }; - pio.sm0.set_config(&cfg); - pio.sm0.set_pin_dirs( - embassy_rp::pio::Direction::Out, - &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, ); + let fade_input = Input::new(p.PIN_0, Pull::Up); + // create two audio buffers (back and front) which will take turns being // filled with new audio data and being sent to the pio fifo using dma const BUFFER_SIZE: usize = 960; @@ -105,20 +69,16 @@ async fn main(_spawner: Spawner) { let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); // start pio state machine - pio.sm0.set_enable(true); - let tx = pio.sm0.tx(); - let mut dma_ref = p.DMA_CH0.into_ref(); - let mut fade_value: i32 = 0; let mut phase: i32 = 0; loop { // trigger transfer of front buffer data to the pio fifo // but don't await the returned future, yet - let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + let dma_future = i2s.write(front_buffer); - // fade in audio - let fade_target = i32::MAX; + // fade in audio when bootsel is pressed + let fade_target = if fade_input.is_low() { i32::MAX } else { 0 }; // fill back buffer with fresh audio samples before awaiting the dma future for s in back_buffer.iter_mut() { diff --git a/examples/rp23/src/bin/pio_onewire.rs b/examples/rp23/src/bin/pio_onewire.rs new file mode 100644 index 000000000..7f227d04b --- /dev/null +++ b/examples/rp23/src/bin/pio_onewire.rs @@ -0,0 +1,88 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + wire: PioOneWire<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.wire.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.wire.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.wire.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/rp23/src/bin/pio_pwm.rs b/examples/rp23/src/bin/pio_pwm.rs index 587f91ac3..11af62a7a 100644 --- a/examples/rp23/src/bin/pio_pwm.rs +++ b/examples/rp23/src/bin/pio_pwm.rs @@ -5,122 +5,32 @@ use core::time::Duration; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const REFRESH_INTERVAL: u64 = 20000; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub 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 -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> 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" - ); - - pio.load_program(&prg.program); - 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(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - 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 - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); pwm_pio.start(); diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp23/src/bin/pio_rotary_encoder.rs index c147351e8..2bb0e67f9 100644 --- a/examples/rp23/src/bin/pio_rotary_encoder.rs +++ b/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -5,85 +5,23 @@ use defmt::info; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Pull; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; -use fixed::traits::ToFixed; -use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -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> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, - ) -> 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 prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); - - 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(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } - } -} - -pub enum Direction { - Clockwise, - CounterClockwise, -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { let mut count = 0; loop { info!("Count: {}", count); @@ -93,3 +31,30 @@ async fn main(_spawner: Spawner) { }; } } + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp23/src/bin/pio_servo.rs b/examples/rp23/src/bin/pio_servo.rs index 5e8714178..4e94103f1 100644 --- a/examples/rp23/src/bin/pio_servo.rs +++ b/examples/rp23/src/bin/pio_servo.rs @@ -5,29 +5,18 @@ use core::time::Duration; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical @@ -37,88 +26,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub 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 -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> 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" - ); - - pio.load_program(&prg.program); - 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(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - 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 - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -126,7 +35,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -168,7 +77,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -205,7 +114,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp23/src/bin/pio_stepper.rs b/examples/rp23/src/bin/pio_stepper.rs index 24785443b..4fabe78ca 100644 --- a/examples/rp23/src/bin/pio_stepper.rs +++ b/examples/rp23/src/bin/pio_stepper.rs @@ -3,158 +3,25 @@ #![no_std] #![no_main] -use core::mem::{self, MaybeUninit}; use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -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> { - 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, - ) -> 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 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(&pio.load_program(&prg.program), &[]); - 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 = (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: MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { f: MaybeUninit::new(f) } - } - - pub fn defuse(self) { - mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -162,14 +29,18 @@ async fn main(_spawner: Spawner) { mut common, irq0, sm0, .. } = Pio::new(p.PIO0, Irqs); - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); stepper.set_frequency(120); loop { info!("CW full steps"); stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/rp23/src/bin/pio_uart.rs b/examples/rp23/src/bin/pio_uart.rs new file mode 100644 index 000000000..f8398c22a --- /dev/null +++ b/examples/rp23/src/bin/pio_uart.rs @@ -0,0 +1,202 @@ +//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART. +//! The PIO module is a very powerful peripheral that can be used to implement many different +//! protocols. It is a very flexible state machine that can be programmed to do almost anything. +//! +//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the +//! PIO module to implement a UART that is connected to the USB serial port. This allows you to +//! communicate with a device connected to the RP2040 over USB serial. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use defmt::{info, panic, trace}; +use embassy_executor::Spawner; +use embassy_futures::join::{join, join3}; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{PIO0, USB}; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("PIO UART example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // PIO UART setup + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); + + // Pipe setup + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); + + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); + + let (mut usb_tx, mut usb_rx) = class.split(); + + // Read + write from USB + let usb_future = async { + loop { + info!("Wait for USB connection"); + usb_rx.wait_connection().await; + info!("Connected"); + let _ = join( + usb_read(&mut usb_rx, &mut uart_pipe_writer), + usb_write(&mut usb_tx, &mut usb_pipe_reader), + ) + .await; + info!("Disconnected"); + } + }; + + // Read + write from UART + let uart_future = join( + uart_read(&mut uart_rx, &mut usb_pipe_writer), + uart_write(&mut uart_tx, &mut uart_pipe_reader), + ); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(usb_fut, usb_future, uart_future).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Read from the USB and write it to the UART TX pipe +async fn usb_read<'d, T: Instance + 'd>( + usb_rx: &mut Receiver<'d, Driver<'d, T>>, + uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = usb_rx.read_packet(&mut buf).await?; + let data = &buf[..n]; + trace!("USB IN: {:x}", data); + (*uart_pipe_writer).write(data).await; + } +} + +/// Read from the USB TX pipe and write it to the USB +async fn usb_write<'d, T: Instance + 'd>( + usb_tx: &mut Sender<'d, Driver<'d, T>>, + usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = (*usb_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("USB OUT: {:x}", data); + usb_tx.write_packet(&data).await?; + } +} + +/// Read from the UART and write it to the USB TX pipe +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, + usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = uart_rx.read(&mut buf).await.expect("UART read error"); + if n == 0 { + continue; + } + let data = &buf[..n]; + trace!("UART IN: {:x}", buf); + (*usb_pipe_writer).write(data).await; + } +} + +/// Read from the UART TX pipe and write it to the UART +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, + uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = (*uart_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("UART OUT: {:x}", data); + let _ = uart_tx.write(&data).await; + } +} diff --git a/examples/rp23/src/bin/pio_ws2812.rs b/examples/rp23/src/bin/pio_ws2812.rs index 00fe5e396..4d258234e 100644 --- a/examples/rp23/src/bin/pio_ws2812.rs +++ b/examples/rp23/src/bin/pio_ws2812.rs @@ -6,16 +6,12 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; @@ -23,110 +19,10 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct Ws2812<'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> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - 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; - - 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 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(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - 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, - } - } - - 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; - } -} - /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { @@ -157,7 +53,8 @@ async fn main(_spawner: Spawner) { // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); // Loop forever making RGB values and pushing them out to the WS2812. let mut ticker = Ticker::every(Duration::from_millis(10)); diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index bfc2c6f67..5a4457158 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -1,6 +1,8 @@ -//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. +//! This example shows how to use PWM (Pulse Width Modulation) in the RP235x chip. //! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle #![no_std] #![no_main] @@ -8,7 +10,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::block::ImageDef; -use embassy_rp::pwm::{Config, Pwm}; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -16,24 +19,23 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} - let mut c: Config = Default::default(); - c.top = 0x8000; +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); loop { info!("current LED duty cycle: {}/32768", c.compare_b); @@ -42,3 +44,37 @@ async fn main(_spawner: Spawner) { pwm.set_config(&c); } } + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let mut c = Config::default(); + let pwm_freq = 25_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + c.top = (clock_freq / pwm_freq) as u16 - 1; + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/pwm_input.rs b/examples/rp23/src/bin/pwm_input.rs index b65f2778b..ef87fe8b5 100644 --- a/examples/rp23/src/bin/pwm_input.rs +++ b/examples/rp23/src/bin/pwm_input.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs new file mode 100644 index 000000000..3fad2928c --- /dev/null +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -0,0 +1,107 @@ +//! # PWM TB6612FNG motor driver +//! +//! This example shows the use of a TB6612FNG motor driver. The driver is built on top of embedded_hal and the example demonstrates how embassy_rp can be used to interact with ist. + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::config::Config; +use embassy_rp::gpio::Output; +use embassy_rp::{gpio, peripherals, pwm}; +use embassy_time::{Duration, Timer}; +use tb6612fng::{DriveCommand, Motor, Tb6612fng}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +assign_resources! { + motor: MotorResources { + standby_pin: PIN_22, + left_slice: PWM_SLICE6, + left_pwm_pin: PIN_28, + left_forward_pin: PIN_21, + left_backward_pin: PIN_20, + right_slice: PWM_SLICE5, + right_pwm_pin: PIN_27, + right_forward_pin: PIN_19, + right_backward_pin: PIN_18, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Config::default()); + let s = split_resources!(p); + let r = s.motor; + + // we want a PWM frequency of 1KHz, especially cheaper motors do not respond well to higher frequencies + let pwm_freq = 1_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + let period = (clock_freq / pwm_freq) as u16 - 1; + + // we need a standby output and two motors to construct a full TB6612FNG + + // standby pin + let stby = Output::new(r.standby_pin, gpio::Level::Low); + + // motor A, here defined to be the left motor + let left_fwd = gpio::Output::new(r.left_forward_pin, gpio::Level::Low); + let left_bckw = gpio::Output::new(r.left_backward_pin, gpio::Level::Low); + let mut left_speed = pwm::Config::default(); + left_speed.top = period; + let left_pwm = pwm::Pwm::new_output_a(r.left_slice, r.left_pwm_pin, left_speed); + let left_motor = Motor::new(left_fwd, left_bckw, left_pwm).unwrap(); + + // motor B, here defined to be the right motor + let right_fwd = gpio::Output::new(r.right_forward_pin, gpio::Level::Low); + let right_bckw = gpio::Output::new(r.right_backward_pin, gpio::Level::Low); + let mut right_speed = pwm::Config::default(); + right_speed.top = period; + let right_pwm = pwm::Pwm::new_output_b(r.right_slice, r.right_pwm_pin, right_speed); + let right_motor = Motor::new(right_fwd, right_bckw, right_pwm).unwrap(); + + // construct the motor driver + let mut control = Tb6612fng::new(left_motor, right_motor, stby).unwrap(); + + loop { + // wake up the motor driver + info!("end standby"); + control.disable_standby().unwrap(); + Timer::after(Duration::from_millis(100)).await; + + // drive a straight line forward at 20% speed for 5s + info!("drive straight"); + control.motor_a.drive(DriveCommand::Forward(80)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(80)).unwrap(); + Timer::after(Duration::from_secs(5)).await; + + // coast for 2s + info!("coast"); + control.motor_a.drive(DriveCommand::Stop).unwrap(); + control.motor_b.drive(DriveCommand::Stop).unwrap(); + Timer::after(Duration::from_secs(2)).await; + + // actively brake + info!("brake"); + control.motor_a.drive(DriveCommand::Brake).unwrap(); + control.motor_b.drive(DriveCommand::Brake).unwrap(); + Timer::after(Duration::from_secs(1)).await; + + // slowly turn for 3s + info!("turn"); + control.motor_a.drive(DriveCommand::Backward(50)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(50)).unwrap(); + Timer::after(Duration::from_secs(3)).await; + + // and put the driver in standby mode and wait for 5s + info!("standby"); + control.enable_standby().unwrap(); + Timer::after(Duration::from_secs(5)).await; + } +} diff --git a/examples/rp23/src/bin/rosc.rs b/examples/rp23/src/bin/rosc.rs index f65b236b1..a096f0b7a 100644 --- a/examples/rp23/src/bin/rosc.rs +++ b/examples/rp23/src/bin/rosc.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = embassy_rp::config::Config::default(); diff --git a/examples/rp23/src/bin/shared_bus.rs b/examples/rp23/src/bin/shared_bus.rs index b3fde13e3..2151ccb56 100644 --- a/examples/rp23/src/bin/shared_bus.rs +++ b/examples/rp23/src/bin/shared_bus.rs @@ -23,16 +23,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type Spi1Bus = Mutex>; type I2c1Bus = Mutex>; diff --git a/examples/rp23/src/bin/sharing.rs b/examples/rp23/src/bin/sharing.rs index 4a3301cfd..68eb5d133 100644 --- a/examples/rp23/src/bin/sharing.rs +++ b/examples/rp23/src/bin/sharing.rs @@ -36,16 +36,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type UartAsyncMutex = mutex::Mutex>; struct MyType { diff --git a/examples/rp23/src/bin/spi.rs b/examples/rp23/src/bin/spi.rs index 924873e60..aacb8c7db 100644 --- a/examples/rp23/src/bin/spi.rs +++ b/examples/rp23/src/bin/spi.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/spi_async.rs b/examples/rp23/src/bin/spi_async.rs index 4a74f991c..ac7f02fa8 100644 --- a/examples/rp23/src/bin/spi_async.rs +++ b/examples/rp23/src/bin/spi_async.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/spi_display.rs b/examples/rp23/src/bin/spi_display.rs index 71dd84658..195db5a97 100644 --- a/examples/rp23/src/bin/spi_display.rs +++ b/examples/rp23/src/bin/spi_display.rs @@ -32,16 +32,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - use crate::my_display_interface::SPIDeviceInterface; use crate::touch::Touch; diff --git a/examples/rp23/src/bin/spi_sdmmc.rs b/examples/rp23/src/bin/spi_sdmmc.rs index dabf41ab8..aa6b44ffa 100644 --- a/examples/rp23/src/bin/spi_sdmmc.rs +++ b/examples/rp23/src/bin/spi_sdmmc.rs @@ -21,16 +21,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - struct DummyTimesource(); impl embedded_sdmmc::TimeSource for DummyTimesource { diff --git a/examples/rp23/src/bin/trng.rs b/examples/rp23/src/bin/trng.rs index e146baa2e..8251ebd8b 100644 --- a/examples/rp23/src/bin/trng.rs +++ b/examples/rp23/src/bin/trng.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { TRNG_IRQ => embassy_rp::trng::InterruptHandler; }); diff --git a/examples/rp23/src/bin/uart.rs b/examples/rp23/src/bin/uart.rs index 0ffe0b293..fe28bb046 100644 --- a/examples/rp23/src/bin/uart.rs +++ b/examples/rp23/src/bin/uart.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/uart_buffered_split.rs b/examples/rp23/src/bin/uart_buffered_split.rs index 4e69a20c4..9ed130727 100644 --- a/examples/rp23/src/bin/uart_buffered_split.rs +++ b/examples/rp23/src/bin/uart_buffered_split.rs @@ -22,16 +22,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); diff --git a/examples/rp23/src/bin/uart_r503.rs b/examples/rp23/src/bin/uart_r503.rs index 5ac8839e3..9aed42785 100644 --- a/examples/rp23/src/bin/uart_r503.rs +++ b/examples/rp23/src/bin/uart_r503.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(pub struct Irqs { UART0_IRQ => UARTInterruptHandler; }); diff --git a/examples/rp23/src/bin/uart_unidir.rs b/examples/rp23/src/bin/uart_unidir.rs index 988e44a79..12214c4c2 100644 --- a/examples/rp23/src/bin/uart_unidir.rs +++ b/examples/rp23/src/bin/uart_unidir.rs @@ -21,16 +21,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART1_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/usb_webusb.rs b/examples/rp23/src/bin/usb_webusb.rs index 3ade2226b..15279cabc 100644 --- a/examples/rp23/src/bin/usb_webusb.rs +++ b/examples/rp23/src/bin/usb_webusb.rs @@ -34,16 +34,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/watchdog.rs b/examples/rp23/src/bin/watchdog.rs index a901c1164..efc24c4e3 100644 --- a/examples/rp23/src/bin/watchdog.rs +++ b/examples/rp23/src/bin/watchdog.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/zerocopy.rs b/examples/rp23/src/bin/zerocopy.rs index 86fca6f12..d317c4b56 100644 --- a/examples/rp23/src/bin/zerocopy.rs +++ b/examples/rp23/src/bin/zerocopy.rs @@ -23,16 +23,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type SampleBuffer = [u16; 512]; bind_interrupts!(struct Irqs { diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 87491b1d2..27d0062fb 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs index 7d0f1327f..ea3fbebef 100644 --- a/examples/std/src/bin/net_ppp.rs +++ b/examples/std/src/bin/net_ppp.rs @@ -16,7 +16,7 @@ use async_io::Async; use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, ConfigV4, Ipv4Cidr, Stack, StackResources}; use embassy_net_ppp::Runner; use embedded_io_async::Write; use futures::io::BufReader; @@ -60,10 +60,10 @@ async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: Seri }; let mut dns_servers = Vec::new(); for s in ipv4.dns_servers.iter().flatten() { - let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + let _ = dns_servers.push(*s); } let config = ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + address: Ipv4Cidr::new(addr, 0), gateway: None, dns_servers, }); diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml index 9102467eb..46a25cc4d 100644 --- a/examples/stm32c0/Cargo.toml +++ b/examples/stm32c0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32c031c6 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 724efdaff..93d9ab7ce 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -13,7 +13,7 @@ defmt = "0.3" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 0084651a3..a75d0fe4c 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f103c8 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index 60eb0eb93..166729211 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f207zg to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 7fda410d9..e60ea80c6 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f303ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index 1cc0a97da..4ba993009 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index b85361596..975e47398 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f429zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs index 27b165f1b..68392847b 100644 --- a/examples/stm32f4/src/bin/i2s_dma.rs +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -1,13 +1,10 @@ #![no_std] #![no_main] -use core::fmt::Write; - use defmt::*; use embassy_executor::Spawner; use embassy_stm32::i2s::{Config, I2S}; use embassy_stm32::time::Hertz; -use heapless::String; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,6 +12,8 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); + let mut dma_buffer = [0x00_u16; 128]; + let mut i2s = I2S::new_txonly( p.SPI2, p.PC3, // sd @@ -22,13 +21,13 @@ async fn main(_spawner: Spawner) { p.PB10, // ck p.PC6, // mck p.DMA1_CH4, + &mut dma_buffer, Hertz(1_000_000), Config::default(), ); - for n in 0u32.. { - let mut write: String<128> = String::new(); - core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); - i2s.write(&mut write.as_bytes()).await.ok(); + for i in 0_u16.. { + i2s.write(&mut [i * 2; 64]).await.ok(); + i2s.write(&mut [i * 2 + 1; 64]).await.ok(); } } diff --git a/examples/stm32f4/src/bin/pwm.rs b/examples/stm32f4/src/bin/pwm.rs index 8844a9f0e..04811162b 100644 --- a/examples/stm32f4/src/bin/pwm.rs +++ b/examples/stm32f4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index cbaff75fc..3ab93d6e0 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { // construct ws2812 non-return-to-zero (NRZ) code bit by bit // ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low - let max_duty = ws2812_pwm.get_max_duty() as u16; + let max_duty = ws2812_pwm.max_duty_cycle(); let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing let n1 = 2 * n0; // ws2812 Bit 1 high level timing @@ -84,7 +84,7 @@ async fn main(_spawner: Spawner) { let pwm_channel = Channel::Ch1; // make sure PWM output keep low on first start - ws2812_pwm.set_duty(pwm_channel, 0); + ws2812_pwm.channel(pwm_channel).set_duty_cycle(0); // flip color at 2 Hz let mut ticker = Ticker::every(Duration::from_millis(500)); diff --git a/examples/stm32f469/Cargo.toml b/examples/stm32f469/Cargo.toml index 6a5bd0b29..4e1a778cd 100644 --- a/examples/stm32f469/Cargo.toml +++ b/examples/stm32f469/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Specific examples only for stm32f469 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 8c591ebd2..879b05d91 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f777zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embedded-io-async = { version = "0.6.1" } diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index a50074ce0..77f70e57d 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32g0b1re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32g0/src/bin/input_capture.rs b/examples/stm32g0/src/bin/input_capture.rs index 69fdae96d..bc814cb13 100644 --- a/examples/stm32g0/src/bin/input_capture.rs +++ b/examples/stm32g0/src/bin/input_capture.rs @@ -47,10 +47,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PB1 and PA8 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - pwm.enable(Channel::Ch1); - pwm.set_duty(Channel::Ch1, 50); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().enable(); + pwm.ch1().set_duty_cycle(50); let ch1 = CapturePin::new_ch1(p.PA0, Pull::None); let mut ic = InputCapture::new(p.TIM2, Some(ch1), None, None, None, Irqs, khz(1000), Default::default()); diff --git a/examples/stm32g0/src/bin/pwm_input.rs b/examples/stm32g0/src/bin/pwm_input.rs index 152ecda86..db9cf4f8a 100644 --- a/examples/stm32g0/src/bin/pwm_input.rs +++ b/examples/stm32g0/src/bin/pwm_input.rs @@ -14,7 +14,6 @@ use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::pwm_input::PwmInput; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_stm32::{bind_interrupts, peripherals, timer}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -43,11 +42,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PA8 and PA6 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - let max = pwm.get_max_duty(); - pwm.set_duty(Channel::Ch1, max / 4); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().set_duty_cycle_fraction(1, 4); + pwm.ch1().enable(); let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000)); pwm_input.enable(); diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 2768147a1..86d29f94e 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32g491re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index d4809a481..6c965012c 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PC0, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 30b1d2be9..9ca8bc969 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32h563zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -23,7 +23,7 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-io-async = { version = "0.6.1" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } rand_core = "0.6.3" diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 13fce7dc7..f2f207395 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 24983ca85..a1558b079 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; @@ -12,7 +14,7 @@ use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; +use embedded_nal_async::TcpConnect; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs index 768d85993..a352ef444 100644 --- a/examples/stm32h7/src/bin/eth_client_mii.rs +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; @@ -12,7 +14,7 @@ use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; +use embedded_nal_async::TcpConnect; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/stm32h7/src/bin/i2c_shared.rs b/examples/stm32h7/src/bin/i2c_shared.rs index 6f4815582..136b91eeb 100644 --- a/examples/stm32h7/src/bin/i2c_shared.rs +++ b/examples/stm32h7/src/bin/i2c_shared.rs @@ -10,8 +10,10 @@ use embassy_stm32::i2c::{self, I2c}; use embassy_stm32::mode::Async; use embassy_stm32::time::Hertz; use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::NoopMutex; use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c as _; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -31,7 +33,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn temperature(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 2]; loop { @@ -48,7 +50,7 @@ async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { } #[embassy_executor::task] -async fn humidity(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn humidity(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 6]; loop { diff --git a/examples/stm32h7/src/bin/pwm.rs b/examples/stm32h7/src/bin/pwm.rs index 1e48ba67b..a1c53fc3f 100644 --- a/examples/stm32h7/src/bin/pwm.rs +++ b/examples/stm32h7/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -37,22 +36,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index f6735e235..04d14bd6b 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -81,8 +81,9 @@ async fn main(_spawner: Spawner) { rx_config.sync_output = false; let tx_buffer: &mut [u32] = unsafe { - TX_BUFFER.initialize_all_copied(0); - let (ptr, len) = TX_BUFFER.get_ptr_len(); + let buf = &mut *core::ptr::addr_of_mut!(TX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; @@ -98,8 +99,9 @@ async fn main(_spawner: Spawner) { ); let rx_buffer: &mut [u32] = unsafe { - RX_BUFFER.initialize_all_copied(0); - let (ptr, len) = RX_BUFFER.get_ptr_len(); + let buf = &mut *core::ptr::addr_of_mut!(RX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index 43fb6b41c..9166fe9b6 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -22,10 +22,11 @@ static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] async fn main_task(mut spi: spi::Spi<'static, Async>) { let (read_buffer, write_buffer) = unsafe { - RAM_D3.initialize_all_copied(0); + let ram = &mut *core::ptr::addr_of_mut!(RAM_D3); + ram.initialize_all_copied(0); ( - RAM_D3.get_subslice_mut_unchecked(0, 128), - RAM_D3.get_subslice_mut_unchecked(128, 128), + ram.get_subslice_mut_unchecked(0, 128), + ram.get_subslice_mut_unchecked(128, 128), ) }; diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml index 93e9575b6..bac76ddfb 100644 --- a/examples/stm32h735/Cargo.toml +++ b/examples/stm32h735/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32h755cm4/Cargo.toml b/examples/stm32h755cm4/Cargo.toml index 7a42fbdaa..1fbceddde 100644 --- a/examples/stm32h755cm4/Cargo.toml +++ b/examples/stm32h755cm4/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h755cm7/Cargo.toml b/examples/stm32h755cm7/Cargo.toml index 4f0f69c3f..8b864169b 100644 --- a/examples/stm32h755cm7/Cargo.toml +++ b/examples/stm32h755cm7/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h7b0/.cargo/config.toml b/examples/stm32h7b0/.cargo/config.toml new file mode 100644 index 000000000..870849a27 --- /dev/null +++ b/examples/stm32h7b0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7B0VBTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h7b0/Cargo.toml b/examples/stm32h7b0/Cargo.toml new file mode 100644 index 000000000..02c620443 --- /dev/null +++ b/examples/stm32h7b0/Cargo.toml @@ -0,0 +1,74 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h7b0/build.rs b/examples/stm32h7b0/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h7b0/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h7b0/memory.x b/examples/stm32h7b0/memory.x new file mode 100644 index 000000000..6eb1bb7c1 --- /dev/null +++ b/examples/stm32h7b0/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ +} \ No newline at end of file diff --git a/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs new file mode 100644 index 000000000..9c397e507 --- /dev/null +++ b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -0,0 +1,433 @@ +#![no_main] +#![no_std] + +// Tested on weact stm32h7b0 board + w25q64 spi flash + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::ospi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, Ospi, + OspiWidth, TransferConfig, WrapSize, +}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // RCC config + let mut config = Config::default(); + info!("START"); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + // Needed for USB + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + // External oscillator 25MHZ + config.rcc.hse = Some(Hse { + freq: Hertz(25_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, + mul: PllMul::MUL112, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let qspi_config = embassy_stm32::ospi::Config { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::_8MiB, + chip_select_high_time: ChipSelectHighTime::_1Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 4, + sample_shifting: true, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + }; + let ospi = embassy_stm32::ospi::Ospi::new_blocking_quadspi( + p.OCTOSPI1, + p.PB2, + p.PD11, + p.PD12, + p.PE2, + p.PD13, + p.PB6, + qspi_config, + ); + + let mut flash = FlashMemory::new(ospi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0x90000000 as *const u32) }; + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0x90000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + flash.disable_mm().await; + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + ospi: Ospi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(ospi: Ospi<'static, I, Blocking>) -> Self { + let mut memory = Self { ospi }; + + memory.reset_memory().await; + memory.enable_quad(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + dwidth: OspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.ospi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: OspiWidth::QUAD, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::QUAD, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x0B), // Fast read in QPI mode + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x32), // Write config + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.command(&transaction).await.unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.ospi.command(&transaction).await.unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_4(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_READ as u32), + address: Some(addr), + dummy: DummyCycles::_8, + ..Default::default() + }; + if use_dma { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.ospi.command(&transaction).await.unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: OspiWidth::SING, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} diff --git a/examples/stm32h7rs/Cargo.toml b/examples/stm32h7rs/Cargo.toml index f97dfd722..29881bf8b 100644 --- a/examples/stm32h7rs/Cargo.toml +++ b/examples/stm32h7rs/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32h743bi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } @@ -22,7 +22,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 2577f19e0..273cdce1d 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l072cz to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 062044f32..af4775a74 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index c5478b17b..70c5c5e26 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l4s5vi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index be4270ada..4a7c01f9f 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -51,7 +51,7 @@ bind_interrupts!(struct Irqs { // MAC-address used by the adin1110 const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; // Static IP settings -const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); +const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 5), 24); // Listen port for the webserver const HTTP_LISTEN_PORT: u16 = 80; diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 16c184de2..78cb79eac 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l552ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml index 2e890cdb5..931409b51 100644 --- a/examples/stm32u0/Cargo.toml +++ b/examples/stm32u0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32u083rc to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32u5/.cargo/config.toml b/examples/stm32u5/.cargo/config.toml index 36c5b63a6..bdbd86354 100644 --- a/examples/stm32u5/.cargo/config.toml +++ b/examples/stm32u5/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32U585AIIx with your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32U585AIIx" +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U5G9ZJTxQ" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 20d64c6f7..8b576425c 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -5,10 +5,10 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -# Change stm32u585ai to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } @@ -21,6 +21,8 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } micromath = "2.0.0" diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5/src/bin/ferris.bmp new file mode 100644 index 000000000..7a222ab84 Binary files /dev/null and b/examples/stm32u5/src/bin/ferris.bmp differ diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs index 19a78eac9..d5f5d6f60 100644 --- a/examples/stm32u5/src/bin/i2c.rs +++ b/examples/stm32u5/src/bin/i2c.rs @@ -13,7 +13,7 @@ const WHOAMI: u8 = 0x0F; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut i2c = I2c::new_blocking(p.I2C2, p.PH4, p.PH5, Hertz(100_000), Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Hertz(100_000), Default::default()); let mut data = [0u8; 1]; unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 000000000..bd59a9148 --- /dev/null +++ b/examples/stm32u5/src/bin/ltdc.rs @@ -0,0 +1,461 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example was derived from examples\stm32h735\src\bin\ltdc.rs +/// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use embedded_graphics::Pixel; +use heapless::{Entry, FnvIndexMap}; +use tinybmp::Bmp; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_WIDTH: usize = 800; +const DISPLAY_HEIGHT: usize = 480; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // enable ICACHE + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // blink the led on another task + let led = Output::new(p.PD2, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_de.set_low(); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.init_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = heapless::FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::time::Hertz; + use embassy_stm32::{rcc, Config, Peripherals}; + + /// Sets up clocks for the stm32u5g9zj mcu + /// change this if you plan to use a different microcontroller + pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, // PLL_M + mul: rcc::PllMul::MUL125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 4d56395da..4bb1a6079 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -13,7 +13,7 @@ use embassy_usb::Builder; use panic_probe as _; bind_interrupts!(struct Irqs { - OTG_FS => usb::InterruptHandler; + OTG_HS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) { // to enable vbus_detection to comply with the USB spec. If you enable it, the board // has to support it or USB won't work at all. See docs on `vbus_detection` for details. config.vbus_detection = false; - let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); // Create embassy-usb Config let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 1e1a0efe2..8cea2ace8 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } diff --git a/examples/stm32wba/Cargo.toml b/examples/stm32wba/Cargo.toml index 401281c0b..4261564dd 100644 --- a/examples/stm32wba/Cargo.toml +++ b/examples/stm32wba/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 46af5218c..f8d5afb86 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32wl55jc-cm4 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 75de079b7..f8db71233 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } wasm-logger = "0.2.0" diff --git a/rust-toolchain-nightly.toml b/rust-toolchain-nightly.toml index 0b10d7194..acab9d615 100644 --- a/rust-toolchain-nightly.toml +++ b/rust-toolchain-nightly.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-09-06" +channel = "nightly-2024-10-13" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = [ "thumbv7em-none-eabi", diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 80acb3b5e..69a964040 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.81" +channel = "1.82" components = [ "rust-src", "rustfmt", "llvm-tools" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index f666634d5..d36ab9c67 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -9,7 +9,7 @@ teleprobe-meta = "1" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt", ] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index e8fca452e..45daaae0c 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); spam_ppi.enable(); - let p = unsafe { TX_BUF.as_mut_ptr() }; + let p = (&raw mut TX_BUF) as *mut u8; spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml index ae2b0e180..0ee7f5a00 100644 --- a/tests/riscv32/Cargo.toml +++ b/tests/riscv32/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] critical-section = { version = "1.1.1", features = ["restore-state-bool"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../embassy-time" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index de7bb0e56..4337924cc 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -11,7 +11,6 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 12f1ec3ce..ce812af78 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" teleprobe-meta = "1.1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 8752da59b..d94342598 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -9,7 +9,6 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 2eac636e5..468d2ed54 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -60,7 +60,7 @@ cm0 = ["portable-atomic/unsafe-assume-single-core"] teleprobe-meta = "1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 53da30fff..2f601ad0e 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -33,6 +33,13 @@ async fn main(_spawner: Spawner) { let mut buf = [0; 2]; usart.blocking_read(&mut buf).unwrap(); assert_eq!(buf, data); + + // Test flush doesn't hang. + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // Test flush doesn't hang if there's nothing to flush + usart.blocking_flush().unwrap(); } // Test error handling with with an overflow error diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index 266b81809..a34498376 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -51,6 +51,23 @@ async fn main(_spawner: Spawner) { assert_eq!(tx_buf, rx_buf); } + // Test flush doesn't hang. Check multiple combinations of async+blocking. + tx.write(&tx_buf).await.unwrap(); + tx.flush().await.unwrap(); + tx.flush().await.unwrap(); + + tx.write(&tx_buf).await.unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.flush().await.unwrap(); + tx.blocking_flush().unwrap(); + info!("Test OK"); cortex_m::asm::bkpt(); } diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs index 98c7ef312..83c0887ac 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -43,8 +43,7 @@ async fn main(spawner: Spawner) { let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config).unwrap(); let (tx, rx) = usart.split(); static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; - let dma_buf = unsafe { DMA_BUF.as_mut() }; - let rx = rx.into_ring_buffered(dma_buf); + let rx = rx.into_ring_buffered(unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUF) }); info!("Spawning tasks"); spawner.spawn(transmit_task(tx)).unwrap();