Compare commits

...

72 Commits

Author SHA1 Message Date
c7be481190 Merge #1075
1075: fix: add required metadata for embassy-boot r=lulf a=lulf



Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-25 13:12:24 +00:00
89821846d7 fix: add required metadata for embassy-boot 2022-11-25 11:43:12 +01:00
758f5d7ea2 Release embassy-executor v0.1.1 2022-11-23 14:53:18 +01:00
f0ba22fc17 Merge pull request #1076 from embassy-rs/executor-docs-rs
executor: enable features for docs.rs
2022-11-23 14:52:17 +01:00
db7e153fc0 executor: enable features for docs.rs
Otherwise the non-raw executor and the macros don't show up.
2022-11-23 14:49:40 +01:00
a4f9e7cbcc Merge #1071
1071: refactor: autodetect macro variant r=Dirbaio a=lulf

Apply heuristics using target_arch, target_os and target_family to determine which variant of the entry point to use.

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-23 13:21:59 +00:00
de95ab264d Merge pull request #1073 from embassy-rs/revert-riscv-race
fix: revert race condition introduced for riscv
2022-11-23 14:00:26 +01:00
04a7d97673 refactor: autodetect macro variant
Export all main macro per target architecture from embassy-macros,
and select the appropriate macro in embassy-executor.
2022-11-23 13:54:59 +01:00
50c5cc5db6 fix: revert race condition introduced for riscv 2022-11-23 13:17:05 +01:00
b76631bebe Merge #1069
1069: GPIOTE InputChannel with mutable reference. r=Dirbaio a=Ardelean-Calin

Adding these changes enables us to define a channel using a mutable reference to `GPIOTE_CH(n)`, similar to how we can do with other drivers. So instead of using:
```rust
let p = embassy_nrf::init(config);
let freq_in = InputChannel::new(
    p.GPIOTE_CH0,
    Input::new(&mut p.P0_19, embassy_nrf::gpio::Pull::Up),
    embassy_nrf::gpiote::InputChannelPolarity::HiToLo,
);
```
we can use:
```rust
let p = embassy_nrf::init(config);
let freq_in = InputChannel::new(
    &mut p.GPIOTE_CH0,
    Input::new(&mut p.P0_19, embassy_nrf::gpio::Pull::Up),
    embassy_nrf::gpiote::InputChannelPolarity::HiToLo,
);
```
therefore not giving ownership to GPIOTE_CH0.

Co-authored-by: Ardelean Călin Petru <ardelean.calin@outlook.com>
Co-authored-by: Ardelean Calin <ardelean.calin@proton.me>
2022-11-23 12:17:02 +00:00
eae67d0be8 Review comments. Corrected unused fields. 2022-11-23 14:16:18 +02:00
2fa2c1a6fe Merge #1054
1054: riscv fixes r=lulf a=swolix

With these changes I can run embassy on our RISC-V processor, please consider merging this, feedback is very welcome.

I don't fully understand the code in the executor, but I have implemented a critical section by globally disabling interrupts, which means the wfi inside the critical section will hang the whole thing.

Co-authored-by: Sijmen Woutersen <sijmen.woutersen@gmail.com>
2022-11-23 09:24:11 +00:00
83b199a874 Merge #1056
1056: embassy-nrf: Add TWIS module r=Dirbaio a=kalkyl

Verified to be working on nrf9160

Co-authored-by: kalkyl <henrik.alser@me.com>
Co-authored-by: Henrik Alsér <henrik.alser@me.com>
2022-11-22 21:50:42 +00:00
cf900a8a3f Rename write to respond_to_read 2022-11-22 22:10:04 +01:00
4f2f375777 Corrected order of use statements. 2022-11-22 17:45:05 +02:00
e7c876d744 Changed pin to private as it is for OutputChannel 2022-11-22 17:36:22 +02:00
64c2e1b9b6 Switched to PeripheralRef for channel. 2022-11-22 17:35:38 +02:00
61be0e75c8 Merge #1068
1068: Add Default to some types r=Dirbaio a=mkj

These are a couple of places I've found `Default` to be handy

Co-authored-by: Matt Johnston <matt@ucc.asn.au>
2022-11-22 15:04:24 +00:00
a074cd0625 Update gpiote.rs
Adding these changes enables us to define a channel using a mutable reference to `GPIOTE_CH(n)`, similar to how we can do with other drivers.
So instead of using:
```rust
let freq_in = InputChannel::new(
    p.GPIOTE_CH0,
    Input::new(&mut p.P0_19, embassy_nrf::gpio::Pull::Up),
    embassy_nrf::gpiote::InputChannelPolarity::HiToLo,
);
```
we can use:
```rust
let freq_in = InputChannel::new(
    &mut p.GPIOTE_CH0,
    Input::new(&mut p.P0_19, embassy_nrf::gpio::Pull::Up),
    embassy_nrf::gpiote::InputChannelPolarity::HiToLo,
);
```
2022-11-22 16:56:04 +02:00
ca4f615b25 Merge #1067
1067: doc: update cargo manifests with keywords r=lulf a=lulf



Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-22 13:59:28 +00:00
536b6a2de5 sync/signal: Implement Default for Signal 2022-11-22 21:55:42 +08:00
51233c0357 doc: update cargo manifests with keywords 2022-11-22 14:51:23 +01:00
5c52d6c217 Merge pull request #1066 from embassy-rs/embassy-macros-doc
doc: add README to embassy-macro
2022-11-22 14:31:56 +01:00
f474817872 doc: add README to embassy-macro
Documents the main and task macros.
2022-11-22 13:57:41 +01:00
97cb95bbf4 Merge #1042
1042: embassy-nrf: Add SPIS module r=Dirbaio a=kalkyl

Verified to be working on nrf9160

Co-authored-by: Henrik Alsér <henrik.alser@me.com>
Co-authored-by: Henrik Alsér <henrik.alser@ucsmindbite.se>
Co-authored-by: kalkyl <henrik.alser@me.com>
2022-11-22 11:20:14 +00:00
99c561a749 Merge #1065
1065: embassy-nrf: Default disable UARTE (nrf9160) r=Dirbaio a=kalkyl

Uarte is enabled by default on the nrf9160, which is both bad for power consumption and renders the other "shared" peripherals unusable. This might be an SPM bug, but had the same issue with all pre-compiled SPM:s available out there, so adding this fix until we figure out.

Co-authored-by: Henrik Alsér <henrik.alser@me.com>
2022-11-22 11:02:50 +00:00
f09745dfe1 embassy-nrf: Default disable UARTE (nrf9160) 2022-11-22 02:21:06 +01:00
da9f82f507 Fix pin refs 2022-11-22 02:13:03 +01:00
f13639e78c Merge #1059
1059: embassy-rp: Add basic ADC module r=kalkyl a=kalkyl

Oneshot ADC

Co-authored-by: Henrik Alsér <henrik.alser@me.com>
2022-11-22 01:06:25 +00:00
908eef2775 Change interrupt modify into write 2022-11-22 02:03:34 +01:00
633ffe46ae config write, docs, add address_match_index 2022-11-22 01:57:00 +01:00
e6b9722a31 Remove nrf9160 UARTE fix 2022-11-22 01:07:59 +01:00
b8f51c6496 Merge #1057
1057: stm32g0: Fix ADC for channels above 14 r=Dirbaio a=jaxter184

using the CHSELR register in sequence mode does not support ADC channels above 14. Also, it seems like the sequencer itself wasn't being used anyway, so I turned it off (maybe the whole block from L72..L76 could be removed?) and used a bit shift.

Co-authored-by: Jaxter Kim <jaxter.kim@elektron.se>
2022-11-21 23:58:28 +00:00
33ee48b9e8 Merge branch 'spis' of github.com:kalkyl/embassy into spis 2022-11-22 00:55:46 +01:00
a6d941fac3 Fix txonly/rxonly data pin dir, _from_ram and doc 2022-11-22 00:55:05 +01:00
15b4ed2c67 Merge #1060
1060: feat: embassy-usb-logger and example for rpi pico r=Dirbaio a=lulf

* Add embassy-usb-logger which allows logging over USB for any device implementing embassy-usb
* Add example using logger for rpi pico

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-21 22:52:58 +00:00
bbfb786139 Merge #1064
1064: Fix LoRaWAN PHY settings for SX126x driver r=Dirbaio a=jbeaurivage

While working on #1023 / #1041, I noticed that the `lorawan_device::PhyTxRx` implementation does not conform to the LoRaWAN standard, and therefore devices using this driver could never communicate with a gateway. This PR backports the changes I've made to fix the offending parameters, and I can confirm that the driver now works with LoRaWAN networks.

* Set preamble length to 8 symbols
* Set polarity to inverted for received messages

Co-authored-by: Justin Beaurivage <justin@wearableavionics.com>
2022-11-21 22:08:46 +00:00
81dc532d2d Fix LoRaWAN PHY settings for SX126x driver
* Set preamble length to 8 symbols
* Set polarity to inverted for received messages
2022-11-21 12:08:44 -05:00
06fb3e4251 docs: add missing README for usb-logger 2022-11-21 11:24:53 +01:00
4943dec1a7 Merge remote-tracking branch 'upstream/master' 2022-11-20 20:04:23 +01:00
a444a65ebf feat: embassy-usb-logger and example for rpi pico
* Add embassy-usb-logger which allows logging over USB for any device
  implementing embassy-usb
* Add example using logger for rpi pico.
2022-11-18 11:22:58 +01:00
9f870a5edf Cleanup 2022-11-15 16:31:19 +01:00
eb149a0bd4 embassy-rp: Add basic ADC module 2022-11-15 16:12:07 +01:00
551b54ddcb stm32g0: Fix ADC for channels above 14 2022-11-15 12:56:47 +01:00
2528f45138 Merge #1058
1058: Fix some errors in the documentation r=lulf a=johannesneyer



Co-authored-by: Johannes Neyer <johannes.neyer@gmail.com>
2022-11-15 11:35:43 +00:00
9505a6f752 [doc] Remove obsolete code sample 2022-11-15 10:10:36 +01:00
ea61c19280 [doc] Fix some grammar 2022-11-15 10:10:36 +01:00
bcec55464f [doc] Fix line indices of basic example 2022-11-15 10:10:33 +01:00
0b066b22d1 Check events_acquired 2022-11-14 16:24:21 +01:00
3a1ddd66c6 Cleanup interrupts 2022-11-14 16:18:11 +01:00
8d2d5a30a5 Single waker 2022-11-14 11:39:55 +01:00
43c1afb6a6 Return number of bytes written, add address match getter 2022-11-14 11:22:14 +01:00
eba42cb5f4 embassy-nrf: Add TWIS module 2022-11-13 22:15:19 +01:00
5cfad3f853 Feature gate UARTE disable 2022-11-13 02:37:23 +01:00
dca11095e2 Disable UARTE in embassy-nrf::init 2022-11-13 01:49:55 +01:00
e70ae71ecc restore SIGNAL_WORK_THREAD_MODE 2022-11-12 10:58:37 +01:00
d05979c708 Merge #1052 #1053
1052: stm32: Fix watchdog division by zero for 256 prescaler, add watchdog … r=lulf a=matoushybl

…example for H7

The problem is that `2u8.powi(8) == 0`, which causes division by zero.

1053: Disable MMC interrupts r=lulf a=matoushybl

MMC interrupts can cause firmware hangup - refer to: https://github.com/stm32-rs/stm32h7xx-hal/issues/275 for more information

Fixes #594 

Co-authored-by: Matous Hybl <hyblmatous@gmail.com>
2022-11-11 08:04:16 +00:00
6e1120e17e riscv support 2022-11-10 17:39:41 +01:00
99682d313b Disable MMC interrupts
MMC interrupts can cause firmware hangup - refer to: https://github.com/stm32-rs/stm32h7xx-hal/issues/275 for more information
2022-11-10 17:21:42 +01:00
cbc97758e3 stm32: Fix watchdog division by zero for 256 prescaler, add watchdog example for H7 2022-11-10 15:56:28 +01:00
059610a8de Merge #1047
1047: Ensure embassy-lora stm32wl supports log crate r=lulf a=lulf



Co-authored-by: Ulf Lilleengen <ulf.lilleengen@gmail.com>
2022-11-09 09:57:36 +00:00
a3a58e8e4a Special handling for log and defmt 2022-11-09 10:04:37 +01:00
bd5ef80bec Ensure embassy-lora stm32wl supports log crate 2022-11-07 20:51:29 +01:00
c53614f057 Merge #1046
1046: embassy-stm32: Fix bug when Uart::read future is dropped and DMA request was not stopped r=lulf a=guillaume-michel

fixes #1045 

regression was introduced with PR #1031

Co-authored-by: Guillaume MICHEL <guillaume@squaremind.io>
2022-11-07 17:48:04 +00:00
1365ce6ab8 embassy-stm32: Fix bug when Uart::read future is dropped and DMA request was not stopped
fixes issue #1045

regression was introduced with PR #1031
2022-11-07 17:46:32 +01:00
14a2d15240 Derive Default for WakerRegistration
This simplifies creating arrays of WakerRegistrations
2022-11-06 11:33:38 +08:00
af34fc4ccc rustfmt 2022-11-05 01:40:20 +01:00
aecfce1159 rustfmt 2022-11-05 01:36:29 +01:00
207fa19551 Acquire semaphore on blocking 2022-11-05 01:34:52 +01:00
7da18e194a Add status checks 2022-11-05 01:12:25 +01:00
a3e8a6bc3a rustfmt 2022-11-05 00:19:52 +01:00
1920e90dcd embassy-nrf: Add SPIS module 2022-11-05 00:15:43 +01:00
49 changed files with 2336 additions and 168 deletions

View File

@ -21,7 +21,7 @@ Then, what follows are some declarations on how to deal with panics and faults.
[source,rust] [source,rust]
---- ----
include::example$basic/src/main.rs[lines="11..12"] include::example$basic/src/main.rs[lines="10"]
---- ----
=== Task declaration === Task declaration
@ -30,7 +30,7 @@ After a bit of import declaration, the tasks run by the application should be de
[source,rust] [source,rust]
---- ----
include::example$basic/src/main.rs[lines="13..22"] include::example$basic/src/main.rs[lines="12..20"]
---- ----
An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking. An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking.
@ -45,23 +45,10 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera
[source,rust] [source,rust]
---- ----
include::example$basic/src/main.rs[lines="23..-1"] include::example$basic/src/main.rs[lines="22..-1"]
---- ----
`#[embassy_executor::main]` takes an optional `config` parameter specifying a function that returns an instance of HAL's `Config` struct. For example: What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
```rust
fn embassy_config() -> embassy_nrf::config::Config {
embassy_nrf::config::Config::default()
}
#[embassy_executor::main(config = "embassy_config()")]
async fn main(_spawner: Spawner, p: embassy_nrf::Peripherals) {
// ...
}
```
What happens when the `blinker` task have been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
. Creates an Embassy Executor . Creates an Embassy Executor
. Initializes the microcontroller HAL to get the `Peripherals` . Initializes the microcontroller HAL to get the `Peripherals`
@ -76,7 +63,7 @@ The project definition needs to contain the embassy dependencies:
[source,toml] [source,toml]
---- ----
include::example$basic/Cargo.toml[lines="8..9"] include::example$basic/Cargo.toml[lines="9..11"]
---- ----
Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well). Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well).

View File

@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh
== PAC version == PAC version
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provide distinct types The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types
to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code. to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use. Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.
@ -20,13 +20,13 @@ The blinky app using PAC is shown below:
include::example$layer-by-layer/blinky-pac/src/main.rs[] include::example$layer-by-layer/blinky-pac/src/main.rs[]
---- ----
As you can see, there are a lot of code needed to enable the peripheral clocks, configuring the input pins and the output pins of the application. As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application.
Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power. Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power.
== HAL version == HAL version
To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as:
* Automatically enabling the peripheral clock when you're using the peripheral * Automatically enabling the peripheral clock when you're using the peripheral
* Deriving and applying register configuration from higher level types * Deriving and applying register configuration from higher level types
@ -39,7 +39,7 @@ The HAL example is shown below:
include::example$layer-by-layer/blinky-hal/src/main.rs[] include::example$layer-by-layer/blinky-hal/src/main.rs[]
---- ----
As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` hides all the details accessing the GPIO registers, and allow you to use a much simpler API to query the state of the button and toggle the LED output accordingly. As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output.
The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary. The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary.

View File

@ -4,9 +4,9 @@ The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy
== The infinite variant problem == The infinite variant problem
STM32 microcontrollers comes in many families and flavors, and supporting all of them is a big undertaking. Embassy has taken advantage of the fact STM32 microcontrollers come in many families, and flavors and supporting all of them is a big undertaking. Embassy has taken advantage of the fact
that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI
peripheral for every STM32 chip family, embassy have a single SPI implementation that depends on peripheral for every STM32 chip family, embassy has a single SPI implementation that depends on
code-generated register types that are identical for STM32 families with the same version of a given peripheral. code-generated register types that are identical for STM32 families with the same version of a given peripheral.
=== The metapac === The metapac

View File

@ -2,13 +2,23 @@
edition = "2021" edition = "2021"
name = "embassy-boot" name = "embassy-boot"
version = "0.1.0" version = "0.1.0"
description = "Bootloader using Embassy" description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/"
target = "thumbv7em-none-eabi" target = "thumbv7em-none-eabi"
features = ["defmt"]
[package.metadata.docs.rs]
features = ["defmt"]
[lib] [lib]

View File

@ -1,7 +1,7 @@
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![no_std] #![no_std]
#![warn(missing_docs)] #![warn(missing_docs)]
#![doc = include_str!("../../README.md")] #![doc = include_str!("../README.md")]
mod fmt; mod fmt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};

View File

@ -0,0 +1,26 @@
# embassy-boot-nrf
An [Embassy](https://embassy.dev) project.
An adaptation of `embassy-boot` for nRF.
## Features
* Load applications with our without the softdevice.
* Configure bootloader partitions based on linker script.
* Using watchdog timer to detect application failure.
## Minimum supported Rust version (MSRV)
`embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,7 +1,7 @@
#![no_std] #![no_std]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![warn(missing_docs)] #![warn(missing_docs)]
#![doc = include_str!("../../README.md")] #![doc = include_str!("../README.md")]
mod fmt; mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig}; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};

View File

@ -1,11 +1,24 @@
# Bootloader for STM32 # embassy-boot-stm32
The bootloader uses `embassy-boot` to interact with the flash. An [Embassy](https://embassy.dev) project.
# Usage An adaptation of `embassy-boot` for STM32.
Flash the bootloader ## Features
``` * Configure bootloader partitions based on linker script.
cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx * Load applications from active partition.
```
## Minimum supported Rust version (MSRV)
`embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,7 +1,7 @@
#![no_std] #![no_std]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![warn(missing_docs)] #![warn(missing_docs)]
#![doc = include_str!("../../README.md")] #![doc = include_str!("../README.md")]
mod fmt; mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};

View File

@ -1,9 +1,15 @@
[package] [package]
name = "embassy-executor" name = "embassy-executor"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
@ -21,10 +27,13 @@ flavors = [
{ name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] }, { name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] },
] ]
[package.metadata.docs.rs]
features = ["std", "nightly", "defmt"]
[features] [features]
default = [] default = []
std = ["embassy-macros/std", "critical-section/std"] std = ["critical-section/std"]
wasm = ["dep:wasm-bindgen", "dep:js-sys", "embassy-macros/wasm"] wasm = ["dep:wasm-bindgen", "dep:js-sys"]
# Enable nightly-only features # Enable nightly-only features
nightly = [] nightly = []

View File

@ -8,18 +8,22 @@
pub(crate) mod fmt; pub(crate) mod fmt;
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
pub use embassy_macros::{main, task}; pub use embassy_macros::task;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(cortex_m)] { if #[cfg(cortex_m)] {
#[path="arch/cortex_m.rs"] #[path="arch/cortex_m.rs"]
mod arch; mod arch;
pub use arch::*; pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
} }
else if #[cfg(target_arch="riscv32")] { else if #[cfg(target_arch="riscv32")] {
#[path="arch/riscv32.rs"] #[path="arch/riscv32.rs"]
mod arch; mod arch;
pub use arch::*; pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main;
} }
else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] { else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] {
#[path="arch/xtensa.rs"] #[path="arch/xtensa.rs"]
@ -30,11 +34,15 @@ cfg_if::cfg_if! {
#[path="arch/wasm.rs"] #[path="arch/wasm.rs"]
mod arch; mod arch;
pub use arch::*; pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_wasm as main;
} }
else if #[cfg(feature="std")] { else if #[cfg(feature="std")] {
#[path="arch/std.rs"] #[path="arch/std.rs"]
mod arch; mod arch;
pub use arch::*; pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main;
} }
} }

View File

@ -70,7 +70,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
/// the upcoming RX window start. /// the upcoming RX window start.
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> { async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
trace!("TX request: {}", config); trace!("TX request: {:?}", config);
self.switch.set_tx(); self.switch.set_tx();
self.radio self.radio
@ -130,7 +130,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// be able to hold a single LoRaWAN packet. /// be able to hold a single LoRaWAN packet.
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> { async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
assert!(buf.len() >= 255); assert!(buf.len() >= 255);
trace!("RX request: {}", config); trace!("RX request: {:?}", config);
self.switch.set_rx(); self.switch.set_rx();
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?; self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
@ -172,7 +172,11 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
self.radio.read_buffer(ptr, &mut buf[..len as usize])?; self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
self.radio.set_standby(StandbyClk::Rc)?; self.radio.set_standby(StandbyClk::Rc)?;
#[cfg(feature = "defmt")]
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]); trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
#[cfg(feature = "log")]
trace!("RX done: {:02x?}", &mut buf[..len as usize]);
return Ok((len as usize, RxQuality::new(rssi, snr as i8))); return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
} }
@ -193,7 +197,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
.clear_irq_status(irq_status) .clear_irq_status(irq_status)
.expect("error clearing irq status"); .expect("error clearing irq status");
trace!("SUGHZ IRQ 0b{=u16:b}, {:?}", irq_status, status); trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
if irq_status == 0 { if irq_status == 0 {
Poll::Pending Poll::Pending

View File

@ -87,7 +87,7 @@ where
config.rf.spreading_factor.into(), config.rf.spreading_factor.into(),
config.rf.bandwidth.into(), config.rf.bandwidth.into(),
config.rf.coding_rate.into(), config.rf.coding_rate.into(),
4, 8,
false, false,
true, true,
false, false,
@ -119,14 +119,14 @@ where
config.spreading_factor.into(), config.spreading_factor.into(),
config.bandwidth.into(), config.bandwidth.into(),
config.coding_rate.into(), config.coding_rate.into(),
4, 8,
4, 4,
false, false,
0u8, 0u8,
true, true,
false, false,
0, 0,
false, true,
true, true,
) )
.await?; .await?;

View File

@ -3,6 +3,13 @@ name = "embassy-macros"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[dependencies] [dependencies]
syn = { version = "1.0.76", features = ["full", "extra-traits"] } syn = { version = "1.0.76", features = ["full", "extra-traits"] }
@ -14,8 +21,5 @@ proc-macro2 = "1.0.29"
proc-macro = true proc-macro = true
[features] [features]
std = []
wasm = []
# Enabling this cause interrupt::take! to require embassy-executor # Enabling this cause interrupt::take! to require embassy-executor
rtos-trace-interrupt = [] rtos-trace-interrupt = []

21
embassy-macros/README.md Normal file
View File

@ -0,0 +1,21 @@
# embassy-macros
An [Embassy](https://embassy.dev) project.
Macros for creating the main entry point and tasks that can be spawned by `embassy-executor`.
NOTE: The macros are re-exported by the `embassy-executor` crate which should be used instead of adding a direct dependency on the `embassy-macros` crate.
## Minimum supported Rust version (MSRV)
The `task` and `main` macros require the type alias impl trait (TAIT) nightly feature in order to compile.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro; extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
@ -6,6 +7,36 @@ mod macros;
mod util; mod util;
use macros::*; use macros::*;
/// 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.
///
///
/// The following restrictions apply:
///
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * The optional `pool_size` attribute must be 1 or greater.
///
///
/// ## Examples
///
/// Declaring a task taking no arguments:
///
/// ``` rust
/// #[embassy_executor::task]
/// async fn mytask() {
/// // Function body
/// }
/// ```
///
/// Declaring a task with a given pool size:
///
/// ``` rust
/// #[embassy_executor::task(pool_size = 4)]
/// async fn mytask() {
/// // Function body
/// }
/// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs); let args = syn::parse_macro_input!(args as syn::AttributeArgs);
@ -14,11 +45,104 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
task::run(args, f).unwrap_or_else(|x| x).into() task::run(args, f).unwrap_or_else(|x| x).into()
} }
/// Creates a new `executor` instance and declares an application entry point for Cortex-M 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.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs); let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn); let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f).unwrap_or_else(|x| x).into() main::run(args, f, main::cortex_m()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for RISC-V 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.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::riscv()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for STD 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.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::std()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM 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.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::wasm()).unwrap_or_else(|x| x).into()
} }
#[proc_macro_attribute] #[proc_macro_attribute]

View File

@ -7,7 +7,62 @@ use crate::util::ctxt::Ctxt;
#[derive(Debug, FromMeta)] #[derive(Debug, FromMeta)]
struct Args {} struct Args {}
pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, TokenStream> { pub fn riscv() -> TokenStream {
quote! {
#[riscv_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 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> {
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new();
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
executor.start(|spawner| {
spawner.spawn(__embassy_main(spawner)).unwrap();
});
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: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
#[allow(unused_variables)] #[allow(unused_variables)]
let args = Args::from_list(&args).map_err(|e| e.write_errors())?; let args = Args::from_list(&args).map_err(|e| e.write_errors())?;
@ -30,46 +85,6 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
let f_body = f.block; let f_body = f.block;
#[cfg(feature = "wasm")]
let main = quote! {
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new();
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
executor.start(|spawner| {
spawner.spawn(__embassy_main(spawner)).unwrap();
});
Ok(())
}
};
#[cfg(all(feature = "std", not(feature = "wasm")))]
let main = 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));
})
}
};
#[cfg(all(not(feature = "std"), not(feature = "wasm")))]
let main = 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));
})
}
};
let result = quote! { let result = quote! {
#[::embassy_executor::task()] #[::embassy_executor::task()]
async fn __embassy_main(#fargs) { async fn __embassy_main(#fargs) {

View File

@ -131,8 +131,12 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0);
impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0);
impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0);
impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER1, TIMER1, TIMER1);
impl_timer!(TIMER2, TIMER2, TIMER2); impl_timer!(TIMER2, TIMER2, TIMER2);

View File

@ -137,8 +137,12 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0);
impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0);
impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0);
impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);

View File

@ -138,8 +138,13 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1); impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1);
impl_spis!(TWISPI0, SPIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_spis!(SPI1, SPIS1, SPIM1_SPIS1_SPI1);
impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_twis!(TWISPI0, TWIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);

View File

@ -136,9 +136,15 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER1, TIMER1, TIMER1);
impl_timer!(TIMER2, TIMER2, TIMER2); impl_timer!(TIMER2, TIMER2, TIMER2);

View File

@ -146,9 +146,16 @@ impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM2, PWM2, PWM2);

View File

@ -174,9 +174,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spim!(SPI3, SPIM3, SPIM3); impl_spim!(SPI3, SPIM3, SPIM3);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM2, PWM2, PWM2);

View File

@ -177,9 +177,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spim!(SPI3, SPIM3, SPIM3); impl_spim!(SPI3, SPIM3, SPIM3);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM2, PWM2, PWM2);

View File

@ -361,11 +361,21 @@ impl_spim!(UARTETWISPI1, SPIM1, SERIAL1);
impl_spim!(UARTETWISPI2, SPIM2, SERIAL2); impl_spim!(UARTETWISPI2, SPIM2, SERIAL2);
impl_spim!(UARTETWISPI3, SPIM3, SERIAL3); impl_spim!(UARTETWISPI3, SPIM3, SERIAL3);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0);
impl_spis!(UARTETWISPI1, SPIS1, SERIAL1);
impl_spis!(UARTETWISPI2, SPIS2, SERIAL2);
impl_spis!(UARTETWISPI3, SPIS3, SERIAL3);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); impl_twim!(UARTETWISPI0, TWIM0, SERIAL0);
impl_twim!(UARTETWISPI1, TWIM1, SERIAL1); impl_twim!(UARTETWISPI1, TWIM1, SERIAL1);
impl_twim!(UARTETWISPI2, TWIM2, SERIAL2); impl_twim!(UARTETWISPI2, TWIM2, SERIAL2);
impl_twim!(UARTETWISPI3, TWIM3, SERIAL3); impl_twim!(UARTETWISPI3, TWIM3, SERIAL3);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0);
impl_twis!(UARTETWISPI1, TWIS1, SERIAL1);
impl_twis!(UARTETWISPI2, TWIS2, SERIAL2);
impl_twis!(UARTETWISPI3, TWIS3, SERIAL3);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM2, PWM2, PWM2);

View File

@ -238,7 +238,9 @@ embassy_hal_common::peripherals! {
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0);
impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); impl_spim!(UARTETWISPI0, SPIM0, SERIAL0);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); impl_twim!(UARTETWISPI0, TWIM0, SERIAL0);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER1, TIMER1, TIMER1);

View File

@ -275,11 +275,21 @@ impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_spis!(UARTETWISPI0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_spis!(UARTETWISPI1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spis!(UARTETWISPI2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spis!(UARTETWISPI3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twis!(UARTETWISPI0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twis!(UARTETWISPI1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twis!(UARTETWISPI2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twis!(UARTETWISPI3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM2, PWM2, PWM2);

View File

@ -2,7 +2,7 @@ use core::convert::Infallible;
use core::future::{poll_fn, Future}; use core::future::{poll_fn, Future};
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use embassy_hal_common::{impl_peripheral, Peripheral, PeripheralRef}; use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::sealed::Pin as _; use crate::gpio::sealed::Pin as _;
@ -148,7 +148,7 @@ impl Iterator for BitIter {
/// GPIOTE channel driver in input mode /// GPIOTE channel driver in input mode
pub struct InputChannel<'d, C: Channel, T: GpioPin> { pub struct InputChannel<'d, C: Channel, T: GpioPin> {
ch: C, ch: PeripheralRef<'d, C>,
pin: Input<'d, T>, pin: Input<'d, T>,
} }
@ -162,7 +162,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> {
} }
impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
pub fn new(ch: C, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self {
into_ref!(ch);
let g = regs(); let g = regs();
let num = ch.number(); let num = ch.number();
@ -215,7 +217,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
/// GPIOTE channel driver in output mode /// GPIOTE channel driver in output mode
pub struct OutputChannel<'d, C: Channel, T: GpioPin> { pub struct OutputChannel<'d, C: Channel, T: GpioPin> {
ch: C, ch: PeripheralRef<'d, C>,
_pin: Output<'d, T>, _pin: Output<'d, T>,
} }
@ -229,7 +231,8 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> {
} }
impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
pub fn new(ch: C, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self {
into_ref!(ch);
let g = regs(); let g = regs();
let num = ch.number(); let num = ch.number();

View File

@ -96,10 +96,12 @@ pub mod rng;
#[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))] #[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))]
pub mod saadc; pub mod saadc;
pub mod spim; pub mod spim;
pub mod spis;
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
pub mod temp; pub mod temp;
pub mod timer; pub mod timer;
pub mod twim; pub mod twim;
pub mod twis;
pub mod uarte; pub mod uarte;
#[cfg(any( #[cfg(any(
feature = "_nrf5340-app", feature = "_nrf5340-app",
@ -267,5 +269,12 @@ pub fn init(config: config::Config) -> Peripherals {
#[cfg(feature = "_time-driver")] #[cfg(feature = "_time-driver")]
time_driver::init(config.time_interrupt_priority); time_driver::init(config.time_interrupt_priority);
// Disable UARTE (enabled by default for some reason)
#[cfg(feature = "_nrf9160")]
unsafe {
(*pac::UARTE0::ptr()).enable.write(|w| w.enable().disabled());
(*pac::UARTE1::ptr()).enable.write(|w| w.enable().disabled());
}
peripherals peripherals
} }

539
embassy-nrf/src/spis.rs Normal file
View File

@ -0,0 +1,539 @@
#![macro_use]
use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_embedded_hal::SetConfig;
use embassy_hal_common::{into_ref, PeripheralRef};
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
use crate::chip::FORCE_COPY_BUFFER_SIZE;
use crate::gpio::sealed::Pin as _;
use crate::gpio::{self, AnyPin, Pin as GpioPin};
use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut};
use crate::{pac, Peripheral};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
TxBufferTooLong,
RxBufferTooLong,
/// EasyDMA can only read from data memory, read only buffers in flash will fail.
DMABufferNotInDataMemory,
}
/// Interface for the SPIS peripheral using EasyDMA to offload the transmission and reception workload.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Spis<'d, T: Instance> {
_p: PeripheralRef<'d, T>,
}
#[non_exhaustive]
pub struct Config {
pub mode: Mode,
pub orc: u8,
pub def: u8,
pub auto_acquire: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
mode: MODE_0,
orc: 0x00,
def: 0x00,
auto_acquire: true,
}
}
}
impl<'d, T: Instance> Spis<'d, T> {
pub fn new(
spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
cs: impl Peripheral<P = impl GpioPin> + 'd,
sck: impl Peripheral<P = impl GpioPin> + 'd,
miso: impl Peripheral<P = impl GpioPin> + 'd,
mosi: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(cs, sck, miso, mosi);
Self::new_inner(
spis,
irq,
cs.map_into(),
sck.map_into(),
Some(miso.map_into()),
Some(mosi.map_into()),
config,
)
}
pub fn new_txonly(
spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
cs: impl Peripheral<P = impl GpioPin> + 'd,
sck: impl Peripheral<P = impl GpioPin> + 'd,
miso: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(cs, sck, miso);
Self::new_inner(
spis,
irq,
cs.map_into(),
sck.map_into(),
Some(miso.map_into()),
None,
config,
)
}
pub fn new_rxonly(
spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
cs: impl Peripheral<P = impl GpioPin> + 'd,
sck: impl Peripheral<P = impl GpioPin> + 'd,
mosi: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(cs, sck, mosi);
Self::new_inner(
spis,
irq,
cs.map_into(),
sck.map_into(),
None,
Some(mosi.map_into()),
config,
)
}
fn new_inner(
spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
cs: PeripheralRef<'d, AnyPin>,
sck: PeripheralRef<'d, AnyPin>,
miso: Option<PeripheralRef<'d, AnyPin>>,
mosi: Option<PeripheralRef<'d, AnyPin>>,
config: Config,
) -> Self {
compiler_fence(Ordering::SeqCst);
into_ref!(spis, irq, cs, sck);
let r = T::regs();
// Configure pins.
sck.conf().write(|w| w.input().connect().drive().h0h1());
r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) });
cs.conf().write(|w| w.input().connect().drive().h0h1());
r.psel.csn.write(|w| unsafe { w.bits(cs.psel_bits()) });
if let Some(mosi) = &mosi {
mosi.conf().write(|w| w.input().connect().drive().h0h1());
r.psel.mosi.write(|w| unsafe { w.bits(mosi.psel_bits()) });
}
if let Some(miso) = &miso {
miso.conf().write(|w| w.dir().output().drive().h0h1());
r.psel.miso.write(|w| unsafe { w.bits(miso.psel_bits()) });
}
// Enable SPIS instance.
r.enable.write(|w| w.enable().enabled());
// Configure mode.
let mode = config.mode;
r.config.write(|w| {
match mode {
MODE_0 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().leading();
}
MODE_1 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().trailing();
}
MODE_2 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().leading();
}
MODE_3 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().trailing();
}
}
w
});
// Set over-read character.
let orc = config.orc;
r.orc.write(|w| unsafe { w.orc().bits(orc) });
// Set default character.
let def = config.def;
r.def.write(|w| unsafe { w.def().bits(def) });
// Configure auto-acquire on 'transfer end' event.
if config.auto_acquire {
r.shorts.write(|w| w.end_acquire().bit(true));
}
// Disable all events interrupts.
r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
irq.set_handler(Self::on_interrupt);
irq.unpend();
irq.enable();
Self { _p: spis }
}
fn on_interrupt(_: *mut ()) {
let r = T::regs();
let s = T::state();
if r.events_end.read().bits() != 0 {
s.waker.wake();
r.intenclr.write(|w| w.end().clear());
}
if r.events_acquired.read().bits() != 0 {
s.waker.wake();
r.intenclr.write(|w| w.acquired().clear());
}
}
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM.
compiler_fence(Ordering::SeqCst);
let r = T::regs();
// Set up the DMA write.
let (ptr, len) = slice_ptr_parts(tx);
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
// Set up the DMA read.
let (ptr, len) = slice_ptr_parts_mut(rx);
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
// Reset end event.
r.events_end.reset();
// Release the semaphore.
r.tasks_release.write(|w| unsafe { w.bits(1) });
Ok(())
}
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> {
compiler_fence(Ordering::SeqCst);
let r = T::regs();
// Acquire semaphore.
if r.semstat.read().bits() != 1 {
r.events_acquired.reset();
r.tasks_acquire.write(|w| unsafe { w.bits(1) });
// Wait until CPU has acquired the semaphore.
while r.semstat.read().bits() != 1 {}
}
self.prepare(rx, tx)?;
// Wait for 'end' event.
while r.events_end.read().bits() == 0 {}
let n_rx = r.rxd.amount.read().bits() as usize;
let n_tx = r.txd.amount.read().bits() as usize;
compiler_fence(Ordering::SeqCst);
Ok((n_rx, n_tx))
}
fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> {
match self.blocking_inner_from_ram(rx, tx) {
Ok(n) => Ok(n),
Err(Error::DMABufferNotInDataMemory) => {
trace!("Copying SPIS tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx);
self.blocking_inner_from_ram(rx, tx_ram_buf)
}
Err(error) => Err(error),
}
}
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> {
let r = T::regs();
let s = T::state();
// Clear status register.
r.status.write(|w| w.overflow().clear().overread().clear());
// Acquire semaphore.
if r.semstat.read().bits() != 1 {
// Reset and enable the acquire event.
r.events_acquired.reset();
r.intenset.write(|w| w.acquired().set());
// Request acquiring the SPIS semaphore.
r.tasks_acquire.write(|w| unsafe { w.bits(1) });
// Wait until CPU has acquired the semaphore.
poll_fn(|cx| {
s.waker.register(cx.waker());
if r.events_acquired.read().bits() == 1 {
r.events_acquired.reset();
return Poll::Ready(());
}
Poll::Pending
})
.await;
}
self.prepare(rx, tx)?;
// Wait for 'end' event.
r.intenset.write(|w| w.end().set());
poll_fn(|cx| {
s.waker.register(cx.waker());
if r.events_end.read().bits() != 0 {
r.events_end.reset();
return Poll::Ready(());
}
Poll::Pending
})
.await;
let n_rx = r.rxd.amount.read().bits() as usize;
let n_tx = r.txd.amount.read().bits() as usize;
compiler_fence(Ordering::SeqCst);
Ok((n_rx, n_tx))
}
async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> {
match self.async_inner_from_ram(rx, tx).await {
Ok(n) => Ok(n),
Err(Error::DMABufferNotInDataMemory) => {
trace!("Copying SPIS tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx);
self.async_inner_from_ram(rx, tx_ram_buf).await
}
Err(error) => Err(error),
}
}
/// Reads data from the SPI bus without sending anything. Blocks until `cs` is deasserted.
/// Returns number of bytes read.
pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<usize, Error> {
self.blocking_inner(data, &[]).map(|n| n.0)
}
/// Simultaneously sends and receives data. Blocks until the transmission is completed.
/// If necessary, the write buffer will be copied into RAM (see struct description for detail).
/// Returns number of bytes transferred `(n_rx, n_tx)`.
pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> {
self.blocking_inner(read, write)
}
/// Same as [`blocking_transfer`](Spis::blocking_transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
/// Returns number of bytes transferred `(n_rx, n_tx)`.
pub fn blocking_transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> {
self.blocking_inner_from_ram(read, write)
}
/// Simultaneously sends and receives data.
/// Places the received data into the same buffer and blocks until the transmission is completed.
/// Returns number of bytes transferred.
pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<usize, Error> {
self.blocking_inner_from_ram(data, data).map(|n| n.0)
}
/// Sends data, discarding any received data. Blocks until the transmission is completed.
/// If necessary, the write buffer will be copied into RAM (see struct description for detail).
/// Returns number of bytes written.
pub fn blocking_write(&mut self, data: &[u8]) -> Result<usize, Error> {
self.blocking_inner(&mut [], data).map(|n| n.1)
}
/// Same as [`blocking_write`](Spis::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
/// Returns number of bytes written.
pub fn blocking_write_from_ram(&mut self, data: &[u8]) -> Result<usize, Error> {
self.blocking_inner_from_ram(&mut [], data).map(|n| n.1)
}
/// Reads data from the SPI bus without sending anything.
/// Returns number of bytes read.
pub async fn read(&mut self, data: &mut [u8]) -> Result<usize, Error> {
self.async_inner(data, &[]).await.map(|n| n.0)
}
/// Simultaneously sends and receives data.
/// If necessary, the write buffer will be copied into RAM (see struct description for detail).
/// Returns number of bytes transferred `(n_rx, n_tx)`.
pub async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> {
self.async_inner(read, write).await
}
/// Same as [`transfer`](Spis::transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
/// Returns number of bytes transferred `(n_rx, n_tx)`.
pub async fn transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> {
self.async_inner_from_ram(read, write).await
}
/// Simultaneously sends and receives data. Places the received data into the same buffer.
/// Returns number of bytes transferred.
pub async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<usize, Error> {
self.async_inner_from_ram(data, data).await.map(|n| n.0)
}
/// Sends data, discarding any received data.
/// If necessary, the write buffer will be copied into RAM (see struct description for detail).
/// Returns number of bytes written.
pub async fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
self.async_inner(&mut [], data).await.map(|n| n.1)
}
/// Same as [`write`](Spis::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
/// Returns number of bytes written.
pub async fn write_from_ram(&mut self, data: &[u8]) -> Result<usize, Error> {
self.async_inner_from_ram(&mut [], data).await.map(|n| n.1)
}
/// Checks if last transaction overread.
pub fn is_overread(&mut self) -> bool {
T::regs().status.read().overread().is_present()
}
/// Checks if last transaction overflowed.
pub fn is_overflow(&mut self) -> bool {
T::regs().status.read().overflow().is_present()
}
}
impl<'d, T: Instance> Drop for Spis<'d, T> {
fn drop(&mut self) {
trace!("spis drop");
// Disable
let r = T::regs();
r.enable.write(|w| w.enable().disabled());
gpio::deconfigure_pin(r.psel.sck.read().bits());
gpio::deconfigure_pin(r.psel.csn.read().bits());
gpio::deconfigure_pin(r.psel.miso.read().bits());
gpio::deconfigure_pin(r.psel.mosi.read().bits());
trace!("spis drop: done");
}
}
pub(crate) mod sealed {
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
pub struct State {
pub waker: AtomicWaker,
}
impl State {
pub const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
}
pub trait Instance {
fn regs() -> &'static pac::spis0::RegisterBlock;
fn state() -> &'static State;
}
}
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
type Interrupt: Interrupt;
}
macro_rules! impl_spis {
($type:ident, $pac_type:ident, $irq:ident) => {
impl crate::spis::sealed::Instance for peripherals::$type {
fn regs() -> &'static pac::spis0::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
fn state() -> &'static crate::spis::sealed::State {
static STATE: crate::spis::sealed::State = crate::spis::sealed::State::new();
&STATE
}
}
impl crate::spis::Instance for peripherals::$type {
type Interrupt = crate::interrupt::$irq;
}
};
}
// ====================
impl<'d, T: Instance> SetConfig for Spis<'d, T> {
type Config = Config;
fn set_config(&mut self, config: &Self::Config) {
let r = T::regs();
// Configure mode.
let mode = config.mode;
r.config.write(|w| {
match mode {
MODE_0 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().leading();
}
MODE_1 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().trailing();
}
MODE_2 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().leading();
}
MODE_3 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().trailing();
}
}
w
});
// Set over-read character.
let orc = config.orc;
r.orc.write(|w| unsafe { w.orc().bits(orc) });
// Set default character.
let def = config.def;
r.def.write(|w| unsafe { w.def().bits(def) });
// Configure auto-acquire on 'transfer end' event.
let auto_acquire = config.auto_acquire;
r.shorts.write(|w| w.end_acquire().bit(auto_acquire));
}
}

759
embassy-nrf/src/twis.rs Normal file
View File

@ -0,0 +1,759 @@
#![macro_use]
//! HAL interface to the TWIS peripheral.
//!
//! See product specification:
//!
//! - nRF52832: Section 33
//! - nRF52840: Section 6.31
use core::future::{poll_fn, Future};
use core::sync::atomic::compiler_fence;
use core::sync::atomic::Ordering::SeqCst;
use core::task::Poll;
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(feature = "time")]
use embassy_time::{Duration, Instant};
use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
use crate::gpio::Pin as GpioPin;
use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::slice_in_ram_or;
use crate::{gpio, pac, Peripheral};
#[non_exhaustive]
pub struct Config {
pub address0: u8,
pub address1: Option<u8>,
pub orc: u8,
pub sda_high_drive: bool,
pub sda_pullup: bool,
pub scl_high_drive: bool,
pub scl_pullup: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
address0: 0x55,
address1: None,
orc: 0x00,
scl_high_drive: false,
sda_pullup: false,
sda_high_drive: false,
scl_pullup: false,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum Status {
Read,
Write,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
TxBufferTooLong,
RxBufferTooLong,
DataNack,
Bus,
DMABufferNotInDataMemory,
Overflow,
OverRead,
Timeout,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Command {
Read,
WriteRead(usize),
Write(usize),
}
/// Interface to a TWIS instance using EasyDMA to offload the transmission and reception workload.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Twis<'d, T: Instance> {
_p: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> Twis<'d, T> {
pub fn new(
twis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
sda: impl Peripheral<P = impl GpioPin> + 'd,
scl: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(twis, irq, sda, scl);
let r = T::regs();
// Configure pins
sda.conf().write(|w| {
w.dir().input();
w.input().connect();
if config.sda_high_drive {
w.drive().h0d1();
} else {
w.drive().s0d1();
}
if config.sda_pullup {
w.pull().pullup();
}
w
});
scl.conf().write(|w| {
w.dir().input();
w.input().connect();
if config.scl_high_drive {
w.drive().h0d1();
} else {
w.drive().s0d1();
}
if config.scl_pullup {
w.pull().pullup();
}
w
});
// Select pins.
r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) });
r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) });
// Enable TWIS instance.
r.enable.write(|w| w.enable().enabled());
// Disable all events interrupts
r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
// Set address
r.address[0].write(|w| unsafe { w.address().bits(config.address0) });
r.config.write(|w| w.address0().enabled());
if let Some(address1) = config.address1 {
r.address[1].write(|w| unsafe { w.address().bits(address1) });
r.config.modify(|_r, w| w.address1().enabled());
}
// Set over-read character
r.orc.write(|w| unsafe { w.orc().bits(config.orc) });
// Generate suspend on read event
r.shorts.write(|w| w.read_suspend().enabled());
irq.set_handler(Self::on_interrupt);
irq.unpend();
irq.enable();
Self { _p: twis }
}
fn on_interrupt(_: *mut ()) {
let r = T::regs();
let s = T::state();
if r.events_read.read().bits() != 0 || r.events_write.read().bits() != 0 {
s.waker.wake();
r.intenclr.modify(|_r, w| w.read().clear().write().clear());
}
if r.events_stopped.read().bits() != 0 {
s.waker.wake();
r.intenclr.modify(|_r, w| w.stopped().clear());
}
if r.events_error.read().bits() != 0 {
s.waker.wake();
r.intenclr.modify(|_r, w| w.error().clear());
}
}
/// 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::DMABufferNotInDataMemory)?;
if buffer.len() > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong);
}
let r = T::regs();
r.txd.ptr.write(|w|
// We're giving the register a pointer to the stack. Since we're
// waiting for the I2C transaction to end before this stack pointer
// becomes invalid, there's nothing wrong here.
//
// The PTR field is a full 32 bits wide and accepts the full range
// of values.
w.ptr().bits(buffer.as_ptr() as u32));
r.txd.maxcnt.write(|w|
// We're giving it the length of the buffer, so no danger of
// accessing invalid memory. We have verified that the length of the
// buffer fits in an `u8`, so the cast to `u8` is also fine.
//
// The MAXCNT field is 8 bits wide and accepts the full range of
// values.
w.maxcnt().bits(buffer.len() as _));
Ok(())
}
/// Set RX buffer, checking that it has suitable length.
unsafe fn set_rx_buffer(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
// NOTE: RAM slice check is not necessary, as a mutable
// slice can only be built from data located in RAM.
if buffer.len() > EASY_DMA_SIZE {
return Err(Error::RxBufferTooLong);
}
let r = T::regs();
r.rxd.ptr.write(|w|
// We're giving the register a pointer to the stack. Since we're
// waiting for the I2C transaction to end before this stack pointer
// becomes invalid, there's nothing wrong here.
//
// The PTR field is a full 32 bits wide and accepts the full range
// of values.
w.ptr().bits(buffer.as_mut_ptr() as u32));
r.rxd.maxcnt.write(|w|
// We're giving it the length of the buffer, so no danger of
// accessing invalid memory. We have verified that the length of the
// buffer fits in an `u8`, so the cast to the type of maxcnt
// is also fine.
//
// Note that that nrf52840 maxcnt is a wider
// type than a u8, so we use a `_` cast rather than a `u8` cast.
// The MAXCNT field is thus at least 8 bits wide and accepts the
// full range of values that fit in a `u8`.
w.maxcnt().bits(buffer.len() as _));
Ok(())
}
fn clear_errorsrc(&mut self) {
let r = T::regs();
r.errorsrc
.write(|w| w.overflow().bit(true).overread().bit(true).dnack().bit(true));
}
/// Returns matched address for latest command.
pub fn address_match(&self) -> u8 {
let r = T::regs();
r.address[r.match_.read().bits() as usize].read().address().bits()
}
/// Returns the index of the address matched in the latest command.
pub fn address_match_index(&self) -> usize {
T::regs().match_.read().bits() as _
}
/// Wait for read, write, stop or error
fn blocking_listen_wait(&mut self) -> Result<Status, Error> {
let r = T::regs();
loop {
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
while r.events_stopped.read().bits() == 0 {}
return Err(Error::Overflow);
}
if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return Err(Error::Bus);
}
if r.events_read.read().bits() != 0 {
r.events_read.reset();
return Ok(Status::Read);
}
if r.events_write.read().bits() != 0 {
r.events_write.reset();
return Ok(Status::Write);
}
}
}
/// Wait for stop, repeated start or error
fn blocking_listen_wait_end(&mut self, status: Status) -> Result<Command, Error> {
let r = T::regs();
loop {
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Err(Error::Overflow);
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return match status {
Status::Read => Ok(Command::Read),
Status::Write => {
let n = r.rxd.amount.read().bits() as usize;
Ok(Command::Write(n))
}
};
} else if r.events_read.read().bits() != 0 {
r.events_read.reset();
let n = r.rxd.amount.read().bits() as usize;
return Ok(Command::WriteRead(n));
}
}
}
/// Wait for stop or error
fn blocking_wait(&mut self) -> Result<usize, Error> {
let r = T::regs();
loop {
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
let errorsrc = r.errorsrc.read();
if errorsrc.overread().is_detected() {
return Err(Error::OverRead);
} else if errorsrc.dnack().is_received() {
return Err(Error::DataNack);
} else {
return Err(Error::Bus);
}
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
let n = r.txd.amount.read().bits() as usize;
return Ok(n);
}
}
}
/// Wait for stop or error with timeout
#[cfg(feature = "time")]
fn blocking_wait_timeout(&mut self, timeout: Duration) -> Result<usize, Error> {
let r = T::regs();
let deadline = Instant::now() + timeout;
loop {
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
let errorsrc = r.errorsrc.read();
if errorsrc.overread().is_detected() {
return Err(Error::OverRead);
} else if errorsrc.dnack().is_received() {
return Err(Error::DataNack);
} else {
return Err(Error::Bus);
}
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
let n = r.txd.amount.read().bits() as usize;
return Ok(n);
} else if Instant::now() > deadline {
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Err(Error::Timeout);
}
}
}
/// Wait for read, write, stop or error with timeout
#[cfg(feature = "time")]
fn blocking_listen_wait_timeout(&mut self, timeout: Duration) -> Result<Status, Error> {
let r = T::regs();
let deadline = Instant::now() + timeout;
loop {
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
while r.events_stopped.read().bits() == 0 {}
return Err(Error::Overflow);
}
if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return Err(Error::Bus);
}
if r.events_read.read().bits() != 0 {
r.events_read.reset();
return Ok(Status::Read);
}
if r.events_write.read().bits() != 0 {
r.events_write.reset();
return Ok(Status::Write);
}
if Instant::now() > deadline {
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Err(Error::Timeout);
}
}
}
/// Wait for stop, repeated start or error with timeout
#[cfg(feature = "time")]
fn blocking_listen_wait_end_timeout(&mut self, status: Status, timeout: Duration) -> Result<Command, Error> {
let r = T::regs();
let deadline = Instant::now() + timeout;
loop {
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Err(Error::Overflow);
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return match status {
Status::Read => Ok(Command::Read),
Status::Write => {
let n = r.rxd.amount.read().bits() as usize;
Ok(Command::Write(n))
}
};
} else if r.events_read.read().bits() != 0 {
r.events_read.reset();
let n = r.rxd.amount.read().bits() as usize;
return Ok(Command::WriteRead(n));
} else if Instant::now() > deadline {
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Err(Error::Timeout);
}
}
}
/// Wait for stop or error
fn async_wait(&mut self) -> impl Future<Output = Result<usize, Error>> {
poll_fn(move |cx| {
let r = T::regs();
let s = T::state();
s.waker.register(cx.waker());
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
let errorsrc = r.errorsrc.read();
if errorsrc.overread().is_detected() {
return Poll::Ready(Err(Error::OverRead));
} else if errorsrc.dnack().is_received() {
return Poll::Ready(Err(Error::DataNack));
} else {
return Poll::Ready(Err(Error::Bus));
}
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
let n = r.txd.amount.read().bits() as usize;
return Poll::Ready(Ok(n));
}
Poll::Pending
})
}
/// Wait for read or write
fn async_listen_wait(&mut self) -> impl Future<Output = Result<Status, Error>> {
poll_fn(move |cx| {
let r = T::regs();
let s = T::state();
s.waker.register(cx.waker());
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Poll::Ready(Err(Error::Overflow));
} else if r.events_read.read().bits() != 0 {
r.events_read.reset();
return Poll::Ready(Ok(Status::Read));
} else if r.events_write.read().bits() != 0 {
r.events_write.reset();
return Poll::Ready(Ok(Status::Write));
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return Poll::Ready(Err(Error::Bus));
}
Poll::Pending
})
}
/// Wait for stop, repeated start or error
fn async_listen_wait_end(&mut self, status: Status) -> impl Future<Output = Result<Command, Error>> {
poll_fn(move |cx| {
let r = T::regs();
let s = T::state();
s.waker.register(cx.waker());
// stop if an error occured
if r.events_error.read().bits() != 0 {
r.events_error.reset();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
return Poll::Ready(Err(Error::Overflow));
} else if r.events_stopped.read().bits() != 0 {
r.events_stopped.reset();
return match status {
Status::Read => Poll::Ready(Ok(Command::Read)),
Status::Write => {
let n = r.rxd.amount.read().bits() as usize;
Poll::Ready(Ok(Command::Write(n)))
}
};
} else if r.events_read.read().bits() != 0 {
r.events_read.reset();
let n = r.rxd.amount.read().bits() as usize;
return Poll::Ready(Ok(Command::WriteRead(n)));
}
Poll::Pending
})
}
fn setup_respond_from_ram(&mut self, buffer: &[u8], inten: bool) -> Result<(), Error> {
let r = T::regs();
compiler_fence(SeqCst);
// Set up the DMA write.
unsafe { self.set_tx_buffer(buffer)? };
// Clear events
r.events_stopped.reset();
r.events_error.reset();
self.clear_errorsrc();
if inten {
r.intenset.write(|w| w.stopped().set().error().set());
} else {
r.intenclr.write(|w| w.stopped().clear().error().clear());
}
// Start write operation.
r.tasks_preparetx.write(|w| unsafe { w.bits(1) });
r.tasks_resume.write(|w| unsafe { w.bits(1) });
Ok(())
}
fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> {
match self.setup_respond_from_ram(wr_buffer, inten) {
Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => {
trace!("Copying TWIS 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_respond_from_ram(&tx_ram_buf, inten)
}
Err(error) => Err(error),
}
}
fn setup_listen(&mut self, buffer: &mut [u8], inten: bool) -> Result<(), Error> {
let r = T::regs();
compiler_fence(SeqCst);
// Set up the DMA read.
unsafe { self.set_rx_buffer(buffer)? };
// Clear events
r.events_read.reset();
r.events_write.reset();
r.events_stopped.reset();
r.events_error.reset();
self.clear_errorsrc();
if inten {
r.intenset
.write(|w| w.stopped().set().error().set().read().set().write().set());
} else {
r.intenclr
.write(|w| w.stopped().clear().error().clear().read().clear().write().clear());
}
// Start read operation.
r.tasks_preparerx.write(|w| unsafe { w.bits(1) });
Ok(())
}
fn setup_listen_end(&mut self, inten: bool) -> Result<(), Error> {
let r = T::regs();
compiler_fence(SeqCst);
// Clear events
r.events_read.reset();
r.events_write.reset();
r.events_stopped.reset();
r.events_error.reset();
self.clear_errorsrc();
if inten {
r.intenset.write(|w| w.stopped().set().error().set().read().set());
} else {
r.intenclr.write(|w| w.stopped().clear().error().clear().read().clear());
}
Ok(())
}
/// Wait for commands from an I2C master.
/// `buffer` is provided in case master does a 'write' and is unused for 'read'.
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
/// To know which one of the addresses were matched, call `address_match` or `address_match_index`
pub fn blocking_listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> {
self.setup_listen(buffer, false)?;
let status = self.blocking_listen_wait()?;
if status == Status::Write {
self.setup_listen_end(false)?;
let command = self.blocking_listen_wait_end(status)?;
return Ok(command);
}
Ok(Command::Read)
}
/// Respond to an I2C master READ command.
/// Returns the number of bytes written.
/// 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_respond_to_read(&mut self, buffer: &[u8]) -> Result<usize, Error> {
self.setup_respond(buffer, false)?;
self.blocking_wait()
}
/// Same as [`blocking_respond_to_read`](Twis::blocking_respond_to_read) but will fail instead of copying data into RAM.
/// Consult the module level documentation to learn more.
pub fn blocking_respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result<usize, Error> {
self.setup_respond_from_ram(buffer, false)?;
self.blocking_wait()
}
// ===========================================
/// Wait for commands from an I2C master, with timeout.
/// `buffer` is provided in case master does a 'write' and is unused for 'read'.
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
/// To know which one of the addresses were matched, call `address_match` or `address_match_index`
#[cfg(feature = "time")]
pub fn blocking_listen_timeout(&mut self, buffer: &mut [u8], timeout: Duration) -> Result<Command, Error> {
self.setup_listen(buffer, false)?;
let status = self.blocking_listen_wait_timeout(timeout)?;
if status == Status::Write {
self.setup_listen_end(false)?;
let command = self.blocking_listen_wait_end_timeout(status, timeout)?;
return Ok(command);
}
Ok(Command::Read)
}
/// Respond to an I2C master READ command with timeout.
/// Returns the number of bytes written.
/// See [`blocking_respond_to_read`].
#[cfg(feature = "time")]
pub fn blocking_respond_to_read_timeout(&mut self, buffer: &[u8], timeout: Duration) -> Result<usize, Error> {
self.setup_respond(buffer, false)?;
self.blocking_wait_timeout(timeout)
}
/// Same as [`blocking_respond_to_read_timeout`](Twis::blocking_respond_to_read_timeout) but will fail instead of copying data into RAM.
/// Consult the module level documentation to learn more.
#[cfg(feature = "time")]
pub fn blocking_respond_to_read_from_ram_timeout(
&mut self,
buffer: &[u8],
timeout: Duration,
) -> Result<usize, Error> {
self.setup_respond_from_ram(buffer, false)?;
self.blocking_wait_timeout(timeout)
}
// ===========================================
/// Wait asynchronously for commands from an I2C master.
/// `buffer` is provided in case master does a 'write' and is unused for 'read'.
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
/// To know which one of the addresses were matched, call `address_match` or `address_match_index`
pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> {
self.setup_listen(buffer, true)?;
let status = self.async_listen_wait().await?;
if status == Status::Write {
self.setup_listen_end(true)?;
let command = self.async_listen_wait_end(status).await?;
return Ok(command);
}
Ok(Command::Read)
}
/// Respond to an I2C master READ command, asynchronously.
/// Returns the number of bytes written.
/// 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 respond_to_read(&mut self, buffer: &[u8]) -> Result<usize, Error> {
self.setup_respond(buffer, true)?;
self.async_wait().await
}
/// Same as [`respond_to_read`](Twis::respond_to_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub async fn respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result<usize, Error> {
self.setup_respond_from_ram(buffer, true)?;
self.async_wait().await
}
}
impl<'a, T: Instance> Drop for Twis<'a, T> {
fn drop(&mut self) {
trace!("twis drop");
// TODO: check for abort
// disable!
let r = T::regs();
r.enable.write(|w| w.enable().disabled());
gpio::deconfigure_pin(r.psel.sda.read().bits());
gpio::deconfigure_pin(r.psel.scl.read().bits());
trace!("twis drop: done");
}
}
pub(crate) mod sealed {
use super::*;
pub struct State {
pub waker: AtomicWaker,
}
impl State {
pub const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
}
pub trait Instance {
fn regs() -> &'static pac::twis0::RegisterBlock;
fn state() -> &'static State;
}
}
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
type Interrupt: Interrupt;
}
macro_rules! impl_twis {
($type:ident, $pac_type:ident, $irq:ident) => {
impl crate::twis::sealed::Instance for peripherals::$type {
fn regs() -> &'static pac::twis0::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
fn state() -> &'static crate::twis::sealed::State {
static STATE: crate::twis::sealed::State = crate::twis::sealed::State::new();
&STATE
}
}
impl crate::twis::Instance for peripherals::$type {
type Interrupt = crate::interrupt::$irq;
}
};
}

173
embassy-rp/src/adc.rs Normal file
View File

@ -0,0 +1,173 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::into_ref;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_02::adc::{Channel, OneShot};
use crate::interrupt::{self, InterruptExt};
use crate::peripherals::ADC;
use crate::{pac, peripherals, Peripheral};
static WAKER: AtomicWaker = AtomicWaker::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
// No errors for now
}
#[non_exhaustive]
pub struct Config {}
impl Default for Config {
fn default() -> Self {
Self {}
}
}
pub struct Adc<'d> {
phantom: PhantomData<&'d ADC>,
}
impl<'d> Adc<'d> {
#[inline]
fn regs() -> pac::adc::Adc {
pac::ADC
}
#[inline]
fn reset() -> pac::resets::regs::Peripherals {
let mut ret = pac::resets::regs::Peripherals::default();
ret.set_adc(true);
ret
}
pub fn new(
_inner: impl Peripheral<P = ADC> + 'd,
irq: impl Peripheral<P = interrupt::ADC_IRQ_FIFO> + 'd,
_config: Config,
) -> Self {
into_ref!(irq);
unsafe {
let reset = Self::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
let r = Self::regs();
// Enable ADC
r.cs().write(|w| w.set_en(true));
// Wait for ADC ready
while !r.cs().read().ready() {}
}
// Setup IRQ
irq.disable();
irq.set_handler(|_| unsafe {
let r = Self::regs();
r.inte().write(|w| w.set_fifo(false));
WAKER.wake();
});
irq.unpend();
irq.enable();
Self { phantom: PhantomData }
}
async fn wait_for_ready() {
let r = Self::regs();
unsafe {
r.inte().write(|w| w.set_fifo(true));
compiler_fence(Ordering::SeqCst);
poll_fn(|cx| {
WAKER.register(cx.waker());
if r.cs().read().ready() {
return Poll::Ready(());
}
Poll::Pending
})
.await;
}
}
pub async fn read<PIN: Channel<Adc<'d>, ID = u8>>(&mut self, _pin: &mut PIN) -> u16 {
let r = Self::regs();
unsafe {
r.cs().modify(|w| {
w.set_ainsel(PIN::channel());
w.set_start_once(true)
});
Self::wait_for_ready().await;
r.result().read().result().into()
}
}
pub async fn read_temperature(&mut self) -> u16 {
let r = Self::regs();
unsafe {
r.cs().modify(|w| w.set_ts_en(true));
if !r.cs().read().ready() {
Self::wait_for_ready().await;
}
r.cs().modify(|w| {
w.set_ainsel(4);
w.set_start_once(true)
});
Self::wait_for_ready().await;
r.result().read().result().into()
}
}
pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8>>(&mut self, _pin: &mut PIN) -> u16 {
let r = Self::regs();
unsafe {
r.cs().modify(|w| {
w.set_ainsel(PIN::channel());
w.set_start_once(true)
});
while !r.cs().read().ready() {}
r.result().read().result().into()
}
}
pub fn blocking_read_temperature(&mut self) -> u16 {
let r = Self::regs();
unsafe {
r.cs().modify(|w| w.set_ts_en(true));
while !r.cs().read().ready() {}
r.cs().modify(|w| {
w.set_ainsel(4);
w.set_start_once(true)
});
while !r.cs().read().ready() {}
r.result().read().result().into()
}
}
}
macro_rules! impl_pin {
($pin:ident, $channel:expr) => {
impl Channel<Adc<'static>> for peripherals::$pin {
type ID = u8;
fn channel() -> u8 {
$channel
}
}
};
}
impl_pin!(PIN_26, 0);
impl_pin!(PIN_27, 1);
impl_pin!(PIN_28, 2);
impl_pin!(PIN_29, 3);
impl<WORD, PIN> OneShot<Adc<'static>, WORD, PIN> for Adc<'static>
where
WORD: From<u16>,
PIN: Channel<Adc<'static>, ID = u8>,
{
type Error = ();
fn read(&mut self, pin: &mut PIN) -> nb::Result<WORD, Self::Error> {
Ok(self.blocking_read(pin).into())
}
}

View File

@ -6,6 +6,7 @@ pub(crate) mod fmt;
mod intrinsics; mod intrinsics;
pub mod adc;
pub mod dma; pub mod dma;
pub mod gpio; pub mod gpio;
pub mod i2c; pub mod i2c;
@ -98,6 +99,8 @@ embassy_hal_common::peripherals! {
RTC, RTC,
FLASH, FLASH,
ADC,
} }
#[link_section = ".boot2"] #[link_section = ".boot2"]

View File

@ -71,7 +71,7 @@ impl<'d, T: Instance> Adc<'d, T> {
#[cfg(adc_g0)] #[cfg(adc_g0)]
T::regs().cfgr1().modify(|reg| { T::regs().cfgr1().modify(|reg| {
reg.set_chselrmod(true); reg.set_chselrmod(false);
}); });
} }
@ -200,7 +200,7 @@ impl<'d, T: Instance> Adc<'d, T> {
#[cfg(not(stm32g0))] #[cfg(not(stm32g0))]
T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel())); T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel()));
#[cfg(stm32g0)] #[cfg(stm32g0)]
T::regs().chselr().write(|reg| reg.set_chsel(pin.channel() as u32)); T::regs().chselr().write(|reg| reg.set_chsel(1 << pin.channel()));
// Some models are affected by an erratum: // Some models are affected by an erratum:
// If we perform conversions slower than 1 kHz, the first read ADC value can be // If we perform conversions slower than 1 kHz, the first read ADC value can be

View File

@ -116,6 +116,24 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); mac.macqtx_fcr().modify(|w| w.set_pt(0x100));
// disable all MMC RX interrupts
mac.mmc_rx_interrupt_mask().write(|w| {
w.set_rxcrcerpim(true);
w.set_rxalgnerpim(true);
w.set_rxucgpim(true);
w.set_rxlpiuscim(true);
w.set_rxlpitrcim(true)
});
// disable all MMC TX interrupts
mac.mmc_tx_interrupt_mask().write(|w| {
w.set_txscolgpim(true);
w.set_txmcolgpim(true);
w.set_txgpktim(true);
w.set_txlpiuscim(true);
w.set_txlpitrcim(true);
});
mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); mtl.mtlrx_qomr().modify(|w| w.set_rsf(true));
mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); mtl.mtltx_qomr().modify(|w| w.set_tsf(true));

View File

@ -6,6 +6,8 @@ use core::task::Poll;
use atomic_polyfill::{compiler_fence, Ordering}; use atomic_polyfill::{compiler_fence, Ordering};
use embassy_cortex_m::interrupt::InterruptExt; use embassy_cortex_m::interrupt::InterruptExt;
use embassy_futures::select::{select, Either};
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_hal_common::{into_ref, PeripheralRef};
use crate::dma::NoDma; use crate::dma::NoDma;
@ -85,6 +87,13 @@ pub enum Error {
BufferTooLong, BufferTooLong,
} }
enum ReadCompletionEvent {
// DMA Read transfer completed first
DmaCompleted,
// Idle line detected first
Idle,
}
pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> { pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> {
tx: UartTx<'d, T, TxDma>, tx: UartTx<'d, T, TxDma>,
rx: UartRx<'d, T, RxDma>, rx: UartRx<'d, T, RxDma>,
@ -385,30 +394,50 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
self.inner_read(buffer, true).await self.inner_read(buffer, true).await
} }
async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result<usize, Error> async fn inner_read_run(
&mut self,
buffer: &mut [u8],
enable_idle_line_detection: bool,
) -> Result<ReadCompletionEvent, Error>
where where
RxDma: crate::usart::RxDma<T>, RxDma: crate::usart::RxDma<T>,
{ {
if buffer.is_empty() {
return Ok(0);
} else if buffer.len() > 0xFFFF {
return Err(Error::BufferTooLong);
}
let r = T::regs(); let r = T::regs();
let buffer_len = buffer.len(); // make sure USART state is restored to neutral state when this future is dropped
let _drop = OnDrop::new(move || {
// defmt::trace!("Clear all USART interrupts and DMA Read Request");
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable RXNE interrupt
w.set_rxneie(false);
// disable parity interrupt
w.set_peie(false);
// disable idle line interrupt
w.set_idleie(false);
});
r.cr3().modify(|w| {
// disable Error Interrupt: (Frame error, Noise error, Overrun error)
w.set_eie(false);
// disable DMA Rx Request
w.set_dmar(false);
});
}
});
let ch = &mut self.rx_dma; let ch = &mut self.rx_dma;
let request = ch.request(); let request = ch.request();
// Start USART DMA
// will not do anything yet because DMAR is not yet set
// future which will complete when DMA Read request completes
let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer);
// SAFETY: The only way we might have a problem is using split rx and tx // SAFETY: The only way we might have a problem is using split rx and tx
// here we only modify or read Rx related flags, interrupts and DMA channel // here we only modify or read Rx related flags, interrupts and DMA channel
unsafe { unsafe {
// Start USART DMA
// will not do anything yet because DMAR is not yet set
ch.start_read(request, rdr(r), buffer, Default::default());
// clear ORE flag just before enabling DMA Rx Request: can be mandatory for the second transfer // clear ORE flag just before enabling DMA Rx Request: can be mandatory for the second transfer
if !self.detect_previous_overrun { if !self.detect_previous_overrun {
let sr = sr(r).read(); let sr = sr(r).read();
@ -443,9 +472,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
// something went wrong // something went wrong
// because the only way to get this flag cleared is to have an interrupt // because the only way to get this flag cleared is to have an interrupt
// abort DMA transfer // DMA will be stopped when transfer is dropped
ch.request_stop();
while ch.is_running() {}
let sr = sr(r).read(); let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1. // This read also clears the error and idle interrupt flags on v1.
@ -468,26 +495,30 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
unreachable!(); unreachable!();
} }
// clear idle flag if !enable_idle_line_detection {
if enable_idle_line_detection { transfer.await;
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
// enable idle interrupt return Ok(ReadCompletionEvent::DmaCompleted);
r.cr1().modify(|w| {
w.set_idleie(true);
});
} }
// clear idle flag
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
// enable idle interrupt
r.cr1().modify(|w| {
w.set_idleie(true);
});
} }
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
let res = poll_fn(move |cx| { // future which completes when idle line is detected
let idle = poll_fn(move |cx| {
let s = T::state(); let s = T::state();
ch.set_waker(cx.waker());
s.rx_waker.register(cx.waker()); s.rx_waker.register(cx.waker());
// SAFETY: read only and we only use Rx related flags // SAFETY: read only and we only use Rx related flags
@ -507,10 +538,6 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
if has_errors { if has_errors {
// all Rx interrupts and Rx DMA Request have already been cleared in interrupt handler // all Rx interrupts and Rx DMA Request have already been cleared in interrupt handler
// stop dma transfer
ch.request_stop();
while ch.is_running() {}
if sr.pe() { if sr.pe() {
return Poll::Ready(Err(Error::Parity)); return Poll::Ready(Err(Error::Parity));
} }
@ -525,45 +552,54 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
} }
} }
if enable_idle_line_detection && sr.idle() { if sr.idle() {
// Idle line // Idle line detected
return Poll::Ready(Ok(()));
// stop dma transfer
ch.request_stop();
while ch.is_running() {}
let n = buffer_len - (ch.remaining_transfers() as usize);
return Poll::Ready(Ok(n));
} else if !ch.is_running() {
// DMA complete
return Poll::Ready(Ok(buffer_len));
} }
Poll::Pending Poll::Pending
}) });
.await;
// clear all interrupts and DMA Rx Request // wait for the first of DMA request or idle line detected to completes
// SAFETY: only clears Rx related flags // select consumes its arguments
unsafe { // when transfer is dropped, it will stop the DMA request
r.cr1().modify(|w| { match select(transfer, idle).await {
// disable RXNE interrupt // DMA transfer completed first
w.set_rxneie(false); Either::First(()) => Ok(ReadCompletionEvent::DmaCompleted),
// disable parity interrupt
w.set_peie(false); // Idle line detected first
// disable idle line interrupt Either::Second(Ok(())) => Ok(ReadCompletionEvent::Idle),
w.set_idleie(false);
}); // error occurred
r.cr3().modify(|w| { Either::Second(Err(e)) => Err(e),
// disable Error Interrupt: (Frame error, Noise error, Overrun error) }
w.set_eie(false); }
// disable DMA Rx Request
w.set_dmar(false); async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result<usize, Error>
}); where
RxDma: crate::usart::RxDma<T>,
{
if buffer.is_empty() {
return Ok(0);
} else if buffer.len() > 0xFFFF {
return Err(Error::BufferTooLong);
} }
res let buffer_len = buffer.len();
// wait for DMA to complete or IDLE line detection if requested
let res = self.inner_read_run(buffer, enable_idle_line_detection).await;
let ch = &mut self.rx_dma;
match res {
Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len),
Ok(ReadCompletionEvent::Idle) => {
let n = buffer_len - (ch.remaining_transfers() as usize);
Ok(n)
}
Err(e) => Err(e),
}
} }
} }

View File

@ -13,12 +13,12 @@ pub struct IndependentWatchdog<'d, T: Instance> {
const MAX_RL: u16 = 0xFFF; const MAX_RL: u16 = 0xFFF;
/// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler /// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler
const fn max_timeout(prescaler: u8) -> u32 { const fn max_timeout(prescaler: u16) -> u32 {
1_000_000 * MAX_RL as u32 / (LSI_FREQ.0 / prescaler as u32) 1_000_000 * MAX_RL as u32 / (LSI_FREQ.0 / prescaler as u32)
} }
/// Calculates watchdog reload value for the given prescaler and desired timeout /// Calculates watchdog reload value for the given prescaler and desired timeout
const fn reload_value(prescaler: u8, timeout_us: u32) -> u16 { const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 {
(timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16
} }
@ -33,12 +33,12 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> {
// Find lowest prescaler value, which makes watchdog period longer or equal to timeout. // Find lowest prescaler value, which makes watchdog period longer or equal to timeout.
// This iterates from 4 (2^2) to 256 (2^8). // This iterates from 4 (2^2) to 256 (2^8).
let psc_power = unwrap!((2..=8).find(|psc_power| { let psc_power = unwrap!((2..=8).find(|psc_power| {
let psc = 2u8.pow(*psc_power); let psc = 2u16.pow(*psc_power);
timeout_us <= max_timeout(psc) timeout_us <= max_timeout(psc)
})); }));
// Prescaler value // Prescaler value
let psc = 2u8.pow(psc_power); let psc = 2u16.pow(psc_power);
// Convert prescaler power to PR register value // Convert prescaler power to PR register value
let pr = psc_power as u8 - 2; let pr = psc_power as u8 - 2;

View File

@ -56,6 +56,15 @@ where
} }
} }
impl<M, T> Default for Signal<M, T>
where
M: RawMutex,
{
fn default() -> Self {
Self::new()
}
}
impl<M, T: Send> Signal<M, T> impl<M, T: Send> Signal<M, T>
where where
M: RawMutex, M: RawMutex,

View File

@ -6,7 +6,7 @@ use crate::blocking_mutex::raw::CriticalSectionRawMutex;
use crate::blocking_mutex::Mutex; use crate::blocking_mutex::Mutex;
/// Utility struct to register and wake a waker. /// Utility struct to register and wake a waker.
#[derive(Debug)] #[derive(Debug, Default)]
pub struct WakerRegistration { pub struct WakerRegistration {
waker: Option<Waker>, waker: Option<Waker>,
} }

View File

@ -0,0 +1,13 @@
[package]
name = "embassy-usb-logger"
version = "0.1.0"
edition = "2021"
[dependencies]
embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
futures = { version = "0.3", default-features = false }
static_cell = "1"
usbd-hid = "0.6.0"
log = "0.4"

View File

@ -0,0 +1,15 @@
# embassy-usb-logger
USB implementation of the `log` crate. This logger can be used by any device that implements `embassy-usb`. When running,
it will output all logging done through the `log` facade to the USB serial peripheral.
## Usage
Add the following embassy task to your application. The `Driver` type is different depending on which HAL you use.
```rust
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
}
```

View File

@ -0,0 +1,146 @@
#![no_std]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use core::fmt::Write as _;
use embassy_futures::join::join;
use embassy_sync::pipe::Pipe;
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
use embassy_usb::driver::Driver;
use embassy_usb::{Builder, Config};
use log::{Metadata, Record};
type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
/// The logger state containing buffers that must live as long as the USB peripheral.
pub struct LoggerState<'d> {
state: State<'d>,
device_descriptor: [u8; 32],
config_descriptor: [u8; 128],
bos_descriptor: [u8; 16],
control_buf: [u8; 64],
}
impl<'d> LoggerState<'d> {
/// Create a new instance of the logger state.
pub fn new() -> Self {
Self {
state: State::new(),
device_descriptor: [0; 32],
config_descriptor: [0; 128],
bos_descriptor: [0; 16],
control_buf: [0; 64],
}
}
}
/// The logger handle, which contains a pipe with configurable size for buffering log messages.
pub struct UsbLogger<const N: usize> {
buffer: Pipe<CS, N>,
}
impl<const N: usize> UsbLogger<N> {
/// Create a new logger instance.
pub const fn new() -> Self {
Self { buffer: Pipe::new() }
}
/// Run the USB logger using the state and USB driver. Never returns.
pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> !
where
D: Driver<'d>,
Self: 'd,
{
const MAX_PACKET_SIZE: u8 = 64;
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("USB-serial logger");
config.serial_number = None;
config.max_power = 100;
config.max_packet_size_0 = MAX_PACKET_SIZE;
// Required for windows compatiblity.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;
let mut builder = Builder::new(
driver,
config,
&mut state.device_descriptor,
&mut state.config_descriptor,
&mut state.bos_descriptor,
&mut state.control_buf,
None,
);
// Create classes on the builder.
let mut class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16);
// Build the builder.
let mut device = builder.build();
loop {
let run_fut = device.run();
let log_fut = async {
let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
class.wait_connection().await;
loop {
let len = self.buffer.read(&mut rx[..]).await;
let _ = class.write_packet(&rx[..len]).await;
}
};
join(run_fut, log_fut).await;
}
}
}
impl<const N: usize> log::Log for UsbLogger<N> {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
}
}
fn flush(&self) {}
}
struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
let _ = self.0.try_write(s.as_bytes());
Ok(())
}
}
/// Initialize and run the USB serial logger, never returns.
///
/// Arguments specify the buffer size, log level and the USB driver, respectively.
///
/// # Usage
///
/// ```
/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
/// ```
///
/// # Safety
///
/// This macro should only be invoked only once since it is setting the global logging state of the application.
#[macro_export]
macro_rules! run {
( $x:expr, $l:expr, $p:ident ) => {
static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new();
unsafe {
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level($l));
}
let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
};
}

View File

@ -0,0 +1,27 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::info;
use embassy_executor::Spawner;
use embassy_nrf::interrupt;
use embassy_nrf::spis::{Config, Spis};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
info!("Running!");
let irq = interrupt::take!(SPIM2_SPIS2_SPI2);
let mut spis = Spis::new(p.SPI2, irq, p.P0_31, p.P0_29, p.P0_28, p.P0_30, Config::default());
loop {
let mut rx_buf = [0_u8; 64];
let tx_buf = [1_u8, 2, 3, 4, 5, 6, 7, 8];
if let Ok((n_rx, n_tx)) = spis.transfer(&mut rx_buf, &tx_buf).await {
info!("RX: {:?}", rx_buf[..n_rx]);
info!("TX: {:?}", tx_buf[..n_tx]);
}
}
}

View File

@ -0,0 +1,46 @@
//! TWIS example
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::*;
use embassy_executor::Spawner;
use embassy_nrf::interrupt;
use embassy_nrf::twis::{self, Command, Twis};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
let mut config = twis::Config::default();
// Set i2c address
config.address0 = 0x55;
let mut i2c = Twis::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config);
info!("Listening...");
loop {
let response = [1, 2, 3, 4, 5, 6, 7, 8];
// This buffer is used if the i2c master performs a Write or WriteRead
let mut buf = [0u8; 16];
match i2c.listen(&mut buf).await {
Ok(Command::Read) => {
info!("Got READ command. Respond with data:\n{:?}\n", response);
if let Err(e) = i2c.respond_to_read(&response).await {
error!("{:?}", e);
}
}
Ok(Command::Write(n)) => info!("Got WRITE command with data:\n{:?}\n", buf[..n]),
Ok(Command::WriteRead(n)) => {
info!("Got WRITE/READ command with data:\n{:?}", buf[..n]);
info!("Respond with data:\n{:?}\n", response);
if let Err(e) = i2c.respond_to_read(&response).await {
error!("{:?}", e);
}
}
Err(e) => error!("{:?}", e),
}
}
}

View File

@ -13,6 +13,7 @@ embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt"
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
defmt = "0.3" defmt = "0.3"
defmt-rtt = "0.3" defmt-rtt = "0.3"
@ -32,6 +33,7 @@ embedded-hal-async = { version = "0.1.0-alpha.3" }
embedded-io = { version = "0.3.1", features = ["async", "defmt"] } embedded-io = { version = "0.3.1", features = ["async", "defmt"] }
embedded-storage = { version = "0.3" } embedded-storage = { version = "0.3" }
static_cell = "1.0.0" static_cell = "1.0.0"
log = "0.4"
[profile.release] [profile.release]
debug = true debug = true

View File

@ -0,0 +1,33 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::adc::{Adc, Config};
use embassy_rp::interrupt;
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let irq = interrupt::take!(ADC_IRQ_FIFO);
let mut adc = Adc::new(p.ADC, irq, Config::default());
let mut p26 = p.PIN_26;
let mut p27 = p.PIN_27;
let mut p28 = p.PIN_28;
loop {
let level = adc.read(&mut p26).await;
info!("Pin 26 ADC: {}", level);
let level = adc.read(&mut p27).await;
info!("Pin 27 ADC: {}", level);
let level = adc.read(&mut p28).await;
info!("Pin 28 ADC: {}", level);
let temp = adc.read_temperature().await;
info!("Temp: {}", temp);
Timer::after(Duration::from_secs(1)).await;
}
}

View File

@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_executor::Spawner;
use embassy_rp::interrupt;
use embassy_rp::peripherals::USB;
use embassy_rp::usb::Driver;
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let irq = interrupt::take!(USBCTRL_IRQ);
let driver = Driver::new(p.USB, irq);
spawner.spawn(logger_task(driver)).unwrap();
let mut counter = 0;
loop {
counter += 1;
log::info!("Tick {}", counter);
Timer::after(Duration::from_secs(1)).await;
}
}

View File

@ -0,0 +1,24 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::wdg::IndependentWatchdog;
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello World!");
let mut wdg = IndependentWatchdog::new(p.IWDG1, 20_000_000);
unsafe { wdg.unleash() };
loop {
Timer::after(Duration::from_secs(1)).await;
unsafe { wdg.pet() };
}
}