diff --git a/Cargo.lock b/Cargo.lock index 5c68c7525e565f298fba7f805997287155f39a78..3ed1e7022da246c38a1958394f3b5f0e205edd3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ version = "0.0.0-alpha.0" dependencies = [ "cortex-m-udf", "generic-array 0.13.2", - "heapless 0.5.3 (git+https://github.com/japaric/heapless?branch=slab)", + "heapless 0.5.3", "pin-utils", "typenum", ] @@ -46,6 +46,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + [[package]] name = "byteorder" version = "1.3.4" @@ -69,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" dependencies = [ "aligned", - "bare-metal", + "bare-metal 0.2.5", "volatile-register", ] @@ -94,6 +100,56 @@ dependencies = [ "syn", ] +[[package]] +name = "cortex-m-rtfm" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc65a600a5c8baf0a5fd3b2c0223b8d8fc7aa7d6d0c9a3350ce40a4da49ccea" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "cortex-m-rtfm-macros", + "heapless 0.5.6", + "rtfm-core", + "version_check", +] + +[[package]] +name = "cortex-m-rtfm-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4a3fc88fff9970152a7f79bf6919d538395bc1f11ba90a1e6970a3521d8b88" +dependencies = [ + "proc-macro2", + "quote", + "rtfm-syntax", + "syn", +] + +[[package]] +name = "cortex-m-rtic" +version = "0.5.5" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "cortex-m-rt", + "cortex-m-rtic-macros", + "heapless 0.5.6", + "rtic-core", + "version_check", +] + +[[package]] +name = "cortex-m-rtic-macros" +version = "0.5.2" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rtic-syntax", + "syn", +] + [[package]] name = "cortex-m-semihosting" version = "0.3.5" @@ -134,6 +190,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "heapless" version = "0.5.3" @@ -146,13 +208,34 @@ dependencies = [ [[package]] name = "heapless" -version = "0.5.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b591a0032f114b7a77d4fbfab452660c553055515b7d7ece355db080d19087" +checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" dependencies = [ "as-slice", "generic-array 0.13.2", "hash32", + "stable_deref_trait", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "lm3s6965" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8698042a7495160eac9f7298a32cd1ddbb6ad2780f766f5a99b4f0a6b915e0ad" +dependencies = [ + "bare-metal 0.2.5", + "cortex-m-rt", ] [[package]] @@ -163,9 +246,10 @@ dependencies = [ "chrono", "cortex-m", "cortex-m-rt", + "cortex-m-rtfm", "cortex-m-semihosting", "cortex-m-udf", - "heapless 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "heapless 0.5.6", "nrf52840-pac", "panic-semihosting", "panic-udf", @@ -177,7 +261,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b780a5afd2621774652f28c82837f6aa6d19cf0ad71c734fc1fe53298a2d73" dependencies = [ - "bare-metal", + "bare-metal 0.2.5", "cortex-m", "cortex-m-rt", "vcell", @@ -202,6 +286,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "panic-halt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" + [[package]] name = "panic-semihosting" version = "0.5.3" @@ -225,6 +315,30 @@ version = "0.1.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.8" @@ -234,6 +348,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "qemu" +version = "0.0.0-alpha.0" +dependencies = [ + "async-cortex-m", + "chrono", + "cortex-m", + "cortex-m-rt", + "cortex-m-rtic", + "cortex-m-semihosting", + "heapless 0.5.6", + "lm3s6965", + "panic-halt", + "panic-semihosting", +] + [[package]] name = "quote" version = "1.0.2" @@ -249,6 +379,40 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +[[package]] +name = "rtfm-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec893edb2aa5b70320b94896ffea22a7ebb1cf3f942bb67cd5b60a865a63493" + +[[package]] +name = "rtfm-syntax" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4455e23c34df3d66454e7e218a4d76a7f83321d04a806be614463341cec4116e" +dependencies = [ + "indexmap", + "proc-macro2", + "syn", +] + +[[package]] +name = "rtic-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab51fe832317e805f869b3d859f91aadf855c2c3da51f9b84bc645c201597158" + +[[package]] +name = "rtic-syntax" +version = "0.5.0-alpha.0" +source = "git+https://github.com/rtic-rs/rtic-syntax?branch=master#291f25f01b2ef4ce55b95b37f0c1c430afbd954d" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -308,6 +472,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + [[package]] name = "volatile-register" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index eb4e3c1a502ad9e61db2fb9f3a8acfe166e6af8a..051404a5af42615ed8a0df23555b5cd15b7f5af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "async-cortex-m", "cortex-m-udf", "nrf52", + "qemu", "panic-udf", ] diff --git a/qemu/.cargo/config b/qemu/.cargo/config new file mode 100644 index 0000000000000000000000000000000000000000..ca67a7f4924a4e873407f6830699552951587d34 --- /dev/null +++ b/qemu/.cargo/config @@ -0,0 +1,11 @@ + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[target.thumbv7m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[build] +target = "thumbv7m-none-eabi" # Cortex-M3 \ No newline at end of file diff --git a/qemu/Cargo.toml b/qemu/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..31d199096f3cd5e92ab811b962c2a68cae7f721b --- /dev/null +++ b/qemu/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Per Lindgren <per.lindgren@ltu.se>"] +edition = "2018" +license = "MIT OR Apache-2.0" +name = "qemu" +publish = false +version = "0.0.0-alpha.0" + +[dev-dependencies] +cortex-m = "0.6.2" +cortex-m-semihosting = "0.3.5" +# cortex-m-udf = { path = "../cortex-m-udf" } +heapless = "0.5.6" +panic-semihosting = "0.5.3" +panic-halt = "0.2.0" + +[dependencies] +async-cortex-m = { path = "../async-cortex-m" } +cortex-m = "0.6.2" +cortex-m-rt = "0.6.12" +lm3s6965 = "0.1.3" +cortex-m-rtic = { version = "0.5.5", path = "../../cortex-m-rtic" } + +[dependencies.chrono] +default-features = false +version = "0.4.10" diff --git a/qemu/README.md b/qemu/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4f33aa0fce08fef774a212f3158711e302c3c880 --- /dev/null +++ b/qemu/README.md @@ -0,0 +1,3 @@ +# `nrf52` + +> RTFM Async examples on the nRF52840 diff --git a/qemu/build.rs b/qemu/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d3211e856949481835d40840da9f03444748b2d --- /dev/null +++ b/qemu/build.rs @@ -0,0 +1,12 @@ +use std::{env, error::Error, fs, path::PathBuf}; + +fn main() -> Result<(), Box<dyn Error>> { + let out_dir = &PathBuf::from(env::var("OUT_DIR")?); + + // place the linker script somewhere the linker can find it + let filename = "memory.x"; + fs::copy(filename, out_dir.join(filename))?; + println!("cargo:rustc-link-search={}", out_dir.display()); + + Ok(()) +} diff --git a/qemu/examples/1-rtfm.rs b/qemu/examples/1-rtfm.rs new file mode 100644 index 0000000000000000000000000000000000000000..5cf4e9791a079be215af79bdb3d8da20f890c0d5 --- /dev/null +++ b/qemu/examples/1-rtfm.rs @@ -0,0 +1,75 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::{task, unsync::Mutex}; +use cortex_m::asm; +use cortex_m_semihosting::{debug, hprintln}; +// use pac::Interrupt::TIMER0; +use panic_halt as _; // panic handler +use rtic::app; + +#[rtic::app(device = lm3s6965)] +mod app { + #[resources] + struct Resources { + #[init(9)] + x: u32, + } + + #[idle(resources = [x])] + fn idle(mut cx: idle::Context) -> ! { + static mut X: Mutex<i64> = Mutex::new(0); + let c: &'static _ = X; + + cx.resources.x.lock(|x| { + hprintln!("x {}", x).ok(); + }); + + task::spawn(async move { + loop { + hprintln!("A: before lock").ok(); + + let mut lock = c.lock().await; + + hprintln!("A: before write {}", *lock).ok(); + *lock += 1; + drop(lock); + + hprintln!("A: after releasing the lock").ok(); + + hprintln!("A: manual yield").ok(); + task::r#yield().await; + } + }); + + task::block_on(async { + loop { + hprintln!("B: before lock").ok(); + + // cannot immediately make progress; context switch to A + let mut lock = c.lock().await; + + hprintln!("B: {}", *lock).ok(); + *lock += 1; + + if *lock > 10 { + debug::exit(debug::EXIT_SUCCESS); + } else { + drop(lock); + hprintln!("B: yield").ok(); + task::r#yield().await; + } + } + }); + + loop { + continue; + } + } + #[task(binds = UART0, resources = [x])] + fn rt_task(cx: rt_task::Context) { + hprintln!("x {}", cx.resources.x).ok(); + } +} diff --git a/qemu/examples/1-yield.rs b/qemu/examples/1-yield.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f2f53412f45f3c5536a2bf4b8761883c22c5bb5 --- /dev/null +++ b/qemu/examples/1-yield.rs @@ -0,0 +1,50 @@ +//! Yielding from a task +//! +//! Expected output: +//! +//! ``` +//! B: yield +//! A: yield +//! B: yield +//! A: yield +//! DONE +//! ``` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::task; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52 as _; // memory layout +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + // task A + task::spawn(async { + loop { + hprintln!("A: yield").ok(); + // context switch to B + task::r#yield().await; + } + }); + + // task B + task::block_on(async { + loop { + hprintln!("B1: yield").ok(); + + // context switch to A + task::r#yield().await; + + hprintln!("B2: yield").ok(); + + task::r#yield().await; + + hprintln!("DONE").ok(); + } + }) +} diff --git a/qemu/examples/2-rtfm.rs b/qemu/examples/2-rtfm.rs new file mode 100644 index 0000000000000000000000000000000000000000..339ab86c993af2abd5476b27a8dbe9323748d8cc --- /dev/null +++ b/qemu/examples/2-rtfm.rs @@ -0,0 +1,97 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::{task, unsync::Mutex}; +use cortex_m::asm; +use cortex_m_semihosting::hprintln; +use pac::Interrupt::TIMER0; +use panic_udf as _; // panic handler +use rtfm::app; + +#[app(device = pac)] +const APP: () = { + struct Resources { + #[init(9)] + rtfm_x:u64, + } + + #[idle(resources = [rtfm_x])] + fn idle(mut cx: idle::Context) -> ! { + static mut X: Mutex<i64> = Mutex::new(0); + let c: &'static _ = X; + // static mut RT_X : impl rtfm::Mutex = &mut cx.resources.x; + + // rt_x.lock(|x| { + // hprintln!("x {}", x); + // }); + + cx.resources.rtfm_x.lock(|x| { + hprintln!("x {}", x); + }); + + // let cc:&'static _ = &cx.resources.x; + + task::spawn(async { + loop { + hprintln!("A: before lock").ok(); + + // let mut lock = c.lock().await; + + // hprintln!("A: before write {}", *lock).ok(); + // *lock += 1; + // drop(lock); + + hprintln!("A: after releasing the lock").ok(); + + hprintln!("A: manual yield").ok(); + task::r#yield().await; + // cc.lock(|x| { + // hprintln!("x {}", x); + // }); + + cx.resources.rtfm_x.lock(|x| { + hprintln!("x {}", x); + }); + + } + }); + + task::block_on(async { + loop { + hprintln!("B: before lock").ok(); + + // cannot immediately make progress; context switch to A + let mut lock = c.lock().await; + + hprintln!("B: {}", *lock).ok(); + *lock += 1; + + + if *lock > 10 { + loop { + asm::bkpt(); + } + } else { + drop(lock); + hprintln!("B: yield").ok(); + task::r#yield().await; + } + + cx.resources.rtfm_x.lock(|x| { + hprintln!("x {}", x); + }); + } + }); + + loop { + continue; + } + } + + #[task(priority = 2, binds = TIMER0, resources = [rtfm_x])] + fn rt_task(cx:rt_task::Context) { + hprintln!("x {}", cx.resources.rtfm_x); + } +}; diff --git a/qemu/examples/2-share.rs b/qemu/examples/2-share.rs new file mode 100644 index 0000000000000000000000000000000000000000..78afaa4d186a528f2b31578a93193970b0daf043 --- /dev/null +++ b/qemu/examples/2-share.rs @@ -0,0 +1,73 @@ +//! Sharing state between tasks using `Cell` and `RefCell` +//! +//! ``` +//! B: initialize x +//! B: post a message through y +//! B: yield +//! A: x=42 +//! A: received a message through y: 42 +//! A: yield +//! DONE +//! ``` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::cell::{Cell, RefCell}; + +use async_cortex_m::task; +use cortex_m::asm; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52 as _; // memory layout +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + static mut X: Cell<i64> = Cell::new(0); + // not-async-aware, one-shot channel + static mut Y: RefCell<Option<i32>> = RefCell::new(None); + + // only references with `'static` lifetimes can be sent to `spawn`-ed tasks + // NOTE we coerce these to a shared (`&-`) reference to avoid the `move` blocks taking ownership + // of the owning static (`&'static mut`) reference + let x: &'static Cell<_> = X; + let y: &'static RefCell<_> = Y; + + // task A + task::spawn(async move { + hprintln!("A: x={}", x.get()).ok(); + + if let Some(msg) = y.borrow_mut().take() { + hprintln!("A: received a message through y: {}", msg).ok(); + } + + loop { + hprintln!("A: yield").ok(); + // context switch to B + task::r#yield().await; + } + }); + + // task B + task::block_on(async { + hprintln!("B: initialize x").ok(); + x.set(42); + + hprintln!("B: post a message through y").ok(); + *y.borrow_mut() = Some(42); + + hprintln!("B: yield").ok(); + + // context switch to A + task::r#yield().await; + + hprintln!("DONE").ok(); + + loop { + asm::bkpt(); + } + }) +} diff --git a/qemu/examples/3-mutex.rs b/qemu/examples/3-mutex.rs new file mode 100644 index 0000000000000000000000000000000000000000..f437572ad40205b5c91c93d5935cc1232f705a96 --- /dev/null +++ b/qemu/examples/3-mutex.rs @@ -0,0 +1,84 @@ +//! Mutex shared between tasks +//! +//! "When to use `Mutex` instead of a `RefCell`?" Both abstractions will give you an exclusive +//! (`&mut-`) reference to the data and that reference can survive across `yield`s (either explicit +//! , i.e. `task::yield`, or implicit, `.await`). +//! +//! The difference between the two is clear when contention occurs. If two or more tasks contend for +//! a `RefCell`, as in they both call `borrow_mut` on it, you'll get a panic. On the other hand, if +//! you use a `Mutex` in a similar scenario, i.e. both tasks call `lock` on it, then one of them +//! will "asynchronous" wait for (i.e. not resume until) the other task to release (releases) the +//! lock. +//! +//! Expected output: +//! +//! ``` +//! B: before recv +//! A: before send +//! A: after send +//! A: yield +//! B: 42 +//! DONE +//! ``` +//! +//! Try to replace the `Mutex` with `RefCell` and re-run the example + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::{task, unsync::Mutex}; +use cortex_m::asm; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52 as _; // memory layout +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + static mut X: Mutex<i64> = Mutex::new(0); + + task::block_on(async { + loop { + hprintln!("A: before lock").ok(); + + let mut lock = X.lock().await; + + hprintln!("A: before write {}", *lock).ok(); + *lock += 1; + drop(lock); + + hprintln!("A: after releasing the lock").ok(); + + hprintln!("A: manual yield").ok(); + task::r#yield().await; + } + }); + + task::block_on(async { + loop { + hprintln!("B: before lock").ok(); + + // cannot immediately make progress; context switch to A + let mut lock = X.lock().await; + + hprintln!("B: {}", *lock).ok(); + *lock += 1; + + if *lock > 10 { + loop { + asm::bkpt(); + } + } else { + drop(lock); + hprintln!("B: yield").ok(); + task::r#yield().await; + } + } + }); + + loop { + continue; + } +} diff --git a/qemu/examples/3-mutex2.rs b/qemu/examples/3-mutex2.rs new file mode 100644 index 0000000000000000000000000000000000000000..429cc1ae32773a00d1c19f936d2fdd252edc4eb7 --- /dev/null +++ b/qemu/examples/3-mutex2.rs @@ -0,0 +1,59 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::{task, unsync::Mutex}; +use cortex_m::asm; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52 as _; // memory layout +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + static mut X: Mutex<i64> = Mutex::new(0); + let c: &'static _ = X; + task::spawn(async move { + loop { + hprintln!("A: before lock").ok(); + + let mut lock = c.lock().await; + + hprintln!("A: before write {}", *lock).ok(); + *lock += 1; + drop(lock); + + hprintln!("A: after releasing the lock").ok(); + + hprintln!("A: manual yield").ok(); + task::r#yield().await; + } + }); + + task::block_on(async { + loop { + hprintln!("B: before lock").ok(); + + // cannot immediately make progress; context switch to A + let mut lock = c.lock().await; + + hprintln!("B: {}", *lock).ok(); + *lock += 1; + + if *lock > 10 { + loop { + asm::bkpt(); + } + } else { + drop(lock); + hprintln!("B: yield").ok(); + task::r#yield().await; + } + } + }); + + loop { + continue; + } +} diff --git a/qemu/examples/4-channel.rs b/qemu/examples/4-channel.rs new file mode 100644 index 0000000000000000000000000000000000000000..acac752add554fcfba92973760dd6eca371b872d --- /dev/null +++ b/qemu/examples/4-channel.rs @@ -0,0 +1,61 @@ +//! Message passing between tasks using a MPMC channel +//! +//! Expected output: +//! +//! ``` +//! B: before recv +//! A: before send +//! A: after send +//! A: yield +//! B: 42 +//! DONE +//! ``` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use async_cortex_m::{task, unsync::Channel}; +use cortex_m::asm; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52 as _; // memory layout +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + static mut C: Channel<i32> = Channel::new(); + + // coerce to a shared (`&-`) reference to avoid _one_ of the `move` blocks taking ownership of + // the owning static (`&'static mut`) reference + let c: &'static _ = C; + + task::spawn(async move { + hprintln!("A: before send").ok(); + + c.send(42).await; + + hprintln!("A: after send").ok(); + + loop { + hprintln!("A: yield").ok(); + task::r#yield().await; + } + }); + + task::block_on(async move { + hprintln!("B: before recv").ok(); + + // cannot immediately make progress; context switch to A + let msg = c.recv().await; + + hprintln!("B: {}", msg).ok(); + + hprintln!("DONE").ok(); + + loop { + asm::bkpt(); + } + }) +} diff --git a/qemu/examples/5-heartbeat.rs b/qemu/examples/5-heartbeat.rs new file mode 100644 index 0000000000000000000000000000000000000000..85ced4829985c6cf03768578b038b041c417215f --- /dev/null +++ b/qemu/examples/5-heartbeat.rs @@ -0,0 +1,42 @@ +//! Yielding from a task +//! +//! Expected output: +//! +//! ``` +//! B: yield +//! A: yield +//! B: yield +//! A: yield +//! DONE +//! ``` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::time::Duration; + +use async_cortex_m::task; +use cortex_m_rt::entry; +use nrf52::{led::Red, timer::Timer}; +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + let mut timer = Timer::take(); + + let dur = Duration::from_millis(100); + task::block_on(async { + loop { + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(dur).await; + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(12 * dur).await; + } + }) +} diff --git a/qemu/examples/6-hello.rs b/qemu/examples/6-hello.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d4df083641b4620a1f56409131701293d13f9a4 --- /dev/null +++ b/qemu/examples/6-hello.rs @@ -0,0 +1,42 @@ +//! Spam "Hello, world!" over the serial line (@ 9600 bauds) +//! +//! TXD = P0.06 +//! RXD = P0.08 + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::time::Duration; + +use async_cortex_m::task; +use cortex_m_rt::entry; +use nrf52::{led::Red, serial, timer::Timer}; +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + // heartbeat task + let mut timer = Timer::take(); + let dur = Duration::from_millis(100); + task::spawn(async move { + loop { + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(dur).await; + // Red.on(); + // timer.wait(dur).await; + // Red.off(); + timer.wait(12 * dur).await; + } + }); + + let (mut tx, _rx) = serial::take(); + task::block_on(async { + loop { + tx.write(b"Hello, world!\n\r").await; + } + }) +} diff --git a/qemu/examples/7-echo.rs b/qemu/examples/7-echo.rs new file mode 100644 index 0000000000000000000000000000000000000000..085a6ebb1aa22cf2a6d3a95c66c428e3d1d6e81e --- /dev/null +++ b/qemu/examples/7-echo.rs @@ -0,0 +1,44 @@ +//! Echo back data received over the serial line (@ 9600 bauds) +//! +//! TXD = P0.06 +//! RXD = P0.08 + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::time::Duration; + +use async_cortex_m::task; +use cortex_m_rt::entry; +use nrf52::{led::Red, serial, timer::Timer}; +use panic_udf as _; // panic handler + +#[entry] +fn main() -> ! { + // heartbeat task + let mut timer = Timer::take(); + let dur = Duration::from_millis(100); + task::spawn(async move { + loop { + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(dur).await; + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(12 * dur).await; + } + }); + + let (mut tx, mut rx) = serial::take(); + task::block_on(async { + let mut buf = [0; 16]; + loop { + rx.read(&mut buf).await; + tx.write(&buf).await; + } + }) +} diff --git a/qemu/examples/8-sensor.rs b/qemu/examples/8-sensor.rs new file mode 100644 index 0000000000000000000000000000000000000000..aaca911f46f5333b3c2a175584c0a0a1a189ee5b --- /dev/null +++ b/qemu/examples/8-sensor.rs @@ -0,0 +1,114 @@ +//! Print sensor data on demand + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{cell::Cell, fmt::Write as _, time::Duration}; + +use async_cortex_m::{task, unsync::Mutex}; +use cortex_m_rt::entry; +use heapless::{consts, String}; +use nrf52::{led::Red, scd30::Scd30, serial, timer::Timer, twim::Twim}; +use panic_udf as _; // panic handler + +#[derive(Clone, Copy)] +enum State { + NotReady, + Ready, + Error, +} + +#[entry] +fn main() -> ! { + // shared state + static mut STATE: Cell<State> = Cell::new(State::NotReady); + // range: 0 - 40,000 ppm + static mut CO2: Cell<u16> = Cell::new(0); + // range: 0 - 100 % + static mut RH: Cell<u8> = Cell::new(0); + // range: -40 - 70 C + static mut T: Cell<i8> = Cell::new(0); + static mut M: Option<Mutex<Twim>> = None; + + let co2: &'static _ = CO2; + let state: &'static _ = STATE; + let rh: &'static _ = RH; + let t: &'static _ = T; + + // heartbeat task + let mut timer = Timer::take(); + let dur = Duration::from_millis(100); + task::spawn(async move { + loop { + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(dur).await; + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(12 * dur).await; + } + }); + + // task to print sensor info on demand + let (mut tx, mut rx) = serial::take(); + task::spawn(async move { + let mut tx_buf = String::<consts::U32>::new(); + let mut rx_buf = [0]; + + loop { + rx.read(&mut rx_buf).await; + + // carriage return; + if rx_buf[0] == 13 { + match state.get() { + State::Error => { + tx.write(b"fatal error: I2C error\n").await; + + loop { + task::r#yield().await; + } + } + + State::NotReady => { + tx.write(b"sensor not ready; try again later\n").await; + } + + State::Ready => { + let co2 = co2.get(); + let t = t.get(); + let rh = rh.get(); + + tx_buf.clear(); + // will not fail; the buffer is big enough + let _ = writeln!(&mut tx_buf, "CO2: {}ppm\nT: {}C\nRH: {}%", co2, t, rh); + tx.write(tx_buf.as_bytes()).await; + } + } + } + } + }); + + // task to continuously poll the sensor + let twim = M.get_or_insert(Mutex::new(Twim::take())); + let mut scd30 = Scd30::new(twim); + task::block_on(async { + loop { + if let Ok(m) = scd30.get_measurement().await { + co2.set(m.co2 as u16); + rh.set(m.rh as u8); + t.set(m.t as i8); + state.set(State::Ready); + } else { + state.set(State::Error); + + loop { + task::r#yield().await; + } + } + } + }) +} diff --git a/qemu/examples/9-clock.rs b/qemu/examples/9-clock.rs new file mode 100644 index 0000000000000000000000000000000000000000..002d597a185745944f20bcc3659ee6cd9c583342 --- /dev/null +++ b/qemu/examples/9-clock.rs @@ -0,0 +1,262 @@ +//! Interactive serial console with access to the clock and gas sensor +//! +//! Example interaction (with local echo enabled): +//! +//! ``` +//! > help +//! Commands: +//! help displays this text +//! date display the current date and time +//! sensors displays the gas sensor data +//! set date %Y-%m-%d changes the date +//! set time %H:%M:%S changes the time +//! > sensors +//! CO2: 652ppm +//! T: 26C +//! RH: 23% +//! > set time 18:49:30 +//! > date +//! 2020-02-28 18:49:32 +//! ``` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{ + cell::Cell, + fmt::Write as _, + str::{self, FromStr}, + time::Duration, +}; + +use async_cortex_m::{task, unsync::Mutex}; +use chrono::{Datelike as _, NaiveDate, NaiveTime}; +use cortex_m_rt::entry; +use heapless::{consts, String, Vec}; +use nrf52::{ + ds3231::{self, Ds3231}, + led::Red, + scd30::Scd30, + serial, + timer::Timer, + twim::Twim, +}; +use panic_udf as _; // panic handler + +#[derive(Clone, Copy)] +enum SensorState { + NotReady, + Ready, + Error, +} + +#[entry] +fn main() -> ! { + // shared state + static mut STATE: Cell<SensorState> = Cell::new(SensorState::NotReady); + // range: 0 - 40,000 ppm + static mut CO2: Cell<u16> = Cell::new(0); + // range: 0 - 100 % + static mut RH: Cell<u8> = Cell::new(0); + // range: -40 - 70 C + static mut T: Cell<i8> = Cell::new(0); + static mut M: Option<Mutex<Twim>> = None; + + let co2: &'static _ = CO2; + let state: &'static _ = STATE; + let rh: &'static _ = RH; + let t: &'static _ = T; + + // heartbeat task + let mut timer = Timer::take(); + let dur = Duration::from_millis(100); + task::spawn(async move { + loop { + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(dur).await; + Red.on(); + timer.wait(dur).await; + Red.off(); + timer.wait(12 * dur).await; + } + }); + + let twim = M.get_or_insert(Mutex::new(Twim::take())); + let mut scd30 = Scd30::new(twim); + task::spawn(async move { + loop { + // TODO instead of continuously polling the sensor we should only read it out when new + // data is ready (there's a pin that signals that), roughly every 2 seconds. + // Alternatively, we could send this task to sleep for 2 seconds after new data is read + let res = scd30.get_measurement().await; + + if let Ok(m) = res { + co2.set(m.co2 as u16); + rh.set(m.rh as u8); + t.set(m.t as i8); + state.set(SensorState::Ready); + + // adds fairness; avoids starving the task below + task::r#yield().await; + } else { + state.set(SensorState::Error); + + loop { + task::r#yield().await; + } + } + } + }); + + let (mut tx, mut rx) = serial::take(); + let mut ds3231 = Ds3231::new(twim); + task::block_on(async { + let mut input = Vec::<u8, consts::U64>::new(); + let mut tx_buf = String::<consts::U32>::new(); + + 'prompt: loop { + tx.write(b"> ").await; + + input.clear(); + loop { + // not the most elegant way to have a responsive input + // Ideally, we want to use a large `rx_buf` and instead read its contents only when + // there has been no new data on the bus for a while + let mut rx_buf = [0]; + rx.read(&mut rx_buf).await; + + if input.push(rx_buf[0]).is_err() { + tx.write(b"input buffer is full\n").await; + continue 'prompt; + } + + if let Ok(s) = str::from_utf8(&input) { + // complete command + if s.ends_with('\r') { + if let Ok(cmd) = s[..s.len() - 1].parse::<Command>() { + match cmd { + Command::Date => { + match ds3231.get_datetime().await { + Ok(datetime) => { + let mut s = String::<consts::U32>::new(); + // will not fail; the buffer is big enough + let _ = writeln!(&mut s, "{}", datetime); + tx.write(s.as_bytes()).await; + } + + Err(ds3231::Error::Twim(..)) => { + tx.write(b"error communicating with the RTC\n").await; + } + + Err(ds3231::Error::InvalidDate) => { + tx.write(b"invalid date stored in the RTC\n").await; + } + } + } + + Command::SetDate(date) => { + // in `Command::parse_str` we validate the input date so no + // `InvalidDate` error should be raised here + if ds3231.set_date(date).await.is_err() { + tx.write(b"error communicating with the RTC\n").await; + } + } + + Command::SetTime(time) => { + if ds3231.set_time(time).await.is_err() { + tx.write(b"error communicating with the RTC\n").await; + } + } + + Command::Sensors => { + tx_buf.clear(); + + // will not fail; the buffer is big enough + let _ = writeln!( + &mut tx_buf, + "CO2: {}ppm\nT: {}C\nRH: {}%", + co2.get(), + t.get(), + rh.get() + ); + tx.write(tx_buf.as_bytes()).await; + } + + Command::Help => { + tx.write( + b"Commands: +help displays this text +date display the current date and time +sensors displays the gas sensor data +set date %Y-%m-%d changes the date +set time %H:%M:%S changes the time +", + ) + .await; + } + } + } else { + tx.write(b"invalid command; try `help`\n").await; + } + + // new prompt; clear command buffer + continue 'prompt; + } + } + } + } + }) +} + +enum Command { + Date, + Help, + Sensors, + SetDate(NaiveDate), + SetTime(NaiveTime), +} + +impl FromStr for Command { + type Err = Error; + + fn from_str(mut s: &str) -> Result<Command, Error> { + const CMD_DATE: &str = "date"; + const CMD_HELP: &str = "help"; + const CMD_SENSORS: &str = "sensors"; + const CMD_SET_DATE: &str = "set date "; + const CMD_SET_TIME: &str = "set time "; + + s = s.trim(); + + Ok(if s == CMD_DATE { + Command::Date + } else if s == CMD_HELP { + Command::Help + } else if s == CMD_SENSORS { + Command::Sensors + } else if s.starts_with(CMD_SET_DATE) { + let date = NaiveDate::parse_from_str(&s[CMD_SET_DATE.len()..], "%Y-%m-%d") + .map_err(|_| Error)?; + let year = date.year(); + // the RTC can only handle a span of roughly 200 years + if year < 2000 || year > 2199 { + return Err(Error); + } + + Command::SetDate(date) + } else if s.starts_with(CMD_SET_TIME) { + let time = NaiveTime::parse_from_str(&s[CMD_SET_TIME.len()..], "%H:%M:%S") + .map_err(|_| Error)?; + + Command::SetTime(time) + } else { + return Err(Error); + }) + } +} + +struct Error; diff --git a/qemu/memory.x b/qemu/memory.x new file mode 100644 index 0000000000000000000000000000000000000000..dbcd285013f39ae817a158482e2b4c0d5a4f2e3b --- /dev/null +++ b/qemu/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1M + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} diff --git a/qemu/openocd.cfg b/qemu/openocd.cfg new file mode 100644 index 0000000000000000000000000000000000000000..080b5b48c55cf5e6214b07ab5b798e5c41616330 --- /dev/null +++ b/qemu/openocd.cfg @@ -0,0 +1,6 @@ +#source [find interface/cmsis-dap.cfg] +source [find interface/jlink.cfg] + +transport select swd + +source [find target/nrf52.cfg] \ No newline at end of file diff --git a/qemu/openocd.gdb b/qemu/openocd.gdb new file mode 100644 index 0000000000000000000000000000000000000000..d42cf8ce519bcb0fd952bff86cde0140d5a72997 --- /dev/null +++ b/qemu/openocd.gdb @@ -0,0 +1,22 @@ +target extended-remote :3333 + +# print demangled symbols +set print asm-demangle on + +# set backtrace limit to not have infinite backtrace loops +set backtrace limit 32 + +# detect unhandled exceptions, hard faults and panics +break DefaultHandler +break HardFault +break rust_begin_unwind + +# *try* to stop at the user entry point (it might be gone due to inlining) +break main + +monitor arm semihosting enable + +load + +# start the process but immediately halt the processor +stepi \ No newline at end of file