cargo takes development comfort to the next level, and Drone extends its philosophy to the embedded world. If your target chip and debug probe are supported by Drone, you can flash your first program and get a feedback right away! $ drone new --toolchain nightly-2020-04-30 --probe bmp --device stm32f103 --flash-size 128K --ram-size 20K hello-world Created binary (application) `hello-world` package Removed src/main.rs Created src/bin.rs Created src/lib.rs Created src/thr.rs Created src/tasks/mod.rs Created src/tasks/root.rs Patched Cargo.toml Created Drone.toml Created Justfile Created rust-toolchain Created .cargo/config Patched .gitignore $ cd hello-world $ just deps rustup target add thumbv7m-none-eabi info: downloading component 'rust-std' for 'thumbv7m-none-eabi' info: installing component 'rust-std' for 'thumbv7m-none-eabi' $ just flash drone env thumbv7m-none-eabi -- cargo build --features "" --release Updating crates.io index Compiling proc-macro2 v1.0.12 Compiling unicode-xid v0.2.0 Compiling syn v1.0.21 Compiling serde v1.0.110 Compiling hello-world v0.1.0 (/home/valff/hello-world) Finished release [optimized + debuginfo] target(s) in 1m 20s drone flash target/thumbv7m-none-eabi/release/hello-world Start address 0x8000040, load size 6712 Transfer rate: 15 KB/sec, 745 bytes/write. $ just log drone log --reset :0:1 ================================== LOG OUTPUT ================================== Hello, world!

Each interrupt is an executor for async tasks. Thanks to Rust's zero-cost asynchronous programming your interrupt handlers look like a conventional synchronous code, except they don't need separate stacks. pub async fn handler ( input : Input ) -> Result < (), Error > { let Input { mut i2c1 , exti4 , gpio_b , gpio_c } = input ; // APDS-9960 interrupt events stream read from B4 pin. let mut exti4_stream = exti4 .create_saturating_stream (); let mut apds9960 = Apds9960Drv :: init (); let mut gestures = Gestures :: init ( & mut apds9960 , & mut i2c1 ) .await ? ; // Wait for a falling edge trigger on PB4. while exti4_stream .next () .await .is_some () { // Repeat until PB4 is back to the high level. while ! gpio_b .gpio_idr .load () .idr4 () { // Read APDS-9960 FIFO buffer. match gestures .advance ( & mut apds9960 , & mut i2c1 ) .await ? { // Turn on the LED. Some ( Gesture :: Up ) => gpio_c .gpio_bsrr .store (| r | r .set_br13 ()), // Turn off the LED. Some ( Gesture :: Down ) => gpio_c .gpio_bsrr .store (| r | r .set_bs13 ()), _ => {} } } } Ok (()) }

Drone includes a dynamic memory allocator that allows you to use familiar Rust's Box , Vec , String , Arc , and other dynamic types. It is lock-free, deterministic, and has a small code footprint, which makes it useful even on simplest micro-controllers. The cost is that it requires tuning for each particular application. Drone automates this by providing utilities for collecting real-time allocator statistics and calculating an optimized layout configuration. $ just features=heaptrace flash $ just heaptrace ^C $ drone heap generate --pools 8 Block Size | Max Load | Total Allocations ------------+----------+------------------- 1 | 7 | 7 8 | 3 | 748 12 | 11 | 756 28 | 3 | 748 56 | 1 | 87 68 | 1 | 220 128 | 1 | 1 152 | 1 | 1 Maximum heap load: 651 / 1.99% =============================== OPTIMIZED LAYOUT =============================== [heap] size = "32K" pools = [ { block = "4", capacity = 522 }, { block = "12", capacity = 515 }, { block = "28", capacity = 289 }, { block = "56", capacity = 143 }, { block = "68", capacity = 90 }, { block = "152", capacity = 15 }, ] # fragmentation: 36 / 0.11% # hint : replace the existing [heap] section in Drone.toml

Drone provides a rich API for working safely with memory-mapped registers. An application starts with a set of zero-sized unique tokens for all available registers. A token can have move or copy semantics, can be shareable with atomic access or non-shareable with non-atomic access, can be split into individual register field tokens. use crate :: consts ::{ FLASH_WS , PLL_M , PLL_N , PLL_P }; use drone_cortexm :: reg :: prelude :: * ; use drone_stm32_map :: reg ; fn init_sys_clk ( mut rcc_cr : reg :: rcc :: Cr < Urt > , mut rcc_pllcfgr : reg :: rcc :: Pllcfgr < Urt > , mut rcc_cfgr : reg :: rcc :: Cfgr < Urt > , mut flash_acr : reg :: flash :: Acr < Urt > , ) { rcc_cr .modify (| r | r .set_hseon ()); // HSE clock enable while ! rcc_cr .load () .hserdy () {} // HSE clock ready flag rcc_pllcfgr .store (| r | { r // HSE oscillator clock selected as PLL and PLLI2S clock entry .set_pllsrc () // division factor for PLL and PLLI2S input clock .write_pllm ( PLL_M ) // PLL multiplication factor for VCO .write_plln ( PLL_N ) // PLL division factor for main system clock .write_pllp ( PLL_P / 2 - 1 ) }); rcc_cr .modify (| r | r .set_pllon ()); // PLL enable while ! rcc_cr .load () .pllrdy () {} // PLL clock ready flag flash_acr .store (| r | { r // the ratio of the CPU clock period to the Flash memory access time .write_latency ( FLASH_WS ) // data cache is enabled .set_dcen () // instruction cache is enabled .set_icen () // prefetch is enabled .set_prften () }); while flash_acr .load () .latency () != FLASH_WS {} rcc_cfgr .store (| r | { r // PLL selected as system clock .write_sw ( 0b10 ) // system clock not divided .write_hpre ( 0b0000 ) // APB1 = AHB / 2 .write_ppre1 ( 0b100 ) // APB2 = AHB / 1 .write_ppre2 ( 0b000 ) }); while rcc_cfgr .load () .sws () != 0b10 {} // PLL used as the system clock }

Drone uses interrupt-based preemptive priority scheduling, where tasks with same priorities are executed cooperatively. An application has a predefined number of threads corresponding to hardware interrupts, but each thread can run dynamic number of fibers. use crate :: thr ; use drone_cortexm ::{ fib , reg :: prelude :: * , thr :: prelude :: * }; use drone_stm32_map :: reg ; async fn enable_hse_clock ( rcc_cir : reg :: rcc :: Cir < Srt > , rcc_cr : reg :: rcc :: Cr < Srt > , thr_rcc : thr :: Rcc , ) { // We need to move ownership of `hserdyc` and `hserdyf` into the following // fiber. let reg :: rcc :: Cir { hserdyc , hserdyf , .. } = rcc_cir ; // Attach a listener that will notify us when RCC_CIR_HSERDYF is asserted. let hserdy = thr_rcc .add_future ( fib :: new_fn ( move || { if hserdyf .read_bit () { hserdyc .set_bit (); fib :: Complete (()) } else { fib :: Yielded (()) } })); // Enable the HSE clock. rcc_cr .modify (| r | r .set_hseon ()); // Sleep until RCC_CIR_HSERDYF is asserted. hserdy .await ; }

Registers and individual register fields can be grouped into peripheral blocks. Drone makes a great effort to abstract from different instances of one peripheral type. Even if these instances have minor differences. use drone_cortexm :: reg :: prelude :: * ; use drone_stm32_map :: periph :: gpio ::{ periph_gpio_a3 , periph_gpio_b14 , pin ::{ GpioPinMap , GpioPinPeriph }, }; /// TPS22917 Load Switch. pub struct LoadSwitch < T : GpioPinMap > { pin : GpioPinPeriph < T > , } impl < T : GpioPinMap > LoadSwitch < T > { /// Sets up a new [`LoadSwitch`]. pub fn init ( pin : GpioPinPeriph < T > ) -> Self { pin .gpio_moder_moder .write_bits ( 0b01 ); // general purpose output Self { pin } } /// Turns the switch on. pub fn on ( & self ) { self .pin.gpio_bsrr_bs .set_bit (); // tie the pin to +3.3v } /// Turns the switch off. pub fn off ( & self ) { self .pin.gpio_bsrr_br .set_bit (); // tie the pin to ground } } pub fn handler ( reg : Regs ) { let sd_card_switch = LoadSwitch :: init ( periph_gpio_a3! ( reg )); let ble_switch = LoadSwitch :: init ( periph_gpio_b14! ( reg )); sd_card_switch .on (); ble_switch .on (); }

Drone provides an abstract logging facade designed after ARM Serial Wire Output. The output can be captured with a generic USB-UART adapter. It has 32 multiplexed streams and supports atomic packets up to 4 bytes. Familiar Rust macros like print! , eprint! , dbg! are mapped to reserved #0 and #1 ports that work as standard output and standard error respectively. use drone_core :: log :: Port ; fn handler () { // Familiar output macros just work! println! ( "Hello, world!" ); dbg! ( 123 ); // You can send raw bytes to different ports. Port :: new ( 2 ) .write_bytes ( & [ 1 , 2 , 3 ]); // If a port accessed concurrently, you can send indivisible packets up to 4 // bytes length. Port :: new ( 3 ) .write :: < u32 > ( 0xABFF_FFCD ) .write :: < u32 > ( 0xDCFF_FFBA ); }