Drone is an Embedded Operating System for writing real-time applications in
Rust. It aims to bring modern development approaches without compromising
performance into the world of embedded programming.
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-worldCreated 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 depsrustup target add thumbv7m-none-eabiinfo: downloading component 'rust-std' for 'thumbv7m-none-eabi'
info: installing component 'rust-std' for 'thumbv7m-none-eabi'
$just flashcargo build --features "" --releaseUpdating 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-worldStart address 0x8000040, load size 6712
Transfer rate: 15 KB/sec, 745 bytes/write.
$just logdrone 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.
pubasyncfnhandler(input:Input)->Result<(),Error>{letInput{muti2c1,exti4,gpio_b,gpio_c}=input;// APDS-9960 interrupt events stream read from B4 pin.letmutexti4_stream=exti4.create_saturating_stream();letmutapds9960=Apds9960Drv::init();letmutgestures=Gestures::init(&mutapds9960,&muti2c1).await?;// Wait for a falling edge trigger on PB4.whileexti4_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.matchgestures.advance(&mutapds9960,&muti2c1).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.
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.
usecrate::consts::{FLASH_WS,PLL_M,PLL_N,PLL_P};usedrone_cortexm::reg::prelude::*;usedrone_stm32_map::reg;fninit_sys_clk(mutrcc_cr:reg::rcc::Cr<Urt>,mutrcc_pllcfgr:reg::rcc::Pllcfgr<Urt>,mutrcc_cfgr:reg::rcc::Cfgr<Urt>,mutflash_acr:reg::flash::Acr<Urt>,){rcc_cr.modify(|r|r.set_hseon());// HSE clock enablewhile!rcc_cr.load().hserdy(){}// HSE clock ready flagrcc_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 enablewhile!rcc_cr.load().pllrdy(){}// PLL clock ready flagflash_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()});whileflash_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)});whilercc_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.
usecrate::thr;usedrone_cortexm::{fib,reg::prelude::*,thr::prelude::*};usedrone_stm32_map::reg;asyncfnenable_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.letreg::rcc::Cir{hserdyc,hserdyf,..}=rcc_cir;// Attach a listener that will notify us when RCC_CIR_HSERDYF is asserted.lethserdy=thr_rcc.add_future(fib::new_fn(move||{ifhserdyf.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.
usedrone_cortexm::reg::prelude::*;usedrone_stm32_map::periph::gpio::{periph_gpio_a3,periph_gpio_b14,pin::{GpioPinMap,GpioPinPeriph},};/// TPS22917 Load Switch.pubstructLoadSwitch<T:GpioPinMap>{pin:GpioPinPeriph<T>,}impl<T:GpioPinMap>LoadSwitch<T>{/// Sets up a new [`LoadSwitch`].pubfninit(pin:GpioPinPeriph<T>)->Self{pin.gpio_moder_moder.write_bits(0b01);// general purpose outputSelf{pin}}/// Turns the switch on.pubfnon(&self){self.pin.gpio_bsrr_bs.set_bit();// tie the pin to +3.3v}/// Turns the switch off.pubfnoff(&self){self.pin.gpio_bsrr_br.set_bit();// tie the pin to ground}}pubfnhandler(reg:Regs){letsd_card_switch=LoadSwitch::init(periph_gpio_a3!(reg));letble_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.
usedrone_core::log::Port;fnhandler(){// 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);}
Drone provides safe API for inherently unsafe low-level
operations. The only required unsafe function is the entry-point
located at src/bin.rs, which is a separate
compilation unit. You can even write a whole micro-controller
firmware with #![forbid(unsafe_code)] at the top
of src/lib.rs.
/// The entry point.////// # Safety////// This function should only be called by hardware.#[no_mangle]pubunsafeextern"C"fnreset()->!{// Fill the memory region allocated for initially zeroed mutable static// variables with zeros. This is safe because none of these variables have// been in use before this line.unsafe{mem::bss_init()};// Fill the memory region for other mutable static variables with initial// values from flash memory. This is safe because none of these variables// have been in use before this line.unsafe{mem::data_init()};// Initialize the Floating Point Unit. This is safe because the unit has not// been in use before this line.unsafe{processor::fpu_init(true)};// Run the root task.tasks::root(// Instantiate a zero-sized collection of tokens for memory-mapped// registers. Safe only if this is the only instance.unsafe{Regs::take()},// Instantiate a zero-sized token needed to initialize the threading// system later. Safe only if this is the only instance.unsafe{ThrsInit::take()},);// If the root task returned, always sleep between interrupts.loop{processor::wait_for_int();}}
Design Principles
Energy effective from the start. Drone encourages
interrupt-driven execution model.
Hard Real-Time. Drone relies on atomic operations instead
of using critical sections.
Fully preemptive multi-tasking with strict priorities. A
higher priority task takes precedence with minimal latency.
Highly concurrent. Multi-tasking is cheap and safe,
following Rust's Fearless Concurrency principle.
Message passing concurrency. Drone ships with
synchronization primitives out of the box.
Single stack by default. Drone concurrency primitives are
essentially stack-less state machines. But stackful tasks are
still supported.
Dynamic memory enabled. Drone lets you use convenient data
structures like mutable strings or vectors while still staying
deterministic and code efficient.