//! Blinks an LED and updates a UI based on an OLED display and rotary encoder //! //! This demonstrates decomposing an RTIC-based project into communicating //! tasks and breaking out lower-level hardware details into separate modules. //! //! The UI model itself is a completely separate crate, and can also be built //! for the `embedded-graphics-simulator` on the host, allowing for rapid UI //! development without having to re-flash. #![deny(unsafe_code)] #![no_std] #![no_main] // RTIC requires that unused interrupts are declared in "dispatchers" when // using software tasks; these free interrupts will be used to dispatch the // software tasks. // // For a list, see: // https://docs.rs/stm32f1xx-hal/0.6.1/stm32f1xx_hal/stm32/enum.Interrupt.html #[rtic::app(device = stm32f1xx_hal::stm32, peripherals = true, dispatchers = [TAMPER])] mod app { use rtt_target::{rprintln, rtt_init_print}; use stm32f1xx_hal::prelude::*; use blue_pill_ui::board::{self, Board, CountDownTimer, TIM2, TIM3, Event}; // Defining this struct makes shared resources available to tasks; // they will be initialized by the values returned from `init` and // will be wrapped in a `Mutex` and must be accessed via a closure // passed to its `lock` method. // If you annotate a field with #[lock_free] you can opt-out of the // mutex but it may only be shared by tasks at the same priority. #[shared] struct Shared { /// This will be used to communicate control updates from the /// control polling task to the idle thread, which manages the /// UI model and display drawing update: Option<(i32, bool)>, } // This struct defines local resources (accessed by only one task); // they will be initialized by the values returned from `init` and // can be accessed directly. #[local] struct Local { led: board::UserLed, encoder: board::Encoder, display: board::Display, poll_timer: CountDownTimer, blink_timer: CountDownTimer, } // This task does startup config; the peripherals are passed in thanks to // `peripherals = true` in the app definition. They are the `device` and // `core` fields of `init::Context`. #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { rtt_init_print!(); rprintln!("init begin"); let Board { encoder, display, led, mut poll_timer, mut blink_timer } = Board::init(cx.device); poll_timer.listen(Event::Update); blink_timer.listen(Event::Update); let delta = 0; let button_up = false; let update = Some((delta, button_up)); rprintln!("init end"); (Shared { update }, Local { led, encoder, display, poll_timer, blink_timer }, init::Monotonics()) } /// The idle task never stops running, so it can hold `!Send` state like /// our UI state. It busy-waits for updates via the shared `update` /// resource. #[idle(local = [display], shared = [update])] fn idle(cx: idle::Context) -> ! { let mut ui: ui::HelloDisplay<128,64> = ui::HelloDisplay::new(); let mut update = cx.shared.update; loop { if let Some((delta, button_up)) = update.lock(|upd| upd.take()) { if delta != 0 { ui.event(ui::HelloEvent::Knob(delta)); } if button_up { ui.event(ui::HelloEvent::Button); } ui.event(ui::HelloEvent::Tick); cx.local.display.draw(&mut ui); } } } // Poll the encoder and send its state to the idle task via the shared // `update` resource. Print out the raw encoder count when the button is // pressed. // // Since `count` is a local, we can have it initialized with a const expr. #[task(local = [count: u16 = 0, encoder], shared = [update])] fn count_update(mut cx: count_update::Context) { let delta = cx.local.encoder.poll_count_delta(); let button_up = cx.local.encoder.poll_button_up(); if button_up { rprintln!("Button pressed: encoder count is {}", cx.local.encoder.count()); } cx.shared.update.lock(|upd| upd.replace((delta, button_up))); } // Interrupt task for TIM2, the control polling timer #[task(binds = TIM2, priority = 2, local = [poll_timer])] fn tim2(cx: tim2::Context) { // Delegate the state update to a software task count_update::spawn().unwrap(); // Restart the timer and clear the interrupt flag cx.local.poll_timer.start(60.hz()); cx.local.poll_timer.clear_update_interrupt_flag(); } // Interrupt task for TIM3, the LED blink timer #[task(binds = TIM3, priority = 1, local = [led, blink_timer])] fn tim3(cx: tim3::Context) { cx.local.led.toggle(); cx.local.blink_timer.start(2.hz()); cx.local.blink_timer.clear_update_interrupt_flag(); } }