summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--example-rtic/Cargo.toml27
-rw-r--r--example-rtic/build.rs31
-rw-r--r--example-rtic/memory.x34
-rw-r--r--example-rtic/src/example_common.rs68
-rw-r--r--example-rtic/src/main.rs134
6 files changed, 295 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index cf53312..8707c83 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ members = [
"async-flash",
"example",
+ "example-rtic",
]
exclude = [
diff --git a/example-rtic/Cargo.toml b/example-rtic/Cargo.toml
new file mode 100644
index 0000000..31ffd5b
--- /dev/null
+++ b/example-rtic/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
+edition = "2018"
+name = "nrf-softdevice-example-rtic"
+version = "0.1.0"
+
+[features]
+default = [ "defmt-default" ]
+defmt-default = []
+defmt-trace = []
+defmt-debug = []
+defmt-info = []
+defmt-warn = []
+defmt-error = []
+
+[dependencies]
+cortex-m = { version = "0.6.3" }
+cortex-m-rtic = "0.5.5"
+defmt = "0.1.0"
+defmt-rtt = "0.1.0"
+panic-probe = "0.1.0"
+nrf52840-hal = { version = "0.11.0" }
+nrf-softdevice = { version = "0.1.0", path = "../nrf-softdevice", features = ["defmt-trace", "nrf52840", "s140", "ble-peripheral", "ble-central"] }
+nrf-softdevice-s140 = { version = "0.1.1", path = "../nrf-softdevice-s140" }
+static-executor = { version = "0.1.0", features=["defmt"]}
+static-executor-cortex-m = { version = "0.1.0" }
+futures = { version = "0.3.5", default-features = false }
diff --git a/example-rtic/build.rs b/example-rtic/build.rs
new file mode 100644
index 0000000..d534cc3
--- /dev/null
+++ b/example-rtic/build.rs
@@ -0,0 +1,31 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+}
diff --git a/example-rtic/memory.x b/example-rtic/memory.x
new file mode 100644
index 0000000..f98ee4a
--- /dev/null
+++ b/example-rtic/memory.x
@@ -0,0 +1,34 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ /* TODO Adjust these memory regions to match your device memory layout */
+ /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */
+ FLASH : ORIGIN = 0x00027000, LENGTH = 256K
+ RAM : ORIGIN = 0x20020000, LENGTH = 128K
+}
+
+/* This is where the call stack will be allocated. */
+/* The stack is of the full descending type. */
+/* You may want to use this variable to locate the call stack and static
+ variables in different memory regions. Below is shown the default value */
+/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
+
+/* You can use this symbol to customize the location of the .text section */
+/* If omitted the .text section will be placed right after the .vector_table
+ section */
+/* This is required only on microcontrollers that store some configuration right
+ after the vector table */
+/* _stext = ORIGIN(FLASH) + 0x400; */
+
+/* Example of putting non-initialized variables into custom RAM locations. */
+/* This assumes you have defined a region RAM2 above, and in the Rust
+ sources added the attribute `#[link_section = ".ram2bss"]` to the data
+ you want to place there. */
+/* Note that the section will not be zero-initialized by the runtime! */
+/* SECTIONS {
+ .ram2bss (NOLOAD) : ALIGN(4) {
+ *(.ram2bss);
+ . = ALIGN(4);
+ } > RAM2
+ } INSERT AFTER .bss;
+*/
diff --git a/example-rtic/src/example_common.rs b/example-rtic/src/example_common.rs
new file mode 100644
index 0000000..e991915
--- /dev/null
+++ b/example-rtic/src/example_common.rs
@@ -0,0 +1,68 @@
+#![macro_use]
+
+use defmt_rtt as _; // global logger
+use nrf52840_hal as _;
+use panic_probe as _;
+use static_executor_cortex_m as _;
+
+pub use defmt::{info, intern};
+
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+#[defmt::timestamp]
+fn timestamp() -> u64 {
+ static COUNT: AtomicUsize = AtomicUsize::new(0);
+ // NOTE(no-CAS) `timestamps` runs with interrupts disabled
+ let n = COUNT.load(Ordering::Relaxed);
+ COUNT.store(n + 1, Ordering::Relaxed);
+ n as u64
+}
+
+macro_rules! depanic {
+ ($( $i:expr ),*) => {
+ {
+ defmt::error!($( $i ),*);
+ panic!();
+ }
+ }
+}
+
+pub trait Dewrap<T> {
+ /// dewrap = defmt unwrap
+ fn dewrap(self) -> T;
+
+ /// dexpect = defmt expect
+ fn dexpect<M: defmt::Format>(self, msg: M) -> T;
+}
+
+impl<T> Dewrap<T> for Option<T> {
+ fn dewrap(self) -> T {
+ match self {
+ Some(t) => t,
+ None => depanic!("Dewrap failed: enum is none"),
+ }
+ }
+
+ fn dexpect<M: defmt::Format>(self, msg: M) -> T {
+ match self {
+ Some(t) => t,
+ None => depanic!("Unexpected None: {:?}", msg),
+ }
+ }
+}
+
+impl<T, E: defmt::Format> Dewrap<T> for Result<T, E> {
+ fn dewrap(self) -> T {
+ match self {
+ Ok(t) => t,
+ Err(e) => depanic!("Dewrap failed: {:?}", e),
+ }
+ }
+
+ fn dexpect<M: defmt::Format>(self, msg: M) -> T {
+ match self {
+ Ok(t) => t,
+ Err(e) => depanic!("Unexpected error: {:?}: {:?}", msg, e),
+ }
+ }
+}
diff --git a/example-rtic/src/main.rs b/example-rtic/src/main.rs
new file mode 100644
index 0000000..4eec5a1
--- /dev/null
+++ b/example-rtic/src/main.rs
@@ -0,0 +1,134 @@
+#![no_main]
+#![no_std]
+#![feature(type_alias_impl_trait)]
+
+mod example_common;
+use example_common::*;
+
+use core::mem;
+use nrf52840_hal::pac::TIMER1;
+use nrf52840_hal::prelude::*;
+use nrf52840_hal::timer::{Periodic, Timer};
+use nrf_softdevice::ble::peripheral;
+use nrf_softdevice::{raw, Softdevice};
+use rtic::app;
+
+// This example showcases how to use nrf-softdevice inside RTIC.
+//
+// It mixes RTIC's real-time interrupt-based multitasking with
+// static-executor's cooperative async/await multitasking.
+//
+// static-executor is run in RTIC's idle task, at lowest priority, so all RTIC
+// tasks will preempt async tasks if needed.
+//
+// Note that this is not fully safe: you must not use the softdevice's reserved
+// priorities for RTIC tasks. There is no compile-time checking for that for now.
+
+#[static_executor::task]
+async fn softdevice_task(sd: &'static Softdevice) {
+ sd.run().await;
+}
+
+#[static_executor::task]
+async fn bluetooth_task(sd: &'static Softdevice) {
+ #[rustfmt::skip]
+ let adv_data = &[
+ 0x02, 0x01, raw::BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE as u8,
+ 0x03, 0x03, 0x09, 0x18,
+ 0x0a, 0x09, b'H', b'e', b'l', b'l', b'o', b'R', b'T', b'I', b'C',
+ ];
+ #[rustfmt::skip]
+ let scan_data = &[
+ 0x03, 0x03, 0x09, 0x18,
+ ];
+
+ loop {
+ let _conn = peripheral::advertise(
+ sd,
+ peripheral::ConnectableAdvertisement::ScannableUndirected {
+ adv_data,
+ scan_data,
+ },
+ )
+ .await
+ .dewrap();
+
+ info!("advertising done!");
+
+ // conn will now get disconnected because it's dropped
+ }
+}
+
+#[app(device = nrf52840_hal::pac, peripherals = true)]
+const APP: () = {
+ struct Resources {
+ timer: Timer<TIMER1, Periodic>,
+ }
+
+ #[init()]
+ fn init(cx: init::Context) -> init::LateResources {
+ info!("init");
+
+ let mut timer = Timer::new(cx.device.TIMER1);
+ timer.enable_interrupt();
+ let mut timer = timer.into_periodic();
+ timer.start(1_000_000u32); // 1Mhz, so once per second
+
+ init::LateResources { timer }
+ }
+
+ #[idle]
+ fn idle(_: idle::Context) -> ! {
+ let config = nrf_softdevice::Config {
+ clock: Some(raw::nrf_clock_lf_cfg_t {
+ source: raw::NRF_CLOCK_LF_SRC_XTAL as u8,
+ rc_ctiv: 0,
+ rc_temp_ctiv: 0,
+ accuracy: 7,
+ }),
+ conn_gap: Some(raw::ble_gap_conn_cfg_t {
+ conn_count: 6,
+ event_length: 6,
+ }),
+ conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 128 }),
+ gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t {
+ attr_tab_size: 32768,
+ }),
+ gap_role_count: Some(raw::ble_gap_cfg_role_count_t {
+ adv_set_count: 1,
+ periph_role_count: 3,
+ central_role_count: 3,
+ central_sec_count: 0,
+ _bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0),
+ }),
+ gap_device_name: Some(raw::ble_gap_cfg_device_name_t {
+ p_value: b"HelloRTIC" as *const u8 as _,
+ current_len: 9,
+ max_len: 9,
+ write_perm: unsafe { mem::zeroed() },
+ _bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(
+ raw::BLE_GATTS_VLOC_STACK as u8,
+ ),
+ }),
+ ..Default::default()
+ };
+
+ // Softdevice enable must not be done in RTIC init
+ // because RTIC runs init with interrupts disabled, and the
+ // softdevice crashes if it's enabled with interrupts disabled.
+ let sd = Softdevice::enable(&config);
+
+ unsafe {
+ softdevice_task.spawn(sd).dewrap();
+ bluetooth_task.spawn(sd).dewrap();
+
+ static_executor::run();
+ }
+ }
+
+ #[task(binds = TIMER1, resources = [timer], priority = 1)]
+ fn exec(cx: exec::Context) {
+ cx.resources.timer.wait().unwrap();
+ info!("tick");
+ }
+};