├── .gitignore ├── nrf52 ├── README.md ├── openocd.cfg ├── memory.x ├── build.rs ├── openocd.gdb ├── Cargo.toml ├── examples │ ├── 5-heartbeat.rs │ ├── 6-hello.rs │ ├── 1-yield.rs │ ├── 7-echo.rs │ ├── 4-channel.rs │ ├── 2-share.rs │ ├── 3-mutex.rs │ ├── 8-sensor.rs │ └── 9-clock.rs └── src │ ├── led.rs │ ├── lib.rs │ ├── scd30.rs │ ├── ds3231.rs │ ├── timer.rs │ ├── serial.rs │ └── twim.rs ├── async-embedded ├── src │ ├── unsync.rs │ ├── task.rs │ ├── alloc.rs │ ├── unsync │ │ ├── mutex.rs │ │ ├── waker_set.rs │ │ └── channel.rs │ ├── lib.rs │ └── executor.rs └── Cargo.toml ├── panic-udf ├── Cargo.toml └── src │ └── lib.rs ├── .cargo └── config ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /nrf52/README.md: -------------------------------------------------------------------------------- 1 | # `nrf52` 2 | 3 | > Async examples on the nRF52840 4 | -------------------------------------------------------------------------------- /nrf52/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/cmsis-dap.cfg] 2 | source [find target/nrf52.cfg] -------------------------------------------------------------------------------- /nrf52/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 6 | } 7 | -------------------------------------------------------------------------------- /async-embedded/src/unsync.rs: -------------------------------------------------------------------------------- 1 | //! Tasks synchronization primitives that are *not* thread / interrupt safe (`!Sync`) 2 | 3 | mod channel; 4 | mod mutex; 5 | mod waker_set; 6 | 7 | pub use channel::Channel; 8 | pub use mutex::Mutex; 9 | -------------------------------------------------------------------------------- /panic-udf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "panic-udf" 6 | publish = false 7 | version = "0.0.0-alpha.0" 8 | 9 | [dependencies] 10 | cortex-m = "0.6.3" 11 | -------------------------------------------------------------------------------- /panic-udf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stable version of `panic-abort` for the Cortex-M architecture 2 | 3 | #![deny(missing_docs)] 4 | #![deny(rust_2018_idioms)] 5 | #![deny(warnings)] 6 | #![no_std] 7 | 8 | use core::panic::PanicInfo; 9 | 10 | #[panic_handler] 11 | fn panic(_: &PanicInfo<'_>) -> ! { 12 | cortex_m::asm::udf() 13 | } 14 | -------------------------------------------------------------------------------- /nrf52/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = &PathBuf::from(env::var("OUT_DIR")?); 5 | 6 | // place the linker script somewhere the linker can find it 7 | let filename = "memory.x"; 8 | fs::copy(filename, out_dir.join(filename))?; 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | be = "build --example" 3 | br = "build --release" 4 | bre = "build --release --example" 5 | re = "run --example" 6 | rr = "run --release" 7 | rre = "run --release --example" 8 | 9 | [target.thumbv7em-none-eabihf] 10 | rustflags = [ 11 | "-C", "linker=arm-none-eabi-ld", 12 | "-C", "link-arg=-Tlink.x", 13 | ] 14 | runner = "arm-none-eabi-gdb -q -x openocd.gdb" 15 | 16 | [build] 17 | target = "thumbv7em-none-eabihf" # Cortex-M4F -------------------------------------------------------------------------------- /nrf52/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | monitor arm semihosting enable 18 | 19 | load 20 | 21 | # start the process but immediately halt the processor 22 | stepi -------------------------------------------------------------------------------- /nrf52/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "nrf52" 6 | publish = false 7 | version = "0.0.0-alpha.0" 8 | 9 | [dev-dependencies] 10 | cortex-m = "0.6" 11 | cortex-m-semihosting = "0.3.5" 12 | heapless = "0.5.3" 13 | panic-semihosting = "0.5.3" 14 | panic-udf = { path = "../panic-udf" } 15 | 16 | [dependencies] 17 | async-embedded = { path = "../async-embedded" } 18 | cortex-m = "0.6.2" 19 | cortex-m-rt = "0.6.12" 20 | pac = { package = "nrf52840-pac", version = "0.9.0", features = ["rt"] } 21 | 22 | [dependencies.chrono] 23 | default-features = false 24 | version = "0.4.10" 25 | -------------------------------------------------------------------------------- /async-embedded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "async-embedded" 6 | publish = false 7 | version = "0.0.0-alpha.0" 8 | 9 | [dependencies] 10 | generic-array = "0.14.2" 11 | heapless = { git = "https://github.com/japaric/heapless", branch = "slab" } 12 | pin-utils = "0.1.0" 13 | typenum = "1.12.0" 14 | 15 | [target.'cfg(target_arch = "arm")'.dependencies] 16 | cortex-m = "0.6.3" 17 | 18 | [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] 19 | riscv = "0.6" 20 | 21 | [features] 22 | default = ["riscv-wait-nop"] 23 | riscv-wait-nop = [] 24 | riscv-wait-wfi-single-hart = [] 25 | riscv-wait-extern = [] 26 | -------------------------------------------------------------------------------- /nrf52/examples/5-heartbeat.rs: -------------------------------------------------------------------------------- 1 | //! Yielding from a task 2 | //! 3 | //! Expected output: 4 | //! 5 | //! ``` 6 | //! B: yield 7 | //! A: yield 8 | //! B: yield 9 | //! A: yield 10 | //! DONE 11 | //! ``` 12 | 13 | #![deny(unsafe_code)] 14 | #![deny(warnings)] 15 | #![no_main] 16 | #![no_std] 17 | 18 | use core::time::Duration; 19 | 20 | use async_embedded::task; 21 | use cortex_m_rt::entry; 22 | use nrf52::{led::Red, timer::Timer}; 23 | use panic_udf as _; // panic handler 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | let mut timer = Timer::take(); 28 | 29 | let dur = Duration::from_millis(100); 30 | task::block_on(async { 31 | loop { 32 | Red.on(); 33 | timer.wait(dur).await; 34 | Red.off(); 35 | timer.wait(dur).await; 36 | Red.on(); 37 | timer.wait(dur).await; 38 | Red.off(); 39 | timer.wait(12 * dur).await; 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "async-embedded", 4 | "nrf52", 5 | "panic-udf", 6 | ] 7 | 8 | [profile.dev] 9 | codegen-units = 1 10 | debug = 1 11 | debug-assertions = true # ! 12 | incremental = false 13 | lto = "fat" 14 | opt-level = 'z' # ! 15 | overflow-checks = true # ! 16 | 17 | [profile.release] 18 | codegen-units = 1 19 | debug = 1 20 | debug-assertions = false 21 | incremental = false 22 | lto = "fat" 23 | opt-level = 3 24 | overflow-checks = false 25 | 26 | # `syn` and other proc-macro crates take very long to build when optimized 27 | # this disables optimizations for them significantly reducing the time it takes 28 | # to build the whole dependency graph from scratch 29 | [profile.dev.build-override] 30 | codegen-units = 16 31 | debug = false 32 | debug-assertions = false 33 | incremental = true 34 | opt-level = 0 35 | overflow-checks = false 36 | 37 | [profile.release.build-override] 38 | codegen-units = 16 39 | debug = false 40 | debug-assertions = false 41 | incremental = true 42 | opt-level = 0 43 | overflow-checks = false -------------------------------------------------------------------------------- /nrf52/examples/6-hello.rs: -------------------------------------------------------------------------------- 1 | //! Spam "Hello, world!" over the serial line (@ 9600 bauds) 2 | //! 3 | //! TXD = P0.06 4 | //! RXD = P0.08 5 | 6 | #![deny(unsafe_code)] 7 | #![deny(warnings)] 8 | #![no_main] 9 | #![no_std] 10 | 11 | use core::time::Duration; 12 | 13 | use async_embedded::task; 14 | use cortex_m_rt::entry; 15 | use nrf52::{led::Red, serial, timer::Timer}; 16 | use panic_udf as _; // panic handler 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | // heartbeat task 21 | let mut timer = Timer::take(); 22 | let dur = Duration::from_millis(100); 23 | task::spawn(async move { 24 | loop { 25 | Red.on(); 26 | timer.wait(dur).await; 27 | Red.off(); 28 | timer.wait(dur).await; 29 | Red.on(); 30 | timer.wait(dur).await; 31 | Red.off(); 32 | timer.wait(12 * dur).await; 33 | } 34 | }); 35 | 36 | let (mut tx, _rx) = serial::take(); 37 | task::block_on(async { 38 | loop { 39 | tx.write(b"Hello, world!\n\r").await; 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /nrf52/examples/1-yield.rs: -------------------------------------------------------------------------------- 1 | //! Yielding from a task 2 | //! 3 | //! Expected output: 4 | //! 5 | //! ``` 6 | //! B: yield 7 | //! A: yield 8 | //! B: yield 9 | //! A: yield 10 | //! DONE 11 | //! ``` 12 | 13 | #![deny(unsafe_code)] 14 | #![deny(warnings)] 15 | #![no_main] 16 | #![no_std] 17 | 18 | use async_embedded::task; 19 | use cortex_m::asm; 20 | use cortex_m_rt::entry; 21 | use cortex_m_semihosting::hprintln; 22 | use nrf52 as _; // memory layout 23 | use panic_udf as _; // panic handler 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | // task A 28 | task::spawn(async { 29 | loop { 30 | hprintln!("A: yield").ok(); 31 | // context switch to B 32 | task::r#yield().await; 33 | } 34 | }); 35 | 36 | // task B 37 | task::block_on(async { 38 | hprintln!("B: yield").ok(); 39 | 40 | // context switch to A 41 | task::r#yield().await; 42 | 43 | hprintln!("B: yield").ok(); 44 | 45 | task::r#yield().await; 46 | 47 | hprintln!("DONE").ok(); 48 | 49 | loop { 50 | asm::bkpt(); 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /nrf52/examples/7-echo.rs: -------------------------------------------------------------------------------- 1 | //! Echo back data received over the serial line (@ 9600 bauds) 2 | //! 3 | //! TXD = P0.06 4 | //! RXD = P0.08 5 | 6 | #![deny(unsafe_code)] 7 | #![deny(warnings)] 8 | #![no_main] 9 | #![no_std] 10 | 11 | use core::time::Duration; 12 | 13 | use async_embedded::task; 14 | use cortex_m_rt::entry; 15 | use nrf52::{led::Red, serial, timer::Timer}; 16 | use panic_udf as _; // panic handler 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | // heartbeat task 21 | let mut timer = Timer::take(); 22 | let dur = Duration::from_millis(100); 23 | task::spawn(async move { 24 | loop { 25 | Red.on(); 26 | timer.wait(dur).await; 27 | Red.off(); 28 | timer.wait(dur).await; 29 | Red.on(); 30 | timer.wait(dur).await; 31 | Red.off(); 32 | timer.wait(12 * dur).await; 33 | } 34 | }); 35 | 36 | let (mut tx, mut rx) = serial::take(); 37 | task::block_on(async { 38 | let mut buf = [0; 1]; 39 | loop { 40 | rx.read(&mut buf).await; 41 | tx.write(&buf).await; 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2020 Ferrous Systems GmbH 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /nrf52/examples/4-channel.rs: -------------------------------------------------------------------------------- 1 | //! Message passing between tasks using a MPMC channel 2 | //! 3 | //! Expected output: 4 | //! 5 | //! ``` 6 | //! B: before recv 7 | //! A: before send 8 | //! A: after send 9 | //! A: yield 10 | //! B: 42 11 | //! DONE 12 | //! ``` 13 | 14 | #![deny(unsafe_code)] 15 | #![deny(warnings)] 16 | #![no_main] 17 | #![no_std] 18 | 19 | use async_embedded::{task, unsync::Channel}; 20 | use cortex_m::asm; 21 | use cortex_m_rt::entry; 22 | use cortex_m_semihosting::hprintln; 23 | use nrf52 as _; // memory layout 24 | use panic_udf as _; // panic handler 25 | 26 | #[entry] 27 | fn main() -> ! { 28 | static mut C: Channel = Channel::new(); 29 | 30 | // coerce to a shared (`&-`) reference to avoid _one_ of the `move` blocks taking ownership of 31 | // the owning static (`&'static mut`) reference 32 | let c: &'static _ = C; 33 | 34 | task::spawn(async move { 35 | hprintln!("A: before send").ok(); 36 | 37 | c.send(42).await; 38 | 39 | hprintln!("A: after send").ok(); 40 | 41 | loop { 42 | hprintln!("A: yield").ok(); 43 | task::r#yield().await; 44 | } 45 | }); 46 | 47 | task::block_on(async move { 48 | hprintln!("B: before recv").ok(); 49 | 50 | // cannot immediately make progress; context switch to A 51 | let msg = c.recv().await; 52 | 53 | hprintln!("B: {}", msg).ok(); 54 | 55 | hprintln!("DONE").ok(); 56 | 57 | loop { 58 | asm::bkpt(); 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /async-embedded/src/task.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous tasks 2 | 3 | use core::{ 4 | future::Future, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use crate::executor; 10 | 11 | /// Drives the future `f` to completion 12 | /// 13 | /// This also makes any previously `spawn`-ed future make progress 14 | pub fn block_on(f: impl Future) -> T { 15 | executor::current().block_on(f) 16 | } 17 | 18 | /// Spawns a task onto the executor 19 | /// 20 | /// The spawned task will not make any progress until `block_on` is called. 21 | /// 22 | /// The future `f` must never terminate. The program will *abort* if `f` (the async code) returns. 23 | /// The right signature here would be `f: impl Future` but that requires nightly 24 | pub fn spawn(f: impl Future + 'static) { 25 | executor::current().spawn(f) 26 | } 27 | 28 | /// Use `r#yield.await` to suspend the execution of a task 29 | pub async fn r#yield() { 30 | struct Yield { 31 | yielded: bool, 32 | } 33 | 34 | impl Future for Yield { 35 | type Output = (); 36 | 37 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 38 | if self.yielded { 39 | Poll::Ready(()) 40 | } else { 41 | self.yielded = true; 42 | // wake ourselves 43 | cx.waker().wake_by_ref(); 44 | unsafe { crate::signal_event_ready(); } 45 | Poll::Pending 46 | } 47 | } 48 | } 49 | 50 | Yield { yielded: false }.await 51 | } 52 | -------------------------------------------------------------------------------- /async-embedded/src/alloc.rs: -------------------------------------------------------------------------------- 1 | //! `no_std`, `no_alloc` bump pointer allocator 2 | //! 3 | //! This allocator manages "static" memory, memory that resides in a `static` variable and that will 4 | //! never be deallocated. This allocator never deallocates the memory it allocates. 5 | 6 | use core::mem::{self, MaybeUninit}; 7 | 8 | pub struct Alloc { 9 | len: usize, 10 | pos: usize, 11 | start: *mut u8, 12 | } 13 | 14 | impl Alloc { 15 | pub(crate) fn new(memory: &'static mut [u8]) -> Self { 16 | Self { 17 | len: memory.len(), 18 | pos: 0, 19 | start: memory.as_mut_ptr(), 20 | } 21 | } 22 | 23 | fn alloc(&mut self) -> &'static mut MaybeUninit { 24 | let size = mem::size_of::(); 25 | let align = mem::align_of::(); 26 | let new_pos = round_up(self.pos, align); 27 | if new_pos + size < self.len { 28 | self.pos = new_pos + size; 29 | unsafe { &mut *(self.start.add(new_pos) as *mut MaybeUninit) } 30 | } else { 31 | // OOM 32 | crate::abort(); 33 | } 34 | } 35 | 36 | /// Effectively stores `val` in static memory and returns a reference to it 37 | pub(crate) fn alloc_init(&mut self, val: T) -> &'static mut T { 38 | let slot = self.alloc::(); 39 | unsafe { 40 | slot.as_mut_ptr().write(val); 41 | &mut *slot.as_mut_ptr() 42 | } 43 | } 44 | } 45 | 46 | /// Rounds up `n` a the nearest multiple `m` 47 | fn round_up(n: usize, m: usize) -> usize { 48 | let rem = n % m; 49 | if rem == 0 { 50 | n 51 | } else { 52 | (n + m) - rem 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nrf52/src/led.rs: -------------------------------------------------------------------------------- 1 | //! LEDs 2 | 3 | // NOTE(borrow_unchecked) all writes are single-instruction, atomic operations 4 | // on a stateless register 5 | 6 | use pac::P0; 7 | 8 | use crate::BorrowUnchecked as _; 9 | 10 | // NOTE called from `pre_init` 11 | pub(crate) fn init() { 12 | pac::P0::borrow_unchecked(|p0| { 13 | // set outputs lows 14 | p0.outset 15 | .write(|w| w.pin13().set_bit().pin14().set_bit().pin15().set_bit()); 16 | 17 | // set pins as output 18 | p0.dirset 19 | .write(|w| w.pin13().set_bit().pin14().set_bit().pin15().set_bit()); 20 | }); 21 | } 22 | 23 | /// Red LED 24 | pub struct Red; 25 | 26 | impl Red { 27 | /// Turns the LED off 28 | pub fn off(&self) { 29 | P0::borrow_unchecked(|p0| p0.outset.write(|w| w.pin13().set_bit())) 30 | } 31 | 32 | /// Turns the LED on 33 | pub fn on(&self) { 34 | P0::borrow_unchecked(|p0| p0.outclr.write(|w| w.pin13().set_bit())) 35 | } 36 | } 37 | 38 | /// Green LED 39 | pub struct Green; 40 | 41 | impl Green { 42 | /// Turns the LED off 43 | pub fn off(&self) { 44 | P0::borrow_unchecked(|p0| p0.outset.write(|w| w.pin14().set_bit())) 45 | } 46 | 47 | /// Turns the LED on 48 | pub fn on(&self) { 49 | P0::borrow_unchecked(|p0| p0.outclr.write(|w| w.pin14().set_bit())) 50 | } 51 | } 52 | 53 | /// Blue LED 54 | pub struct Blue; 55 | 56 | impl Blue { 57 | /// Turns the LED off 58 | pub fn off(&self) { 59 | P0::borrow_unchecked(|p0| p0.outset.write(|w| w.pin15().set_bit())) 60 | } 61 | 62 | /// Turns the LED on 63 | pub fn on(&self) { 64 | P0::borrow_unchecked(|p0| p0.outclr.write(|w| w.pin15().set_bit())) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `async-on-embedded` 2 | 3 | > `async fn` / `.await` on embedded (Cortex-M edition): no-global-alloc, no-threads runtime 4 | 5 | **Status:** Proof of Concept. Do not use in production. 6 | 7 | ***NOTE: [Embassy](https://github.com/embassy-rs/embassy) is a maintained, production executor and library with similar functionality.*** 8 | 9 | ***NOTE: This library contains known bugs.*** 10 | 11 | --- 12 | 13 | Check `nrf52/examples` for an overview of what can be done with the runtime. 14 | 15 | **NOTE** You need a rustc build that includes PR [rust-lang/rust#69033] 16 | 17 | [rust-lang/rust#69033]: https://github.com/rust-lang/rust/pull/69033 18 | 19 | ## License 20 | 21 | Licensed under either of 22 | 23 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 24 | http://www.apache.org/licenses/LICENSE-2.0) 25 | 26 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 27 | 28 | at your option. 29 | 30 | ### Contribution 31 | 32 | Unless you explicitly state otherwise, any contribution intentionally submitted 33 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 34 | licensed as above, without any additional terms or conditions. 35 | 36 | ### Provenance 37 | 38 | 42 | 43 | This work was originally developed by [Ferrous Systems](https://ferrous-systems.com), as part of our [async-on-embedded](https://github.com/ferrous-systems/async-on-embedded) investigation. 44 | 45 | You can read more about the initial development efforts on the blog posts we wrote on the topic: 46 | 47 | - [Bringing async/await to embedded Rust](https://ferrous-systems.com/blog/embedded-async-await/) 48 | - [async/await on embedded Rust](https://ferrous-systems.com/blog/async-on-embedded/) 49 | - [no_std async/await - soon on stable](https://ferrous-systems.com/blog/stable-async-on-embedded/) 50 | -------------------------------------------------------------------------------- /nrf52/examples/2-share.rs: -------------------------------------------------------------------------------- 1 | //! Sharing state between tasks using `Cell` and `RefCell` 2 | //! 3 | //! ``` 4 | //! B: initialize x 5 | //! B: post a message through y 6 | //! B: yield 7 | //! A: x=42 8 | //! A: received a message through y: 42 9 | //! A: yield 10 | //! DONE 11 | //! ``` 12 | 13 | #![deny(unsafe_code)] 14 | #![deny(warnings)] 15 | #![no_main] 16 | #![no_std] 17 | 18 | use core::cell::{Cell, RefCell}; 19 | 20 | use async_embedded::task; 21 | use cortex_m::asm; 22 | use cortex_m_rt::entry; 23 | use cortex_m_semihosting::hprintln; 24 | use nrf52 as _; // memory layout 25 | use panic_udf as _; // panic handler 26 | 27 | #[entry] 28 | fn main() -> ! { 29 | static mut X: Cell = Cell::new(0); 30 | // not-async-aware, one-shot channel 31 | static mut Y: RefCell> = RefCell::new(None); 32 | 33 | // only references with `'static` lifetimes can be sent to `spawn`-ed tasks 34 | // NOTE we coerce these to a shared (`&-`) reference to avoid the `move` blocks taking ownership 35 | // of the owning static (`&'static mut`) reference 36 | let x: &'static Cell<_> = X; 37 | let y: &'static RefCell<_> = Y; 38 | 39 | // task A 40 | task::spawn(async move { 41 | hprintln!("A: x={}", x.get()).ok(); 42 | 43 | if let Some(msg) = y.borrow_mut().take() { 44 | hprintln!("A: received a message through y: {}", msg).ok(); 45 | } 46 | 47 | loop { 48 | hprintln!("A: yield").ok(); 49 | // context switch to B 50 | task::r#yield().await; 51 | } 52 | }); 53 | 54 | // task B 55 | task::block_on(async { 56 | hprintln!("B: initialize x").ok(); 57 | x.set(42); 58 | 59 | hprintln!("B: post a message through y").ok(); 60 | *y.borrow_mut() = Some(42); 61 | 62 | hprintln!("B: yield").ok(); 63 | 64 | // context switch to A 65 | task::r#yield().await; 66 | 67 | hprintln!("DONE").ok(); 68 | 69 | loop { 70 | asm::bkpt(); 71 | } 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /nrf52/examples/3-mutex.rs: -------------------------------------------------------------------------------- 1 | //! Mutex shared between tasks 2 | //! 3 | //! "When to use `Mutex` instead of a `RefCell`?" Both abstractions will give you an exclusive 4 | //! (`&mut-`) reference to the data and that reference can survive across `yield`s (either explicit 5 | //! , i.e. `task::yield`, or implicit, `.await`). 6 | //! 7 | //! The difference between the two is clear when contention occurs. If two or more tasks contend for 8 | //! a `RefCell`, as in they both call `borrow_mut` on it, you'll get a panic. On the other hand, if 9 | //! you use a `Mutex` in a similar scenario, i.e. both tasks call `lock` on it, then one of them 10 | //! will "asynchronous" wait for (i.e. not resume until) the other task to release (releases) the 11 | //! lock. 12 | //! 13 | //! Expected output: 14 | //! 15 | //! ``` 16 | //! B: before lock 17 | //! A: before write 18 | //! A: after releasing the lock 19 | //! A: yield 20 | //! B: 42 21 | //! DONE 22 | //! ``` 23 | //! 24 | //! Try to replace the `Mutex` with `RefCell` and re-run the example 25 | 26 | #![deny(unsafe_code)] 27 | #![deny(warnings)] 28 | #![no_main] 29 | #![no_std] 30 | 31 | use async_embedded::{task, unsync::Mutex}; 32 | use cortex_m::asm; 33 | use cortex_m_rt::entry; 34 | use cortex_m_semihosting::hprintln; 35 | use nrf52 as _; // memory layout 36 | use panic_udf as _; // panic handler 37 | 38 | #[entry] 39 | fn main() -> ! { 40 | static mut X: Mutex = Mutex::new(0); 41 | 42 | let mut lock = X.try_lock().unwrap(); 43 | 44 | task::spawn(async { 45 | hprintln!("A: before write").ok(); 46 | *lock = 42; 47 | drop(lock); 48 | 49 | hprintln!("A: after releasing the lock").ok(); 50 | 51 | loop { 52 | hprintln!("A: yield").ok(); 53 | task::r#yield().await; 54 | } 55 | }); 56 | 57 | task::block_on(async { 58 | hprintln!("B: before lock").ok(); 59 | 60 | // cannot immediately make progress; context switch to A 61 | let lock = X.lock().await; 62 | 63 | hprintln!("B: {}", *lock).ok(); 64 | 65 | hprintln!("DONE").ok(); 66 | 67 | loop { 68 | asm::bkpt(); 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /nrf52/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous HAL for the nRF52840 2 | 3 | #![deny(missing_docs)] 4 | #![deny(rust_2018_idioms)] 5 | #![deny(warnings)] 6 | #![no_std] 7 | 8 | use core::{marker::PhantomData, mem}; 9 | 10 | use cortex_m_rt::pre_init; 11 | 12 | pub mod ds3231; 13 | pub mod led; 14 | pub mod scd30; 15 | pub mod serial; 16 | pub mod timer; 17 | pub mod twim; 18 | 19 | pub use timer::Timer; 20 | 21 | // peripheral initialization 22 | #[pre_init] 23 | unsafe fn pre_init() { 24 | // configure the LFCLK to use the external crystal (32.768Hz) 25 | pac::CLOCK::borrow_unchecked(|clock| { 26 | clock.lfclksrc.write(|w| w.src().xtal()); 27 | clock 28 | .tasks_lfclkstart 29 | .write(|w| w.tasks_lfclkstart().set_bit()); 30 | while clock 31 | .events_lfclkstarted 32 | .read() 33 | .events_lfclkstarted() 34 | .bit_is_clear() 35 | { 36 | // busy wait 37 | continue; 38 | } 39 | }); 40 | 41 | // LEDs 42 | led::init(); 43 | 44 | // Serial port 45 | serial::init(); 46 | 47 | // TWIM 48 | twim::init(); 49 | 50 | // start the RTC 51 | timer::init(); 52 | 53 | // sadly we cannot seal the configuration of the peripherals from this 54 | // context (static variables are uninitialized at this point) 55 | // drop(pac::Peripheral::take()); 56 | } 57 | 58 | /// Borrows a peripheral without checking if it has already been taken 59 | unsafe trait BorrowUnchecked { 60 | fn borrow_unchecked(f: impl FnOnce(&Self) -> T) -> T; 61 | } 62 | 63 | macro_rules! borrow_unchecked { 64 | ($($peripheral:ident),*) => { 65 | $( 66 | unsafe impl BorrowUnchecked for pac::$peripheral { 67 | fn borrow_unchecked(f: impl FnOnce(&Self) -> T) -> T { 68 | let p = unsafe { mem::transmute(()) }; 69 | f(&p) 70 | } 71 | } 72 | )* 73 | } 74 | } 75 | 76 | borrow_unchecked!(CLOCK, P0, RTC0, TWIM0, UARTE0); 77 | 78 | struct NotSync { 79 | _inner: PhantomData<*mut ()>, 80 | } 81 | 82 | impl NotSync { 83 | fn new() -> Self { 84 | Self { 85 | _inner: PhantomData, 86 | } 87 | } 88 | } 89 | 90 | unsafe impl Send for NotSync {} 91 | 92 | fn slice_in_ram(slice: &[u8]) -> bool { 93 | const RAM_START: usize = 0x2000_0000; 94 | const RAM_SIZE: usize = 128 * 1024; 95 | const RAM_END: usize = RAM_START + RAM_SIZE; 96 | 97 | let start = slice.as_ptr() as usize; 98 | let end = start + slice.len(); 99 | 100 | RAM_START <= start && end < RAM_END 101 | } 102 | -------------------------------------------------------------------------------- /nrf52/src/scd30.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous SCD30 (gas sensor) driver 2 | 3 | // Reference: Interface Description Sensirion SCD30 Sensor Module (Version 4 | // 0.94–D1 –June 2019) 5 | 6 | use async_embedded::unsync::Mutex; 7 | 8 | use crate::twim::{self, Twim}; 9 | 10 | /// Sensor measurement 11 | #[derive(Clone, Copy)] 12 | pub struct Measurement { 13 | /// CO2 concentrain in parts per million (0 - 40,000 ppm) 14 | pub co2: f32, 15 | 16 | /// Relative humidity (0 - 100%) 17 | pub rh: f32, 18 | 19 | /// Temperature in Celsius (-40 - 70 C) 20 | pub t: f32, 21 | } 22 | 23 | const ADDRESS: u8 = 0x61; 24 | 25 | /// SCD30 I2C driver 26 | pub struct Scd30<'a> { 27 | twim: &'a Mutex, 28 | } 29 | 30 | /// Driver error 31 | #[derive(Debug)] 32 | pub enum Error { 33 | /// Checksum error 34 | Checksum, 35 | 36 | /// I2C error 37 | Twim(twim::Error), 38 | } 39 | 40 | impl From for Error { 41 | fn from(e: twim::Error) -> Self { 42 | Error::Twim(e) 43 | } 44 | } 45 | 46 | impl<'a> Scd30<'a> { 47 | /// Creates a new driver 48 | pub fn new(twim: &'a Mutex) -> Self { 49 | Self { twim } 50 | } 51 | 52 | /// Returns the last sensor measurement 53 | pub async fn get_measurement(&mut self) -> Result { 54 | while !self.data_ready().await? { 55 | continue; 56 | } 57 | 58 | let mut buf = [0; 18]; 59 | { 60 | let mut twim = self.twim.lock().await; 61 | twim.write(ADDRESS, &[0x03, 0x00]).await?; 62 | twim.read(ADDRESS, &mut buf).await?; 63 | drop(twim); 64 | } 65 | 66 | for chunk in buf.chunks(3) { 67 | if !crc_check(&chunk[..2], chunk[2]) { 68 | return Err(Error::Checksum); 69 | } 70 | } 71 | 72 | let co2 = f32::from_le_bytes([buf[4], buf[3], buf[1], buf[0]]); 73 | let t = f32::from_le_bytes([buf[10], buf[9], buf[7], buf[6]]); 74 | let rh = f32::from_le_bytes([buf[16], buf[15], buf[13], buf[12]]); 75 | 76 | Ok(Measurement { co2, t, rh }) 77 | } 78 | 79 | async fn data_ready(&mut self) -> Result { 80 | let mut buf = [0; 3]; 81 | { 82 | let mut twim = self.twim.lock().await; 83 | twim.write(ADDRESS, &[0x02, 0x02]).await?; 84 | twim.read(ADDRESS, &mut buf).await?; 85 | drop(twim); 86 | } 87 | 88 | if !crc_check(&buf[..2], buf[2]) { 89 | return Err(Error::Checksum); 90 | } 91 | 92 | Ok(buf[1] == 1) 93 | } 94 | } 95 | 96 | // TODO 97 | fn crc_check(_bytes: &[u8], _crc: u8) -> bool { 98 | true 99 | } 100 | -------------------------------------------------------------------------------- /async-embedded/src/unsync/mutex.rs: -------------------------------------------------------------------------------- 1 | // NOTE waker logic is based on async-std v1.5.0 2 | 3 | use core::{ 4 | cell::{Cell, UnsafeCell}, 5 | future::Future, 6 | ops, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | use super::waker_set::WakerSet; 12 | 13 | /// A mutual exclusion primitive for protecting shared data 14 | pub struct Mutex { 15 | locked: Cell, 16 | value: UnsafeCell, 17 | wakers: WakerSet, 18 | } 19 | 20 | impl Mutex { 21 | /// Creates a new mutex 22 | pub const fn new(t: T) -> Self { 23 | Self { 24 | locked: Cell::new(false), 25 | wakers: WakerSet::new(), 26 | value: UnsafeCell::new(t), 27 | } 28 | } 29 | 30 | /// Acquires the lock 31 | /// 32 | /// Returns a guard that release the lock when dropped 33 | pub async fn lock(&self) -> MutexGuard<'_, T> { 34 | struct Lock<'a, T> { 35 | mutex: &'a Mutex, 36 | opt_key: Option, 37 | } 38 | 39 | impl<'a, T> Future for Lock<'a, T> { 40 | type Output = MutexGuard<'a, T>; 41 | 42 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 43 | // If the current task is in the set, remove it. 44 | if let Some(key) = self.opt_key.take() { 45 | self.mutex.wakers.remove(key); 46 | } 47 | 48 | // Try acquiring the lock. 49 | match self.mutex.try_lock() { 50 | Some(guard) => Poll::Ready(guard), 51 | None => { 52 | // Insert this lock operation. 53 | self.opt_key = Some(self.mutex.wakers.insert(cx)); 54 | 55 | Poll::Pending 56 | } 57 | } 58 | } 59 | } 60 | 61 | impl Drop for Lock<'_, T> { 62 | fn drop(&mut self) { 63 | // If the current task is still in the set, that means it is being cancelled now. 64 | if let Some(key) = self.opt_key { 65 | self.mutex.wakers.cancel(key); 66 | } 67 | } 68 | } 69 | 70 | Lock { 71 | mutex: self, 72 | opt_key: None, 73 | } 74 | .await 75 | } 76 | 77 | /// Attempts to acquire the lock 78 | pub fn try_lock(&self) -> Option> { 79 | if !self.locked.get() { 80 | self.locked.set(true); 81 | Some(MutexGuard(self)) 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | 88 | /// A guard that releases the lock when dropped 89 | pub struct MutexGuard<'a, T>(&'a Mutex); 90 | 91 | impl Drop for MutexGuard<'_, T> { 92 | fn drop(&mut self) { 93 | self.0.locked.set(false); 94 | self.0.wakers.notify_any(); 95 | unsafe { crate::signal_event_ready(); } 96 | } 97 | } 98 | 99 | impl ops::Deref for MutexGuard<'_, T> { 100 | type Target = T; 101 | 102 | fn deref(&self) -> &T { 103 | unsafe { &*self.0.value.get() } 104 | } 105 | } 106 | 107 | impl ops::DerefMut for MutexGuard<'_, T> { 108 | fn deref_mut(&mut self) -> &mut T { 109 | unsafe { &mut *self.0.value.get() } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /nrf52/examples/8-sensor.rs: -------------------------------------------------------------------------------- 1 | //! Print sensor data on demand 2 | 3 | #![deny(unsafe_code)] 4 | #![deny(warnings)] 5 | #![no_main] 6 | #![no_std] 7 | 8 | use core::{cell::Cell, fmt::Write as _, time::Duration}; 9 | 10 | use async_embedded::{task, unsync::Mutex}; 11 | use cortex_m_rt::entry; 12 | use heapless::{consts, String}; 13 | use nrf52::{led::Red, scd30::Scd30, serial, timer::Timer, twim::Twim}; 14 | use panic_udf as _; // panic handler 15 | 16 | #[derive(Clone, Copy)] 17 | enum State { 18 | NotReady, 19 | Ready, 20 | Error, 21 | } 22 | 23 | #[entry] 24 | fn main() -> ! { 25 | // shared state 26 | static mut STATE: Cell = Cell::new(State::NotReady); 27 | // range: 0 - 40,000 ppm 28 | static mut CO2: Cell = Cell::new(0); 29 | // range: 0 - 100 % 30 | static mut RH: Cell = Cell::new(0); 31 | // range: -40 - 70 C 32 | static mut T: Cell = Cell::new(0); 33 | static mut M: Option> = None; 34 | 35 | let co2: &'static _ = CO2; 36 | let state: &'static _ = STATE; 37 | let rh: &'static _ = RH; 38 | let t: &'static _ = T; 39 | 40 | // heartbeat task 41 | let mut timer = Timer::take(); 42 | let dur = Duration::from_millis(100); 43 | task::spawn(async move { 44 | loop { 45 | Red.on(); 46 | timer.wait(dur).await; 47 | Red.off(); 48 | timer.wait(dur).await; 49 | Red.on(); 50 | timer.wait(dur).await; 51 | Red.off(); 52 | timer.wait(12 * dur).await; 53 | } 54 | }); 55 | 56 | // task to print sensor info on demand 57 | let (mut tx, mut rx) = serial::take(); 58 | task::spawn(async move { 59 | let mut tx_buf = String::::new(); 60 | let mut rx_buf = [0]; 61 | 62 | loop { 63 | rx.read(&mut rx_buf).await; 64 | 65 | // carriage return; 66 | if rx_buf[0] == 13 { 67 | match state.get() { 68 | State::Error => { 69 | tx.write(b"fatal error: I2C error\n").await; 70 | 71 | loop { 72 | task::r#yield().await; 73 | } 74 | } 75 | 76 | State::NotReady => { 77 | tx.write(b"sensor not ready; try again later\n").await; 78 | } 79 | 80 | State::Ready => { 81 | let co2 = co2.get(); 82 | let t = t.get(); 83 | let rh = rh.get(); 84 | 85 | tx_buf.clear(); 86 | // will not fail; the buffer is big enough 87 | let _ = writeln!(&mut tx_buf, "CO2: {}ppm\nT: {}C\nRH: {}%", co2, t, rh); 88 | tx.write(tx_buf.as_bytes()).await; 89 | } 90 | } 91 | } 92 | } 93 | }); 94 | 95 | // task to continuously poll the sensor 96 | let twim = M.get_or_insert(Mutex::new(Twim::take())); 97 | let mut scd30 = Scd30::new(twim); 98 | task::block_on(async { 99 | loop { 100 | if let Ok(m) = scd30.get_measurement().await { 101 | co2.set(m.co2 as u16); 102 | rh.set(m.rh as u8); 103 | t.set(m.t as i8); 104 | state.set(State::Ready); 105 | } else { 106 | state.set(State::Error); 107 | 108 | loop { 109 | task::r#yield().await; 110 | } 111 | } 112 | } 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /async-embedded/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Proof of Concept async runtime for the Cortex-M architecture 2 | 3 | #![deny(missing_docs)] 4 | #![deny(rust_2018_idioms)] 5 | #![deny(warnings)] 6 | #![no_std] 7 | 8 | mod alloc; 9 | mod executor; 10 | pub mod task; 11 | pub mod unsync; 12 | 13 | #[cfg(target_arch = "arm")] 14 | use cortex_m::asm; 15 | 16 | 17 | #[cfg(target_arch = "arm")] 18 | pub use cortex_m::asm::udf as abort; 19 | 20 | #[cfg(target_arch = "arm")] 21 | #[inline] 22 | /// Prevent next `wait_for_interrupt` from sleeping, wake up other harts if needed. 23 | /// This particular implementation does nothing, since `wait_for_interrupt` never sleeps 24 | pub(crate) unsafe fn signal_event_ready() { 25 | asm::sev(); 26 | } 27 | 28 | #[cfg(target_arch = "arm")] 29 | #[inline] 30 | /// Wait for an interrupt or until notified by other hart via `signal_task_ready` 31 | /// This particular implementation does nothing 32 | pub(crate) unsafe fn wait_for_event() { 33 | asm::wfe(); 34 | } 35 | 36 | #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] 37 | /// This keeps dropping into the debugger and never returns 38 | pub fn abort() -> ! { 39 | loop { 40 | unsafe { riscv::asm::ebreak() } 41 | } 42 | } 43 | 44 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-nop"))] 45 | #[inline] 46 | /// Prevent next `wait_for_interrupt` from sleeping, wake up other harts if needed. 47 | /// This particular implementation does nothing, since `wait_for_interrupt` never sleeps 48 | pub(crate) unsafe fn signal_event_ready() {} 49 | 50 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-nop"))] 51 | #[inline] 52 | /// Wait for an interrupt or until notified by other hart via `signal_task_ready` 53 | /// This particular implementation does nothing 54 | pub(crate) unsafe fn wait_for_event() {} 55 | 56 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-extern"))] 57 | extern "C" { 58 | /// Prevent next `wait_for_interrupt` from sleeping, wake up other harts if needed. 59 | /// User is expected to provide an actual implementation, like the one shown below. 60 | /// 61 | /// #[no_mangle] 62 | /// pub extern "C" fn signal_event_ready() { 63 | /// unimplemented!(); 64 | /// } 65 | pub(crate) fn signal_event_ready(); 66 | 67 | /// Wait for an interrupt or until notified by other hart via `signal_task_ready` 68 | /// User is expected to provide an actual implementation, like the one shown below. 69 | /// 70 | /// #[no_mangle] 71 | /// pub extern "C" fn wait_for_event() { 72 | /// unimplemented!(); 73 | /// } 74 | pub(crate) fn wait_for_event(); 75 | } 76 | 77 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-wfi-single-hart"))] 78 | static mut TASK_READY: bool = false; 79 | 80 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-wfi-single-hart"))] 81 | #[inline] 82 | /// Prevent next `wait_for_interrupt` from sleeping, wake up other harts if needed. 83 | /// This particular implementation prevents `wait_for_interrupt` from sleeping by setting 84 | /// a global mutable flag 85 | pub(crate) unsafe fn signal_event_ready() { 86 | TASK_READY = true; 87 | } 88 | 89 | #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), feature = "riscv-wait-wfi-single-hart"))] 90 | #[inline] 91 | /// Wait for an interrupt or until notified by other hart via `signal_task_ready` 92 | /// This particular implementation decides whether to sleep or not by checking 93 | /// a global mutable flag that's set by `signal_task_ready` 94 | pub(crate) unsafe fn wait_for_event() { 95 | if !TASK_READY { 96 | riscv::asm::wfi(); 97 | } 98 | TASK_READY = false; 99 | } 100 | 101 | /// Maximum number of tasks (TODO this could be user configurable) 102 | type NTASKS = typenum::consts::U8; 103 | -------------------------------------------------------------------------------- /nrf52/src/ds3231.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous DS3231 (Real-Time Clock) driver 2 | 3 | // Reference: DS3231 datasheet (19-5170; Rev 10; 3/15) 4 | 5 | use async_embedded::unsync::Mutex; 6 | use chrono::{Datelike as _, NaiveDate, NaiveDateTime, NaiveTime, Timelike as _}; 7 | 8 | use crate::twim::{self, Twim}; 9 | 10 | const ADDRESS: u8 = 0b110_1000; 11 | 12 | // Address map 13 | const SECONDS: u8 = 0; 14 | const DATE: u8 = 4; 15 | 16 | /// DS3231 I2C driver 17 | pub struct Ds3231<'a> { 18 | twim: &'a Mutex, 19 | } 20 | 21 | // 12-hour format (AM / PM) 22 | const HOUR12: u8 = 1 << 6; 23 | // PM half 24 | const PM: u8 = 1 << 5; 25 | 26 | const CENTURY: u8 = 1 << 7; 27 | 28 | /// Driver error 29 | #[derive(Debug)] 30 | pub enum Error { 31 | /// The RTC cannot hold this date 32 | InvalidDate, 33 | 34 | /// I2C error 35 | Twim(twim::Error), 36 | } 37 | 38 | impl From for Error { 39 | fn from(e: twim::Error) -> Error { 40 | Error::Twim(e) 41 | } 42 | } 43 | 44 | impl<'a> Ds3231<'a> { 45 | /// Creates a new driver 46 | pub fn new(twim: &'a Mutex) -> Self { 47 | Self { twim } 48 | } 49 | 50 | /// Returns the current date 51 | pub async fn get_date(&mut self) -> Result { 52 | let mut buf = [0; 3]; 53 | self.twim 54 | .lock() 55 | .await 56 | .write_then_read(ADDRESS, &[DATE], &mut buf) 57 | .await?; 58 | 59 | date_from_regs(&buf) 60 | } 61 | 62 | /// Returns the current date and time 63 | pub async fn get_datetime(&mut self) -> Result { 64 | let mut buf = [0; 7]; 65 | self.twim 66 | .lock() 67 | .await 68 | .write_then_read(ADDRESS, &[SECONDS], &mut buf) 69 | .await?; 70 | 71 | let time = time_from_regs(&buf[..3]); 72 | let date = date_from_regs(&buf[4..])?; 73 | 74 | Ok(date.and_time(time)) 75 | } 76 | 77 | /// Returns the current time 78 | pub async fn get_time(&mut self) -> Result { 79 | let mut buf = [0; 3]; 80 | self.twim 81 | .lock() 82 | .await 83 | .write_then_read(ADDRESS, &[SECONDS], &mut buf) 84 | .await?; 85 | 86 | Ok(time_from_regs(&buf)) 87 | } 88 | 89 | /// Changes the current date 90 | pub async fn set_date(&mut self, date: NaiveDate) -> Result<(), Error> { 91 | let day = to_bcd(date.day() as u8); 92 | let mut month = to_bcd(date.month() as u8); 93 | let mut year = date.year(); 94 | if year < 2000 || year > 2199 { 95 | return Err(Error::InvalidDate); 96 | } 97 | year -= 2000; 98 | if year >= 100 { 99 | month |= CENTURY; 100 | year -= 100; 101 | } 102 | let year = to_bcd(year as u8); 103 | 104 | self.twim 105 | .lock() 106 | .await 107 | .write(ADDRESS, &[DATE, day, month, year]) 108 | .await?; 109 | Ok(()) 110 | } 111 | 112 | /// Changes the current time 113 | pub async fn set_time(&mut self, time: NaiveTime) -> Result<(), twim::Error> { 114 | let sec = to_bcd(time.second() as u8); 115 | let min = to_bcd(time.minute() as u8); 116 | let hour = to_bcd(time.hour() as u8); 117 | 118 | self.twim 119 | .lock() 120 | .await 121 | .write(ADDRESS, &[SECONDS, sec, min, hour]) 122 | .await 123 | } 124 | } 125 | 126 | fn time_from_regs(regs: &[u8]) -> NaiveTime { 127 | let sec = from_bcd(regs[0]); 128 | let min = from_bcd(regs[1]); 129 | let hour = if regs[2] & HOUR12 != 0 { 130 | if regs[2] & PM != 0 { 131 | from_bcd(12 + (regs[2] & !PM)) 132 | } else { 133 | from_bcd(regs[2]) 134 | } 135 | } else { 136 | // 24-hour format 137 | from_bcd(regs[2]) 138 | }; 139 | 140 | NaiveTime::from_hms(hour.into(), min.into(), sec.into()) 141 | } 142 | 143 | fn date_from_regs(regs: &[u8]) -> Result { 144 | let day = from_bcd(regs[0]); 145 | let month = from_bcd(regs[1] & !CENTURY); 146 | let year = i32::from(from_bcd(regs[2])) + if regs[1] & CENTURY != 0 { 2100 } else { 2000 }; 147 | 148 | NaiveDate::from_ymd_opt(year, month.into(), day.into()).ok_or(Error::InvalidDate) 149 | } 150 | 151 | fn from_bcd(bcd: u8) -> u8 { 152 | let units = bcd & 0b1111; 153 | let tens = bcd >> 4; 154 | 155 | 10 * tens + units 156 | } 157 | 158 | #[allow(dead_code)] 159 | fn to_bcd(x: u8) -> u8 { 160 | let units = x % 10; 161 | let tens = x / 10; 162 | tens << 4 | units 163 | } 164 | -------------------------------------------------------------------------------- /async-embedded/src/unsync/waker_set.rs: -------------------------------------------------------------------------------- 1 | // NOTE based on async-std v1.5.0 2 | 3 | use core::{ 4 | cell::UnsafeCell, 5 | task::{Context, Waker}, 6 | }; 7 | 8 | // TODO replace with `heapless::Slab` but then we need to pick a fixed capacity 9 | // (equal to the maximum number of in-flight tasks) for the `Slab` 10 | use heapless::{i, Slab}; 11 | 12 | // NOTE this should only ever be used in "Thread mode" 13 | pub struct WakerSet { 14 | inner: UnsafeCell, 15 | } 16 | 17 | impl WakerSet { 18 | pub const fn new() -> Self { 19 | Self { 20 | inner: UnsafeCell::new(Inner::new()), 21 | } 22 | } 23 | 24 | pub fn cancel(&self, key: usize) -> bool { 25 | // NOTE(unsafe) single-threaded context; OK as long as no references are returned 26 | unsafe { (*self.inner.get()).cancel(key) } 27 | } 28 | 29 | pub fn notify_any(&self) -> bool { 30 | // NOTE(unsafe) single-threaded context; OK as long as no references are returned 31 | unsafe { (*self.inner.get()).notify_any() } 32 | } 33 | 34 | pub fn notify_one(&self) -> bool { 35 | // NOTE(unsafe) single-threaded context; OK as long as no references are returned 36 | unsafe { (*self.inner.get()).notify_one() } 37 | } 38 | 39 | pub fn insert(&self, cx: &Context<'_>) -> usize { 40 | // NOTE(unsafe) single-threaded context; OK as long as no references are returned 41 | unsafe { (*self.inner.get()).insert(cx) } 42 | } 43 | 44 | pub fn remove(&self, key: usize) { 45 | // NOTE(unsafe) single-threaded context; OK as long as no references are returned 46 | unsafe { (*self.inner.get()).remove(key) } 47 | } 48 | } 49 | 50 | #[derive(Clone, Copy, Eq, PartialEq)] 51 | enum Notify { 52 | /// Make sure at least one entry is notified. 53 | Any, 54 | /// Notify one additional entry. 55 | One, 56 | // Notify all entries. 57 | // All, 58 | } 59 | 60 | struct Inner { 61 | // NOTE the number of entries is capped at `NTASKS` 62 | entries: Slab, crate::NTASKS>, 63 | notifiable: usize, 64 | } 65 | 66 | impl Inner { 67 | const fn new() -> Self { 68 | Self { 69 | entries: Slab(i::Slab::new()), 70 | notifiable: 0, 71 | } 72 | } 73 | 74 | /// Removes the waker of a cancelled operation. 75 | /// 76 | /// Returns `true` if another blocked operation from the set was notified. 77 | fn cancel(&mut self, key: usize) -> bool { 78 | match self.entries.remove(key) { 79 | Some(_) => self.notifiable -= 1, 80 | None => { 81 | // The operation was cancelled and notified so notify another operation instead. 82 | for (_, opt_waker) in self.entries.iter_mut() { 83 | // If there is no waker in this entry, that means it was already woken. 84 | if let Some(w) = opt_waker.take() { 85 | w.wake(); 86 | self.notifiable -= 1; 87 | return true; 88 | } 89 | } 90 | } 91 | } 92 | 93 | false 94 | } 95 | 96 | /// Notifies a blocked operation if none have been notified already. 97 | /// 98 | /// Returns `true` if an operation was notified. 99 | fn notify_any(&mut self) -> bool { 100 | self.notify(Notify::Any) 101 | } 102 | 103 | /// Notifies one additional blocked operation. 104 | /// 105 | /// Returns `true` if an operation was notified. 106 | fn notify_one(&mut self) -> bool { 107 | self.notify(Notify::One) 108 | } 109 | 110 | /// Notifies blocked operations, either one or all of them. 111 | /// 112 | /// Returns `true` if at least one operation was notified. 113 | fn notify(&mut self, n: Notify) -> bool { 114 | let mut notified = false; 115 | 116 | for (_, opt_waker) in self.entries.iter_mut() { 117 | // If there is no waker in this entry, that means it was already woken. 118 | if let Some(w) = opt_waker.take() { 119 | w.wake(); 120 | self.notifiable -= 1; 121 | notified = true; 122 | 123 | if n == Notify::One { 124 | break; 125 | } 126 | } 127 | 128 | if n == Notify::Any { 129 | break; 130 | } 131 | } 132 | 133 | notified 134 | } 135 | 136 | fn insert(&mut self, cx: &Context<'_>) -> usize { 137 | let w = cx.waker().clone(); 138 | let key = self.entries.insert(Some(w)).expect("OOM"); 139 | self.notifiable += 1; 140 | key 141 | } 142 | 143 | /// Removes the waker of an operation. 144 | fn remove(&mut self, key: usize) { 145 | if self.entries.remove(key).is_some() { 146 | self.notifiable -= 1; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /nrf52/src/timer.rs: -------------------------------------------------------------------------------- 1 | //! Timers 2 | 3 | use core::{ 4 | future::Future, 5 | pin::Pin, 6 | sync::atomic::{self, AtomicBool, Ordering}, 7 | task::{Context, Poll, Waker}, 8 | time::Duration, 9 | }; 10 | 11 | use cortex_m::peripheral::NVIC; 12 | use pac::{Interrupt, RTC0}; 13 | 14 | use crate::{BorrowUnchecked as _, NotSync}; 15 | 16 | // NOTE called from `pre_init` 17 | pub(crate) fn init() { 18 | pac::RTC0::borrow_unchecked(|rtc| { 19 | // enable compare0 interrupt 20 | rtc.intenset.write(|w| w.compare0().set_bit()); 21 | rtc.tasks_clear.write(|w| w.tasks_clear().set_bit()); 22 | rtc.tasks_start.write(|w| w.tasks_start().set_bit()); 23 | }); 24 | } 25 | 26 | /// [singleton] An `async`-aware timer 27 | pub struct Timer { 28 | _not_sync: NotSync, 29 | } 30 | 31 | impl Timer { 32 | /// Takes the singleton instance of this timer 33 | /// 34 | /// This returns the `Some` variant only once 35 | pub fn take() -> Self { 36 | // NOTE peripheral initialization is done in `#[pre_init]` 37 | 38 | static TAKEN: AtomicBool = AtomicBool::new(false); 39 | 40 | if TAKEN 41 | .compare_exchange_weak(false, true, Ordering::Relaxed, Ordering::Relaxed) 42 | .is_ok() 43 | { 44 | Self { 45 | _not_sync: NotSync::new(), 46 | } 47 | } else { 48 | panic!("`Timer` has already been taken") 49 | } 50 | } 51 | 52 | /// Waits for at least `dur` 53 | // NOTE we could support several "timeouts" by making this take `&self` and 54 | // using a priority queue (sorted queue) to store the deadlines 55 | pub async fn wait(&mut self, dur: Duration) { 56 | struct Wait<'a> { 57 | _timer: &'a mut Timer, 58 | installed_waker: bool, 59 | } 60 | 61 | impl<'a> Future for Wait<'a> { 62 | type Output = (); 63 | 64 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 65 | static mut WAKER: Option = None; 66 | 67 | if has_expired() { 68 | if self.installed_waker { 69 | // uninstall the waker 70 | NVIC::mask(Interrupt::RTC0); 71 | // NOTE(compiler_fence) the interrupt must be disabled 72 | // before we take down the waker 73 | atomic::compiler_fence(Ordering::SeqCst); 74 | drop(unsafe { WAKER.take() }) 75 | } 76 | 77 | Poll::Ready(()) 78 | } else { 79 | if !self.installed_waker { 80 | unsafe { 81 | WAKER = Some(cx.waker().clone()); 82 | // NOTE(compiler_fence) `WAKER` write must complete 83 | // before we enable the interrupt 84 | atomic::compiler_fence(Ordering::Release); 85 | NVIC::unmask(Interrupt::RTC0); // atomic write 86 | } 87 | 88 | #[allow(non_snake_case)] 89 | #[no_mangle] 90 | fn RTC0() { 91 | // NOTE(unsafe) the only other context that can 92 | // access this static variable runs at lower 93 | // priority -- that context won't overlap in 94 | // execution with this operation 95 | if let Some(waker) = unsafe { WAKER.as_ref() } { 96 | waker.wake_by_ref(); 97 | 98 | // one shot interrupt -- this won't fire again 99 | NVIC::mask(Interrupt::RTC0); 100 | } else { 101 | // this could be have been triggered by the user 102 | } 103 | } 104 | } else { 105 | // prepare another one-shot interrupt 106 | unsafe { 107 | NVIC::unmask(Interrupt::RTC0); 108 | } 109 | } 110 | 111 | Poll::Pending 112 | } 113 | } 114 | } 115 | 116 | // TODO do this without 64-bit arithmetic 117 | const F: u64 = 32_768; // frequency of the LFCLK 118 | let ticks = dur.as_secs() * F + (u64::from(dur.subsec_nanos()) * F) / 1_000_000_000; 119 | // NOTE we could support 64-bit ticks 120 | assert!(ticks < (1 << 24)); 121 | let ticks = ticks as u32; 122 | 123 | NVIC::mask(Interrupt::RTC0); 124 | RTC0::borrow_unchecked(|rtc| { 125 | let now = rtc.counter.read().bits(); 126 | rtc.events_compare[0].reset(); 127 | // NOTE(unsafe) this operation shouldn't be marked as `unsafe` 128 | rtc.cc[0].write(|w| unsafe { w.compare().bits(now.wrapping_add(ticks)) }); 129 | }); 130 | 131 | Wait { 132 | _timer: self, 133 | installed_waker: false, 134 | } 135 | .await 136 | } 137 | } 138 | 139 | fn has_expired() -> bool { 140 | RTC0::borrow_unchecked(|rtc| { 141 | if rtc.events_compare[0].read().events_compare().bit_is_set() { 142 | rtc.events_compare[0].reset(); 143 | true 144 | } else { 145 | false 146 | } 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /async-embedded/src/unsync/channel.rs: -------------------------------------------------------------------------------- 1 | // NOTE waker logic is based on async-std v1.5.0 2 | 3 | // A `!Sync` MPMC queue / Channel is just a classic ring buffer that consists of 4 | // an array with read and write cursors 5 | 6 | use core::{ 7 | cell::{Cell, UnsafeCell}, 8 | future::Future, 9 | marker::Unpin, 10 | mem::MaybeUninit, 11 | pin::Pin, 12 | task::{Context, Poll}, 13 | }; 14 | 15 | use generic_array::{typenum::Unsigned, GenericArray}; 16 | 17 | use super::waker_set::WakerSet; 18 | 19 | /// MPMC channel 20 | // FIXME this needs a destructor 21 | // TODO make this generic over the capacity -- that would require the newtype with public field hack 22 | // to keep the `const-fn` `new`. See `heapless` for examples of the workaround 23 | // TODO a SPSC version of this. It should not need the `WakerSet` but rather something like 24 | // `Option` 25 | pub struct Channel { 26 | buffer: UnsafeCell>>, 27 | read: Cell, 28 | write: Cell, 29 | send_wakers: WakerSet, 30 | recv_wakers: WakerSet, 31 | } 32 | 33 | impl Channel { 34 | /// Creates a new fixed capacity channel 35 | pub const fn new() -> Self { 36 | Self { 37 | buffer: UnsafeCell::new(MaybeUninit::uninit()), 38 | read: Cell::new(0), 39 | write: Cell::new(0), 40 | send_wakers: WakerSet::new(), 41 | recv_wakers: WakerSet::new(), 42 | } 43 | } 44 | 45 | /// Sends a message into the channel 46 | pub async fn send(&self, val: T) { 47 | struct Send<'a, T> { 48 | channel: &'a Channel, 49 | msg: Option, 50 | opt_key: Option, 51 | } 52 | 53 | // XXX(japaric) why is this required here but not in `Recv`? is it due 54 | // to `msg.take()`? 55 | impl Unpin for Send<'_, T> {} 56 | 57 | impl Future for Send<'_, T> { 58 | type Output = (); 59 | 60 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 61 | let msg = self.msg.take().expect("UNREACHABLE"); 62 | 63 | // If the current task is in the set, remove it. 64 | if let Some(key) = self.opt_key.take() { 65 | self.channel.send_wakers.remove(key); 66 | } 67 | 68 | if let Err(msg) = self.channel.try_send(msg) { 69 | self.msg = Some(msg); 70 | 71 | // Insert this send operation. 72 | self.opt_key = Some(self.channel.send_wakers.insert(cx)); 73 | 74 | Poll::Pending 75 | } else { 76 | Poll::Ready(()) 77 | } 78 | } 79 | } 80 | 81 | Send { 82 | channel: self, 83 | msg: Some(val), 84 | opt_key: None, 85 | } 86 | .await 87 | } 88 | 89 | /// Receives a message from the channel 90 | pub async fn recv(&self) -> T { 91 | struct Recv<'a, T> { 92 | channel: &'a Channel, 93 | opt_key: Option, 94 | } 95 | 96 | impl Future for Recv<'_, T> { 97 | type Output = T; 98 | 99 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 100 | // If the current task is in the set, remove it. 101 | if let Some(key) = self.opt_key.take() { 102 | self.channel.recv_wakers.remove(key); 103 | } 104 | 105 | // Try receiving a message. 106 | if let Some(msg) = self.channel.try_recv() { 107 | Poll::Ready(msg) 108 | } else { 109 | // Insert this receive operation. 110 | self.opt_key = Some(self.channel.recv_wakers.insert(cx)); 111 | Poll::Pending 112 | } 113 | } 114 | } 115 | 116 | Recv { 117 | channel: self, 118 | opt_key: None, 119 | } 120 | .await 121 | } 122 | 123 | /// Attempts to receive a message from the channel 124 | /// 125 | /// Returns None if the channel is currently empty 126 | pub fn try_recv(&self) -> Option { 127 | unsafe { 128 | let read = self.read.get(); 129 | let write = self.write.get(); 130 | let cap = crate::NTASKS::USIZE; 131 | let bufferp = self.buffer.get() as *mut T; 132 | 133 | if write > read { 134 | let cursor = read % cap; 135 | let val = bufferp.add(cursor).read(); 136 | self.read.set(read.wrapping_add(1)); 137 | // notify a sender 138 | self.send_wakers.notify_one(); 139 | crate::signal_event_ready(); 140 | Some(val) 141 | } else { 142 | // empty 143 | None 144 | } 145 | } 146 | } 147 | 148 | /// Attempts to send a message into the channel 149 | /// 150 | /// Returns an error if the channel buffer is currently full 151 | pub fn try_send(&self, val: T) -> Result<(), T> { 152 | unsafe { 153 | let read = self.read.get(); 154 | let write = self.write.get(); 155 | let cap = crate::NTASKS::USIZE; 156 | let bufferp = self.buffer.get() as *mut T; 157 | 158 | if write < read + cap { 159 | let cursor = write % cap; 160 | bufferp.add(cursor).write(val); 161 | self.write.set(write.wrapping_add(1)); 162 | // notify a receiver 163 | self.recv_wakers.notify_one(); 164 | crate::signal_event_ready(); 165 | Ok(()) 166 | } else { 167 | // full 168 | Err(val) 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /async-embedded/src/executor.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::{Cell, UnsafeCell}, 3 | future::Future, 4 | mem::MaybeUninit, 5 | pin::Pin, 6 | sync::atomic::{self, AtomicBool, Ordering}, 7 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 8 | }; 9 | 10 | use heapless::Vec; 11 | use pin_utils::pin_mut; 12 | 13 | use crate::{alloc::Alloc, NTASKS}; 14 | 15 | /// A single-threaded executor that only works in ARM Cortex-M "Thread mode" 16 | /// (outside of interrupt context) 17 | /// 18 | /// This is a singleton 19 | pub struct Executor { 20 | in_block_on: Cell, 21 | // NOTE `UnsafeCell` is used to minimize the span of references to the `Vec` 22 | tasks: UnsafeCell>, 23 | } 24 | 25 | // NOTE `*const ()` is &AtomicBool 26 | static VTABLE: RawWakerVTable = { 27 | unsafe fn clone(p: *const ()) -> RawWaker { 28 | RawWaker::new(p, &VTABLE) 29 | } 30 | unsafe fn wake(p: *const ()) { 31 | wake_by_ref(p) 32 | } 33 | unsafe fn wake_by_ref(p: *const ()) { 34 | (*(p as *const AtomicBool)).store(true, Ordering::Release) 35 | } 36 | unsafe fn drop(_: *const ()) { 37 | // no-op 38 | } 39 | 40 | RawWakerVTable::new(clone, wake, wake_by_ref, drop) 41 | }; 42 | 43 | impl Executor { 44 | /// Creates a new instance of the executor 45 | pub fn new() -> Self { 46 | Self { 47 | in_block_on: Cell::new(false), 48 | tasks: UnsafeCell::new(Vec::new()), 49 | } 50 | } 51 | 52 | pub fn block_on(&self, f: impl Future) -> T { 53 | // we want to avoid reentering `block_on` because then all the code 54 | // below has to become more complex. It's also likely that the 55 | // application will only call `block_on` once on an infinite task 56 | // (`Future`) 57 | if self.in_block_on.get() { 58 | // nested `block_on` 59 | crate::abort(); 60 | } 61 | self.in_block_on.set(true); 62 | 63 | pin_mut!(f); 64 | let ready = AtomicBool::new(true); 65 | let waker = 66 | unsafe { Waker::from_raw(RawWaker::new(&ready as *const _ as *const _, &VTABLE)) }; 67 | let val = loop { 68 | let mut task_woken = false; 69 | 70 | // advance the main task 71 | if ready.load(Ordering::Acquire) { 72 | task_woken = true; 73 | ready.store(false, Ordering::Release); 74 | 75 | let mut cx = Context::from_waker(&waker); 76 | if let Poll::Ready(val) = f.as_mut().poll(&mut cx) { 77 | break val; 78 | } 79 | } 80 | 81 | // advance other tasks 82 | // NOTE iteration ought to be OK because `tasks` can't be reallocated (it's a statically 83 | // allocated `heapless::Vec`); `tasks` can't shrink either 84 | let len = unsafe { (*self.tasks.get()).len() }; // (A) 85 | for i in 0..len { 86 | let task = unsafe { (*self.tasks.get()).get_unchecked(i) }; 87 | 88 | // NOTE we don't need a CAS operation here because `wake` invocations that come from 89 | // interrupt handlers (the only source of 'race conditions' (!= data races)) are 90 | // "oneshot": they'll issue a `wake` and then disable themselves to not run again 91 | // until the woken task has made more work 92 | if task.ready.load(Ordering::Acquire) { 93 | task_woken = true; 94 | 95 | // we are about to service the task so switch the `ready` flag to `false` 96 | task.ready.store(false, Ordering::Release); 97 | 98 | // NOTE we never deallocate tasks so `&ready` is always pointing to 99 | // allocated memory (`&'static AtomicBool`) 100 | let waker = unsafe { 101 | Waker::from_raw(RawWaker::new(&task.ready as *const _ as *const _, &VTABLE)) 102 | }; 103 | let mut cx = Context::from_waker(&waker); 104 | // this points into a `static` memory so it's already pinned 105 | if unsafe { 106 | !Pin::new_unchecked(&mut *task.f.get()) 107 | .poll(&mut cx) 108 | .is_ready() 109 | } { 110 | continue; 111 | } 112 | } 113 | } 114 | 115 | if task_woken { 116 | // If at least one task was woken up, do not sleep, try again 117 | continue; 118 | } 119 | 120 | // try to sleep; this will be a no-op if any of the previous tasks generated a SEV or an 121 | // interrupt ran (regardless of whether it generated a wake-up or not) 122 | unsafe { crate::wait_for_event() }; 123 | }; 124 | self.in_block_on.set(false); 125 | val 126 | } 127 | 128 | // NOTE CAREFUL! this method can overlap with `block_on` 129 | // FIXME we want to use `Future` here but the never type (`!`) is unstable; so as a 130 | // workaround we'll "abort" if the task / future terminates (see `Task::new`) 131 | pub fn spawn(&self, f: impl Future + 'static) { 132 | // NOTE(unsafe) only safe as long as `spawn` is never re-entered and this does not overlap 133 | // with operation `(A)` (see `Task::block_on`) 134 | let res = unsafe { (*self.tasks.get()).push(Task::new(f)) }; 135 | if res.is_err() { 136 | // OOM 137 | crate::abort() 138 | } 139 | } 140 | } 141 | 142 | type Task = Node + 'static>; 143 | 144 | pub struct Node 145 | where 146 | F: ?Sized, 147 | { 148 | ready: AtomicBool, 149 | f: UnsafeCell, 150 | } 151 | 152 | impl Task { 153 | fn new(f: impl Future + 'static) -> &'static mut Self { 154 | // NOTE(unsafe) Only safe as long as `Executor::spawn` is not re-entered 155 | unsafe { 156 | // Already initialized at this point 157 | let alloc = ALLOC.get() as *mut Alloc; 158 | (*alloc).alloc_init(Node { 159 | ready: AtomicBool::new(true), 160 | f: UnsafeCell::new(async { 161 | f.await; 162 | // `spawn`-ed tasks must never terminate 163 | crate::abort() 164 | }), 165 | }) 166 | } 167 | } 168 | } 169 | 170 | static mut ALLOC: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); 171 | 172 | /// Returns a handle to the executor singleton 173 | /// 174 | /// This lazily initializes the executor and allocator when first called 175 | pub(crate) fn current() -> &'static Executor { 176 | static INIT: AtomicBool = AtomicBool::new(false); 177 | static mut EXECUTOR: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); 178 | 179 | if !in_thread_mode() { 180 | // tried to access the executor from a thread that's not `main` 181 | crate::abort() 182 | } 183 | 184 | if INIT.load(Ordering::Relaxed) { 185 | unsafe { &*(EXECUTOR.get() as *const Executor) } 186 | } else { 187 | unsafe { 188 | /// Reserved memory for the bump allocator (TODO this could be user configurable) 189 | static mut MEMORY: [u8; 1024] = [0; 1024]; 190 | 191 | let executorp = EXECUTOR.get() as *mut Executor; 192 | executorp.write(Executor::new()); 193 | let allocp = ALLOC.get() as *mut Alloc; 194 | allocp.write(Alloc::new(&mut MEMORY)); 195 | // force the `allocp` write to complete before returning from this function 196 | atomic::compiler_fence(Ordering::Release); 197 | INIT.store(true, Ordering::Relaxed); 198 | &*executorp 199 | } 200 | } 201 | } 202 | 203 | fn in_thread_mode() -> bool { 204 | const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; 205 | // NOTE(unsafe) single-instruction load with no side effects 206 | unsafe { SCB_ICSR.read_volatile() as u8 == 0 } 207 | } 208 | -------------------------------------------------------------------------------- /nrf52/examples/9-clock.rs: -------------------------------------------------------------------------------- 1 | //! Interactive serial console with access to the clock and gas sensor 2 | //! 3 | //! Example interaction (with local echo enabled): 4 | //! 5 | //! ``` 6 | //! > help 7 | //! Commands: 8 | //! help displays this text 9 | //! date display the current date and time 10 | //! sensors displays the gas sensor data 11 | //! set date %Y-%m-%d changes the date 12 | //! set time %H:%M:%S changes the time 13 | //! > sensors 14 | //! CO2: 652ppm 15 | //! T: 26C 16 | //! RH: 23% 17 | //! > set time 18:49:30 18 | //! > date 19 | //! 2020-02-28 18:49:32 20 | //! ``` 21 | 22 | #![deny(unsafe_code)] 23 | #![deny(warnings)] 24 | #![no_main] 25 | #![no_std] 26 | 27 | use core::{ 28 | cell::Cell, 29 | fmt::Write as _, 30 | str::{self, FromStr}, 31 | time::Duration, 32 | }; 33 | 34 | use async_embedded::{task, unsync::Mutex}; 35 | use chrono::{Datelike as _, NaiveDate, NaiveTime}; 36 | use cortex_m_rt::entry; 37 | use heapless::{consts, String, Vec}; 38 | use nrf52::{ 39 | ds3231::{self, Ds3231}, 40 | led::Red, 41 | scd30::Scd30, 42 | serial, 43 | timer::Timer, 44 | twim::Twim, 45 | }; 46 | use panic_udf as _; // panic handler 47 | 48 | #[derive(Clone, Copy)] 49 | enum SensorState { 50 | NotReady, 51 | Ready, 52 | Error, 53 | } 54 | 55 | #[entry] 56 | fn main() -> ! { 57 | // shared state 58 | static mut STATE: Cell = Cell::new(SensorState::NotReady); 59 | // range: 0 - 40,000 ppm 60 | static mut CO2: Cell = Cell::new(0); 61 | // range: 0 - 100 % 62 | static mut RH: Cell = Cell::new(0); 63 | // range: -40 - 70 C 64 | static mut T: Cell = Cell::new(0); 65 | static mut M: Option> = None; 66 | 67 | let co2: &'static _ = CO2; 68 | let state: &'static _ = STATE; 69 | let rh: &'static _ = RH; 70 | let t: &'static _ = T; 71 | 72 | // heartbeat task 73 | let mut timer = Timer::take(); 74 | let dur = Duration::from_millis(100); 75 | task::spawn(async move { 76 | loop { 77 | Red.on(); 78 | timer.wait(dur).await; 79 | Red.off(); 80 | timer.wait(dur).await; 81 | Red.on(); 82 | timer.wait(dur).await; 83 | Red.off(); 84 | timer.wait(12 * dur).await; 85 | } 86 | }); 87 | 88 | let twim = M.get_or_insert(Mutex::new(Twim::take())); 89 | let mut scd30 = Scd30::new(twim); 90 | task::spawn(async move { 91 | loop { 92 | // TODO instead of continuously polling the sensor we should only read it out when new 93 | // data is ready (there's a pin that signals that), roughly every 2 seconds. 94 | // Alternatively, we could send this task to sleep for 2 seconds after new data is read 95 | let res = scd30.get_measurement().await; 96 | 97 | if let Ok(m) = res { 98 | co2.set(m.co2 as u16); 99 | rh.set(m.rh as u8); 100 | t.set(m.t as i8); 101 | state.set(SensorState::Ready); 102 | 103 | // adds fairness; avoids starving the task below 104 | task::r#yield().await; 105 | } else { 106 | state.set(SensorState::Error); 107 | 108 | loop { 109 | task::r#yield().await; 110 | } 111 | } 112 | } 113 | }); 114 | 115 | let (mut tx, mut rx) = serial::take(); 116 | let mut ds3231 = Ds3231::new(twim); 117 | task::block_on(async { 118 | let mut input = Vec::::new(); 119 | let mut tx_buf = String::::new(); 120 | 121 | 'prompt: loop { 122 | tx.write(b"> ").await; 123 | 124 | input.clear(); 125 | loop { 126 | // not the most elegant way to have a responsive input 127 | // Ideally, we want to use a large `rx_buf` and instead read its contents only when 128 | // there has been no new data on the bus for a while 129 | let mut rx_buf = [0]; 130 | rx.read(&mut rx_buf).await; 131 | 132 | if input.push(rx_buf[0]).is_err() { 133 | tx.write(b"input buffer is full\n").await; 134 | continue 'prompt; 135 | } 136 | 137 | if let Ok(s) = str::from_utf8(&input) { 138 | // complete command 139 | if s.ends_with('\r') { 140 | if let Ok(cmd) = s[..s.len() - 1].parse::() { 141 | match cmd { 142 | Command::Date => { 143 | match ds3231.get_datetime().await { 144 | Ok(datetime) => { 145 | let mut s = String::::new(); 146 | // will not fail; the buffer is big enough 147 | let _ = writeln!(&mut s, "{}", datetime); 148 | tx.write(s.as_bytes()).await; 149 | } 150 | 151 | Err(ds3231::Error::Twim(..)) => { 152 | tx.write(b"error communicating with the RTC\n").await; 153 | } 154 | 155 | Err(ds3231::Error::InvalidDate) => { 156 | tx.write(b"invalid date stored in the RTC\n").await; 157 | } 158 | } 159 | } 160 | 161 | Command::SetDate(date) => { 162 | // in `Command::parse_str` we validate the input date so no 163 | // `InvalidDate` error should be raised here 164 | if ds3231.set_date(date).await.is_err() { 165 | tx.write(b"error communicating with the RTC\n").await; 166 | } 167 | } 168 | 169 | Command::SetTime(time) => { 170 | if ds3231.set_time(time).await.is_err() { 171 | tx.write(b"error communicating with the RTC\n").await; 172 | } 173 | } 174 | 175 | Command::Sensors => { 176 | tx_buf.clear(); 177 | 178 | // will not fail; the buffer is big enough 179 | let _ = writeln!( 180 | &mut tx_buf, 181 | "CO2: {}ppm\nT: {}C\nRH: {}%", 182 | co2.get(), 183 | t.get(), 184 | rh.get() 185 | ); 186 | tx.write(tx_buf.as_bytes()).await; 187 | } 188 | 189 | Command::Help => { 190 | tx.write( 191 | b"Commands: 192 | help displays this text 193 | date display the current date and time 194 | sensors displays the gas sensor data 195 | set date %Y-%m-%d changes the date 196 | set time %H:%M:%S changes the time 197 | ", 198 | ) 199 | .await; 200 | } 201 | } 202 | } else { 203 | tx.write(b"invalid command; try `help`\n").await; 204 | } 205 | 206 | // new prompt; clear command buffer 207 | continue 'prompt; 208 | } 209 | } 210 | } 211 | } 212 | }) 213 | } 214 | 215 | enum Command { 216 | Date, 217 | Help, 218 | Sensors, 219 | SetDate(NaiveDate), 220 | SetTime(NaiveTime), 221 | } 222 | 223 | impl FromStr for Command { 224 | type Err = Error; 225 | 226 | fn from_str(mut s: &str) -> Result { 227 | const CMD_DATE: &str = "date"; 228 | const CMD_HELP: &str = "help"; 229 | const CMD_SENSORS: &str = "sensors"; 230 | const CMD_SET_DATE: &str = "set date "; 231 | const CMD_SET_TIME: &str = "set time "; 232 | 233 | s = s.trim(); 234 | 235 | Ok(if s == CMD_DATE { 236 | Command::Date 237 | } else if s == CMD_HELP { 238 | Command::Help 239 | } else if s == CMD_SENSORS { 240 | Command::Sensors 241 | } else if s.starts_with(CMD_SET_DATE) { 242 | let date = NaiveDate::parse_from_str(&s[CMD_SET_DATE.len()..], "%Y-%m-%d") 243 | .map_err(|_| Error)?; 244 | let year = date.year(); 245 | // the RTC can only handle a span of roughly 200 years 246 | if year < 2000 || year > 2199 { 247 | return Err(Error); 248 | } 249 | 250 | Command::SetDate(date) 251 | } else if s.starts_with(CMD_SET_TIME) { 252 | let time = NaiveTime::parse_from_str(&s[CMD_SET_TIME.len()..], "%H:%M:%S") 253 | .map_err(|_| Error)?; 254 | 255 | Command::SetTime(time) 256 | } else { 257 | return Err(Error); 258 | }) 259 | } 260 | } 261 | 262 | struct Error; 263 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "aligned" 14 | version = "0.3.2" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" 17 | dependencies = [ 18 | "as-slice", 19 | ] 20 | 21 | [[package]] 22 | name = "as-slice" 23 | version = "0.1.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" 26 | dependencies = [ 27 | "generic-array 0.12.3", 28 | "generic-array 0.13.2", 29 | "stable_deref_trait", 30 | ] 31 | 32 | [[package]] 33 | name = "async-embedded" 34 | version = "0.0.0-alpha.0" 35 | dependencies = [ 36 | "cortex-m", 37 | "generic-array 0.14.2", 38 | "heapless 0.5.3 (git+https://github.com/japaric/heapless?branch=slab)", 39 | "pin-utils", 40 | "riscv", 41 | "typenum", 42 | ] 43 | 44 | [[package]] 45 | name = "autocfg" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 49 | 50 | [[package]] 51 | name = "bare-metal" 52 | version = "0.2.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 55 | dependencies = [ 56 | "rustc_version", 57 | ] 58 | 59 | [[package]] 60 | name = "bit_field" 61 | version = "0.10.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 64 | 65 | [[package]] 66 | name = "bitfield" 67 | version = "0.13.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 70 | 71 | [[package]] 72 | name = "byteorder" 73 | version = "1.3.4" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 76 | 77 | [[package]] 78 | name = "chrono" 79 | version = "0.4.10" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" 82 | dependencies = [ 83 | "num-integer", 84 | "num-traits", 85 | ] 86 | 87 | [[package]] 88 | name = "cortex-m" 89 | version = "0.6.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "2be99930c99669a74d986f7fd2162085498b322e6daae8ef63a97cc9ac1dc73c" 92 | dependencies = [ 93 | "aligned", 94 | "bare-metal", 95 | "bitfield", 96 | "volatile-register", 97 | ] 98 | 99 | [[package]] 100 | name = "cortex-m-rt" 101 | version = "0.6.12" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" 104 | dependencies = [ 105 | "cortex-m-rt-macros", 106 | "r0", 107 | ] 108 | 109 | [[package]] 110 | name = "cortex-m-rt-macros" 111 | version = "0.1.8" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 114 | dependencies = [ 115 | "proc-macro2", 116 | "quote", 117 | "syn", 118 | ] 119 | 120 | [[package]] 121 | name = "cortex-m-semihosting" 122 | version = "0.3.5" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" 125 | dependencies = [ 126 | "cortex-m", 127 | ] 128 | 129 | [[package]] 130 | name = "generic-array" 131 | version = "0.12.3" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 134 | dependencies = [ 135 | "typenum", 136 | ] 137 | 138 | [[package]] 139 | name = "generic-array" 140 | version = "0.13.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 143 | dependencies = [ 144 | "typenum", 145 | ] 146 | 147 | [[package]] 148 | name = "generic-array" 149 | version = "0.14.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" 152 | dependencies = [ 153 | "typenum", 154 | "version_check", 155 | ] 156 | 157 | [[package]] 158 | name = "hash32" 159 | version = "0.1.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" 162 | dependencies = [ 163 | "byteorder", 164 | ] 165 | 166 | [[package]] 167 | name = "heapless" 168 | version = "0.5.3" 169 | source = "git+https://github.com/japaric/heapless?branch=slab#10e88349aef0957062f46700ccb2752485d3aa50" 170 | dependencies = [ 171 | "as-slice", 172 | "generic-array 0.13.2", 173 | "hash32", 174 | ] 175 | 176 | [[package]] 177 | name = "heapless" 178 | version = "0.5.3" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "10b591a0032f114b7a77d4fbfab452660c553055515b7d7ece355db080d19087" 181 | dependencies = [ 182 | "as-slice", 183 | "generic-array 0.13.2", 184 | "hash32", 185 | ] 186 | 187 | [[package]] 188 | name = "lazy_static" 189 | version = "1.4.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 192 | 193 | [[package]] 194 | name = "memchr" 195 | version = "2.3.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 198 | 199 | [[package]] 200 | name = "nrf52" 201 | version = "0.0.0-alpha.0" 202 | dependencies = [ 203 | "async-embedded", 204 | "chrono", 205 | "cortex-m", 206 | "cortex-m-rt", 207 | "cortex-m-semihosting", 208 | "heapless 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "nrf52840-pac", 210 | "panic-semihosting", 211 | "panic-udf", 212 | ] 213 | 214 | [[package]] 215 | name = "nrf52840-pac" 216 | version = "0.9.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e1b780a5afd2621774652f28c82837f6aa6d19cf0ad71c734fc1fe53298a2d73" 219 | dependencies = [ 220 | "bare-metal", 221 | "cortex-m", 222 | "cortex-m-rt", 223 | "vcell", 224 | ] 225 | 226 | [[package]] 227 | name = "num-integer" 228 | version = "0.1.42" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 231 | dependencies = [ 232 | "autocfg", 233 | "num-traits", 234 | ] 235 | 236 | [[package]] 237 | name = "num-traits" 238 | version = "0.2.11" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 241 | dependencies = [ 242 | "autocfg", 243 | ] 244 | 245 | [[package]] 246 | name = "panic-semihosting" 247 | version = "0.5.3" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c" 250 | dependencies = [ 251 | "cortex-m", 252 | "cortex-m-semihosting", 253 | ] 254 | 255 | [[package]] 256 | name = "panic-udf" 257 | version = "0.0.0-alpha.0" 258 | dependencies = [ 259 | "cortex-m", 260 | ] 261 | 262 | [[package]] 263 | name = "pin-utils" 264 | version = "0.1.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 267 | 268 | [[package]] 269 | name = "proc-macro2" 270 | version = "1.0.8" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" 273 | dependencies = [ 274 | "unicode-xid", 275 | ] 276 | 277 | [[package]] 278 | name = "quote" 279 | version = "1.0.2" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 282 | dependencies = [ 283 | "proc-macro2", 284 | ] 285 | 286 | [[package]] 287 | name = "r0" 288 | version = "0.2.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 291 | 292 | [[package]] 293 | name = "regex" 294 | version = "1.3.9" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 297 | dependencies = [ 298 | "aho-corasick", 299 | "memchr", 300 | "regex-syntax", 301 | "thread_local", 302 | ] 303 | 304 | [[package]] 305 | name = "regex-syntax" 306 | version = "0.6.18" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 309 | 310 | [[package]] 311 | name = "riscv" 312 | version = "0.6.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "a2f0b705d428e9d0f78e2bb73093887ee58a83c9688de3faedbb4c0631c4618e" 315 | dependencies = [ 316 | "bare-metal", 317 | "bit_field", 318 | "riscv-target", 319 | ] 320 | 321 | [[package]] 322 | name = "riscv-target" 323 | version = "0.1.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222" 326 | dependencies = [ 327 | "lazy_static", 328 | "regex", 329 | ] 330 | 331 | [[package]] 332 | name = "rustc_version" 333 | version = "0.2.3" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 336 | dependencies = [ 337 | "semver", 338 | ] 339 | 340 | [[package]] 341 | name = "semver" 342 | version = "0.9.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 345 | dependencies = [ 346 | "semver-parser", 347 | ] 348 | 349 | [[package]] 350 | name = "semver-parser" 351 | version = "0.7.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 354 | 355 | [[package]] 356 | name = "stable_deref_trait" 357 | version = "1.1.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 360 | 361 | [[package]] 362 | name = "syn" 363 | version = "1.0.14" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" 366 | dependencies = [ 367 | "proc-macro2", 368 | "quote", 369 | "unicode-xid", 370 | ] 371 | 372 | [[package]] 373 | name = "thread_local" 374 | version = "1.0.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 377 | dependencies = [ 378 | "lazy_static", 379 | ] 380 | 381 | [[package]] 382 | name = "typenum" 383 | version = "1.12.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 386 | 387 | [[package]] 388 | name = "unicode-xid" 389 | version = "0.2.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 392 | 393 | [[package]] 394 | name = "vcell" 395 | version = "0.1.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" 398 | 399 | [[package]] 400 | name = "version_check" 401 | version = "0.9.2" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 404 | 405 | [[package]] 406 | name = "volatile-register" 407 | version = "0.2.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 410 | dependencies = [ 411 | "vcell", 412 | ] 413 | -------------------------------------------------------------------------------- /nrf52/src/serial.rs: -------------------------------------------------------------------------------- 1 | //! Serial interface 2 | 3 | // Based on https://github.com/nrf-rs/nrf52-hal/commit/f05d471996c63f605cab43aa76c8fd990b852460 4 | 5 | use core::{ 6 | future::Future, 7 | pin::Pin, 8 | sync::atomic::{self, AtomicBool, Ordering}, 9 | task::{Context, Poll, Waker}, 10 | }; 11 | 12 | use cortex_m::peripheral::NVIC; 13 | use pac::{Interrupt, UARTE0}; 14 | 15 | use crate::{BorrowUnchecked as _, NotSync}; 16 | 17 | // NOTE called from `pre_init` 18 | pub(crate) fn init() { 19 | use pac::uarte0::baudrate::BAUDRATE_A; 20 | 21 | pac::UARTE0::borrow_unchecked(|uarte| { 22 | const TX_PIN: u8 = 6; 23 | const RX_PIN: u8 = 8; 24 | const UARTE_PORT: bool = false; // 0 25 | 26 | // Select pins 27 | uarte.psel.rxd.write(|w| unsafe { 28 | w.pin() 29 | .bits(RX_PIN) 30 | .port() 31 | .bit(UARTE_PORT) 32 | .connect() 33 | .connected() 34 | }); 35 | // pins.txd.set_high().unwrap(); 36 | uarte.psel.txd.write(|w| unsafe { 37 | w.pin() 38 | .bits(TX_PIN) 39 | .port() 40 | .bit(UARTE_PORT) 41 | .connect() 42 | .connected() 43 | }); 44 | 45 | // Enable UARTE instance 46 | uarte.enable.write(|w| w.enable().enabled()); 47 | 48 | // enable interrupts 49 | uarte 50 | .intenset 51 | .write(|w| w.endtx().set_bit().endrx().set_bit()); 52 | 53 | // Configure frequency 54 | uarte 55 | .baudrate 56 | .write(|w| w.baudrate().variant(BAUDRATE_A::BAUD9600)); 57 | }); 58 | } 59 | 60 | const INTERRUPT: Interrupt = Interrupt::UARTE0_UART0; 61 | 62 | /// Takes the singleton instance of the serial interface 63 | /// 64 | /// The interface is split in transmitter and receiver parts 65 | /// 66 | /// This returns the `Some` variant only once 67 | pub fn take() -> (Tx, Rx) { 68 | // NOTE peripheral initialization is done in `#[pre_init]` 69 | 70 | static TAKEN: AtomicBool = AtomicBool::new(false); 71 | 72 | if TAKEN 73 | .compare_exchange_weak(false, true, Ordering::Relaxed, Ordering::Relaxed) 74 | .is_ok() 75 | { 76 | ( 77 | Tx { 78 | _not_sync: NotSync::new(), 79 | }, 80 | Rx { 81 | _not_sync: NotSync::new(), 82 | }, 83 | ) 84 | } else { 85 | panic!("serial device has already been taken"); 86 | } 87 | } 88 | 89 | /// [Singleton] Receiver component of the serial interface 90 | pub struct Rx { 91 | _not_sync: NotSync, 92 | } 93 | 94 | impl Rx { 95 | /// *Completely* fills the given `buffer` with bytes received over the serial interface 96 | // XXX(Soundness?) The following operation is potentially unsound: `buf` 97 | // points into RAM; the future returned by this method is `poll`-ed once and 98 | // then `mem::forget`-ed (forgotten). This lets the caller return from the 99 | // current stack frame, freeing `buf`: now the DMA can overwrite the stack 100 | // frames of the program 101 | // TODO bubble up errors 102 | pub async fn read(&mut self, buf: &mut [u8]) { 103 | struct Read<'t, 'b> { 104 | _rx: &'t mut Rx, 105 | buf: &'b mut [u8], 106 | state: State, 107 | } 108 | 109 | impl Future for Read<'_, '_> { 110 | type Output = (); 111 | 112 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 113 | match self.state { 114 | // nothing to do 115 | State::NotStarted if self.buf.len() == 0 => { 116 | self.state = State::Finished; 117 | 118 | Poll::Ready(()) 119 | } 120 | 121 | State::NotStarted => { 122 | UARTE0::borrow_unchecked(|uarte| { 123 | // reset events 124 | uarte.events_endrx.reset(); 125 | 126 | uarte 127 | .rxd 128 | .maxcnt 129 | .write(|w| unsafe { w.maxcnt().bits(self.buf.len() as u16) }); 130 | 131 | uarte.rxd.ptr.write(|w| unsafe { 132 | w.ptr().bits(self.buf.as_mut_ptr() as usize as u32) 133 | }); 134 | 135 | // install the waker 136 | NVIC::mask(INTERRUPT); 137 | unsafe { 138 | RX_WAKER = Some(cx.waker().clone()); 139 | // NOTE(compiler_fence) writing the waker must 140 | // complete before the interrupt is unmasked 141 | atomic::compiler_fence(Ordering::Release); 142 | NVIC::unmask(INTERRUPT); 143 | } 144 | 145 | // start the transfer 146 | // semantically this complete the transfer of the 147 | // reference to the DMA; any pending write to 148 | // `bytes` must complete before the transfer, hence 149 | // the compiler fence -- but it's redundant because 150 | // of the preceding barrier 151 | atomic::compiler_fence(Ordering::Release); 152 | uarte.tasks_startrx.write(|w| unsafe { w.bits(1) }); 153 | }); 154 | 155 | self.state = State::InProgress; 156 | 157 | Poll::Pending 158 | } 159 | 160 | State::InProgress => { 161 | UARTE0::borrow_unchecked(|uarte| { 162 | if uarte.events_endrx.read().bits() != 0 { 163 | uarte.events_endrx.reset(); 164 | 165 | self.state = State::Finished; 166 | 167 | // uninstall the waker 168 | NVIC::mask(INTERRUPT); 169 | // NOTE(compiler_fence) the interrupt must be 170 | // disabled before we take down the waker 171 | atomic::compiler_fence(Ordering::SeqCst); 172 | drop(unsafe { RX_WAKER.take() }); 173 | unsafe { 174 | // the TX waker may still need to be serviced 175 | if TX_WAKER.is_some() { 176 | NVIC::unmask(INTERRUPT); 177 | } 178 | } 179 | 180 | Poll::Ready(()) 181 | } else { 182 | // spurious wake up; re-arm the one-shot interrupt 183 | unsafe { 184 | NVIC::unmask(INTERRUPT); 185 | } 186 | 187 | Poll::Pending 188 | } 189 | }) 190 | } 191 | 192 | State::Finished => unreachable!(), 193 | } 194 | } 195 | } 196 | 197 | impl Drop for Read<'_, '_> { 198 | fn drop(&mut self) { 199 | if self.state == State::InProgress { 200 | // stop the transfer 201 | todo!() 202 | } 203 | } 204 | } 205 | 206 | // TODO for large buffers do transfers in chunks 207 | assert!(buf.len() < (1 << 10)); 208 | 209 | Read { 210 | _rx: self, 211 | buf, 212 | state: State::NotStarted, 213 | } 214 | .await 215 | } 216 | } 217 | 218 | /// [Singleton] Receiver component of the serial interface 219 | pub struct Tx { 220 | _not_sync: NotSync, 221 | } 222 | 223 | impl Tx { 224 | /// Sends *all* `bytes` over the serial interface 225 | // NOTE like with `read`, starting a `write` on a `bytes` that points into 226 | // the stack, `poll`-ing the future and then `mem::forget`-ing it is a Bad 227 | // Thing To Do. This operation is not unsound on the device side but will 228 | // sent junk through the serial interface 229 | // TODO bubble up errors 230 | pub async fn write(&mut self, bytes: &[u8]) { 231 | if crate::slice_in_ram(bytes) { 232 | self.write_from_ram(bytes).await 233 | } else { 234 | const BUFSZ: usize = 128; 235 | let mut on_the_stack = [0; BUFSZ]; 236 | for chunk in bytes.chunks(BUFSZ) { 237 | let n = chunk.len(); 238 | on_the_stack[..n].copy_from_slice(chunk); 239 | self.write_from_ram(&on_the_stack[..n]).await 240 | } 241 | } 242 | } 243 | 244 | // `bytes` has already been checked to point into RAM 245 | async fn write_from_ram(&mut self, bytes: &[u8]) { 246 | struct Write<'t, 'b> { 247 | _tx: &'t mut Tx, 248 | bytes: &'b [u8], 249 | state: State, 250 | } 251 | 252 | impl Future for Write<'_, '_> { 253 | type Output = (); 254 | 255 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 256 | match self.state { 257 | // nothing to do 258 | State::NotStarted if self.bytes.len() == 0 => { 259 | self.state = State::Finished; 260 | 261 | Poll::Ready(()) 262 | } 263 | 264 | State::NotStarted => { 265 | UARTE0::borrow_unchecked(|uarte| { 266 | // reset events 267 | uarte.events_endtx.reset(); 268 | 269 | uarte 270 | .txd 271 | .maxcnt 272 | .write(|w| unsafe { w.maxcnt().bits(self.bytes.len() as u16) }); 273 | 274 | uarte.txd.ptr.write(|w| unsafe { 275 | w.ptr().bits(self.bytes.as_ptr() as usize as u32) 276 | }); 277 | 278 | // install the waker 279 | NVIC::mask(INTERRUPT); 280 | unsafe { 281 | TX_WAKER = Some(cx.waker().clone()); 282 | // NOTE(compiler_fence) writing the waker must 283 | // complete before the interrupt is unmasked 284 | atomic::compiler_fence(Ordering::Release); 285 | NVIC::unmask(INTERRUPT); 286 | } 287 | 288 | // start the transfer 289 | // semantically this complete the transfer of the 290 | // reference to the DMA; any pending write to 291 | // `bytes` must complete before the transfer, hence 292 | // the compiler fence -- but it's redundant because 293 | // of the preceding barrier 294 | atomic::compiler_fence(Ordering::Release); 295 | uarte.tasks_starttx.write(|w| unsafe { w.bits(1) }); 296 | }); 297 | 298 | self.state = State::InProgress; 299 | 300 | Poll::Pending 301 | } 302 | 303 | State::InProgress => { 304 | UARTE0::borrow_unchecked(|uarte| { 305 | if uarte.events_endtx.read().bits() != 0 { 306 | uarte.events_endtx.reset(); 307 | 308 | self.state = State::Finished; 309 | 310 | // uninstall the waker 311 | NVIC::mask(INTERRUPT); 312 | // NOTE(compiler_fence) the interrupt must be 313 | // disabled before we take down the waker 314 | atomic::compiler_fence(Ordering::SeqCst); 315 | drop(unsafe { TX_WAKER.take() }); 316 | unsafe { 317 | // the RX waker may still need to be serviced 318 | if RX_WAKER.is_some() { 319 | NVIC::unmask(INTERRUPT); 320 | } 321 | } 322 | 323 | Poll::Ready(()) 324 | } else { 325 | // spurious wake up; re-arm the one-shot interrupt 326 | unsafe { 327 | NVIC::unmask(INTERRUPT); 328 | } 329 | 330 | Poll::Pending 331 | } 332 | }) 333 | } 334 | 335 | State::Finished => unreachable!(), 336 | } 337 | } 338 | } 339 | 340 | impl Drop for Write<'_, '_> { 341 | fn drop(&mut self) { 342 | if self.state == State::InProgress { 343 | // stop the transfer 344 | todo!() 345 | } 346 | } 347 | } 348 | 349 | // TODO for large buffers do transfers in chunks 350 | assert!(bytes.len() < (1 << 10)); 351 | 352 | Write { 353 | _tx: self, 354 | bytes, 355 | state: State::NotStarted, 356 | } 357 | .await 358 | } 359 | } 360 | 361 | static mut RX_WAKER: Option = None; 362 | static mut TX_WAKER: Option = None; 363 | 364 | #[allow(non_snake_case)] 365 | #[no_mangle] 366 | fn UARTE0_UART0() { 367 | let mut ran_a_waker = false; 368 | unsafe { 369 | if let Some(waker) = RX_WAKER.as_ref() { 370 | waker.wake_by_ref(); 371 | ran_a_waker = true; 372 | } 373 | 374 | if let Some(waker) = TX_WAKER.as_ref() { 375 | waker.wake_by_ref(); 376 | ran_a_waker = true; 377 | } 378 | } 379 | 380 | if ran_a_waker { 381 | // avoid continuously re-entering this interrupt handler 382 | NVIC::mask(INTERRUPT); 383 | } 384 | } 385 | 386 | #[derive(Clone, Copy, PartialEq)] 387 | enum State { 388 | NotStarted, 389 | InProgress, 390 | Finished, 391 | } 392 | -------------------------------------------------------------------------------- /nrf52/src/twim.rs: -------------------------------------------------------------------------------- 1 | //! Two-Wire Interface (AKA I2C) 2 | 3 | // Based on https://github.com/nrf-rs/nrf52-hal/commit/f05d471996c63f605cab43aa76c8fd990b852460 4 | 5 | use core::{ 6 | future::Future, 7 | pin::Pin, 8 | sync::atomic::{self, AtomicBool, Ordering}, 9 | task::{Context, Poll, Waker}, 10 | }; 11 | 12 | use cortex_m::peripheral::NVIC; 13 | use pac::{Interrupt, TWIM0}; 14 | 15 | use crate::{BorrowUnchecked, NotSync}; 16 | 17 | // NOTE called from `pre_init` 18 | pub(crate) fn init() { 19 | use pac::twim0::frequency::FREQUENCY_A; 20 | 21 | const SDA_PIN: u8 = 26; 22 | const SCL_PIN: u8 = 27; 23 | const TWIM_PORT: bool = false; // 0 24 | 25 | // pin configuration 26 | pac::P0::borrow_unchecked(|p0| { 27 | for pin in [SDA_PIN, SCL_PIN].iter() { 28 | p0.pin_cnf[*pin as usize].write(|w| { 29 | w.dir() 30 | .input() 31 | .input() 32 | .connect() 33 | .pull() 34 | .pullup() 35 | .drive() 36 | .s0d1() 37 | .sense() 38 | .disabled() 39 | }); 40 | } 41 | }); 42 | 43 | pac::TWIM0::borrow_unchecked(|twim| { 44 | twim.psel.scl.write(|w| unsafe { 45 | w.pin() 46 | .bits(SCL_PIN) 47 | .port() 48 | .bit(TWIM_PORT) 49 | .connect() 50 | .connected() 51 | }); 52 | 53 | twim.psel.sda.write(|w| unsafe { 54 | w.pin() 55 | .bits(SDA_PIN) 56 | .port() 57 | .bit(TWIM_PORT) 58 | .connect() 59 | .connected() 60 | }); 61 | 62 | // Enable the TWIM interface 63 | twim.enable.write(|w| w.enable().enabled()); 64 | 65 | // Configure frequency 66 | twim.frequency 67 | .write(|w| w.frequency().variant(FREQUENCY_A::K100)); 68 | 69 | twim.intenset 70 | .write(|w| w.error().set_bit().stopped().set_bit()); 71 | }); 72 | } 73 | 74 | const INTERRUPT: Interrupt = Interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0; 75 | const MAXCNT: usize = 256; 76 | 77 | /// [singleton] An `async`-aware I2C host 78 | pub struct Twim { 79 | _not_sync: NotSync, 80 | } 81 | 82 | impl Twim { 83 | /// Takes the singleton instance of this I2C bus 84 | /// 85 | /// This returns the `Some` variant only once 86 | pub fn take() -> Self { 87 | // NOTE peripheral initialization is done in `#[pre_init]` 88 | 89 | static TAKEN: AtomicBool = AtomicBool::new(false); 90 | 91 | if TAKEN 92 | .compare_exchange_weak(false, true, Ordering::Relaxed, Ordering::Relaxed) 93 | .is_ok() 94 | { 95 | Self { 96 | _not_sync: NotSync::new(), 97 | } 98 | } else { 99 | panic!("`Twim` has already been taken") 100 | } 101 | } 102 | 103 | /// Fills the given buffer with data from the device with the specified address 104 | /// 105 | /// Events: START - ADDR - (D -> H) - STOP 106 | /// 107 | /// `(D -> H)` denotes data being sent from the Device to the Host 108 | pub async fn read(&mut self, address: u8, buf: &mut [u8]) -> Result<(), Error> { 109 | struct Read<'t, 'b> { 110 | _twim: &'t mut Twim, 111 | address: u8, 112 | buf: &'b mut [u8], 113 | state: State, 114 | } 115 | 116 | impl Future for Read<'_, '_> { 117 | type Output = Result<(), Error>; 118 | 119 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 120 | match self.state { 121 | State::NotStarted => { 122 | TWIM0::borrow_unchecked(|twim| { 123 | NVIC::mask(INTERRUPT); 124 | 125 | // NOTE program defensively: the user could poll a `Read` future once 126 | // (and start the DMA transfer) and then `mem::forget` (or `drop`) it. 127 | // We cannot assume any `async` method was driven to completion 128 | if twim.events_rxstarted.read().bits() != 0 129 | || twim.events_txstarted.read().bits() != 0 130 | { 131 | // abort any pending transaction 132 | twim.tasks_stop.write(|w| unsafe { w.bits(1) }); 133 | 134 | // clear any unhandled error 135 | twim.errorsrc.reset(); 136 | 137 | // clear any unhandled event 138 | twim.events_error.reset(); 139 | twim.events_lastrx.reset(); 140 | twim.events_lasttx.reset(); 141 | twim.events_stopped.reset(); 142 | } 143 | 144 | // NOTE(unsafe) this operation is not unsafe at all 145 | twim.address 146 | .write(|w| unsafe { w.address().bits(self.address) }); 147 | 148 | twim.rxd 149 | .ptr 150 | .write(|w| unsafe { w.ptr().bits(self.buf.as_mut_ptr() as u32) }); 151 | twim.rxd 152 | .maxcnt 153 | .write(|w| unsafe { w.maxcnt().bits(self.buf.len() as u16) }); 154 | 155 | // send STOP after last byte is transmitted 156 | twim.shorts.write(|w| w.lastrx_stop().set_bit()); 157 | 158 | // here we finishing transferring the slice to the 159 | // DMA; all previous memory operations on the slice 160 | // should be finished before then, thus the compiler 161 | // fence 162 | atomic::compiler_fence(Ordering::Release); 163 | twim.tasks_startrx.write(|w| unsafe { w.bits(1) }); 164 | 165 | // install the waker 166 | unsafe { 167 | WAKER = Some(cx.waker().clone()); 168 | 169 | // updating the `WAKER` needs to be completed before unmasking the 170 | // interrupt; hence the compiler fence 171 | atomic::compiler_fence(Ordering::Release); 172 | NVIC::unmask(INTERRUPT); 173 | } 174 | 175 | self.state = State::InProgress; 176 | 177 | Poll::Pending 178 | }) 179 | } 180 | 181 | State::InProgress => { 182 | TWIM0::borrow_unchecked(|twim| { 183 | if twim.events_error.read().bits() != 0 { 184 | // slice has been handed back to us; any future operation on the 185 | // slice should not be reordered to before this point 186 | atomic::compiler_fence(Ordering::Acquire); 187 | 188 | // XXX do we need to clear `events_{stopped,lastrx}` here? 189 | twim.events_stopped.reset(); 190 | twim.events_rxstarted.reset(); 191 | twim.events_lastrx.reset(); 192 | 193 | self.state = State::Finished; 194 | 195 | Poll::Ready(Err(Error::Src(twim.errorsrc.read().bits() as u8))) 196 | } else if twim.events_stopped.read().bits() != 0 { 197 | // slice has been handed back to us; any future operation on the 198 | // slice should not be reordered to before this point 199 | atomic::compiler_fence(Ordering::Acquire); 200 | 201 | // events have been successfully handled 202 | twim.events_stopped.reset(); 203 | twim.events_rxstarted.reset(); 204 | twim.events_lastrx.reset(); 205 | 206 | // uninstall the waker 207 | NVIC::mask(INTERRUPT); 208 | // NOTE(compiler_fence) the interrupt must be 209 | // disabled before we take down the waker 210 | atomic::compiler_fence(Ordering::Release); 211 | drop(unsafe { WAKER.take() }); 212 | 213 | let amount = twim.rxd.amount.read().bits() as u8; 214 | 215 | self.state = State::Finished; 216 | 217 | let n = self.buf.len() as u8; 218 | if amount == n { 219 | Poll::Ready(Ok(())) 220 | } else { 221 | Poll::Ready(Err(Error::ShortRead(amount))) 222 | } 223 | } else { 224 | // spurious wake up; re-arm the one-shot interrupt 225 | unsafe { 226 | NVIC::unmask(INTERRUPT); 227 | } 228 | 229 | Poll::Pending 230 | } 231 | }) 232 | } 233 | 234 | State::Finished => unreachable!(), 235 | } 236 | } 237 | } 238 | 239 | impl Drop for Read<'_, '_> { 240 | fn drop(&mut self) { 241 | if self.state == State::InProgress { 242 | // stop the transfer 243 | todo!() 244 | } 245 | } 246 | } 247 | 248 | // TODO do reads/writes in chunks? 249 | assert!(buf.len() < MAXCNT); 250 | 251 | Read { 252 | _twim: self, 253 | address, 254 | buf, 255 | state: State::NotStarted, 256 | } 257 | .await 258 | } 259 | 260 | /// `write` followed by `read` in a single transaction (without an intermediate STOP) 261 | /// 262 | /// Events: START - ADDR - (H -> D) - reSTART - ADDR - (D -> H) - STOP 263 | /// 264 | /// `reSTART` denotes a "repeated START" 265 | pub async fn write_then_read( 266 | &mut self, 267 | address: u8, 268 | wr_buf: &[u8], 269 | rd_buf: &mut [u8], 270 | ) -> Result<(), Error> { 271 | // TODO do reads/writes in chunks? 272 | assert!(wr_buf.len() < MAXCNT && rd_buf.len() < MAXCNT); 273 | 274 | if crate::slice_in_ram(wr_buf) { 275 | self.write_from_ram_then_read(address, wr_buf, rd_buf).await 276 | } else { 277 | let mut buf = [0; MAXCNT]; 278 | let n = wr_buf.len(); 279 | buf[..n].copy_from_slice(wr_buf); 280 | self.write_from_ram_then_read(address, &buf[..n], rd_buf) 281 | .await 282 | } 283 | } 284 | 285 | async fn write_from_ram_then_read( 286 | &mut self, 287 | address: u8, 288 | wr_buf: &[u8], 289 | rd_buf: &mut [u8], 290 | ) -> Result<(), Error> { 291 | struct WriteThenRead<'t, 'b> { 292 | _twim: &'t mut Twim, 293 | address: u8, 294 | rd_buf: &'b mut [u8], 295 | state: State, 296 | wr_buf: &'b [u8], 297 | } 298 | 299 | impl Future for WriteThenRead<'_, '_> { 300 | type Output = Result<(), Error>; 301 | 302 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 303 | match self.state { 304 | State::NotStarted => { 305 | TWIM0::borrow_unchecked(|twim| { 306 | NVIC::mask(INTERRUPT); 307 | 308 | // NOTE program defensively: the user could poll a `WriteThenRead` 309 | // future once (and start the DMA transfer) and then `mem::forget` (or 310 | // `drop`) it. We cannot assume any `async` method was driven to 311 | // completion 312 | if twim.events_rxstarted.read().bits() != 0 313 | || twim.events_txstarted.read().bits() != 0 314 | { 315 | // abort any pending transaction 316 | twim.tasks_stop.write(|w| unsafe { w.bits(1) }); 317 | 318 | // clear any unhandled error 319 | twim.errorsrc.reset(); 320 | 321 | // clear any unhandled event 322 | twim.events_error.reset(); 323 | twim.events_lastrx.reset(); 324 | twim.events_lasttx.reset(); 325 | twim.events_stopped.reset(); 326 | } 327 | 328 | // NOTE(unsafe) this operation is not unsafe at all 329 | twim.address 330 | .write(|w| unsafe { w.address().bits(self.address) }); 331 | 332 | twim.rxd.ptr.write(|w| unsafe { 333 | w.ptr().bits(self.rd_buf.as_mut_ptr() as u32) 334 | }); 335 | twim.rxd 336 | .maxcnt 337 | .write(|w| unsafe { w.maxcnt().bits(self.rd_buf.len() as u16) }); 338 | 339 | twim.txd 340 | .ptr 341 | .write(|w| unsafe { w.ptr().bits(self.wr_buf.as_ptr() as u32) }); 342 | twim.txd 343 | .maxcnt 344 | .write(|w| unsafe { w.maxcnt().bits(self.wr_buf.len() as u16) }); 345 | 346 | // start read after write is finished and trigger a 347 | // STOP after the read is finished 348 | twim.shorts 349 | .write(|w| w.lasttx_startrx().set_bit().lastrx_stop().set_bit()); 350 | 351 | // here we finishing transferring the slices to the 352 | // DMA; all previous memory operations on the slices 353 | // should be finished before then, thus the compiler fence 354 | atomic::compiler_fence(Ordering::Release); 355 | twim.tasks_starttx.write(|w| unsafe { w.bits(1) }); 356 | 357 | // install the waker 358 | unsafe { 359 | WAKER = Some(cx.waker().clone()); 360 | 361 | // updating the `WAKER` needs to be done before 362 | // unmasking the interrupt; hence the compiler fence 363 | atomic::compiler_fence(Ordering::Release); 364 | NVIC::unmask(INTERRUPT); 365 | } 366 | 367 | self.state = State::InProgress; 368 | 369 | Poll::Pending 370 | }) 371 | } 372 | 373 | State::InProgress => { 374 | TWIM0::borrow_unchecked(|twim| { 375 | if twim.events_error.read().bits() != 0 { 376 | // slice has been handed back to us; any future operation on the 377 | // slice should not be reordered to before this point 378 | atomic::compiler_fence(Ordering::Acquire); 379 | 380 | // XXX do we need to clear `events_{stopped,lastrx,lasttx}` here? 381 | twim.events_stopped.reset(); 382 | twim.events_rxstarted.reset(); 383 | twim.events_lastrx.reset(); 384 | twim.events_txstarted.reset(); 385 | twim.events_lasttx.reset(); 386 | 387 | self.state = State::Finished; 388 | 389 | Poll::Ready(Err(Error::Src(twim.errorsrc.read().bits() as u8))) 390 | } else if twim.events_stopped.read().bits() != 0 { 391 | // slice has been handed back to us; any future operation on the 392 | // slice should not be reordered to before this point 393 | atomic::compiler_fence(Ordering::Acquire); 394 | 395 | // events have been successfully handled 396 | twim.events_stopped.reset(); 397 | twim.events_rxstarted.reset(); 398 | twim.events_lastrx.reset(); 399 | twim.events_txstarted.reset(); 400 | twim.events_lasttx.reset(); 401 | 402 | // uninstall the waker 403 | NVIC::mask(INTERRUPT); 404 | // NOTE(compiler_fence) the interrupt must be 405 | // disabled before we take down the waker 406 | atomic::compiler_fence(Ordering::Release); 407 | drop(unsafe { WAKER.take() }); 408 | 409 | let amount = twim.rxd.amount.read().bits() as u8; 410 | 411 | if amount != self.rd_buf.len() as u8 { 412 | return Poll::Ready(Err(Error::ShortRead(amount))); 413 | } 414 | 415 | let amount = twim.txd.amount.read().bits() as u8; 416 | if amount != self.wr_buf.len() as u8 { 417 | return Poll::Ready(Err(Error::ShortWrite(amount))); 418 | } 419 | 420 | self.state = State::Finished; 421 | 422 | Poll::Ready(Ok(())) 423 | } else { 424 | // spurious wake up; re-arm the one-shot interrupt 425 | unsafe { 426 | NVIC::unmask(INTERRUPT); 427 | } 428 | 429 | Poll::Pending 430 | } 431 | }) 432 | } 433 | 434 | State::Finished => unreachable!(), 435 | } 436 | } 437 | } 438 | 439 | impl Drop for WriteThenRead<'_, '_> { 440 | fn drop(&mut self) { 441 | if self.state == State::InProgress { 442 | // stop the transfer 443 | todo!() 444 | } 445 | } 446 | } 447 | 448 | WriteThenRead { 449 | _twim: self, 450 | address, 451 | rd_buf, 452 | state: State::NotStarted, 453 | wr_buf, 454 | } 455 | .await 456 | } 457 | 458 | /// Sends `bytes` to the device with the specified address 459 | /// 460 | /// Events: START - ADDR - (H -> D) - STOP 461 | /// 462 | /// `(H -> D)` denotes data being sent from the Host to the Device 463 | pub async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> { 464 | // TODO do writes in chunks? 465 | assert!(bytes.len() < MAXCNT); 466 | 467 | if crate::slice_in_ram(bytes) { 468 | self.write_from_ram(address, bytes).await 469 | } else { 470 | let mut buf = [0; MAXCNT]; 471 | let n = bytes.len(); 472 | buf[..n].copy_from_slice(bytes); 473 | self.write_from_ram(address, &buf[..n]).await 474 | } 475 | } 476 | 477 | // NOTE `bytes` points into RAM 478 | async fn write_from_ram(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> { 479 | struct Write<'t, 'b> { 480 | _twim: &'t Twim, 481 | address: u8, 482 | bytes: &'b [u8], 483 | state: State, 484 | } 485 | 486 | impl Future for Write<'_, '_> { 487 | type Output = Result<(), Error>; 488 | 489 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 490 | match self.state { 491 | State::NotStarted => { 492 | TWIM0::borrow_unchecked(|twim| { 493 | NVIC::mask(INTERRUPT); 494 | 495 | // NOTE program defensively: the user could poll a `Write` future (start 496 | // the transfer) and then `mem::forget` it. We cannot assume any `async` 497 | // method was driven to completion 498 | if twim.events_rxstarted.read().bits() != 0 499 | || twim.events_txstarted.read().bits() != 0 500 | { 501 | // abort any pending transaction 502 | twim.tasks_stop.write(|w| unsafe { w.bits(1) }); 503 | 504 | // clear any unhandled error 505 | twim.errorsrc.reset(); 506 | 507 | // clear any unhandled event 508 | twim.events_error.reset(); 509 | twim.events_lastrx.reset(); 510 | twim.events_lasttx.reset(); 511 | twim.events_stopped.reset(); 512 | } 513 | 514 | // NOTE(unsafe) this operation is not unsafe at all 515 | twim.address 516 | .write(|w| unsafe { w.address().bits(self.address) }); 517 | 518 | twim.txd 519 | .ptr 520 | .write(|w| unsafe { w.ptr().bits(self.bytes.as_ptr() as u32) }); 521 | twim.txd 522 | .maxcnt 523 | .write(|w| unsafe { w.maxcnt().bits(self.bytes.len() as u16) }); 524 | 525 | // send STOP after last byte is transmitted 526 | twim.shorts.write(|w| w.lasttx_stop().set_bit()); 527 | 528 | // here we finishing transferring the slice to the DMA; all previous 529 | // memory operations on the slice should be finished before then, thus 530 | // the compiler fence 531 | atomic::compiler_fence(Ordering::Release); 532 | twim.tasks_starttx.write(|w| unsafe { w.bits(1) }); 533 | 534 | // install the waker 535 | unsafe { 536 | WAKER = Some(cx.waker().clone()); 537 | 538 | // updating the `WAKER` needs to be complete before unmasking the 539 | // interrupt; hence the compiler fence 540 | atomic::compiler_fence(Ordering::Release); 541 | NVIC::unmask(INTERRUPT); 542 | } 543 | 544 | self.state = State::InProgress; 545 | 546 | Poll::Pending 547 | }) 548 | } 549 | 550 | State::InProgress => { 551 | TWIM0::borrow_unchecked(|twim| { 552 | if twim.events_error.read().bits() != 0 { 553 | // slice has been handed back to us; any future operation on the 554 | // slice should not be reordered to before this point 555 | atomic::compiler_fence(Ordering::Acquire); 556 | 557 | // XXX do we need to clear `events_{stopped,lasttx}` here? 558 | twim.events_stopped.reset(); 559 | twim.events_txstarted.reset(); 560 | twim.events_lasttx.reset(); 561 | 562 | self.state = State::Finished; 563 | 564 | Poll::Ready(Err(Error::Src(twim.errorsrc.read().bits() as u8))) 565 | } else if twim.events_stopped.read().bits() != 0 { 566 | // slice has been handed back to us; any future operation on the 567 | // slice should not be reordered to before this point 568 | atomic::compiler_fence(Ordering::Acquire); 569 | 570 | // events have been successfully handled 571 | twim.events_stopped.reset(); 572 | twim.events_txstarted.reset(); 573 | twim.events_lasttx.reset(); 574 | 575 | // uninstall the waker 576 | NVIC::mask(INTERRUPT); 577 | // NOTE(compiler_fence) the interrupt must be 578 | // disabled before we take down the waker 579 | atomic::compiler_fence(Ordering::Release); 580 | drop(unsafe { WAKER.take() }); 581 | 582 | let amount = twim.txd.amount.read().bits() as u8; 583 | 584 | self.state = State::Finished; 585 | 586 | let n = self.bytes.len() as u8; 587 | if amount == n { 588 | Poll::Ready(Ok(())) 589 | } else { 590 | Poll::Ready(Err(Error::ShortWrite(amount))) 591 | } 592 | } else { 593 | // spurious wake up; re-arm the one-shot interrupt 594 | unsafe { 595 | NVIC::unmask(INTERRUPT); 596 | } 597 | 598 | Poll::Pending 599 | } 600 | }) 601 | } 602 | 603 | State::Finished => unreachable!(), 604 | } 605 | } 606 | } 607 | 608 | impl Drop for Write<'_, '_> { 609 | fn drop(&mut self) { 610 | if self.state == State::InProgress { 611 | // stop the transfer 612 | todo!() 613 | } 614 | } 615 | } 616 | 617 | Write { 618 | _twim: self, 619 | address, 620 | bytes, 621 | state: State::NotStarted, 622 | } 623 | .await 624 | } 625 | } 626 | 627 | static mut WAKER: Option = None; 628 | 629 | #[allow(non_snake_case)] 630 | #[no_mangle] 631 | fn SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0() { 632 | // NOTE(unsafe) the only other context that can access this static variable 633 | // runs at lower priority 634 | if let Some(waker) = unsafe { WAKER.as_ref() } { 635 | waker.wake_by_ref(); 636 | 637 | // avoid continuously re-entering this interrupt handler 638 | NVIC::mask(INTERRUPT); 639 | } else { 640 | // reachable if the user manually pends this interrupt 641 | } 642 | } 643 | 644 | #[derive(Clone, Copy, PartialEq)] 645 | enum State { 646 | NotStarted, 647 | InProgress, 648 | Finished, 649 | } 650 | 651 | /// I2C error 652 | #[derive(Debug)] 653 | pub enum Error { 654 | /// Wrote less data than requested 655 | ShortWrite(u8), 656 | 657 | /// Read less data than requested 658 | ShortRead(u8), 659 | 660 | /// ERRORSRC encoded error 661 | Src(u8), 662 | } 663 | --------------------------------------------------------------------------------