mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-25 00:02:28 +00:00
Merge branch 'embassy-rs:main' into main
This commit is contained in:
commit
7350382e70
3
.github/ci/test-nightly.sh
vendored
3
.github/ci/test-nightly.sh
vendored
@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
||||
|
||||
cargo test --manifest-path ./embassy-executor/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
||||
|
||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
|
||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml
|
||||
|
1
.github/ci/test.sh
vendored
1
.github/ci/test.sh
vendored
@ -12,6 +12,7 @@ export CARGO_TARGET_DIR=/ci/cache/target
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
|
||||
cargo test --manifest-path ./embassy-executor/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-futures/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||
|
9
ci.sh
9
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
|
||||
|
@ -9,7 +9,8 @@
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
mod bluetooth;
|
||||
/// Bluetooth module.
|
||||
pub mod bluetooth;
|
||||
mod bus;
|
||||
mod consts;
|
||||
mod control;
|
||||
|
@ -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; }
|
||||
}
|
||||
----
|
||||
|
@ -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]
|
||||
----
|
||||
|
@ -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.
|
||||
|
||||
|
@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
/// Firmware updater flash configuration holding the two flashes used by the updater
|
||||
///
|
||||
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
|
||||
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
|
||||
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
|
||||
/// the provided flash according to symbols defined in the linkerfile.
|
||||
pub struct FirmwareUpdaterConfig<DFU, STATE> {
|
||||
/// The dfu flash partition
|
||||
|
@ -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"
|
||||
|
@ -1,28 +1,11 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
extern crate proc_macro;
|
||||
|
||||
use darling::ast::NestedMeta;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod macros;
|
||||
mod util;
|
||||
use macros::*;
|
||||
use syn::parse::{Parse, ParseBuffer};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::Token;
|
||||
|
||||
struct Args {
|
||||
meta: Vec<NestedMeta>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
|
||||
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
|
||||
Ok(Args {
|
||||
meta: meta.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
|
||||
/// many concurrent tasks can be spawned (default is 1) for the function.
|
||||
@ -56,17 +39,12 @@ impl Parse for Args {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
|
||||
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
|
||||
task::run(args.into(), item.into()).into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into()
|
||||
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
|
||||
@ -89,9 +67,32 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
|
||||
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
|
||||
/// the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// A user-defined entry macro must provided via the `entry` argument
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main(entry = "qingke_rt::entry")]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
|
||||
@ -124,11 +125,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
main::run(&args.meta, f, main::riscv(&args.meta))
|
||||
.unwrap_or_else(|x| x)
|
||||
.into()
|
||||
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
|
||||
@ -151,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
|
||||
main::run(args.into(), item.into(), &main::ARCH_STD).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
|
||||
@ -176,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
|
||||
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
|
||||
}
|
||||
|
@ -1,125 +1,107 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use darling::export::NestedMeta;
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Expr, ReturnType, Type};
|
||||
use syn::{ReturnType, Type};
|
||||
|
||||
use crate::util::ctxt::Ctxt;
|
||||
use crate::util::*;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
enum Flavor {
|
||||
Standard,
|
||||
Wasm,
|
||||
}
|
||||
|
||||
pub(crate) struct Arch {
|
||||
default_entry: Option<&'static str>,
|
||||
flavor: Flavor,
|
||||
}
|
||||
|
||||
pub static ARCH_AVR: Arch = Arch {
|
||||
default_entry: Some("avr_device::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_RISCV: Arch = Arch {
|
||||
default_entry: Some("riscv_rt::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_CORTEX_M: Arch = Arch {
|
||||
default_entry: Some("cortex_m_rt::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_SPIN: Arch = Arch {
|
||||
default_entry: None,
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_STD: Arch = Arch {
|
||||
default_entry: None,
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_WASM: Arch = Arch {
|
||||
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
|
||||
flavor: Flavor::Wasm,
|
||||
};
|
||||
|
||||
#[derive(Debug, FromMeta, Default)]
|
||||
struct Args {
|
||||
#[darling(default)]
|
||||
entry: Option<String>,
|
||||
}
|
||||
|
||||
pub fn avr() -> TokenStream {
|
||||
quote! {
|
||||
#[avr_device::entry]
|
||||
fn main() -> ! {
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
|
||||
let mut errors = TokenStream::new();
|
||||
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn riscv(args: &[NestedMeta]) -> TokenStream {
|
||||
let maybe_entry = match Args::from_list(args) {
|
||||
Ok(args) => args.entry,
|
||||
Err(e) => return e.write_errors(),
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let entry = maybe_entry.unwrap_or("riscv_rt::entry".into());
|
||||
let entry = match Expr::from_string(&entry) {
|
||||
Ok(expr) => expr,
|
||||
Err(e) => return e.write_errors(),
|
||||
let args = match NestedMeta::parse_meta_list(args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[#entry]
|
||||
fn main() -> ! {
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
let args = match Args::from_list(&args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
errors.extend(e.write_errors());
|
||||
Args::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cortex_m() -> TokenStream {
|
||||
quote! {
|
||||
#[cortex_m_rt::entry]
|
||||
fn main() -> ! {
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wasm() -> TokenStream {
|
||||
quote! {
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(start)]
|
||||
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
|
||||
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
|
||||
|
||||
executor.start(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn std() -> TokenStream {
|
||||
quote! {
|
||||
fn main() -> ! {
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
|
||||
#[allow(unused_variables)]
|
||||
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
|
||||
};
|
||||
|
||||
let fargs = f.sig.inputs.clone();
|
||||
|
||||
let ctxt = Ctxt::new();
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must be async");
|
||||
error(&mut errors, &f.sig, "main function must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
|
||||
error(&mut errors, &f.sig, "main function must not be generic");
|
||||
}
|
||||
if !f.sig.generics.where_clause.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
|
||||
error(&mut errors, &f.sig, "main function must not have `where` clauses");
|
||||
}
|
||||
if !f.sig.abi.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
|
||||
error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
|
||||
}
|
||||
if !f.sig.variadic.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
|
||||
error(&mut errors, &f.sig, "main function must not be variadic");
|
||||
}
|
||||
match &f.sig.output {
|
||||
ReturnType::Default => {}
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||
Type::Never(_) => {}
|
||||
_ => ctxt.error_spanned_by(
|
||||
_ => error(
|
||||
&mut errors,
|
||||
&f.sig,
|
||||
"main function must either not return a value, return `()` or return `!`",
|
||||
),
|
||||
@ -127,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
|
||||
}
|
||||
|
||||
if fargs.len() != 1 {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
|
||||
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
|
||||
}
|
||||
|
||||
ctxt.check()?;
|
||||
let entry = match args.entry.as_deref().or(arch.default_entry) {
|
||||
None => TokenStream::new(),
|
||||
Some(x) => match TokenStream::from_str(x) {
|
||||
Ok(x) => quote!(#[#x]),
|
||||
Err(e) => {
|
||||
error(&mut errors, &f.sig, e);
|
||||
TokenStream::new()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let f_body = f.block;
|
||||
let f_body = f.body;
|
||||
let out = &f.sig.output;
|
||||
|
||||
let (main_ret, mut main_body) = match arch.flavor {
|
||||
Flavor::Standard => (
|
||||
quote!(!),
|
||||
quote! {
|
||||
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
||||
::core::mem::transmute(t)
|
||||
}
|
||||
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
},
|
||||
),
|
||||
Flavor::Wasm => (
|
||||
quote!(Result<(), wasm_bindgen::JsValue>),
|
||||
quote! {
|
||||
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
|
||||
|
||||
executor.start(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
if !errors.is_empty() {
|
||||
main_body = quote! {loop{}};
|
||||
}
|
||||
|
||||
let result = quote! {
|
||||
#[::embassy_executor::task()]
|
||||
async fn __embassy_main(#fargs) #out {
|
||||
#f_body
|
||||
}
|
||||
|
||||
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
||||
::core::mem::transmute(t)
|
||||
#entry
|
||||
fn main() -> #main_ret {
|
||||
#main_body
|
||||
}
|
||||
|
||||
#main
|
||||
#errors
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
result
|
||||
}
|
||||
|
@ -2,47 +2,68 @@ use darling::export::NestedMeta;
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type};
|
||||
use syn::visit::{self, Visit};
|
||||
use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
|
||||
|
||||
use crate::util::ctxt::Ctxt;
|
||||
use crate::util::*;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[derive(Debug, FromMeta, Default)]
|
||||
struct Args {
|
||||
#[darling(default)]
|
||||
pool_size: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
|
||||
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
|
||||
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut errors = TokenStream::new();
|
||||
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match NestedMeta::parse_meta_list(args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match Args::from_list(&args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
errors.extend(e.write_errors());
|
||||
Args::default()
|
||||
}
|
||||
};
|
||||
|
||||
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
|
||||
attrs: vec![],
|
||||
lit: Lit::Int(LitInt::new("1", Span::call_site())),
|
||||
}));
|
||||
|
||||
let ctxt = Ctxt::new();
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "task functions must be async");
|
||||
error(&mut errors, &f.sig, "task functions must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
ctxt.error_spanned_by(&f.sig, "task functions must not be generic");
|
||||
error(&mut errors, &f.sig, "task functions must not be generic");
|
||||
}
|
||||
if !f.sig.generics.where_clause.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses");
|
||||
error(&mut errors, &f.sig, "task functions must not have `where` clauses");
|
||||
}
|
||||
if !f.sig.abi.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier");
|
||||
error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
|
||||
}
|
||||
if !f.sig.variadic.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "task functions must not be variadic");
|
||||
error(&mut errors, &f.sig, "task functions must not be variadic");
|
||||
}
|
||||
match &f.sig.output {
|
||||
ReturnType::Default => {}
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||
Type::Never(_) => {}
|
||||
_ => ctxt.error_spanned_by(
|
||||
_ => error(
|
||||
&mut errors,
|
||||
&f.sig,
|
||||
"task functions must either not return a value, return `()` or return `!`",
|
||||
),
|
||||
@ -55,26 +76,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
||||
for arg in fargs.iter_mut() {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(_) => {
|
||||
ctxt.error_spanned_by(arg, "task functions must not have receiver arguments");
|
||||
error(&mut errors, arg, "task functions must not have `self` arguments");
|
||||
}
|
||||
syn::FnArg::Typed(t) => match t.pat.as_mut() {
|
||||
syn::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()));
|
||||
}
|
||||
_ => {
|
||||
ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
|
||||
}
|
||||
},
|
||||
error(
|
||||
&mut errors,
|
||||
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,8 +117,7 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
let mut task_outer: ItemFn = parse_quote! {
|
||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
||||
let mut task_outer_body = quote! {
|
||||
trait _EmbassyInternalTaskTrait {
|
||||
type Fut: ::core::future::Future + 'static;
|
||||
fn construct(#fargs) -> Self::Fut;
|
||||
@ -108,18 +133,27 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
||||
const POOL_SIZE: usize = #pool_size;
|
||||
static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
|
||||
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let mut task_outer: ItemFn = parse_quote! {
|
||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
||||
let mut task_outer_body = quote! {
|
||||
const POOL_SIZE: usize = #pool_size;
|
||||
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
|
||||
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
|
||||
}
|
||||
};
|
||||
|
||||
task_outer.attrs.append(&mut task_inner.attrs.clone());
|
||||
let task_outer_attrs = task_inner.attrs.clone();
|
||||
|
||||
if !errors.is_empty() {
|
||||
task_outer_body = quote! {
|
||||
#![allow(unused_variables, unreachable_code)]
|
||||
let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!();
|
||||
_x
|
||||
};
|
||||
}
|
||||
|
||||
// Copy the generics + where clause to avoid more spurious errors.
|
||||
let generics = &f.sig.generics;
|
||||
let where_clause = &f.sig.generics.where_clause;
|
||||
|
||||
let result = quote! {
|
||||
// This is the user's task function, renamed.
|
||||
@ -129,8 +163,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
||||
#[doc(hidden)]
|
||||
#task_inner
|
||||
|
||||
#task_outer
|
||||
#(#task_outer_attrs)*
|
||||
#visibility fn #task_ident #generics (#fargs) -> ::embassy_executor::SpawnToken<impl Sized> #where_clause{
|
||||
#task_outer_body
|
||||
}
|
||||
|
||||
#errors
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
result
|
||||
}
|
||||
|
||||
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
|
||||
struct Visitor<'a> {
|
||||
errors: &'a mut TokenStream,
|
||||
}
|
||||
|
||||
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
|
||||
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
|
||||
// only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
|
||||
if i.lifetime.is_none() {
|
||||
error(
|
||||
self.errors,
|
||||
i.and_token,
|
||||
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||
)
|
||||
}
|
||||
visit::visit_type_reference(self, i);
|
||||
}
|
||||
|
||||
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
|
||||
if i.ident.to_string() != "static" {
|
||||
error(
|
||||
self.errors,
|
||||
i,
|
||||
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
|
||||
error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
|
||||
}
|
||||
}
|
||||
|
||||
Visit::visit_type(&mut Visitor { errors }, ty);
|
||||
}
|
||||
|
74
embassy-executor-macros/src/util.rs
Normal file
74
embassy-executor-macros/src/util.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
|
||||
|
||||
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(error.into_compile_error());
|
||||
tokens
|
||||
}
|
||||
|
||||
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
|
||||
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
|
||||
}
|
||||
|
||||
/// Function signature and body.
|
||||
///
|
||||
/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
|
||||
/// parsing it. This makes the macro not error if there's a syntax error in the body,
|
||||
/// which helps IDE autocomplete work better.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ItemFn {
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub vis: Visibility,
|
||||
pub sig: Signature,
|
||||
pub brace_token: token::Brace,
|
||||
pub body: TokenStream,
|
||||
}
|
||||
|
||||
impl Parse for ItemFn {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
while content.peek(Token![#]) && content.peek2(Token![!]) {
|
||||
let content2;
|
||||
attrs.push(Attribute {
|
||||
pound_token: content.parse()?,
|
||||
style: AttrStyle::Inner(content.parse()?),
|
||||
bracket_token: bracketed!(content2 in content),
|
||||
meta: content2.parse()?,
|
||||
});
|
||||
}
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !content.is_empty() {
|
||||
body.push(content.parse::<TokenTree>()?);
|
||||
}
|
||||
let body = body.into_iter().collect();
|
||||
|
||||
Ok(ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
brace_token,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ItemFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
|
||||
self.vis.to_tokens(tokens);
|
||||
self.sig.to_tokens(tokens);
|
||||
self.brace_token.surround(tokens, |tokens| {
|
||||
tokens.append_all(self.body.clone());
|
||||
});
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
// nifty utility borrowed from serde :)
|
||||
// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Display;
|
||||
use std::thread;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
/// A type to collect errors together and format them.
|
||||
///
|
||||
/// Dropping this object will cause a panic. It must be consumed using `check`.
|
||||
///
|
||||
/// References can be shared since this type uses run-time exclusive mut checking.
|
||||
#[derive(Default)]
|
||||
pub struct Ctxt {
|
||||
// The contents will be set to `None` during checking. This is so that checking can be
|
||||
// enforced.
|
||||
errors: RefCell<Option<Vec<syn::Error>>>,
|
||||
}
|
||||
|
||||
impl Ctxt {
|
||||
/// Create a new context object.
|
||||
///
|
||||
/// This object contains no errors, but will still trigger a panic if it is not `check`ed.
|
||||
pub fn new() -> Self {
|
||||
Ctxt {
|
||||
errors: RefCell::new(Some(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an error to the context object with a tokenenizable object.
|
||||
///
|
||||
/// The object is used for spanning in error messages.
|
||||
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
|
||||
self.errors
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
// Curb monomorphization from generating too many identical methods.
|
||||
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
|
||||
}
|
||||
|
||||
/// Add one of Syn's parse errors.
|
||||
#[allow(unused)]
|
||||
pub fn syn_error(&self, err: syn::Error) {
|
||||
self.errors.borrow_mut().as_mut().unwrap().push(err);
|
||||
}
|
||||
|
||||
/// Consume this object, producing a formatted error string if there are errors.
|
||||
pub fn check(self) -> Result<(), TokenStream> {
|
||||
let errors = self.errors.borrow_mut().take().unwrap();
|
||||
match errors.len() {
|
||||
0 => Ok(()),
|
||||
_ => Err(to_compile_errors(errors)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
|
||||
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
||||
quote!(#(#compile_errors)*)
|
||||
}
|
||||
|
||||
impl Drop for Ctxt {
|
||||
fn drop(&mut self) {
|
||||
if !thread::panicking() && self.errors.borrow().is_some() {
|
||||
panic!("forgot to check for errors");
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub mod ctxt;
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
58
embassy-executor/src/arch/spin.rs
Normal file
58
embassy-executor/src/arch/spin.rs
Normal file
@ -0,0 +1,58 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-spin`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_spin as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {}
|
||||
|
||||
/// Spin Executor
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(core::ptr::null_mut()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.poll() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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")]
|
||||
|
23
embassy-executor/tests/ui.rs
Normal file
23
embassy-executor/tests/ui.rs
Normal file
@ -0,0 +1,23 @@
|
||||
#[cfg(not(miri))]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/abi.rs");
|
||||
t.compile_fail("tests/ui/bad_return.rs");
|
||||
t.compile_fail("tests/ui/generics.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_nested.rs");
|
||||
t.compile_fail("tests/ui/impl_trait.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_static.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
|
||||
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
|
||||
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
|
||||
t.compile_fail("tests/ui/not_async.rs");
|
||||
t.compile_fail("tests/ui/self_ref.rs");
|
||||
t.compile_fail("tests/ui/self.rs");
|
||||
t.compile_fail("tests/ui/where_clause.rs");
|
||||
}
|
8
embassy-executor/tests/ui/abi.rs
Normal file
8
embassy-executor/tests/ui/abi.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async extern "C" fn task() {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/abi.stderr
Normal file
5
embassy-executor/tests/ui/abi.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: task functions must not have an ABI qualifier
|
||||
--> tests/ui/abi.rs:6:1
|
||||
|
|
||||
6 | async extern "C" fn task() {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
10
embassy-executor/tests/ui/bad_return.rs
Normal file
10
embassy-executor/tests/ui/bad_return.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: task functions must either not return a value, return `()` or return `!`
|
||||
--> tests/ui/bad_return.rs:6:1
|
||||
|
|
||||
6 | async fn task() -> u32 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
8
embassy-executor/tests/ui/generics.rs
Normal file
8
embassy-executor/tests/ui/generics.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<T: Sized>(_t: T) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/generics.stderr
Normal file
5
embassy-executor/tests/ui/generics.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/generics.rs:6:1
|
||||
|
|
||||
6 | async fn task<T: Sized>(_t: T) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized) {}
|
||||
| ^^^^^^^^^^
|
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<T>(T);
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_nested.rs:6:22
|
||||
|
|
||||
6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized + 'static) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_static.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized + 'static) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'_ u32) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
||||
|
|
||||
4 | async fn foo(_x: &'_ u32) {}
|
||||
| ^^
|
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'static &'_ u32) {}
|
||||
|
||||
fn main() {}
|
@ -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) {}
|
||||
| ^^
|
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &u32) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: &u32) {}
|
||||
| ^
|
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo<'a>(_x: &'a u32) {}
|
||||
|
||||
fn main() {}
|
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal file
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^
|
8
embassy-executor/tests/ui/nonstatic_struct_anon.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_anon.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo<'_>) {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/nonstatic_struct_anon.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_struct_anon.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_anon.rs:6:23
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ^^
|
8
embassy-executor/tests/ui/nonstatic_struct_elided.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_elided.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo) {}
|
||||
|
||||
fn main() {}
|
10
embassy-executor/tests/ui/nonstatic_struct_elided.stderr
Normal file
10
embassy-executor/tests/ui/nonstatic_struct_elided.stderr
Normal file
@ -0,0 +1,10 @@
|
||||
error[E0726]: implicit elided lifetime not allowed here
|
||||
--> tests/ui/nonstatic_struct_elided.rs:6:19
|
||||
|
|
||||
6 | async fn task(_x: Foo) {}
|
||||
| ^^^ expected lifetime parameter
|
||||
|
|
||||
help: indicate the anonymous lifetime
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ++++
|
8
embassy-executor/tests/ui/nonstatic_struct_generic.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_generic.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<'a>(_x: Foo<'a>) {}
|
||||
|
||||
fn main() {}
|
11
embassy-executor/tests/ui/nonstatic_struct_generic.stderr
Normal file
11
embassy-executor/tests/ui/nonstatic_struct_generic.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:1
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:27
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^
|
8
embassy-executor/tests/ui/not_async.rs
Normal file
8
embassy-executor/tests/ui/not_async.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
fn task() {}
|
||||
|
||||
fn main() {}
|
5
embassy-executor/tests/ui/not_async.stderr
Normal file
5
embassy-executor/tests/ui/not_async.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: task functions must be async
|
||||
--> tests/ui/not_async.rs:6:1
|
||||
|
|
||||
6 | fn task() {}
|
||||
| ^^^^^^^^^
|
8
embassy-executor/tests/ui/self.rs
Normal file
8
embassy-executor/tests/ui/self.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(self) {}
|
||||
|
||||
fn main() {}
|
13
embassy-executor/tests/ui/self.stderr
Normal file
13
embassy-executor/tests/ui/self.stderr
Normal file
@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
8
embassy-executor/tests/ui/self_ref.rs
Normal file
8
embassy-executor/tests/ui/self_ref.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(&mut self) {}
|
||||
|
||||
fn main() {}
|
13
embassy-executor/tests/ui/self_ref.stderr
Normal file
13
embassy-executor/tests/ui/self_ref.stderr
Normal file
@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
12
embassy-executor/tests/ui/where_clause.rs
Normal file
12
embassy-executor/tests/ui/where_clause.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task()
|
||||
where
|
||||
(): Sized,
|
||||
{
|
||||
}
|
||||
|
||||
fn main() {}
|
7
embassy-executor/tests/ui/where_clause.stderr
Normal file
7
embassy-executor/tests/ui/where_clause.stderr
Normal file
@ -0,0 +1,7 @@
|
||||
error: task functions must not have `where` clauses
|
||||
--> tests/ui/where_clause.rs:6:1
|
||||
|
|
||||
6 | / async fn task()
|
||||
7 | | where
|
||||
8 | | (): Sized,
|
||||
| |______________^
|
@ -188,6 +188,169 @@ where
|
||||
|
||||
// ====================================================================
|
||||
|
||||
/// Result for [`select5`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Either5<A, B, C, D, E> {
|
||||
/// First future finished first.
|
||||
First(A),
|
||||
/// Second future finished first.
|
||||
Second(B),
|
||||
/// Third future finished first.
|
||||
Third(C),
|
||||
/// Fourth future finished first.
|
||||
Fourth(D),
|
||||
/// Fifth future finished first.
|
||||
Fifth(E),
|
||||
}
|
||||
|
||||
/// Same as [`select`], but with more futures.
|
||||
pub fn select5<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E) -> Select5<A, B, C, D, E>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
C: Future,
|
||||
D: Future,
|
||||
E: Future,
|
||||
{
|
||||
Select5 { a, b, c, d, e }
|
||||
}
|
||||
|
||||
/// Future for the [`select5`] function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Select5<A, B, C, D, E> {
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
d: D,
|
||||
e: E,
|
||||
}
|
||||
|
||||
impl<A, B, C, D, E> Future for Select5<A, B, C, D, E>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
C: Future,
|
||||
D: Future,
|
||||
E: Future,
|
||||
{
|
||||
type Output = Either5<A::Output, B::Output, C::Output, D::Output, E::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let a = unsafe { Pin::new_unchecked(&mut this.a) };
|
||||
let b = unsafe { Pin::new_unchecked(&mut this.b) };
|
||||
let c = unsafe { Pin::new_unchecked(&mut this.c) };
|
||||
let d = unsafe { Pin::new_unchecked(&mut this.d) };
|
||||
let e = unsafe { Pin::new_unchecked(&mut this.e) };
|
||||
if let Poll::Ready(x) = a.poll(cx) {
|
||||
return Poll::Ready(Either5::First(x));
|
||||
}
|
||||
if let Poll::Ready(x) = b.poll(cx) {
|
||||
return Poll::Ready(Either5::Second(x));
|
||||
}
|
||||
if let Poll::Ready(x) = c.poll(cx) {
|
||||
return Poll::Ready(Either5::Third(x));
|
||||
}
|
||||
if let Poll::Ready(x) = d.poll(cx) {
|
||||
return Poll::Ready(Either5::Fourth(x));
|
||||
}
|
||||
if let Poll::Ready(x) = e.poll(cx) {
|
||||
return Poll::Ready(Either5::Fifth(x));
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
|
||||
/// Result for [`select6`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Either6<A, B, C, D, E, F> {
|
||||
/// First future finished first.
|
||||
First(A),
|
||||
/// Second future finished first.
|
||||
Second(B),
|
||||
/// Third future finished first.
|
||||
Third(C),
|
||||
/// Fourth future finished first.
|
||||
Fourth(D),
|
||||
/// Fifth future finished first.
|
||||
Fifth(E),
|
||||
/// Sixth future finished first.
|
||||
Sixth(F),
|
||||
}
|
||||
|
||||
/// Same as [`select`], but with more futures.
|
||||
pub fn select6<A, B, C, D, E, F>(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6<A, B, C, D, E, F>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
C: Future,
|
||||
D: Future,
|
||||
E: Future,
|
||||
F: Future,
|
||||
{
|
||||
Select6 { a, b, c, d, e, f }
|
||||
}
|
||||
|
||||
/// Future for the [`select6`] function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Select6<A, B, C, D, E, F> {
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
d: D,
|
||||
e: E,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<A, B, C, D, E, F> Future for Select6<A, B, C, D, E, F>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
C: Future,
|
||||
D: Future,
|
||||
E: Future,
|
||||
F: Future,
|
||||
{
|
||||
type Output = Either6<A::Output, B::Output, C::Output, D::Output, E::Output, F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let a = unsafe { Pin::new_unchecked(&mut this.a) };
|
||||
let b = unsafe { Pin::new_unchecked(&mut this.b) };
|
||||
let c = unsafe { Pin::new_unchecked(&mut this.c) };
|
||||
let d = unsafe { Pin::new_unchecked(&mut this.d) };
|
||||
let e = unsafe { Pin::new_unchecked(&mut this.e) };
|
||||
let f = unsafe { Pin::new_unchecked(&mut this.f) };
|
||||
if let Poll::Ready(x) = a.poll(cx) {
|
||||
return Poll::Ready(Either6::First(x));
|
||||
}
|
||||
if let Poll::Ready(x) = b.poll(cx) {
|
||||
return Poll::Ready(Either6::Second(x));
|
||||
}
|
||||
if let Poll::Ready(x) = c.poll(cx) {
|
||||
return Poll::Ready(Either6::Third(x));
|
||||
}
|
||||
if let Poll::Ready(x) = d.poll(cx) {
|
||||
return Poll::Ready(Either6::Fourth(x));
|
||||
}
|
||||
if let Poll::Ready(x) = e.poll(cx) {
|
||||
return Poll::Ready(Either6::Fifth(x));
|
||||
}
|
||||
if let Poll::Ready(x) = f.poll(cx) {
|
||||
return Poll::Ready(Either6::Sixth(x));
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
|
||||
/// Future for the [`select_array`] function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
|
@ -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() {
|
||||
|
@ -83,10 +83,12 @@ pub trait Driver {
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Driver> Driver for &mut T {
|
||||
type RxToken<'a> = T::RxToken<'a>
|
||||
type RxToken<'a>
|
||||
= T::RxToken<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
type TxToken<'a> = T::TxToken<'a>
|
||||
type TxToken<'a>
|
||||
= T::TxToken<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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];
|
||||
|
@ -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"
|
||||
|
@ -73,8 +73,11 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
|
||||
&self,
|
||||
host: &str,
|
||||
addr_type: embedded_nal_async::AddrType,
|
||||
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
|
||||
use embedded_nal_async::{AddrType, IpAddr};
|
||||
) -> Result<core::net::IpAddr, Self::Error> {
|
||||
use core::net::IpAddr;
|
||||
|
||||
use embedded_nal_async::AddrType;
|
||||
|
||||
let (qtype, secondary_qtype) = match addr_type {
|
||||
AddrType::IPv4 => (DnsQueryType::A, None),
|
||||
AddrType::IPv6 => (DnsQueryType::Aaaa, None),
|
||||
@ -98,20 +101,16 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
|
||||
if let Some(first) = addrs.get(0) {
|
||||
Ok(match first {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
|
||||
IpAddress::Ipv4(addr) => IpAddr::V4(*addr),
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
|
||||
IpAddress::Ipv6(addr) => IpAddr::V6(*addr),
|
||||
})
|
||||
} else {
|
||||
Err(Error::Failed)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_host_by_address(
|
||||
&self,
|
||||
_addr: embedded_nal_async::IpAddr,
|
||||
_result: &mut [u8],
|
||||
) -> Result<usize, Self::Error> {
|
||||
async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
|
||||
where
|
||||
T: Driver,
|
||||
{
|
||||
type RxToken<'a> = RxTokenAdapter<T::RxToken<'a>> where Self: 'a;
|
||||
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> where Self: 'a;
|
||||
type RxToken<'a>
|
||||
= RxTokenAdapter<T::RxToken<'a>>
|
||||
where
|
||||
Self: 'a;
|
||||
type TxToken<'a>
|
||||
= TxTokenAdapter<T::TxToken<'a>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||
self.inner
|
||||
|
@ -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<'_> {
|
||||
|
@ -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<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
|
||||
self.stack.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<tcp::Socket>(self.handle);
|
||||
let res = f(socket, &mut i.iface);
|
||||
@ -436,6 +470,17 @@ impl<'d> TcpIo<'d> {
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
self.with_mut(|s, _| {
|
||||
if s.can_recv() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
s.register_recv_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
poll_fn(move |cx| {
|
||||
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
|
||||
@ -464,6 +509,17 @@ impl<'d> TcpIo<'d> {
|
||||
.await
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
self.with_mut(|s, _| {
|
||||
if s.can_send() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
s.register_send_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| match s.send_slice(buf) {
|
||||
@ -675,10 +731,9 @@ mod embedded_io_impls {
|
||||
pub mod client {
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::mem::MaybeUninit;
|
||||
use core::net::IpAddr;
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use embedded_nal_async::IpAddr;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// TCP client connection pool compatible with `embedded-nal-async` traits.
|
||||
@ -713,19 +768,19 @@ pub mod client {
|
||||
for TcpClient<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
type Error = Error;
|
||||
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
|
||||
type Connection<'m>
|
||||
= TcpConnection<'m, N, TX_SZ, RX_SZ>
|
||||
where
|
||||
Self: 'm;
|
||||
|
||||
async fn connect<'a>(
|
||||
&'a self,
|
||||
remote: embedded_nal_async::SocketAddr,
|
||||
) -> Result<Self::Connection<'a>, Self::Error> {
|
||||
async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result<Self::Connection<'a>, Self::Error> {
|
||||
let addr: crate::IpAddress = match remote.ip() {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
|
||||
IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr),
|
||||
#[cfg(not(feature = "proto-ipv4"))]
|
||||
IpAddr::V4(_) => panic!("ipv4 support not enabled"),
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
|
||||
IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr),
|
||||
#[cfg(not(feature = "proto-ipv6"))]
|
||||
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
|
||||
};
|
||||
|
@ -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())
|
||||
|
@ -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 {}
|
||||
)*
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::marker::PhantomData;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::sync::atomic::compiler_fence;
|
||||
use core::sync::atomic::Ordering::SeqCst;
|
||||
use core::task::Poll;
|
||||
@ -13,11 +14,12 @@ use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant};
|
||||
use embedded_hal_1::i2c::Operation;
|
||||
|
||||
use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
|
||||
use crate::gpio::Pin as GpioPin;
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::util::{slice_in_ram, slice_in_ram_or};
|
||||
use crate::util::slice_in_ram;
|
||||
use crate::{gpio, interrupt, pac, Peripheral};
|
||||
|
||||
/// TWI frequency
|
||||
@ -103,6 +105,10 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
|
||||
let r = T::regs();
|
||||
let s = T::state();
|
||||
|
||||
if r.events_suspended.read().bits() != 0 {
|
||||
s.end_waker.wake();
|
||||
r.intenclr.write(|w| w.suspended().clear());
|
||||
}
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
s.end_waker.wake();
|
||||
r.intenclr.write(|w| w.stopped().clear());
|
||||
@ -182,8 +188,22 @@ impl<'d, T: Instance> Twim<'d, T> {
|
||||
}
|
||||
|
||||
/// Set TX buffer, checking that it is in RAM and has suitable length.
|
||||
unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
||||
slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
|
||||
unsafe fn set_tx_buffer(
|
||||
&mut self,
|
||||
buffer: &[u8],
|
||||
ram_buffer: Option<&mut [MaybeUninit<u8>; FORCE_COPY_BUFFER_SIZE]>,
|
||||
) -> Result<(), Error> {
|
||||
let buffer = if slice_in_ram(buffer) {
|
||||
buffer
|
||||
} else {
|
||||
let ram_buffer = ram_buffer.ok_or(Error::BufferNotInRAM)?;
|
||||
trace!("Copying TWIM tx buffer into RAM for DMA");
|
||||
let ram_buffer = &mut ram_buffer[..buffer.len()];
|
||||
// Inline implementation of the nightly API MaybeUninit::copy_from_slice(ram_buffer, buffer)
|
||||
let uninit_src: &[MaybeUninit<u8>] = unsafe { core::mem::transmute(buffer) };
|
||||
ram_buffer.copy_from_slice(uninit_src);
|
||||
unsafe { &*(ram_buffer as *const [MaybeUninit<u8>] as *const [u8]) }
|
||||
};
|
||||
|
||||
if buffer.len() > EASY_DMA_SIZE {
|
||||
return Err(Error::TxBufferTooLong);
|
||||
@ -290,7 +310,8 @@ impl<'d, T: Instance> Twim<'d, T> {
|
||||
fn blocking_wait(&mut self) {
|
||||
let r = T::regs();
|
||||
loop {
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
|
||||
r.events_suspended.reset();
|
||||
r.events_stopped.reset();
|
||||
break;
|
||||
}
|
||||
@ -307,7 +328,7 @@ impl<'d, T: Instance> Twim<'d, T> {
|
||||
let r = T::regs();
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
|
||||
r.events_stopped.reset();
|
||||
break;
|
||||
}
|
||||
@ -331,7 +352,7 @@ impl<'d, T: Instance> Twim<'d, T> {
|
||||
let s = T::state();
|
||||
|
||||
s.end_waker.register(cx.waker());
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 {
|
||||
r.events_stopped.reset();
|
||||
|
||||
return Poll::Ready(());
|
||||
@ -347,168 +368,316 @@ impl<'d, T: Instance> Twim<'d, T> {
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_write_from_ram(&mut self, address: u8, buffer: &[u8], inten: bool) -> Result<(), Error> {
|
||||
fn setup_operations(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [Operation<'_>],
|
||||
tx_ram_buffer: Option<&mut [MaybeUninit<u8>; FORCE_COPY_BUFFER_SIZE]>,
|
||||
last_op: Option<&Operation<'_>>,
|
||||
inten: bool,
|
||||
) -> Result<usize, 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_suspended.reset();
|
||||
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());
|
||||
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 operation.
|
||||
r.shorts.write(|w| w.lasttx_stop().enabled());
|
||||
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) });
|
||||
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());
|
||||
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.shorts.write(|w| w.lastrx_stop().enabled());
|
||||
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(())
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
fn setup_write_read_from_ram(
|
||||
&mut self,
|
||||
address: u8,
|
||||
wr_buffer: &[u8],
|
||||
rd_buffer: &mut [u8],
|
||||
inten: bool,
|
||||
) -> Result<(), Error> {
|
||||
let r = T::regs();
|
||||
|
||||
compiler_fence(SeqCst);
|
||||
|
||||
r.address.write(|w| unsafe { w.address().bits(address) });
|
||||
|
||||
[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)?;
|
||||
self.set_tx_buffer(wr_buffer, tx_ram_buffer)?;
|
||||
self.set_rx_buffer(rd_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 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) });
|
||||
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)
|
||||
/// 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;
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -832,47 +932,14 @@ impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> {
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> {
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, buffer)
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, buffer)
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, wr_buffer, rd_buffer)
|
||||
}
|
||||
|
||||
fn transaction(
|
||||
&mut self,
|
||||
_address: u8,
|
||||
_operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
|
||||
self.blocking_transaction(address, operations)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> {
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(address, read).await
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(address, write).await
|
||||
}
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.write_read(address, write, read).await
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
|
||||
self.transaction(address, operations).await
|
||||
}
|
||||
}
|
||||
|
||||
|
17
embassy-nxp/Cargo.toml
Normal file
17
embassy-nxp/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "embassy-nxp"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.7.7"
|
||||
cortex-m-rt = "0.7.0"
|
||||
critical-section = "1.1.2"
|
||||
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
lpc55-pac = "0.5.0"
|
||||
defmt = "0.3.8"
|
||||
|
||||
[features]
|
||||
default = ["rt"]
|
||||
rt = ["lpc55-pac/rt"]
|
361
embassy-nxp/src/gpio.rs
Normal file
361
embassy-nxp/src/gpio.rs
Normal file
@ -0,0 +1,361 @@
|
||||
use embassy_hal_internal::impl_peripheral;
|
||||
|
||||
use crate::pac_utils::*;
|
||||
use crate::{peripherals, Peripheral, PeripheralRef};
|
||||
|
||||
pub(crate) fn init() {
|
||||
// Enable clocks for GPIO, PINT, and IOCON
|
||||
syscon_reg()
|
||||
.ahbclkctrl0
|
||||
.modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable());
|
||||
}
|
||||
|
||||
/// The GPIO pin level for pins set on "Digital" mode.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum Level {
|
||||
/// Logical low. Corresponds to 0V.
|
||||
Low,
|
||||
/// Logical high. Corresponds to VDD.
|
||||
High,
|
||||
}
|
||||
|
||||
/// Pull setting for a GPIO input set on "Digital" mode.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Pull {
|
||||
/// No pull.
|
||||
None,
|
||||
/// Internal pull-up resistor.
|
||||
Up,
|
||||
/// Internal pull-down resistor.
|
||||
Down,
|
||||
}
|
||||
|
||||
/// The LPC55 boards have two GPIO banks, each with 32 pins. This enum represents the two banks.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum Bank {
|
||||
Bank0 = 0,
|
||||
Bank1 = 1,
|
||||
}
|
||||
|
||||
/// GPIO output driver. Internally, this is a specialized [Flex] pin.
|
||||
pub struct Output<'d> {
|
||||
pub(crate) pin: Flex<'d>,
|
||||
}
|
||||
|
||||
impl<'d> Output<'d> {
|
||||
/// Create GPIO output driver for a [Pin] with the provided [initial output](Level).
|
||||
#[inline]
|
||||
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level) -> Self {
|
||||
let mut pin = Flex::new(pin);
|
||||
pin.set_as_output();
|
||||
let mut result = Self { pin };
|
||||
|
||||
match initial_output {
|
||||
Level::High => result.set_high(),
|
||||
Level::Low => result.set_low(),
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn set_high(&mut self) {
|
||||
gpio_reg().set[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
|
||||
}
|
||||
|
||||
pub fn set_low(&mut self) {
|
||||
gpio_reg().clr[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self) {
|
||||
gpio_reg().not[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) })
|
||||
}
|
||||
|
||||
/// Get the current output level of the pin. Note that the value returned by this function is
|
||||
/// the voltage level reported by the pin, not the value set by the output driver.
|
||||
pub fn level(&self) -> Level {
|
||||
let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits();
|
||||
if bits & self.pin.bit() != 0 {
|
||||
Level::High
|
||||
} else {
|
||||
Level::Low
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GPIO input driver. Internally, this is a specialized [Flex] pin.
|
||||
pub struct Input<'d> {
|
||||
pub(crate) pin: Flex<'d>,
|
||||
}
|
||||
|
||||
impl<'d> Input<'d> {
|
||||
/// Create GPIO output driver for a [Pin] with the provided [Pull].
|
||||
#[inline]
|
||||
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, pull: Pull) -> Self {
|
||||
let mut pin = Flex::new(pin);
|
||||
pin.set_as_input();
|
||||
let mut result = Self { pin };
|
||||
result.set_pull(pull);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Set the pull configuration for the pin. To disable the pull, use [Pull::None].
|
||||
pub fn set_pull(&mut self, pull: Pull) {
|
||||
match_iocon!(register, iocon_reg(), self.pin.pin_bank(), self.pin.pin_number(), {
|
||||
register.modify(|_, w| match pull {
|
||||
Pull::None => w.mode().inactive(),
|
||||
Pull::Up => w.mode().pull_up(),
|
||||
Pull::Down => w.mode().pull_down(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the current input level of the pin.
|
||||
pub fn read(&self) -> Level {
|
||||
let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits();
|
||||
if bits & self.pin.bit() != 0 {
|
||||
Level::High
|
||||
} else {
|
||||
Level::Low
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A flexible GPIO (digital mode) pin whose mode is not yet determined. Under the hood, this is a
|
||||
/// reference to a type-erased pin called ["AnyPin"](AnyPin).
|
||||
pub struct Flex<'d> {
|
||||
pub(crate) pin: PeripheralRef<'d, AnyPin>,
|
||||
}
|
||||
|
||||
impl<'d> Flex<'d> {
|
||||
/// Wrap the pin in a `Flex`.
|
||||
///
|
||||
/// Note: you cannot assume that the pin will be in Digital mode after this call.
|
||||
#[inline]
|
||||
pub fn new(pin: impl Peripheral<P = impl Pin> + 'd) -> Self {
|
||||
Self {
|
||||
pin: pin.into_ref().map_into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the bank of this pin. See also [Bank].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use embassy_nxp::gpio::{Bank, Flex};
|
||||
///
|
||||
/// let p = embassy_nxp::init(Default::default());
|
||||
/// let pin = Flex::new(p.PIO1_15);
|
||||
///
|
||||
/// assert_eq!(pin.pin_bank(), Bank::Bank1);
|
||||
/// ```
|
||||
pub fn pin_bank(&self) -> Bank {
|
||||
self.pin.pin_bank()
|
||||
}
|
||||
|
||||
/// Get the number of this pin within its bank. See also [Bank].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use embassy_nxp::gpio::Flex;
|
||||
///
|
||||
/// let p = embassy_nxp::init(Default::default());
|
||||
/// let pin = Flex::new(p.PIO1_15);
|
||||
///
|
||||
/// assert_eq!(pin.pin_number(), 15 as u8);
|
||||
/// ```
|
||||
pub fn pin_number(&self) -> u8 {
|
||||
self.pin.pin_number()
|
||||
}
|
||||
|
||||
/// Get the bit mask for this pin. Useful for setting or clearing bits in a register. Note:
|
||||
/// PIOx_0 is bit 0, PIOx_1 is bit 1, etc.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use embassy_nxp::gpio::Flex;
|
||||
///
|
||||
/// let p = embassy_nxp::init(Default::default());
|
||||
/// let pin = Flex::new(p.PIO1_3);
|
||||
///
|
||||
/// assert_eq!(pin.bit(), 0b0000_1000);
|
||||
/// ```
|
||||
pub fn bit(&self) -> u32 {
|
||||
1 << self.pin.pin_number()
|
||||
}
|
||||
|
||||
/// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default
|
||||
/// setting for pins is (usually) non-digital.
|
||||
fn set_as_digital(&mut self) {
|
||||
match_iocon!(register, iocon_reg(), self.pin_bank(), self.pin_number(), {
|
||||
register.modify(|_, w| w.digimode().digital());
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the pin in output mode. This implies setting the pin to digital mode, which this
|
||||
/// function handles itself.
|
||||
pub fn set_as_output(&mut self) {
|
||||
self.set_as_digital();
|
||||
gpio_reg().dirset[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirsetp().bits(self.bit()) })
|
||||
}
|
||||
|
||||
pub fn set_as_input(&mut self) {
|
||||
self.set_as_digital();
|
||||
gpio_reg().dirclr[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirclrp().bits(self.bit()) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate.
|
||||
pub(crate) trait SealedPin: Sized {
|
||||
fn pin_bank(&self) -> Bank;
|
||||
fn pin_number(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an
|
||||
/// [AnyPin]. By default, this trait is sealed and cannot be implemented outside of the
|
||||
/// `embassy-nxp` crate due to the [SealedPin] trait.
|
||||
#[allow(private_bounds)]
|
||||
pub trait Pin: Peripheral<P = Self> + Into<AnyPin> + SealedPin + Sized + 'static {
|
||||
/// Degrade to a generic pin struct
|
||||
fn degrade(self) -> AnyPin {
|
||||
AnyPin {
|
||||
pin_bank: self.pin_bank(),
|
||||
pin_number: self.pin_number(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pin number within a bank
|
||||
#[inline]
|
||||
fn pin(&self) -> u8 {
|
||||
self.pin_number()
|
||||
}
|
||||
|
||||
/// Returns the bank of this pin
|
||||
#[inline]
|
||||
fn bank(&self) -> Bank {
|
||||
self.pin_bank()
|
||||
}
|
||||
}
|
||||
|
||||
/// Type-erased GPIO pin.
|
||||
pub struct AnyPin {
|
||||
pin_bank: Bank,
|
||||
pin_number: u8,
|
||||
}
|
||||
|
||||
impl AnyPin {
|
||||
/// Unsafely create a new type-erased pin.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that 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<peripherals::$name> for crate::gpio::AnyPin {
|
||||
fn from(val: peripherals::$name) -> Self {
|
||||
crate::gpio::Pin::degrade(val)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pin!(PIO0_0, Bank::Bank0, 0);
|
||||
impl_pin!(PIO0_1, Bank::Bank0, 1);
|
||||
impl_pin!(PIO0_2, Bank::Bank0, 2);
|
||||
impl_pin!(PIO0_3, Bank::Bank0, 3);
|
||||
impl_pin!(PIO0_4, Bank::Bank0, 4);
|
||||
impl_pin!(PIO0_5, Bank::Bank0, 5);
|
||||
impl_pin!(PIO0_6, Bank::Bank0, 6);
|
||||
impl_pin!(PIO0_7, Bank::Bank0, 7);
|
||||
impl_pin!(PIO0_8, Bank::Bank0, 8);
|
||||
impl_pin!(PIO0_9, Bank::Bank0, 9);
|
||||
impl_pin!(PIO0_10, Bank::Bank0, 10);
|
||||
impl_pin!(PIO0_11, Bank::Bank0, 11);
|
||||
impl_pin!(PIO0_12, Bank::Bank0, 12);
|
||||
impl_pin!(PIO0_13, Bank::Bank0, 13);
|
||||
impl_pin!(PIO0_14, Bank::Bank0, 14);
|
||||
impl_pin!(PIO0_15, Bank::Bank0, 15);
|
||||
impl_pin!(PIO0_16, Bank::Bank0, 16);
|
||||
impl_pin!(PIO0_17, Bank::Bank0, 17);
|
||||
impl_pin!(PIO0_18, Bank::Bank0, 18);
|
||||
impl_pin!(PIO0_19, Bank::Bank0, 19);
|
||||
impl_pin!(PIO0_20, Bank::Bank0, 20);
|
||||
impl_pin!(PIO0_21, Bank::Bank0, 21);
|
||||
impl_pin!(PIO0_22, Bank::Bank0, 22);
|
||||
impl_pin!(PIO0_23, Bank::Bank0, 23);
|
||||
impl_pin!(PIO0_24, Bank::Bank0, 24);
|
||||
impl_pin!(PIO0_25, Bank::Bank0, 25);
|
||||
impl_pin!(PIO0_26, Bank::Bank0, 26);
|
||||
impl_pin!(PIO0_27, Bank::Bank0, 27);
|
||||
impl_pin!(PIO0_28, Bank::Bank0, 28);
|
||||
impl_pin!(PIO0_29, Bank::Bank0, 29);
|
||||
impl_pin!(PIO0_30, Bank::Bank0, 30);
|
||||
impl_pin!(PIO0_31, Bank::Bank0, 31);
|
||||
impl_pin!(PIO1_0, Bank::Bank1, 0);
|
||||
impl_pin!(PIO1_1, Bank::Bank1, 1);
|
||||
impl_pin!(PIO1_2, Bank::Bank1, 2);
|
||||
impl_pin!(PIO1_3, Bank::Bank1, 3);
|
||||
impl_pin!(PIO1_4, Bank::Bank1, 4);
|
||||
impl_pin!(PIO1_5, Bank::Bank1, 5);
|
||||
impl_pin!(PIO1_6, Bank::Bank1, 6);
|
||||
impl_pin!(PIO1_7, Bank::Bank1, 7);
|
||||
impl_pin!(PIO1_8, Bank::Bank1, 8);
|
||||
impl_pin!(PIO1_9, Bank::Bank1, 9);
|
||||
impl_pin!(PIO1_10, Bank::Bank1, 10);
|
||||
impl_pin!(PIO1_11, Bank::Bank1, 11);
|
||||
impl_pin!(PIO1_12, Bank::Bank1, 12);
|
||||
impl_pin!(PIO1_13, Bank::Bank1, 13);
|
||||
impl_pin!(PIO1_14, Bank::Bank1, 14);
|
||||
impl_pin!(PIO1_15, Bank::Bank1, 15);
|
||||
impl_pin!(PIO1_16, Bank::Bank1, 16);
|
||||
impl_pin!(PIO1_17, Bank::Bank1, 17);
|
||||
impl_pin!(PIO1_18, Bank::Bank1, 18);
|
||||
impl_pin!(PIO1_19, Bank::Bank1, 19);
|
||||
impl_pin!(PIO1_20, Bank::Bank1, 20);
|
||||
impl_pin!(PIO1_21, Bank::Bank1, 21);
|
||||
impl_pin!(PIO1_22, Bank::Bank1, 22);
|
||||
impl_pin!(PIO1_23, Bank::Bank1, 23);
|
||||
impl_pin!(PIO1_24, Bank::Bank1, 24);
|
||||
impl_pin!(PIO1_25, Bank::Bank1, 25);
|
||||
impl_pin!(PIO1_26, Bank::Bank1, 26);
|
||||
impl_pin!(PIO1_27, Bank::Bank1, 27);
|
||||
impl_pin!(PIO1_28, Bank::Bank1, 28);
|
||||
impl_pin!(PIO1_29, Bank::Bank1, 29);
|
||||
impl_pin!(PIO1_30, Bank::Bank1, 30);
|
||||
impl_pin!(PIO1_31, Bank::Bank1, 31);
|
95
embassy-nxp/src/lib.rs
Normal file
95
embassy-nxp/src/lib.rs
Normal file
@ -0,0 +1,95 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod gpio;
|
||||
mod pac_utils;
|
||||
pub mod pint;
|
||||
|
||||
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||
pub use lpc55_pac as pac;
|
||||
|
||||
/// Initialize the `embassy-nxp` HAL with the provided configuration.
|
||||
///
|
||||
/// This returns the peripheral singletons that can be used for creating drivers.
|
||||
///
|
||||
/// This should only be called once and at startup, otherwise it panics.
|
||||
pub fn init(_config: config::Config) -> Peripherals {
|
||||
gpio::init();
|
||||
pint::init();
|
||||
|
||||
crate::Peripherals::take()
|
||||
}
|
||||
|
||||
embassy_hal_internal::peripherals! {
|
||||
// External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other
|
||||
// peripheral types (e.g. I2C).
|
||||
PIO0_0,
|
||||
PIO0_1,
|
||||
PIO0_2,
|
||||
PIO0_3,
|
||||
PIO0_4,
|
||||
PIO0_5,
|
||||
PIO0_6,
|
||||
PIO0_7,
|
||||
PIO0_8,
|
||||
PIO0_9,
|
||||
PIO0_10,
|
||||
PIO0_11,
|
||||
PIO0_12,
|
||||
PIO0_13,
|
||||
PIO0_14,
|
||||
PIO0_15,
|
||||
PIO0_16,
|
||||
PIO0_17,
|
||||
PIO0_18,
|
||||
PIO0_19,
|
||||
PIO0_20,
|
||||
PIO0_21,
|
||||
PIO0_22,
|
||||
PIO0_23,
|
||||
PIO0_24,
|
||||
PIO0_25,
|
||||
PIO0_26,
|
||||
PIO0_27,
|
||||
PIO0_28,
|
||||
PIO0_29,
|
||||
PIO0_30,
|
||||
PIO0_31,
|
||||
PIO1_0,
|
||||
PIO1_1,
|
||||
PIO1_2,
|
||||
PIO1_3,
|
||||
PIO1_4,
|
||||
PIO1_5,
|
||||
PIO1_6,
|
||||
PIO1_7,
|
||||
PIO1_8,
|
||||
PIO1_9,
|
||||
PIO1_10,
|
||||
PIO1_11,
|
||||
PIO1_12,
|
||||
PIO1_13,
|
||||
PIO1_14,
|
||||
PIO1_15,
|
||||
PIO1_16,
|
||||
PIO1_17,
|
||||
PIO1_18,
|
||||
PIO1_19,
|
||||
PIO1_20,
|
||||
PIO1_21,
|
||||
PIO1_22,
|
||||
PIO1_23,
|
||||
PIO1_24,
|
||||
PIO1_25,
|
||||
PIO1_26,
|
||||
PIO1_27,
|
||||
PIO1_28,
|
||||
PIO1_29,
|
||||
PIO1_30,
|
||||
PIO1_31,
|
||||
}
|
||||
|
||||
/// HAL configuration for the NXP board.
|
||||
pub mod config {
|
||||
#[derive(Default)]
|
||||
pub struct Config {}
|
||||
}
|
323
embassy-nxp/src/pac_utils.rs
Normal file
323
embassy-nxp/src/pac_utils.rs
Normal file
@ -0,0 +1,323 @@
|
||||
/// Get the GPIO register block. This is used to configure all GPIO pins.
|
||||
///
|
||||
/// # Safety
|
||||
/// Due to the type system of peripherals, access to the settings of a single pin is possible only
|
||||
/// by a single thread at a time. Read/Write operations on a single registers are NOT atomic. You
|
||||
/// must ensure that the GPIO registers are not accessed concurrently by multiple threads.
|
||||
pub(crate) fn gpio_reg() -> &'static lpc55_pac::gpio::RegisterBlock {
|
||||
unsafe { &*lpc55_pac::GPIO::ptr() }
|
||||
}
|
||||
|
||||
/// Get the IOCON register block.
|
||||
///
|
||||
/// # Safety
|
||||
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
|
||||
/// registers are not accessed concurrently by multiple threads.
|
||||
pub(crate) fn iocon_reg() -> &'static lpc55_pac::iocon::RegisterBlock {
|
||||
unsafe { &*lpc55_pac::IOCON::ptr() }
|
||||
}
|
||||
|
||||
/// Get the INPUTMUX register block.
|
||||
///
|
||||
/// # Safety
|
||||
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
|
||||
/// registers are not accessed concurrently by multiple threads.
|
||||
pub(crate) fn inputmux_reg() -> &'static lpc55_pac::inputmux::RegisterBlock {
|
||||
unsafe { &*lpc55_pac::INPUTMUX::ptr() }
|
||||
}
|
||||
|
||||
/// Get the SYSCON register block.
|
||||
///
|
||||
/// # Safety
|
||||
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
|
||||
/// registers are not accessed concurrently by multiple threads.
|
||||
pub(crate) fn syscon_reg() -> &'static lpc55_pac::syscon::RegisterBlock {
|
||||
unsafe { &*lpc55_pac::SYSCON::ptr() }
|
||||
}
|
||||
|
||||
/// Get the PINT register block.
|
||||
///
|
||||
/// # Safety
|
||||
/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO
|
||||
/// registers are not accessed concurrently by multiple threads.
|
||||
pub(crate) fn pint_reg() -> &'static lpc55_pac::pint::RegisterBlock {
|
||||
unsafe { &*lpc55_pac::PINT::ptr() }
|
||||
}
|
||||
|
||||
/// Match the pin bank and number of a pin to the corresponding IOCON register.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use embassy_nxp::gpio::Bank;
|
||||
/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon};
|
||||
///
|
||||
/// // Make pin PIO1_6 digital and set it to pull-down mode.
|
||||
/// match_iocon!(register, iocon_reg(), Bank::Bank1, 6, {
|
||||
/// register.modify(|_, w| w.mode().pull_down().digimode().digital());
|
||||
/// });
|
||||
/// ```
|
||||
macro_rules! match_iocon {
|
||||
($register:ident, $iocon_register:expr, $pin_bank:expr, $pin_number:expr, $action:expr) => {
|
||||
match ($pin_bank, $pin_number) {
|
||||
(Bank::Bank0, 0) => {
|
||||
let $register = &($iocon_register).pio0_0;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 1) => {
|
||||
let $register = &($iocon_register).pio0_1;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 2) => {
|
||||
let $register = &($iocon_register).pio0_2;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 3) => {
|
||||
let $register = &($iocon_register).pio0_3;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 4) => {
|
||||
let $register = &($iocon_register).pio0_4;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 5) => {
|
||||
let $register = &($iocon_register).pio0_5;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 6) => {
|
||||
let $register = &($iocon_register).pio0_6;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 7) => {
|
||||
let $register = &($iocon_register).pio0_7;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 8) => {
|
||||
let $register = &($iocon_register).pio0_8;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 9) => {
|
||||
let $register = &($iocon_register).pio0_9;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 10) => {
|
||||
let $register = &($iocon_register).pio0_10;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 11) => {
|
||||
let $register = &($iocon_register).pio0_11;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 12) => {
|
||||
let $register = &($iocon_register).pio0_12;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 13) => {
|
||||
let $register = &($iocon_register).pio0_13;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 14) => {
|
||||
let $register = &($iocon_register).pio0_14;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 15) => {
|
||||
let $register = &($iocon_register).pio0_15;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 16) => {
|
||||
let $register = &($iocon_register).pio0_16;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 17) => {
|
||||
let $register = &($iocon_register).pio0_17;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 18) => {
|
||||
let $register = &($iocon_register).pio0_18;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 19) => {
|
||||
let $register = &($iocon_register).pio0_19;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 20) => {
|
||||
let $register = &($iocon_register).pio0_20;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 21) => {
|
||||
let $register = &($iocon_register).pio0_21;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 22) => {
|
||||
let $register = &($iocon_register).pio0_22;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 23) => {
|
||||
let $register = &($iocon_register).pio0_23;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 24) => {
|
||||
let $register = &($iocon_register).pio0_24;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 25) => {
|
||||
let $register = &($iocon_register).pio0_25;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 26) => {
|
||||
let $register = &($iocon_register).pio0_26;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 27) => {
|
||||
let $register = &($iocon_register).pio0_27;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 28) => {
|
||||
let $register = &($iocon_register).pio0_28;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 29) => {
|
||||
let $register = &($iocon_register).pio0_29;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 30) => {
|
||||
let $register = &($iocon_register).pio0_30;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank0, 31) => {
|
||||
let $register = &($iocon_register).pio0_31;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 0) => {
|
||||
let $register = &($iocon_register).pio1_0;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 1) => {
|
||||
let $register = &($iocon_register).pio1_1;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 2) => {
|
||||
let $register = &($iocon_register).pio1_2;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 3) => {
|
||||
let $register = &($iocon_register).pio1_3;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 4) => {
|
||||
let $register = &($iocon_register).pio1_4;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 5) => {
|
||||
let $register = &($iocon_register).pio1_5;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 6) => {
|
||||
let $register = &($iocon_register).pio1_6;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 7) => {
|
||||
let $register = &($iocon_register).pio1_7;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 8) => {
|
||||
let $register = &($iocon_register).pio1_8;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 9) => {
|
||||
let $register = &($iocon_register).pio1_9;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 10) => {
|
||||
let $register = &($iocon_register).pio1_10;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 11) => {
|
||||
let $register = &($iocon_register).pio1_11;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 12) => {
|
||||
let $register = &($iocon_register).pio1_12;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 13) => {
|
||||
let $register = &($iocon_register).pio1_13;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 14) => {
|
||||
let $register = &($iocon_register).pio1_14;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 15) => {
|
||||
let $register = &($iocon_register).pio1_15;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 16) => {
|
||||
let $register = &($iocon_register).pio1_16;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 17) => {
|
||||
let $register = &($iocon_register).pio1_17;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 18) => {
|
||||
let $register = &($iocon_register).pio1_18;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 19) => {
|
||||
let $register = &($iocon_register).pio1_19;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 20) => {
|
||||
let $register = &($iocon_register).pio1_20;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 21) => {
|
||||
let $register = &($iocon_register).pio1_21;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 22) => {
|
||||
let $register = &($iocon_register).pio1_22;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 23) => {
|
||||
let $register = &($iocon_register).pio1_23;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 24) => {
|
||||
let $register = &($iocon_register).pio1_24;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 25) => {
|
||||
let $register = &($iocon_register).pio1_25;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 26) => {
|
||||
let $register = &($iocon_register).pio1_26;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 27) => {
|
||||
let $register = &($iocon_register).pio1_27;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 28) => {
|
||||
let $register = &($iocon_register).pio1_28;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 29) => {
|
||||
let $register = &($iocon_register).pio1_29;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 30) => {
|
||||
let $register = &($iocon_register).pio1_30;
|
||||
$action;
|
||||
}
|
||||
(Bank::Bank1, 31) => {
|
||||
let $register = &($iocon_register).pio1_31;
|
||||
$action;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use match_iocon;
|
440
embassy-nxp/src/pint.rs
Normal file
440
embassy-nxp/src/pint.rs
Normal file
@ -0,0 +1,440 @@
|
||||
//! Pin Interrupt module.
|
||||
use core::cell::RefCell;
|
||||
use core::future::Future;
|
||||
use core::pin::Pin as FuturePin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_hal_internal::{Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::gpio::{self, AnyPin, Level, SealedPin};
|
||||
use crate::pac::interrupt;
|
||||
use crate::pac_utils::*;
|
||||
|
||||
struct PinInterrupt {
|
||||
assigned: bool,
|
||||
waker: AtomicWaker,
|
||||
/// If true, the interrupt was triggered due to this PinInterrupt. This is used to determine if
|
||||
/// an [InputFuture] should return Poll::Ready.
|
||||
at_fault: bool,
|
||||
}
|
||||
|
||||
impl PinInterrupt {
|
||||
pub fn interrupt_active(&self) -> bool {
|
||||
self.assigned
|
||||
}
|
||||
|
||||
/// Mark the interrupt as assigned to a pin.
|
||||
pub fn enable(&mut self) {
|
||||
self.assigned = true;
|
||||
self.at_fault = false;
|
||||
}
|
||||
|
||||
/// Mark the interrupt as available.
|
||||
pub fn disable(&mut self) {
|
||||
self.assigned = false;
|
||||
self.at_fault = false;
|
||||
}
|
||||
|
||||
/// Returns true if the interrupt was triggered due to this PinInterrupt.
|
||||
///
|
||||
/// If this function returns true, it will also reset the at_fault flag.
|
||||
pub fn at_fault(&mut self) -> bool {
|
||||
let val = self.at_fault;
|
||||
self.at_fault = false;
|
||||
val
|
||||
}
|
||||
|
||||
/// Set the at_fault flag to true.
|
||||
pub fn fault(&mut self) {
|
||||
self.at_fault = true;
|
||||
}
|
||||
}
|
||||
|
||||
const NEW_PIN_INTERRUPT: PinInterrupt = PinInterrupt {
|
||||
assigned: false,
|
||||
waker: AtomicWaker::new(),
|
||||
at_fault: false,
|
||||
};
|
||||
const INTERUPT_COUNT: usize = 8;
|
||||
static PIN_INTERRUPTS: Mutex<RefCell<[PinInterrupt; INTERUPT_COUNT]>> =
|
||||
Mutex::new(RefCell::new([NEW_PIN_INTERRUPT; INTERUPT_COUNT]));
|
||||
|
||||
fn next_available_interrupt() -> Option<usize> {
|
||||
critical_section::with(|cs| {
|
||||
for (i, pin_interrupt) in PIN_INTERRUPTS.borrow(cs).borrow().iter().enumerate() {
|
||||
if !pin_interrupt.interrupt_active() {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Edge {
|
||||
Rising,
|
||||
Falling,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum InterruptOn {
|
||||
Level(Level),
|
||||
Edge(Edge),
|
||||
}
|
||||
|
||||
pub(crate) fn init() {
|
||||
syscon_reg().ahbclkctrl0.modify(|_, w| w.pint().enable());
|
||||
|
||||
// Enable interrupts
|
||||
unsafe {
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT0);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT1);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT2);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT3);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT4);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT5);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6);
|
||||
crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7);
|
||||
};
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
struct InputFuture<'d> {
|
||||
#[allow(dead_code)]
|
||||
pin: PeripheralRef<'d, AnyPin>,
|
||||
interrupt_number: usize,
|
||||
}
|
||||
|
||||
impl<'d> InputFuture<'d> {
|
||||
/// Create a new input future. Returns None if all interrupts are in use.
|
||||
fn new(pin: impl Peripheral<P = impl gpio::Pin> + 'd, interrupt_on: InterruptOn) -> Option<Self> {
|
||||
let pin = pin.into_ref().map_into();
|
||||
let interrupt_number = next_available_interrupt()?;
|
||||
|
||||
// Clear interrupt, just in case
|
||||
pint_reg()
|
||||
.rise
|
||||
.write(|w| unsafe { w.rdet().bits(1 << interrupt_number) });
|
||||
pint_reg()
|
||||
.fall
|
||||
.write(|w| unsafe { w.fdet().bits(1 << interrupt_number) });
|
||||
|
||||
// Enable input multiplexing on pin interrupt register 0 for pin (32*bank + pin_number)
|
||||
inputmux_reg().pintsel[interrupt_number]
|
||||
.write(|w| unsafe { w.intpin().bits(32 * pin.pin_bank() as u8 + pin.pin_number()) });
|
||||
|
||||
match interrupt_on {
|
||||
InterruptOn::Level(level) => {
|
||||
// Set pin interrupt register to edge sensitive or level sensitive
|
||||
// 0 = edge sensitive, 1 = level sensitive
|
||||
pint_reg()
|
||||
.isel
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << interrupt_number)) });
|
||||
|
||||
// Enable level interrupt.
|
||||
//
|
||||
// Note: Level sensitive interrupts are enabled by the same register as rising edge
|
||||
// is activated.
|
||||
|
||||
// 0 = no-op, 1 = enable
|
||||
pint_reg()
|
||||
.sienr
|
||||
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
|
||||
|
||||
// Set active level
|
||||
match level {
|
||||
Level::Low => {
|
||||
// 0 = no-op, 1 = select LOW
|
||||
pint_reg()
|
||||
.cienf
|
||||
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
|
||||
}
|
||||
Level::High => {
|
||||
// 0 = no-op, 1 = select HIGH
|
||||
pint_reg()
|
||||
.sienf
|
||||
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
|
||||
}
|
||||
}
|
||||
}
|
||||
InterruptOn::Edge(edge) => {
|
||||
// Set pin interrupt register to edge sensitive or level sensitive
|
||||
// 0 = edge sensitive, 1 = level sensitive
|
||||
pint_reg()
|
||||
.isel
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << interrupt_number)) });
|
||||
|
||||
// Enable rising/falling edge detection
|
||||
match edge {
|
||||
Edge::Rising => {
|
||||
// 0 = no-op, 1 = enable rising edge
|
||||
pint_reg()
|
||||
.sienr
|
||||
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
|
||||
// 0 = no-op, 1 = disable falling edge
|
||||
pint_reg()
|
||||
.cienf
|
||||
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
|
||||
}
|
||||
Edge::Falling => {
|
||||
// 0 = no-op, 1 = enable falling edge
|
||||
pint_reg()
|
||||
.sienf
|
||||
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
|
||||
// 0 = no-op, 1 = disable rising edge
|
||||
pint_reg()
|
||||
.cienr
|
||||
.write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) });
|
||||
}
|
||||
Edge::Both => {
|
||||
// 0 = no-op, 1 = enable
|
||||
pint_reg()
|
||||
.sienr
|
||||
.write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) });
|
||||
pint_reg()
|
||||
.sienf
|
||||
.write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
|
||||
let pin_interrupt = &mut pin_interrupts[interrupt_number];
|
||||
|
||||
pin_interrupt.enable();
|
||||
});
|
||||
|
||||
Some(Self { pin, interrupt_number })
|
||||
}
|
||||
|
||||
/// Returns true if the interrupt was triggered for this pin.
|
||||
fn interrupt_triggered(&self) -> bool {
|
||||
let interrupt_number = self.interrupt_number;
|
||||
|
||||
// Initially, we determine if the interrupt was triggered by this InputFuture by checking
|
||||
// the flags of the interrupt_number. However, by the time we get to this point, the
|
||||
// interrupt may have been triggered again, so we needed to clear the cpu flags immediately.
|
||||
// As a solution, we mark which [PinInterrupt] is responsible for the interrupt ("at fault")
|
||||
critical_section::with(|cs| {
|
||||
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
|
||||
let pin_interrupt = &mut pin_interrupts[interrupt_number];
|
||||
|
||||
pin_interrupt.at_fault()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Drop for InputFuture<'d> {
|
||||
fn drop(&mut self) {
|
||||
let interrupt_number = self.interrupt_number;
|
||||
|
||||
// Disable pin interrupt
|
||||
// 0 = no-op, 1 = disable
|
||||
pint_reg()
|
||||
.cienr
|
||||
.write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) });
|
||||
pint_reg()
|
||||
.cienf
|
||||
.write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) });
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
|
||||
let pin_interrupt = &mut pin_interrupts[interrupt_number];
|
||||
|
||||
pin_interrupt.disable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Future for InputFuture<'d> {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let interrupt_number = self.interrupt_number;
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
|
||||
let pin_interrupt = &mut pin_interrupts[interrupt_number];
|
||||
|
||||
pin_interrupt.waker.register(cx.waker());
|
||||
});
|
||||
|
||||
if self.interrupt_triggered() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_interrupt(interrupt_number: usize) {
|
||||
pint_reg()
|
||||
.rise
|
||||
.write(|w| unsafe { w.rdet().bits(1 << interrupt_number) });
|
||||
pint_reg()
|
||||
.fall
|
||||
.write(|w| unsafe { w.fdet().bits(1 << interrupt_number) });
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut();
|
||||
let pin_interrupt = &mut pin_interrupts[interrupt_number];
|
||||
|
||||
pin_interrupt.fault();
|
||||
pin_interrupt.waker.wake();
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT0() {
|
||||
handle_interrupt(0);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT1() {
|
||||
handle_interrupt(1);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT2() {
|
||||
handle_interrupt(2);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT3() {
|
||||
handle_interrupt(3);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT4() {
|
||||
handle_interrupt(4);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT5() {
|
||||
handle_interrupt(5);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT6() {
|
||||
handle_interrupt(6);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[interrupt]
|
||||
fn PIN_INT7() {
|
||||
handle_interrupt(7);
|
||||
}
|
||||
|
||||
impl gpio::Flex<'_> {
|
||||
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
|
||||
/// try to wait for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
|
||||
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Both))?.await;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
|
||||
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Falling))?.await;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
|
||||
InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Rising))?.await;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_low(&mut self) -> Option<()> {
|
||||
InputFuture::new(&mut self.pin, InterruptOn::Level(Level::Low))?.await;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_high(&mut self) -> Option<()> {
|
||||
InputFuture::new(&mut self.pin, InterruptOn::Level(Level::High))?.await;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl gpio::Input<'_> {
|
||||
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
|
||||
/// try to wait for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_any_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_falling_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_rising_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_low(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_low().await
|
||||
}
|
||||
|
||||
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_high(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_high().await
|
||||
}
|
||||
}
|
||||
|
||||
impl gpio::Output<'_> {
|
||||
/// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you
|
||||
/// try to wait for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_any_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_any_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_falling_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_falling_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait
|
||||
/// for more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_rising_edge(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_rising_edge().await
|
||||
}
|
||||
|
||||
/// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_low(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_low().await
|
||||
}
|
||||
|
||||
/// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for
|
||||
/// more than 8 pins, this function will return `None`.
|
||||
pub async fn wait_for_high(&mut self) -> Option<()> {
|
||||
self.pin.wait_for_high().await
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {}
|
||||
)*
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ const RESUME_TOKEN: u32 = !0xDEADBEEF;
|
||||
static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[inline(always)]
|
||||
fn core1_setup(stack_bottom: *mut usize) {
|
||||
unsafe fn core1_setup(stack_bottom: *mut usize) {
|
||||
if install_stack_guard(stack_bottom).is_err() {
|
||||
// currently only happens if the MPU was already set up, which
|
||||
// would indicate that the core is already in use from outside
|
||||
@ -148,7 +148,7 @@ where
|
||||
entry: *mut ManuallyDrop<F>,
|
||||
stack_bottom: *mut usize,
|
||||
) -> ! {
|
||||
core1_setup(stack_bottom);
|
||||
unsafe { core1_setup(stack_bottom) };
|
||||
|
||||
let entry = unsafe { ManuallyDrop::take(&mut *entry) };
|
||||
|
||||
@ -169,6 +169,13 @@ where
|
||||
interrupt::SIO_IRQ_FIFO.enable()
|
||||
};
|
||||
|
||||
// Enable FPU
|
||||
#[cfg(all(feature = "_rp235x", has_fpu))]
|
||||
unsafe {
|
||||
let p = cortex_m::Peripherals::steal();
|
||||
p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22));
|
||||
}
|
||||
|
||||
entry()
|
||||
}
|
||||
|
||||
|
203
embassy-rp/src/pio_programs/hd44780.rs
Normal file
203
embassy-rp/src/pio_programs/hd44780.rs
Normal file
@ -0,0 +1,203 @@
|
||||
//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
|
||||
|
||||
use crate::dma::{AnyChannel, Channel};
|
||||
use crate::pio::{
|
||||
Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
|
||||
StateMachine,
|
||||
};
|
||||
use crate::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
|
||||
pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
r#"
|
||||
.side_set 1 opt
|
||||
.origin 20
|
||||
|
||||
loop:
|
||||
out x, 24
|
||||
delay:
|
||||
jmp x--, delay
|
||||
out pins, 4 side 1
|
||||
out null, 4 side 0
|
||||
jmp !osre, loop
|
||||
irq 0
|
||||
"#,
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
|
||||
pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
// many side sets are only there to free up a delay bit!
|
||||
let prg = pio_proc::pio_asm!(
|
||||
r#"
|
||||
.origin 27
|
||||
.side_set 1
|
||||
|
||||
.wrap_target
|
||||
pull side 0
|
||||
out x 1 side 0 ; !rs
|
||||
out y 7 side 0 ; #data - 1
|
||||
|
||||
; rs/rw to e: >= 60ns
|
||||
; e high time: >= 500ns
|
||||
; e low time: >= 500ns
|
||||
; read data valid after e falling: ~5ns
|
||||
; write data hold after e falling: ~10ns
|
||||
|
||||
loop:
|
||||
pull side 0
|
||||
jmp !x data side 0
|
||||
command:
|
||||
set pins 0b00 side 0
|
||||
jmp shift side 0
|
||||
data:
|
||||
set pins 0b01 side 0
|
||||
shift:
|
||||
out pins 4 side 1 [9]
|
||||
nop side 0 [9]
|
||||
out pins 4 side 1 [9]
|
||||
mov osr null side 0 [7]
|
||||
out pindirs 4 side 0
|
||||
set pins 0b10 side 0
|
||||
busy:
|
||||
nop side 1 [9]
|
||||
jmp pin more side 0 [9]
|
||||
mov osr ~osr side 1 [9]
|
||||
nop side 0 [4]
|
||||
out pindirs 4 side 0
|
||||
jmp y-- loop side 0
|
||||
.wrap
|
||||
more:
|
||||
nop side 1 [9]
|
||||
jmp busy side 0 [9]
|
||||
"#
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed HD44780 driver
|
||||
pub struct PioHD44780<'l, P: Instance, const S: usize> {
|
||||
dma: PeripheralRef<'l, AnyChannel>,
|
||||
sm: StateMachine<'l, P, S>,
|
||||
|
||||
buf: [u8; 40],
|
||||
}
|
||||
|
||||
impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
|
||||
/// Configure the given state machine to first init, then write data to, a HD44780 display.
|
||||
pub async fn new(
|
||||
common: &mut Common<'l, P>,
|
||||
mut sm: StateMachine<'l, P, S>,
|
||||
mut irq: Irq<'l, P, S>,
|
||||
dma: impl Peripheral<P = impl Channel> + 'l,
|
||||
rs: impl PioPin,
|
||||
rw: impl PioPin,
|
||||
e: impl PioPin,
|
||||
db4: impl PioPin,
|
||||
db5: impl PioPin,
|
||||
db6: impl PioPin,
|
||||
db7: impl PioPin,
|
||||
word_prg: &PioHD44780CommandWordProgram<'l, P>,
|
||||
seq_prg: &PioHD44780CommandSequenceProgram<'l, P>,
|
||||
) -> PioHD44780<'l, P, S> {
|
||||
into_ref!(dma);
|
||||
|
||||
let rs = common.make_pio_pin(rs);
|
||||
let rw = common.make_pio_pin(rw);
|
||||
let e = common.make_pio_pin(e);
|
||||
let db4 = common.make_pio_pin(db4);
|
||||
let db5 = common.make_pio_pin(db5);
|
||||
let db6 = common.make_pio_pin(db6);
|
||||
let db7 = common.make_pio_pin(db7);
|
||||
|
||||
sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&word_prg.prg, &[&e]);
|
||||
cfg.clock_divider = 125u8.into();
|
||||
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
|
||||
cfg.shift_out = ShiftConfig {
|
||||
auto_fill: true,
|
||||
direction: ShiftDirection::Left,
|
||||
threshold: 32,
|
||||
};
|
||||
cfg.fifo_join = FifoJoin::TxOnly;
|
||||
sm.set_config(&cfg);
|
||||
|
||||
sm.set_enable(true);
|
||||
// init to 8 bit thrice
|
||||
sm.tx().push((50000 << 8) | 0x30);
|
||||
sm.tx().push((5000 << 8) | 0x30);
|
||||
sm.tx().push((200 << 8) | 0x30);
|
||||
// init 4 bit
|
||||
sm.tx().push((200 << 8) | 0x20);
|
||||
// set font and lines
|
||||
sm.tx().push((50 << 8) | 0x20);
|
||||
sm.tx().push(0b1100_0000);
|
||||
|
||||
irq.wait().await;
|
||||
sm.set_enable(false);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&seq_prg.prg, &[&e]);
|
||||
cfg.clock_divider = 8u8.into(); // ~64ns/insn
|
||||
cfg.set_jmp_pin(&db7);
|
||||
cfg.set_set_pins(&[&rs, &rw]);
|
||||
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
|
||||
cfg.shift_out.direction = ShiftDirection::Left;
|
||||
cfg.fifo_join = FifoJoin::TxOnly;
|
||||
sm.set_config(&cfg);
|
||||
|
||||
sm.set_enable(true);
|
||||
|
||||
// display on and cursor on and blinking, reset display
|
||||
sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await;
|
||||
|
||||
Self {
|
||||
dma: dma.map_into(),
|
||||
sm,
|
||||
buf: [0x20; 40],
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a line to the display
|
||||
pub async fn add_line(&mut self, s: &[u8]) {
|
||||
// move cursor to 0:0, prepare 16 characters
|
||||
self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]);
|
||||
// move line 2 up
|
||||
self.buf.copy_within(22..38, 3);
|
||||
// move cursor to 1:0, prepare 16 characters
|
||||
self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]);
|
||||
// file line 2 with spaces
|
||||
self.buf[22..38].fill(0x20);
|
||||
// copy input line
|
||||
let len = s.len().min(16);
|
||||
self.buf[22..22 + len].copy_from_slice(&s[0..len]);
|
||||
// set cursor to 1:15
|
||||
self.buf[38..].copy_from_slice(&[0x80, 0xcf]);
|
||||
|
||||
self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await;
|
||||
}
|
||||
}
|
95
embassy-rp/src/pio_programs/i2s.rs
Normal file
95
embassy-rp/src/pio_programs/i2s.rs
Normal file
@ -0,0 +1,95 @@
|
||||
//! Pio backed I2s output
|
||||
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
use crate::dma::{AnyChannel, Channel, Transfer};
|
||||
use crate::pio::{
|
||||
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
||||
};
|
||||
use crate::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
/// This struct represents an i2s output driver program
|
||||
pub struct PioI2sOutProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
".side_set 2",
|
||||
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
|
||||
"left_data:",
|
||||
" out pins, 1 side 0b00",
|
||||
" jmp x-- left_data side 0b01",
|
||||
" out pins 1 side 0b10",
|
||||
" set x, 14 side 0b11",
|
||||
"right_data:",
|
||||
" out pins 1 side 0b10",
|
||||
" jmp x-- right_data side 0b11",
|
||||
" out pins 1 side 0b00",
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed I2s output driver
|
||||
pub struct PioI2sOut<'a, P: Instance, const S: usize> {
|
||||
dma: PeripheralRef<'a, AnyChannel>,
|
||||
sm: StateMachine<'a, P, S>,
|
||||
}
|
||||
|
||||
impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
|
||||
/// Configure a state machine to output I2s
|
||||
pub fn new(
|
||||
common: &mut Common<'a, P>,
|
||||
mut sm: StateMachine<'a, P, S>,
|
||||
dma: impl Peripheral<P = impl Channel> + 'a,
|
||||
data_pin: impl PioPin,
|
||||
bit_clock_pin: impl PioPin,
|
||||
lr_clock_pin: impl PioPin,
|
||||
sample_rate: u32,
|
||||
bit_depth: u32,
|
||||
channels: u32,
|
||||
program: &PioI2sOutProgram<'a, P>,
|
||||
) -> Self {
|
||||
into_ref!(dma);
|
||||
|
||||
let data_pin = common.make_pio_pin(data_pin);
|
||||
let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
|
||||
let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
|
||||
|
||||
let cfg = {
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
|
||||
cfg.set_out_pins(&[&data_pin]);
|
||||
let clock_frequency = sample_rate * bit_depth * channels;
|
||||
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
|
||||
cfg.shift_out = ShiftConfig {
|
||||
threshold: 32,
|
||||
direction: ShiftDirection::Left,
|
||||
auto_fill: true,
|
||||
};
|
||||
// join fifos to have twice the time to start the next dma transfer
|
||||
cfg.fifo_join = FifoJoin::TxOnly;
|
||||
cfg
|
||||
};
|
||||
sm.set_config(&cfg);
|
||||
sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
|
||||
|
||||
sm.set_enable(true);
|
||||
|
||||
Self {
|
||||
dma: dma.map_into(),
|
||||
sm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
|
||||
pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
|
||||
self.sm.tx().dma_push(self.dma.reborrow(), buff)
|
||||
}
|
||||
}
|
10
embassy-rp/src/pio_programs/mod.rs
Normal file
10
embassy-rp/src/pio_programs/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Pre-built pio programs for common interfaces
|
||||
|
||||
pub mod hd44780;
|
||||
pub mod i2s;
|
||||
pub mod onewire;
|
||||
pub mod pwm;
|
||||
pub mod rotary_encoder;
|
||||
pub mod stepper;
|
||||
pub mod uart;
|
||||
pub mod ws2812;
|
109
embassy-rp/src/pio_programs/onewire.rs
Normal file
109
embassy-rp/src/pio_programs/onewire.rs
Normal file
@ -0,0 +1,109 @@
|
||||
//! OneWire pio driver
|
||||
|
||||
use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine};
|
||||
|
||||
/// This struct represents an onewire driver program
|
||||
pub struct PioOneWireProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
r#"
|
||||
.wrap_target
|
||||
again:
|
||||
pull block
|
||||
mov x, osr
|
||||
jmp !x, read
|
||||
write:
|
||||
set pindirs, 1
|
||||
set pins, 0
|
||||
loop1:
|
||||
jmp x--,loop1
|
||||
set pindirs, 0 [31]
|
||||
wait 1 pin 0 [31]
|
||||
pull block
|
||||
mov x, osr
|
||||
bytes1:
|
||||
pull block
|
||||
set y, 7
|
||||
set pindirs, 1
|
||||
bit1:
|
||||
set pins, 0 [1]
|
||||
out pins,1 [31]
|
||||
set pins, 1 [20]
|
||||
jmp y--,bit1
|
||||
jmp x--,bytes1
|
||||
set pindirs, 0 [31]
|
||||
jmp again
|
||||
read:
|
||||
pull block
|
||||
mov x, osr
|
||||
bytes2:
|
||||
set y, 7
|
||||
bit2:
|
||||
set pindirs, 1
|
||||
set pins, 0 [1]
|
||||
set pindirs, 0 [5]
|
||||
in pins,1 [10]
|
||||
jmp y--,bit2
|
||||
jmp x--,bytes2
|
||||
.wrap
|
||||
"#,
|
||||
);
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed OneWire driver
|
||||
pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> {
|
||||
sm: StateMachine<'d, PIO, SM>,
|
||||
}
|
||||
|
||||
impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
|
||||
/// Create a new instance the driver
|
||||
pub fn new(
|
||||
common: &mut Common<'d, PIO>,
|
||||
mut sm: StateMachine<'d, PIO, SM>,
|
||||
pin: impl PioPin,
|
||||
program: &PioOneWireProgram<'d, PIO>,
|
||||
) -> Self {
|
||||
let pin = common.make_pio_pin(pin);
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&program.prg, &[]);
|
||||
cfg.set_out_pins(&[&pin]);
|
||||
cfg.set_in_pins(&[&pin]);
|
||||
cfg.set_set_pins(&[&pin]);
|
||||
cfg.shift_in = ShiftConfig {
|
||||
auto_fill: true,
|
||||
direction: ShiftDirection::Right,
|
||||
threshold: 8,
|
||||
};
|
||||
cfg.clock_divider = 255_u8.into();
|
||||
sm.set_config(&cfg);
|
||||
sm.set_enable(true);
|
||||
Self { sm }
|
||||
}
|
||||
|
||||
/// Write bytes over the wire
|
||||
pub async fn write_bytes(&mut self, bytes: &[u8]) {
|
||||
self.sm.tx().wait_push(250).await;
|
||||
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
|
||||
for b in bytes {
|
||||
self.sm.tx().wait_push(*b as u32).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes from the wire
|
||||
pub async fn read_bytes(&mut self, bytes: &mut [u8]) {
|
||||
self.sm.tx().wait_push(0).await;
|
||||
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
|
||||
for b in bytes.iter_mut() {
|
||||
*b = (self.sm.rx().wait_pull().await >> 24) as u8;
|
||||
}
|
||||
}
|
||||
}
|
121
embassy-rp/src/pio_programs/pwm.rs
Normal file
121
embassy-rp/src/pio_programs/pwm.rs
Normal file
@ -0,0 +1,121 @@
|
||||
//! PIO backed PWM driver
|
||||
|
||||
use core::time::Duration;
|
||||
|
||||
use pio::InstructionOperands;
|
||||
|
||||
use crate::clocks;
|
||||
use crate::gpio::Level;
|
||||
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
|
||||
|
||||
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
|
||||
fn to_pio_cycles(duration: Duration) -> u32 {
|
||||
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
|
||||
}
|
||||
|
||||
/// This struct represents a PWM program loaded into pio instruction memory.
|
||||
pub struct PioPwmProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
".side_set 1 opt"
|
||||
"pull noblock side 0"
|
||||
"mov x, osr"
|
||||
"mov y, isr"
|
||||
"countloop:"
|
||||
"jmp x!=y noset"
|
||||
"jmp skip side 1"
|
||||
"noset:"
|
||||
"nop"
|
||||
"skip:"
|
||||
"jmp y-- countloop"
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed PWM output
|
||||
pub struct PioPwm<'d, T: Instance, const SM: usize> {
|
||||
sm: StateMachine<'d, T, SM>,
|
||||
pin: Pin<'d, T>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
|
||||
/// Configure a state machine as a PWM output
|
||||
pub fn new(
|
||||
pio: &mut Common<'d, T>,
|
||||
mut sm: StateMachine<'d, T, SM>,
|
||||
pin: impl PioPin,
|
||||
program: &PioPwmProgram<'d, T>,
|
||||
) -> Self {
|
||||
let pin = pio.make_pio_pin(pin);
|
||||
sm.set_pins(Level::High, &[&pin]);
|
||||
sm.set_pin_dirs(Direction::Out, &[&pin]);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&program.prg, &[&pin]);
|
||||
|
||||
sm.set_config(&cfg);
|
||||
|
||||
Self { sm, pin }
|
||||
}
|
||||
|
||||
/// Enable's the PIO program, continuing the wave generation from the PIO program.
|
||||
pub fn start(&mut self) {
|
||||
self.sm.set_enable(true);
|
||||
}
|
||||
|
||||
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
|
||||
pub fn stop(&mut self) {
|
||||
self.sm.set_enable(false);
|
||||
}
|
||||
|
||||
/// Sets the pwm period, which is the length of time for each pio wave until reset.
|
||||
pub fn set_period(&mut self, duration: Duration) {
|
||||
let is_enabled = self.sm.is_enabled();
|
||||
while !self.sm.tx().empty() {} // Make sure that the queue is empty
|
||||
self.sm.set_enable(false);
|
||||
self.sm.tx().push(to_pio_cycles(duration));
|
||||
unsafe {
|
||||
self.sm.exec_instr(
|
||||
InstructionOperands::PULL {
|
||||
if_empty: false,
|
||||
block: false,
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
self.sm.exec_instr(
|
||||
InstructionOperands::OUT {
|
||||
destination: ::pio::OutDestination::ISR,
|
||||
bit_count: 32,
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
};
|
||||
if is_enabled {
|
||||
self.sm.set_enable(true) // Enable if previously enabled
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the number of pio cycles to set the wave on high to.
|
||||
pub fn set_level(&mut self, level: u32) {
|
||||
self.sm.tx().push(level);
|
||||
}
|
||||
|
||||
/// Set the pulse width high time
|
||||
pub fn write(&mut self, duration: Duration) {
|
||||
self.set_level(to_pio_cycles(duration));
|
||||
}
|
||||
|
||||
/// Return the state machine and pin.
|
||||
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
|
||||
(self.sm, self.pin)
|
||||
}
|
||||
}
|
73
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
73
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
@ -0,0 +1,73 @@
|
||||
//! PIO backed quadrature encoder
|
||||
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
use crate::gpio::Pull;
|
||||
use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
|
||||
|
||||
/// This struct represents an Encoder program loaded into pio instruction memory.
|
||||
pub struct PioEncoderProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio Backed quadrature encoder reader
|
||||
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
|
||||
sm: StateMachine<'d, T, SM>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
|
||||
/// Configure a state machine with the loaded [PioEncoderProgram]
|
||||
pub fn new(
|
||||
pio: &mut Common<'d, T>,
|
||||
mut sm: StateMachine<'d, T, SM>,
|
||||
pin_a: impl PioPin,
|
||||
pin_b: impl PioPin,
|
||||
program: &PioEncoderProgram<'d, T>,
|
||||
) -> Self {
|
||||
let mut pin_a = pio.make_pio_pin(pin_a);
|
||||
let mut pin_b = pio.make_pio_pin(pin_b);
|
||||
pin_a.set_pull(Pull::Up);
|
||||
pin_b.set_pull(Pull::Up);
|
||||
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.set_in_pins(&[&pin_a, &pin_b]);
|
||||
cfg.fifo_join = FifoJoin::RxOnly;
|
||||
cfg.shift_in.direction = ShiftDirection::Left;
|
||||
cfg.clock_divider = 10_000.to_fixed();
|
||||
cfg.use_program(&program.prg, &[]);
|
||||
sm.set_config(&cfg);
|
||||
sm.set_enable(true);
|
||||
Self { sm }
|
||||
}
|
||||
|
||||
/// Read a single count from the encoder
|
||||
pub async fn read(&mut self) -> Direction {
|
||||
loop {
|
||||
match self.sm.rx().wait_pull().await {
|
||||
0 => return Direction::CounterClockwise,
|
||||
1 => return Direction::Clockwise,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoder Count Direction
|
||||
pub enum Direction {
|
||||
/// Encoder turned clockwise
|
||||
Clockwise,
|
||||
/// Encoder turned counter clockwise
|
||||
CounterClockwise,
|
||||
}
|
147
embassy-rp/src/pio_programs/stepper.rs
Normal file
147
embassy-rp/src/pio_programs/stepper.rs
Normal file
@ -0,0 +1,147 @@
|
||||
//! Pio Stepper Driver for 5-wire steppers
|
||||
|
||||
use core::mem::{self, MaybeUninit};
|
||||
|
||||
use fixed::traits::ToFixed;
|
||||
use fixed::types::extra::U8;
|
||||
use fixed::FixedU32;
|
||||
|
||||
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
|
||||
|
||||
/// This struct represents a Stepper driver program loaded into pio instruction memory.
|
||||
pub struct PioStepperProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
|
||||
/// Load the program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
"pull block",
|
||||
"mov x, osr",
|
||||
"pull block",
|
||||
"mov y, osr",
|
||||
"jmp !x end",
|
||||
"loop:",
|
||||
"jmp !osre step",
|
||||
"mov osr, y",
|
||||
"step:",
|
||||
"out pins, 4 [31]"
|
||||
"jmp x-- loop",
|
||||
"end:",
|
||||
"irq 0 rel"
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed Stepper driver
|
||||
pub struct PioStepper<'d, T: Instance, const SM: usize> {
|
||||
irq: Irq<'d, T, SM>,
|
||||
sm: StateMachine<'d, T, SM>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
|
||||
/// Configure a state machine to drive a stepper
|
||||
pub fn new(
|
||||
pio: &mut Common<'d, T>,
|
||||
mut sm: StateMachine<'d, T, SM>,
|
||||
irq: Irq<'d, T, SM>,
|
||||
pin0: impl PioPin,
|
||||
pin1: impl PioPin,
|
||||
pin2: impl PioPin,
|
||||
pin3: impl PioPin,
|
||||
program: &PioStepperProgram<'d, T>,
|
||||
) -> Self {
|
||||
let pin0 = pio.make_pio_pin(pin0);
|
||||
let pin1 = pio.make_pio_pin(pin1);
|
||||
let pin2 = pio.make_pio_pin(pin2);
|
||||
let pin3 = pio.make_pio_pin(pin3);
|
||||
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
|
||||
let mut cfg = Config::default();
|
||||
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
|
||||
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
|
||||
cfg.use_program(&program.prg, &[]);
|
||||
sm.set_config(&cfg);
|
||||
sm.set_enable(true);
|
||||
Self { irq, sm }
|
||||
}
|
||||
|
||||
/// Set pulse frequency
|
||||
pub fn set_frequency(&mut self, freq: u32) {
|
||||
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
|
||||
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
|
||||
assert!(clock_divider >= 1, "clkdiv must be >= 1");
|
||||
self.sm.set_clock_divider(clock_divider);
|
||||
self.sm.clkdiv_restart();
|
||||
}
|
||||
|
||||
/// Full step, one phase
|
||||
pub async fn step(&mut self, steps: i32) {
|
||||
if steps > 0 {
|
||||
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
|
||||
} else {
|
||||
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Full step, two phase
|
||||
pub async fn step2(&mut self, steps: i32) {
|
||||
if steps > 0 {
|
||||
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
|
||||
} else {
|
||||
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Half step
|
||||
pub async fn step_half(&mut self, steps: i32) {
|
||||
if steps > 0 {
|
||||
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
|
||||
} else {
|
||||
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self, steps: i32, pattern: u32) {
|
||||
self.sm.tx().wait_push(steps as u32).await;
|
||||
self.sm.tx().wait_push(pattern).await;
|
||||
let drop = OnDrop::new(|| {
|
||||
self.sm.clear_fifos();
|
||||
unsafe {
|
||||
self.sm.exec_instr(
|
||||
pio::InstructionOperands::JMP {
|
||||
address: 0,
|
||||
condition: pio::JmpCondition::Always,
|
||||
}
|
||||
.encode(),
|
||||
);
|
||||
}
|
||||
});
|
||||
self.irq.wait().await;
|
||||
drop.defuse();
|
||||
}
|
||||
}
|
||||
|
||||
struct OnDrop<F: FnOnce()> {
|
||||
f: MaybeUninit<F>,
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> OnDrop<F> {
|
||||
pub fn new(f: F) -> Self {
|
||||
Self { f: MaybeUninit::new(f) }
|
||||
}
|
||||
|
||||
pub fn defuse(self) {
|
||||
mem::forget(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.f.as_ptr().read()() }
|
||||
}
|
||||
}
|
185
embassy-rp/src/pio_programs/uart.rs
Normal file
185
embassy-rp/src/pio_programs/uart.rs
Normal file
@ -0,0 +1,185 @@
|
||||
//! Pio backed uart drivers
|
||||
|
||||
use core::convert::Infallible;
|
||||
|
||||
use embedded_io_async::{ErrorType, Read, Write};
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
use crate::clocks::clk_sys_freq;
|
||||
use crate::gpio::Level;
|
||||
use crate::pio::{
|
||||
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
|
||||
};
|
||||
|
||||
/// This struct represents a uart tx program loaded into pio instruction memory.
|
||||
pub struct PioUartTxProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
|
||||
/// Load the uart tx program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
r#"
|
||||
.side_set 1 opt
|
||||
|
||||
; An 8n1 UART transmit program.
|
||||
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
|
||||
|
||||
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
|
||||
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
|
||||
bitloop: ; This loop will run 8 times (8n1 UART)
|
||||
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
|
||||
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
|
||||
"#
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// PIO backed Uart transmitter
|
||||
pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
|
||||
sm_tx: StateMachine<'a, PIO, SM>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
|
||||
/// Configure a pio state machine to use the loaded tx program.
|
||||
pub fn new(
|
||||
baud: u32,
|
||||
common: &mut Common<'a, PIO>,
|
||||
mut sm_tx: StateMachine<'a, PIO, SM>,
|
||||
tx_pin: impl PioPin,
|
||||
program: &PioUartTxProgram<'a, PIO>,
|
||||
) -> Self {
|
||||
let tx_pin = common.make_pio_pin(tx_pin);
|
||||
sm_tx.set_pins(Level::High, &[&tx_pin]);
|
||||
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
|
||||
cfg.set_out_pins(&[&tx_pin]);
|
||||
cfg.use_program(&program.prg, &[&tx_pin]);
|
||||
cfg.shift_out.auto_fill = false;
|
||||
cfg.shift_out.direction = ShiftDirection::Right;
|
||||
cfg.fifo_join = FifoJoin::TxOnly;
|
||||
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
|
||||
sm_tx.set_config(&cfg);
|
||||
sm_tx.set_enable(true);
|
||||
|
||||
Self { sm_tx }
|
||||
}
|
||||
|
||||
/// Write a single u8
|
||||
pub async fn write_u8(&mut self, data: u8) {
|
||||
self.sm_tx.tx().wait_push(data as u32).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
|
||||
for byte in buf {
|
||||
self.write_u8(*byte).await;
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents a Uart Rx program loaded into pio instruction memory.
|
||||
pub struct PioUartRxProgram<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
|
||||
/// Load the uart rx program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let prg = pio_proc::pio_asm!(
|
||||
r#"
|
||||
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
|
||||
; break conditions more gracefully.
|
||||
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
|
||||
|
||||
start:
|
||||
wait 0 pin 0 ; Stall until start bit is asserted
|
||||
set x, 7 [10] ; Preload bit counter, then delay until halfway through
|
||||
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
|
||||
in pins, 1 ; Shift data bit into ISR
|
||||
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
|
||||
jmp pin good_rx_stop ; Check stop bit (should be high)
|
||||
|
||||
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
|
||||
wait 1 pin 0 ; and wait for line to return to idle state.
|
||||
jmp start ; Don't push data if we didn't see good framing.
|
||||
|
||||
good_rx_stop: ; No delay before returning to start; a little slack is
|
||||
in null 24
|
||||
push ; important in case the TX clock is slightly too fast.
|
||||
"#
|
||||
);
|
||||
|
||||
let prg = common.load_program(&prg.program);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// PIO backed Uart reciever
|
||||
pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
|
||||
sm_rx: StateMachine<'a, PIO, SM>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
|
||||
/// Configure a pio state machine to use the loaded rx program.
|
||||
pub fn new(
|
||||
baud: u32,
|
||||
common: &mut Common<'a, PIO>,
|
||||
mut sm_rx: StateMachine<'a, PIO, SM>,
|
||||
rx_pin: impl PioPin,
|
||||
program: &PioUartRxProgram<'a, PIO>,
|
||||
) -> Self {
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&program.prg, &[]);
|
||||
|
||||
let rx_pin = common.make_pio_pin(rx_pin);
|
||||
sm_rx.set_pins(Level::High, &[&rx_pin]);
|
||||
cfg.set_in_pins(&[&rx_pin]);
|
||||
cfg.set_jmp_pin(&rx_pin);
|
||||
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
|
||||
|
||||
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
|
||||
cfg.shift_in.auto_fill = false;
|
||||
cfg.shift_in.direction = ShiftDirection::Right;
|
||||
cfg.shift_in.threshold = 32;
|
||||
cfg.fifo_join = FifoJoin::RxOnly;
|
||||
sm_rx.set_config(&cfg);
|
||||
sm_rx.set_enable(true);
|
||||
|
||||
Self { sm_rx }
|
||||
}
|
||||
|
||||
/// Wait for a single u8
|
||||
pub async fn read_u8(&mut self) -> u8 {
|
||||
self.sm_rx.rx().wait_pull().await as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
|
||||
let mut i = 0;
|
||||
while i < buf.len() {
|
||||
buf[i] = self.read_u8().await;
|
||||
i += 1;
|
||||
}
|
||||
Ok(i)
|
||||
}
|
||||
}
|
118
embassy-rp/src/pio_programs/ws2812.rs
Normal file
118
embassy-rp/src/pio_programs/ws2812.rs
Normal file
@ -0,0 +1,118 @@
|
||||
//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
|
||||
|
||||
use embassy_time::Timer;
|
||||
use fixed::types::U24F8;
|
||||
use smart_leds::RGB8;
|
||||
|
||||
use crate::clocks::clk_sys_freq;
|
||||
use crate::dma::{AnyChannel, Channel};
|
||||
use crate::pio::{
|
||||
Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
||||
};
|
||||
use crate::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
const T1: u8 = 2; // start bit
|
||||
const T2: u8 = 5; // data bit
|
||||
const T3: u8 = 3; // stop bit
|
||||
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
|
||||
|
||||
/// This struct represents a ws2812 program loaded into pio instruction memory.
|
||||
pub struct PioWs2812Program<'a, PIO: Instance> {
|
||||
prg: LoadedProgram<'a, PIO>,
|
||||
}
|
||||
|
||||
impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
|
||||
/// Load the ws2812 program into the given pio
|
||||
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||
let side_set = pio::SideSet::new(false, 1, false);
|
||||
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
|
||||
|
||||
let mut wrap_target = a.label();
|
||||
let mut wrap_source = a.label();
|
||||
let mut do_zero = a.label();
|
||||
a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
|
||||
a.bind(&mut wrap_target);
|
||||
// Do stop bit
|
||||
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
|
||||
// Do start bit
|
||||
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
|
||||
// Do data bit = 1
|
||||
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
|
||||
a.bind(&mut do_zero);
|
||||
// Do data bit = 0
|
||||
a.nop_with_delay_and_side_set(T2 - 1, 0);
|
||||
a.bind(&mut wrap_source);
|
||||
|
||||
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
|
||||
let prg = common.load_program(&prg);
|
||||
|
||||
Self { prg }
|
||||
}
|
||||
}
|
||||
|
||||
/// Pio backed ws2812 driver
|
||||
/// Const N is the number of ws2812 leds attached to this pin
|
||||
pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
|
||||
dma: PeripheralRef<'d, AnyChannel>,
|
||||
sm: StateMachine<'d, P, S>,
|
||||
}
|
||||
|
||||
impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
|
||||
/// Configure a pio state machine to use the loaded ws2812 program.
|
||||
pub fn new(
|
||||
pio: &mut Common<'d, P>,
|
||||
mut sm: StateMachine<'d, P, S>,
|
||||
dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
pin: impl PioPin,
|
||||
program: &PioWs2812Program<'d, P>,
|
||||
) -> Self {
|
||||
into_ref!(dma);
|
||||
|
||||
// Setup sm0
|
||||
let mut cfg = Config::default();
|
||||
|
||||
// Pin config
|
||||
let out_pin = pio.make_pio_pin(pin);
|
||||
cfg.set_out_pins(&[&out_pin]);
|
||||
cfg.set_set_pins(&[&out_pin]);
|
||||
|
||||
cfg.use_program(&program.prg, &[&out_pin]);
|
||||
|
||||
// Clock config, measured in kHz to avoid overflows
|
||||
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
|
||||
let ws2812_freq = U24F8::from_num(800);
|
||||
let bit_freq = ws2812_freq * CYCLES_PER_BIT;
|
||||
cfg.clock_divider = clock_freq / bit_freq;
|
||||
|
||||
// FIFO config
|
||||
cfg.fifo_join = FifoJoin::TxOnly;
|
||||
cfg.shift_out = ShiftConfig {
|
||||
auto_fill: true,
|
||||
threshold: 24,
|
||||
direction: ShiftDirection::Left,
|
||||
};
|
||||
|
||||
sm.set_config(&cfg);
|
||||
sm.set_enable(true);
|
||||
|
||||
Self {
|
||||
dma: dma.map_into(),
|
||||
sm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a buffer of [smart_leds::RGB8] to the ws2812 string
|
||||
pub async fn write(&mut self, colors: &[RGB8; N]) {
|
||||
// Precompute the word bytes from the colors
|
||||
let mut words = [0u32; N];
|
||||
for i in 0..N {
|
||||
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
|
||||
words[i] = word;
|
||||
}
|
||||
|
||||
// DMA transfer
|
||||
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
|
||||
|
||||
Timer::after_micros(55).await;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
//! Pulse Width Modulation (PWM)
|
||||
|
||||
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||
pub use embedded_hal_1::pwm::SetDutyCycle;
|
||||
use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType};
|
||||
use fixed::traits::ToFixed;
|
||||
use fixed::FixedU16;
|
||||
use pac::pwm::regs::{ChDiv, Intr};
|
||||
@ -80,6 +82,21 @@ impl From<InputMode> for Divmode {
|
||||
}
|
||||
}
|
||||
|
||||
/// PWM error.
|
||||
#[derive(Debug)]
|
||||
pub enum PwmError {
|
||||
/// Invalid Duty Cycle.
|
||||
InvalidDutyCycle,
|
||||
}
|
||||
|
||||
impl Error for PwmError {
|
||||
fn kind(&self) -> ErrorKind {
|
||||
match self {
|
||||
PwmError::InvalidDutyCycle => ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PWM driver.
|
||||
pub struct Pwm<'d> {
|
||||
pin_a: Option<PeripheralRef<'d, AnyPin>>,
|
||||
@ -87,6 +104,30 @@ pub struct Pwm<'d> {
|
||||
slice: usize,
|
||||
}
|
||||
|
||||
impl<'d> ErrorType for Pwm<'d> {
|
||||
type Error = PwmError;
|
||||
}
|
||||
|
||||
impl<'d> SetDutyCycle for Pwm<'d> {
|
||||
fn max_duty_cycle(&self) -> u16 {
|
||||
pac::PWM.ch(self.slice).top().read().top()
|
||||
}
|
||||
|
||||
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||
let max_duty = self.max_duty_cycle();
|
||||
if duty > max_duty {
|
||||
return Err(PwmError::InvalidDutyCycle);
|
||||
}
|
||||
|
||||
let p = pac::PWM.ch(self.slice);
|
||||
p.cc().modify(|w| {
|
||||
w.set_a(duty);
|
||||
w.set_b(duty);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Pwm<'d> {
|
||||
fn new_inner(
|
||||
slice: usize,
|
||||
@ -303,6 +344,119 @@ impl<'d> Pwm<'d> {
|
||||
fn bit(&self) -> u32 {
|
||||
1 << self.slice as usize
|
||||
}
|
||||
|
||||
/// Splits the PWM driver into separate `PwmOutput` instances for channels A and B.
|
||||
#[inline]
|
||||
pub fn split(mut self) -> (Option<PwmOutput<'d>>, Option<PwmOutput<'d>>) {
|
||||
(
|
||||
self.pin_a
|
||||
.take()
|
||||
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)),
|
||||
self.pin_b
|
||||
.take()
|
||||
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)),
|
||||
)
|
||||
}
|
||||
/// Splits the PWM driver by reference to allow for separate duty cycle control
|
||||
/// of each channel (A and B) without taking ownership of the PWM instance.
|
||||
#[inline]
|
||||
pub fn split_by_ref(&mut self) -> (Option<PwmOutput<'_>>, Option<PwmOutput<'_>>) {
|
||||
(
|
||||
self.pin_a
|
||||
.as_mut()
|
||||
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)),
|
||||
self.pin_b
|
||||
.as_mut()
|
||||
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum PwmChannelPin<'d> {
|
||||
A(PeripheralRef<'d, AnyPin>),
|
||||
B(PeripheralRef<'d, AnyPin>),
|
||||
}
|
||||
|
||||
/// Single channel of Pwm driver.
|
||||
pub struct PwmOutput<'d> {
|
||||
//pin that can be ether ChannelAPin or ChannelBPin
|
||||
channel_pin: PwmChannelPin<'d>,
|
||||
slice: usize,
|
||||
is_owned: bool,
|
||||
}
|
||||
|
||||
impl<'d> PwmOutput<'d> {
|
||||
fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self {
|
||||
Self {
|
||||
channel_pin,
|
||||
slice,
|
||||
is_owned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Drop for PwmOutput<'d> {
|
||||
fn drop(&mut self) {
|
||||
if self.is_owned {
|
||||
let p = pac::PWM.ch(self.slice);
|
||||
match &self.channel_pin {
|
||||
PwmChannelPin::A(pin) => {
|
||||
p.cc().modify(|w| {
|
||||
w.set_a(0);
|
||||
});
|
||||
|
||||
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
|
||||
//Enable pin PULL-DOWN
|
||||
pin.pad_ctrl().modify(|w| {
|
||||
w.set_pde(true);
|
||||
});
|
||||
}
|
||||
PwmChannelPin::B(pin) => {
|
||||
p.cc().modify(|w| {
|
||||
w.set_b(0);
|
||||
});
|
||||
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
|
||||
//Enable pin PULL-DOWN
|
||||
pin.pad_ctrl().modify(|w| {
|
||||
w.set_pde(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ErrorType for PwmOutput<'d> {
|
||||
type Error = PwmError;
|
||||
}
|
||||
|
||||
impl<'d> SetDutyCycle for PwmOutput<'d> {
|
||||
fn max_duty_cycle(&self) -> u16 {
|
||||
pac::PWM.ch(self.slice).top().read().top()
|
||||
}
|
||||
|
||||
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||
let max_duty = self.max_duty_cycle();
|
||||
if duty > max_duty {
|
||||
return Err(PwmError::InvalidDutyCycle);
|
||||
}
|
||||
|
||||
let p = pac::PWM.ch(self.slice);
|
||||
match self.channel_pin {
|
||||
PwmChannelPin::A(_) => {
|
||||
p.cc().modify(|w| {
|
||||
w.set_a(duty);
|
||||
});
|
||||
}
|
||||
PwmChannelPin::B(_) => {
|
||||
p.cc().modify(|w| {
|
||||
w.set_b(duty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch representation of PWM slices.
|
||||
|
@ -359,17 +359,18 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
|
||||
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
|
||||
tx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(rx_dma, clk, miso);
|
||||
into_ref!(tx_dma, rx_dma, clk, miso);
|
||||
Self::new_inner(
|
||||
inner,
|
||||
Some(clk.map_into()),
|
||||
None,
|
||||
Some(miso.map_into()),
|
||||
None,
|
||||
None,
|
||||
Some(tx_dma.map_into()),
|
||||
Some(rx_dma.map_into()),
|
||||
config,
|
||||
)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
@ -55,7 +55,7 @@ fn main() {
|
||||
let mut singletons: Vec<String> = Vec::new();
|
||||
for p in METADATA.peripherals {
|
||||
if let Some(r) = &p.registers {
|
||||
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" {
|
||||
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" || r.kind == "octospi" {
|
||||
// TODO: should we emit this for all peripherals? if so, we will need a list of all
|
||||
// possible peripherals across all chips, so that we can declare the configs
|
||||
// (replacing the hard-coded list of `peri_*` cfgs below)
|
||||
@ -113,6 +113,7 @@ fn main() {
|
||||
"peri_ucpd2",
|
||||
"peri_usb_otg_fs",
|
||||
"peri_usb_otg_hs",
|
||||
"peri_octospi2",
|
||||
]);
|
||||
cfgs.declare_all(&["mco", "mco1", "mco2"]);
|
||||
|
||||
@ -1067,6 +1068,30 @@ fn main() {
|
||||
(("octospi", "NCS"), quote!(crate::ospi::NSSPin)),
|
||||
(("octospi", "CLK"), quote!(crate::ospi::SckPin)),
|
||||
(("octospi", "NCLK"), quote!(crate::ospi::NckPin)),
|
||||
(("octospim", "P1_IO0"), quote!(crate::ospi::D0Pin)),
|
||||
(("octospim", "P1_IO1"), quote!(crate::ospi::D1Pin)),
|
||||
(("octospim", "P1_IO2"), quote!(crate::ospi::D2Pin)),
|
||||
(("octospim", "P1_IO3"), quote!(crate::ospi::D3Pin)),
|
||||
(("octospim", "P1_IO4"), quote!(crate::ospi::D4Pin)),
|
||||
(("octospim", "P1_IO5"), quote!(crate::ospi::D5Pin)),
|
||||
(("octospim", "P1_IO6"), quote!(crate::ospi::D6Pin)),
|
||||
(("octospim", "P1_IO7"), quote!(crate::ospi::D7Pin)),
|
||||
(("octospim", "P1_DQS"), quote!(crate::ospi::DQSPin)),
|
||||
(("octospim", "P1_NCS"), quote!(crate::ospi::NSSPin)),
|
||||
(("octospim", "P1_CLK"), quote!(crate::ospi::SckPin)),
|
||||
(("octospim", "P1_NCLK"), quote!(crate::ospi::NckPin)),
|
||||
(("octospim", "P2_IO0"), quote!(crate::ospi::D0Pin)),
|
||||
(("octospim", "P2_IO1"), quote!(crate::ospi::D1Pin)),
|
||||
(("octospim", "P2_IO2"), quote!(crate::ospi::D2Pin)),
|
||||
(("octospim", "P2_IO3"), quote!(crate::ospi::D3Pin)),
|
||||
(("octospim", "P2_IO4"), quote!(crate::ospi::D4Pin)),
|
||||
(("octospim", "P2_IO5"), quote!(crate::ospi::D5Pin)),
|
||||
(("octospim", "P2_IO6"), quote!(crate::ospi::D6Pin)),
|
||||
(("octospim", "P2_IO7"), quote!(crate::ospi::D7Pin)),
|
||||
(("octospim", "P2_DQS"), quote!(crate::ospi::DQSPin)),
|
||||
(("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)),
|
||||
(("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)),
|
||||
(("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)),
|
||||
(("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)),
|
||||
(("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)),
|
||||
(("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)),
|
||||
@ -1124,6 +1149,18 @@ fn main() {
|
||||
peri = format_ident!("{}", pin.signal.replace('_', ""));
|
||||
}
|
||||
|
||||
// OCTOSPIM is special
|
||||
if p.name == "OCTOSPIM" {
|
||||
// Some chips have OCTOSPIM but not OCTOSPI2.
|
||||
if METADATA.peripherals.iter().any(|p| p.name == "OCTOSPI2") {
|
||||
peri = format_ident!("{}", "OCTOSPI2");
|
||||
g.extend(quote! {
|
||||
pin_trait_impl!(#tr, #peri, #pin_name, #af);
|
||||
});
|
||||
}
|
||||
peri = format_ident!("{}", "OCTOSPI1");
|
||||
}
|
||||
|
||||
g.extend(quote! {
|
||||
pin_trait_impl!(#tr, #peri, #pin_name, #af);
|
||||
})
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker};
|
||||
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer};
|
||||
use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::{AnyChannel, Channel, Dir, Request, STATE};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
@ -299,7 +299,6 @@ impl AnyChannel {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
state.waker.wake();
|
||||
}
|
||||
#[cfg(bdma)]
|
||||
@ -763,10 +762,6 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> {
|
||||
self.0.get_remaining_transfers() as _
|
||||
}
|
||||
|
||||
fn get_complete_count(&self) -> usize {
|
||||
STATE[self.0.id as usize].complete_count.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn reset_complete_count(&mut self) -> usize {
|
||||
let state = &STATE[self.0.id as usize];
|
||||
#[cfg(not(armv6m))]
|
||||
@ -832,27 +827,28 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
|
||||
///
|
||||
/// You must call this after creating it for it to work.
|
||||
pub fn start(&mut self) {
|
||||
self.channel.start()
|
||||
self.channel.start();
|
||||
self.clear();
|
||||
}
|
||||
|
||||
/// Clear all data in the ring buffer.
|
||||
pub fn clear(&mut self) {
|
||||
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
|
||||
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow()));
|
||||
}
|
||||
|
||||
/// Read elements from the ring buffer
|
||||
/// Return a tuple of the length read and the length remaining in the buffer
|
||||
/// If not all of the elements were read, then there will be some elements in the buffer remaining
|
||||
/// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
|
||||
/// Error is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf)
|
||||
}
|
||||
|
||||
/// Read an exact number of elements from the ringbuffer.
|
||||
///
|
||||
/// Returns the remaining number of elements available for immediate reading.
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
/// Error is returned if the portion to be read was overwritten by the DMA controller.
|
||||
///
|
||||
/// Async/Wake Behavior:
|
||||
/// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
|
||||
@ -860,12 +856,17 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
|
||||
/// ring buffer was created with a buffer of size 'N':
|
||||
/// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
|
||||
/// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
|
||||
pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, OverrunError> {
|
||||
pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, Error> {
|
||||
self.ringbuf
|
||||
.read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The current length of the ringbuffer
|
||||
pub fn len(&mut self) -> Result<usize, Error> {
|
||||
Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?)
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn capacity(&self) -> usize {
|
||||
self.ringbuf.cap()
|
||||
@ -979,34 +980,40 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> {
|
||||
///
|
||||
/// You must call this after creating it for it to work.
|
||||
pub fn start(&mut self) {
|
||||
self.channel.start()
|
||||
self.channel.start();
|
||||
self.clear();
|
||||
}
|
||||
|
||||
/// Clear all data in the ring buffer.
|
||||
pub fn clear(&mut self) {
|
||||
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
|
||||
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow()));
|
||||
}
|
||||
|
||||
/// Write elements directly to the raw buffer.
|
||||
/// This can be used to fill the buffer before starting the DMA transfer.
|
||||
#[allow(dead_code)]
|
||||
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
|
||||
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf.write_immediate(buf)
|
||||
}
|
||||
|
||||
/// Write elements from the ring buffer
|
||||
/// Return a tuple of the length written and the length remaining in the buffer
|
||||
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
|
||||
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf)
|
||||
}
|
||||
|
||||
/// Write an exact number of elements to the ringbuffer.
|
||||
pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, OverrunError> {
|
||||
pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, Error> {
|
||||
self.ringbuf
|
||||
.write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The current length of the ringbuffer
|
||||
pub fn len(&mut self) -> Result<usize, Error> {
|
||||
Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?)
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn capacity(&self) -> usize {
|
||||
self.ringbuf.cap()
|
||||
|
@ -1,668 +0,0 @@
|
||||
#![cfg_attr(gpdma, allow(unused))]
|
||||
|
||||
use core::future::poll_fn;
|
||||
use core::ops::Range;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::{Poll, Waker};
|
||||
|
||||
use super::word::Word;
|
||||
|
||||
/// A "read-only" ring-buffer to be used together with the DMA controller which
|
||||
/// writes in a circular way, "uncontrolled" to the buffer.
|
||||
///
|
||||
/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field
|
||||
/// to the current register value. `ndtr` describes the current position of the DMA
|
||||
/// write.
|
||||
///
|
||||
/// # Buffer layout
|
||||
///
|
||||
/// ```text
|
||||
/// Without wraparound: With wraparound:
|
||||
///
|
||||
/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+
|
||||
/// | | | | | |
|
||||
/// v v v v v v
|
||||
/// +-----------------------------------------+ +-----------------------------------------+
|
||||
/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX|
|
||||
/// +-----------------------------------------+ +-----------------------------------------+
|
||||
/// ^ ^ ^ ^ ^ ^
|
||||
/// | | | | | |
|
||||
/// +- start --+ | +- end ------+ |
|
||||
/// | | | |
|
||||
/// +- end --------------------+ +- start ----------------+
|
||||
/// ```
|
||||
pub struct ReadableDmaRingBuffer<'a, W: Word> {
|
||||
pub(crate) dma_buf: &'a mut [W],
|
||||
start: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct OverrunError;
|
||||
|
||||
pub trait DmaCtrl {
|
||||
/// Get the NDTR register value, i.e. the space left in the underlying
|
||||
/// buffer until the dma writer wraps.
|
||||
fn get_remaining_transfers(&self) -> usize;
|
||||
|
||||
/// Get the transfer completed counter.
|
||||
/// This counter is incremented by the dma controller when NDTR is reloaded,
|
||||
/// i.e. when the writing wraps.
|
||||
fn get_complete_count(&self) -> usize;
|
||||
|
||||
/// Reset the transfer completed counter to 0 and return the value just prior to the reset.
|
||||
fn reset_complete_count(&mut self) -> usize;
|
||||
|
||||
/// Set the waker for a running poll_fn
|
||||
fn set_waker(&mut self, waker: &Waker);
|
||||
}
|
||||
|
||||
impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> {
|
||||
pub fn new(dma_buf: &'a mut [W]) -> Self {
|
||||
Self { dma_buf, start: 0 }
|
||||
}
|
||||
|
||||
/// Reset the ring buffer to its initial state
|
||||
pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
|
||||
self.start = 0;
|
||||
dma.reset_complete_count();
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn cap(&self) -> usize {
|
||||
self.dma_buf.len()
|
||||
}
|
||||
|
||||
/// The current position of the ringbuffer
|
||||
fn pos(&self, dma: &mut impl DmaCtrl) -> usize {
|
||||
self.cap() - dma.get_remaining_transfers()
|
||||
}
|
||||
|
||||
/// Read an exact number of elements from the ringbuffer.
|
||||
///
|
||||
/// Returns the remaining number of elements available for immediate reading.
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
///
|
||||
/// Async/Wake Behavior:
|
||||
/// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
|
||||
/// and when it wraps around. This means that when called with a buffer of length 'M', when this
|
||||
/// ring buffer was created with a buffer of size 'N':
|
||||
/// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
|
||||
/// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
|
||||
pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result<usize, OverrunError> {
|
||||
let mut read_data = 0;
|
||||
let buffer_len = buffer.len();
|
||||
|
||||
poll_fn(|cx| {
|
||||
dma.set_waker(cx.waker());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
match self.read(dma, &mut buffer[read_data..buffer_len]) {
|
||||
Ok((len, remaining)) => {
|
||||
read_data += len;
|
||||
if read_data == buffer_len {
|
||||
Poll::Ready(Ok(remaining))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Read elements from the ring buffer
|
||||
/// Return a tuple of the length read and the length remaining in the buffer
|
||||
/// If not all of the elements were read, then there will be some elements in the buffer remaining
|
||||
/// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
|
||||
/*
|
||||
This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check
|
||||
after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed
|
||||
to fire in the same clock cycle that a register is read, so checking get_complete_count early does
|
||||
not yield relevant information.
|
||||
|
||||
Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full
|
||||
buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error
|
||||
conditions.
|
||||
|
||||
After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that
|
||||
the dma has not overrun within the data we could have copied. We check the data we could have copied
|
||||
rather than the data we actually copied because it costs nothing and confirms an error condition
|
||||
earlier.
|
||||
*/
|
||||
let end = self.pos(dma);
|
||||
if self.start == end && dma.get_complete_count() == 0 {
|
||||
// No elements are available in the buffer
|
||||
Ok((0, self.cap()))
|
||||
} else if self.start < end {
|
||||
// The available, unread portion in the ring buffer DOES NOT wrap
|
||||
// Copy out the elements from the dma buffer
|
||||
let len = self.copy_to(buf, self.start..end);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
/*
|
||||
first, check if the dma has wrapped at all if it's after end
|
||||
or more than once if it's before start
|
||||
|
||||
this is in a critical section to try to reduce mushy behavior.
|
||||
it's not ideal but it's the best we can do
|
||||
|
||||
then, get the current position of of the dma write and check
|
||||
if it's inside data we could have copied
|
||||
*/
|
||||
let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count()));
|
||||
if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.start = (self.start + len) % self.cap();
|
||||
|
||||
Ok((len, self.cap() - self.start))
|
||||
}
|
||||
} else if self.start + buf.len() < self.cap() {
|
||||
// The available, unread portion in the ring buffer DOES wrap
|
||||
// The DMA writer has wrapped since we last read and is currently
|
||||
// writing (or the next byte added will be) in the beginning of the ring buffer.
|
||||
|
||||
// The provided read buffer is not large enough to include all elements from the tail of the dma buffer.
|
||||
|
||||
// Copy out from the dma buffer
|
||||
let len = self.copy_to(buf, self.start..self.cap());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
/*
|
||||
first, check if the dma has wrapped around more than once
|
||||
|
||||
then, get the current position of of the dma write and check
|
||||
if it's inside data we could have copied
|
||||
*/
|
||||
let pos = self.pos(dma);
|
||||
if pos > self.start || pos < end || dma.get_complete_count() > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.start = (self.start + len) % self.cap();
|
||||
|
||||
Ok((len, self.start + end))
|
||||
}
|
||||
} else {
|
||||
// The available, unread portion in the ring buffer DOES wrap
|
||||
// The DMA writer has wrapped since we last read and is currently
|
||||
// writing (or the next byte added will be) in the beginning of the ring buffer.
|
||||
|
||||
// The provided read buffer is large enough to include all elements from the tail of the dma buffer,
|
||||
// so the next read will not have any unread tail elements in the ring buffer.
|
||||
|
||||
// Copy out from the dma buffer
|
||||
let tail = self.copy_to(buf, self.start..self.cap());
|
||||
let head = self.copy_to(&mut buf[tail..], 0..end);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
/*
|
||||
first, check if the dma has wrapped around more than once
|
||||
|
||||
then, get the current position of of the dma write and check
|
||||
if it's inside data we could have copied
|
||||
*/
|
||||
let pos = self.pos(dma);
|
||||
if pos > self.start || pos < end || dma.reset_complete_count() > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.start = head;
|
||||
Ok((tail + head, self.cap() - self.start))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Copy from the dma buffer at `data_range` into `buf`
|
||||
fn copy_to(&mut self, buf: &mut [W], data_range: Range<usize>) -> usize {
|
||||
// Limit the number of elements that can be copied
|
||||
let length = usize::min(data_range.len(), buf.len());
|
||||
|
||||
// Copy from dma buffer into read buffer
|
||||
// We need to do it like this instead of a simple copy_from_slice() because
|
||||
// reading from a part of memory that may be simultaneously written to is unsafe
|
||||
unsafe {
|
||||
let dma_buf = self.dma_buf.as_ptr();
|
||||
|
||||
for i in 0..length {
|
||||
buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize));
|
||||
}
|
||||
}
|
||||
|
||||
length
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WritableDmaRingBuffer<'a, W: Word> {
|
||||
pub(crate) dma_buf: &'a mut [W],
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl<'a, W: Word> WritableDmaRingBuffer<'a, W> {
|
||||
pub fn new(dma_buf: &'a mut [W]) -> Self {
|
||||
Self { dma_buf, end: 0 }
|
||||
}
|
||||
|
||||
/// Reset the ring buffer to its initial state
|
||||
pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
|
||||
self.end = 0;
|
||||
dma.reset_complete_count();
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn cap(&self) -> usize {
|
||||
self.dma_buf.len()
|
||||
}
|
||||
|
||||
/// The current position of the ringbuffer
|
||||
fn pos(&self, dma: &mut impl DmaCtrl) -> usize {
|
||||
self.cap() - dma.get_remaining_transfers()
|
||||
}
|
||||
|
||||
/// Write elements directly to the buffer. This must be done before the DMA is started
|
||||
/// or after the buffer has been cleared using `clear()`.
|
||||
pub fn write_immediate(&mut self, buffer: &[W]) -> Result<(usize, usize), OverrunError> {
|
||||
if self.end != 0 {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
let written = self.copy_from(buffer, 0..self.cap());
|
||||
self.end = written % self.cap();
|
||||
Ok((written, self.cap() - written))
|
||||
}
|
||||
|
||||
/// Write an exact number of elements to the ringbuffer.
|
||||
pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result<usize, OverrunError> {
|
||||
let mut written_data = 0;
|
||||
let buffer_len = buffer.len();
|
||||
|
||||
poll_fn(|cx| {
|
||||
dma.set_waker(cx.waker());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
match self.write(dma, &buffer[written_data..buffer_len]) {
|
||||
Ok((len, remaining)) => {
|
||||
written_data += len;
|
||||
if written_data == buffer_len {
|
||||
Poll::Ready(Ok(remaining))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Write elements from the ring buffer
|
||||
/// Return a tuple of the length written and the capacity remaining to be written in the buffer
|
||||
pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> {
|
||||
let start = self.pos(dma);
|
||||
if start > self.end {
|
||||
// The occupied portion in the ring buffer DOES wrap
|
||||
let len = self.copy_from(buf, self.end..start);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// Confirm that the DMA is not inside data we could have written
|
||||
let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count()));
|
||||
if (pos >= self.end && pos < start) || (complete_count > 0 && pos >= start) || complete_count > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.end = (self.end + len) % self.cap();
|
||||
|
||||
Ok((len, self.cap() - (start - self.end)))
|
||||
}
|
||||
} else if start == self.end && dma.get_complete_count() == 0 {
|
||||
Ok((0, 0))
|
||||
} else if start <= self.end && self.end + buf.len() < self.cap() {
|
||||
// The occupied portion in the ring buffer DOES NOT wrap
|
||||
// and copying elements into the buffer WILL NOT cause it to
|
||||
|
||||
// Copy into the dma buffer
|
||||
let len = self.copy_from(buf, self.end..self.cap());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// Confirm that the DMA is not inside data we could have written
|
||||
let pos = self.pos(dma);
|
||||
if pos > self.end || pos < start || dma.get_complete_count() > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.end = (self.end + len) % self.cap();
|
||||
|
||||
Ok((len, self.cap() - (self.end - start)))
|
||||
}
|
||||
} else {
|
||||
// The occupied portion in the ring buffer DOES NOT wrap
|
||||
// and copying elements into the buffer WILL cause it to
|
||||
|
||||
let tail = self.copy_from(buf, self.end..self.cap());
|
||||
let head = self.copy_from(&buf[tail..], 0..start);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// Confirm that the DMA is not inside data we could have written
|
||||
let pos = self.pos(dma);
|
||||
if pos > self.end || pos < start || dma.reset_complete_count() > 1 {
|
||||
Err(OverrunError)
|
||||
} else {
|
||||
self.end = head;
|
||||
|
||||
Ok((tail + head, self.cap() - (start - self.end)))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Copy into the dma buffer at `data_range` from `buf`
|
||||
fn copy_from(&mut self, buf: &[W], data_range: Range<usize>) -> usize {
|
||||
// Limit the number of elements that can be copied
|
||||
let length = usize::min(data_range.len(), buf.len());
|
||||
|
||||
// Copy into dma buffer from read buffer
|
||||
// We need to do it like this instead of a simple copy_from_slice() because
|
||||
// reading from a part of memory that may be simultaneously written to is unsafe
|
||||
unsafe {
|
||||
let dma_buf = self.dma_buf.as_mut_ptr();
|
||||
|
||||
for i in 0..length {
|
||||
core::ptr::write_volatile(dma_buf.offset((data_range.start + i) as isize), buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
length
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::array;
|
||||
use std::{cell, vec};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum TestCircularTransferRequest {
|
||||
GetCompleteCount(usize),
|
||||
ResetCompleteCount(usize),
|
||||
PositionRequest(usize),
|
||||
}
|
||||
|
||||
struct TestCircularTransfer {
|
||||
len: usize,
|
||||
requests: cell::RefCell<vec::Vec<TestCircularTransferRequest>>,
|
||||
}
|
||||
|
||||
impl DmaCtrl for TestCircularTransfer {
|
||||
fn get_remaining_transfers(&self) -> usize {
|
||||
match self.requests.borrow_mut().pop().unwrap() {
|
||||
TestCircularTransferRequest::PositionRequest(pos) => {
|
||||
let len = self.len;
|
||||
|
||||
assert!(len >= pos);
|
||||
|
||||
len - pos
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_complete_count(&self) -> usize {
|
||||
match self.requests.borrow_mut().pop().unwrap() {
|
||||
TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_complete_count(&mut self) -> usize {
|
||||
match self.requests.get_mut().pop().unwrap() {
|
||||
TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {}
|
||||
}
|
||||
|
||||
impl TestCircularTransfer {
|
||||
pub fn new(len: usize) -> Self {
|
||||
Self {
|
||||
requests: cell::RefCell::new(vec![]),
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(&self, mut requests: vec::Vec<TestCircularTransferRequest>) {
|
||||
requests.reverse();
|
||||
self.requests.replace(requests);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_and_read_not_started() {
|
||||
let mut dma_buf = [0u8; 16];
|
||||
let ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(8),
|
||||
TestCircularTransferRequest::PositionRequest(10),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!([0, 1], buf);
|
||||
assert_eq!(2, ringbuf.start);
|
||||
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(10),
|
||||
TestCircularTransferRequest::PositionRequest(12),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!([2, 3], buf);
|
||||
assert_eq!(4, ringbuf.start);
|
||||
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(12),
|
||||
TestCircularTransferRequest::PositionRequest(14),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 8];
|
||||
assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]);
|
||||
assert_eq!(12, ringbuf.start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_with_wrap() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
/*
|
||||
Read to close to the end of the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(14),
|
||||
TestCircularTransferRequest::PositionRequest(16),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 14];
|
||||
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(14, ringbuf.start);
|
||||
|
||||
/*
|
||||
Now, read around the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::PositionRequest(8),
|
||||
TestCircularTransferRequest::ResetCompleteCount(1),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(4, ringbuf.start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
/*
|
||||
Read to close to the end of the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(14),
|
||||
TestCircularTransferRequest::PositionRequest(16),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 14];
|
||||
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(14, ringbuf.start);
|
||||
|
||||
/*
|
||||
Now, read to the end of the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::PositionRequest(8),
|
||||
TestCircularTransferRequest::ResetCompleteCount(1),
|
||||
]);
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(0, ringbuf.start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_when_dma_writer_wraps_once_with_same_ndtr() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
/*
|
||||
Read to about the middle of the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(6, ringbuf.start);
|
||||
|
||||
/*
|
||||
Now, wrap the DMA controller around
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::GetCompleteCount(1),
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::GetCompleteCount(1),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(12, ringbuf.start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
/*
|
||||
Read a few bytes
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(2),
|
||||
TestCircularTransferRequest::PositionRequest(2),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(2, ringbuf.start);
|
||||
|
||||
/*
|
||||
Now, overtake the reader
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(4),
|
||||
TestCircularTransferRequest::PositionRequest(6),
|
||||
TestCircularTransferRequest::GetCompleteCount(1),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() {
|
||||
let mut dma = TestCircularTransfer::new(16);
|
||||
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert_eq!(0, ringbuf.start);
|
||||
assert_eq!(16, ringbuf.cap());
|
||||
|
||||
/*
|
||||
Read to close to the end of the buffer
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(14),
|
||||
TestCircularTransferRequest::PositionRequest(16),
|
||||
TestCircularTransferRequest::GetCompleteCount(0),
|
||||
]);
|
||||
let mut buf = [0; 14];
|
||||
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
|
||||
assert_eq!(14, ringbuf.start);
|
||||
|
||||
/*
|
||||
Now, overtake the reader
|
||||
*/
|
||||
dma.setup(vec![
|
||||
TestCircularTransferRequest::PositionRequest(8),
|
||||
TestCircularTransferRequest::PositionRequest(10),
|
||||
TestCircularTransferRequest::ResetCompleteCount(2),
|
||||
]);
|
||||
let mut buf = [0; 6];
|
||||
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user