-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Getting Started
To begin this example, you should:
- Have an MCU that embassy supports. We currently support stm32f4, stm32l0, and nrf series MCUs. We aim to support all common stm32 families.
- Know how to compile, load, and debug a rust program on an embedded target.
- Have an embedded probe that your IDE supports.
- Have the nightly rust compiler with the applicable eabi target, for example
thumbv7em-none-eabi
.
If you're not familiar with embedded development, consult the rust embedded development book.
Because embassy uses some unstable features to support async, embassy requires the rust nightly compiler. Even though embassy currently requires the nightly compiler, you can be confident that software written with embassy will continue to be supported into the future as the required compiler features become stabilized.
Currently, some advanced configuration is required in all embassy programs, though we aim to make this simpler in the future.
Now, let's begin with our entry function:
static RTC: Forever<rtc::RTC<pac::TIM2>> = Forever::new();
static ALARM: Forever<rtc::Alarm<pac::TIM2>> = Forever::new();
static EXECUTOR: Forever<Executor> = Forever::new();
#[entry]
fn main() -> ! {
let p = unwrap!(pac::Peripherals::take());
let rcc = p.RCC.constrain();
let clocks = rcc.cfgr.freeze();
let rtc = RTC.put(rtc::RTC::new(p.TIM2, interrupt::take!(TIM2), clocks));
rtc.start();
unsafe { embassy::time::set_clock(rtc) };
let alarm = ALARM.put(rtc.alarm1());
let executor = EXECUTOR.put(Executor::new());
executor.set_alarm(alarm);
executor.run(|spawner| {
unwrap!(spawner.spawn(run1()));
unwrap!(spawner.spawn(run2()));
});
}
First, we set-up our peripherals and configure the clocks:
let p = unwrap!(pac::Peripherals::take());
let rcc = p.RCC.constrain();
let clocks = rcc.cfgr.freeze();
Since you're familiar with embedded development, much of this function should not be a surprise to you. Keep in mind though, that as the embedded developer, you are always responsible for making sure that the clock configuration in the entry function is applicable to your target. Invalid clock configuration can cause Embassy to work incorrectly, and may yield invalid I/O data.
Next, we create a new instance of RTC
and store it in a Forever
struct:
static ALARM: Forever<rtc::Alarm<pac::TIM2>> = Forever::new();
...
let rtc = RTC.put(rtc::RTC::new(p.TIM2, interrupt::take!(TIM2), clocks));
There are several new concepts in this line. First, interrupt::take!(TIM2)
is a macro that takes an interrupt from the global interrupt table and
assigns it to an object. Programs using embassy are not permitted to declare their own interrupt handlers using the standard rust methodology. Rather,
programs must use the owned interrupts that Embassy provides in order to ensure that there are no conflicts.
Next, RTC
is a wrapper for one of the MCU's timers that allows Embassy's executor
to set alarms and retrieve the current time. An RTC must be
present for every MCU on which Embassy operates where delays are required. Finally, the RTC
is stored in a Forever
object which allows objects
created after initialization to have a static lifetime.
The next lines set the clock, create a new executor
, and set the alarm for that executor as RTC
.
rtc.start();
unsafe { embassy::time::set_clock(rtc) };
let alarm = ALARM.put(rtc.alarm1());
let executor = EXECUTOR.put(Executor::new());
executor.set_alarm(alarm);
Since Embassy can have multiple executor
s with different priorities, though we won't use that functionality here, each executor
may have its own
alarm. However, the clock must be consistent across all executors, which necessitates a universal clock function.
The final step in the initialization function spawns two tasks:
executor.run(|spawner| {
unwrap!(spawner.spawn(run1()));
unwrap!(spawner.spawn(run2()));
});
#[task]
async fn run1() {
loop {
info!("BIG INFREQUENT TICK");
Timer::after(Duration::from_ticks(32768 * 2)).await;
}
}