safe example with spin
We would like to add an example using spin to make sure the heap is initialized once :).
@Dirbaio what do you propose instead for ensuring the heap is only initialised once?
Using
spinis always a bug in bare-metal.For example, if you use it to sync data between main and interrupt (as a substitute of
std::sync) you WILL get deadlocks if, while main holds the lock, an interrupt fires. The interrupt will spin waiting on the already-acquired lock, which should be released by main, but main will never contine running until the interrupt returns.Same for all the other sync primitives
spinhas, includingspin::Once.We really shouldn't promote it in embedded contexts, or people will see "ooh, nostd mutex, shiny!" and use it and shoot themselves in the foot.
I agree. Spinning is an easy way to deadlock when there are interrupts handled. It is important to know if you are in cooperative or preemptive execution.
I use the heap with the nrf-softdevice, spawning cooperative executors from the preemptive executor after the heap is initialized. Just initialize the heap first thing when nothing is spawned that can allocate. Nothing is going to be allocating before you have an uncontested chance to initialize.
const HEAP_SIZE: usize = 192 * 1024;
#[repr(align(1024))]
struct HeapBuffer([u8; HEAP_SIZE]);
static mut HEAP_BUFFER: HeapBuffer = HeapBuffer([0; HEAP_SIZE]);
#[global_allocator]
static HEAP: Heap = Heap::empty();
///
/// SoftDevice gets 0,1,and 4 priorities
/// GPIO and Timer get 2 priorities
/// SAADC gets 3 priority
///
/// SoftDevice uses:
///
/// TIMER0
/// SWI1_EGU1 is priority 6 Radio Notification
/// SWI2_EGU2 is priority 6 SoftDevice Event Notification
/// SWI4_EGU4 is reserved for future use
/// SWI5_EGU5 is priority 4 SoftDevice Processing
/// PPI 20 and up ... and maybe more
/// check at https://infocenter.nordicsemi.com/pdf/S140_SDS_v2.1.pdf
///
///
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
// Configure for low-power SoftDevice operation
let mut config = embassy_nrf::config::Config::default();
config.gpiote_interrupt_priority = Priority::P2;
config.time_interrupt_priority = Priority::P2;
config.lfclk_source = embassy_nrf::config::LfclkSource::ExternalXtal;
let mut p = embassy_nrf::init(config);
// Confirm to the bootloader that we are alive by consuming the watchdog
let wdt_config = wdt::Config::try_new(&p.WDT).unwrap();
let (_wdt, [wdt_handle]) = match Watchdog::try_new(p.WDT, wdt_config) {
Ok(x) => x,
Err(_) => {
// Watchdog already active with the wrong number of handles, waiting for it to timeout...
loop {
cortex_m::asm::wfe();
}
}
};
// Okay, we are booted.
info!("Hello World!");
// Before anything can attempt to allocate from the HEAP, initialize the heap memory
// SAFETY: No tasks that can touch the heap may be spawned before this initialization
unsafe {
HEAP.init(HEAP_BUFFER.0.as_mut_ptr() as usize, HEAP_BUFFER.0.len());
}
// Configuration ...
// High-priority executor: SWI0_EGU0
info!("Starting SWI0 executor and task");
interrupt::SWI0_EGU0.set_priority(Priority::P5);
let swi_spawner = EXECUTOR_SWI0.start(interrupt::SWI0_EGU0);
unwrap!(swi_spawner.spawn(run_watchdog(wdt_handle)));
unwrap!(swi_spawner.spawn(run_ble()));
// Medium-priority executor: SWI3_EGU3
info!("Starting SWI3 executor and task");
interrupt::SWI3_EGU3.set_priority(Priority::P6);
let swi_spawner = EXECUTOR_SWI3.start(interrupt::SWI3_EGU3);
unwrap!(swi_spawner.spawn(run_battery_check(saadc, p.P0_02.degrade())));
unwrap!(swi_spawner.spawn(run_imu(imu_spim, imu_spi_cs, imu_spi_int)));
unwrap!(swi_spawner.spawn(run_pdm(pdm)));
// Run low priority, expensive/blocking work in thread mode
unwrap!(spawner.spawn(run_cpu_imu()));
unwrap!(spawner.spawn(run_cpu_pdm()));
unwrap!(spawner.spawn(run_cpu_heartbeat()));
unwrap!(spawner.spawn(run_cpu_temperature()));
unwrap!(spawner.spawn(run_cpu_nand(nand_spim, nand_spi_cs, nand_wp, nand_hold)));
unwrap!(spawner.spawn(run_dfu()));
...
```