//! rtic_bare7.rs //! //! HAL OutputPin abstractions //! //! What it covers: //! - using embedded hal, and the OutputPin abstraction #![no_main] #![no_std] use panic_rtt_target as _; use rtic::cyccnt::{Instant, U32Ext as _}; use rtt_target::{rprintln, rtt_init_print}; use stm32f4xx_hal::stm32; use stm32f4xx_hal::{ gpio::{gpioa::PA1, gpioa::PA2, gpioa::PA3, Output, PushPull}, prelude::*, }; use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin}; const OFFSET: u32 = 50_000_000; #[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)] const APP: () = { struct Resources { // late resources //GPIOA: stm32::GPIOA, led_red: PA3<Output<PushPull>>, led_green: PA2<Output<PushPull>>, led_blue: PA1<Output<PushPull>>, } #[init(schedule = [toggle])] fn init(cx: init::Context) -> init::LateResources { rtt_init_print!(); rprintln!("init"); let mut core = cx.core; let device = cx.device; // Initialize (enable) the monotonic timer (CYCCNT) core.DCB.enable_trace(); core.DWT.enable_cycle_counter(); // semantically, the monotonic timer is frozen at time "zero" during `init` // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value let now = cx.start; // the start time of the system // Schedule `toggle` to run 8e6 cycles (clock cycles) in the future let number_of_toggles = 0; cx.schedule.toggle(now + OFFSET.cycles(),number_of_toggles).unwrap(); // power on GPIOA, RM0368 6.3.11 device.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); // configure PA3 as output, RM0368 8.4.1 device.GPIOA.moder.modify(|_, w| w.moder3().bits(1)); device.GPIOA.moder.modify(|_, w| w.moder2().bits(1)); device.GPIOA.moder.modify(|_, w| w.moder1().bits(1)); let gpioa = device.GPIOA.split(); // pass on late resources init::LateResources { //GPIOA: device.GPIOA, led_red: gpioa.pa3.into_push_pull_output(), led_green: gpioa.pa2.into_push_pull_output(), led_blue: gpioa.pa1.into_push_pull_output(), } } #[idle] fn idle(_cx: idle::Context) -> ! { rprintln!("idle"); loop { continue; } } #[task(resources = [led_green,led_blue,led_red], schedule = [toggle])] fn toggle(cx: toggle::Context, mut no_toggled: i32) { static mut TOGGLE: bool = false; //rprintln!("toggle @ {:?}", Instant::now()); //rprintln!("times I have toggled {:?}", no_toggled); no_toggled +=1; if no_toggled % 8 == 0{ rprintln!("White"); cx.resources.led_red.set_high().ok(); cx.resources.led_green.set_high().ok(); cx.resources.led_blue.set_high().ok(); } else if no_toggled % 8 == 1{ rprintln!("Green-yellow"); //Needs more oomph to be real yellow. cx.resources.led_red.set_high().ok(); cx.resources.led_green.set_high().ok(); cx.resources.led_blue.set_low().ok(); } else if no_toggled % 8 == 2{ rprintln!("Purple"); cx.resources.led_red.set_high().ok(); cx.resources.led_green.set_low().ok(); cx.resources.led_blue.set_high().ok(); } else if no_toggled % 8 == 3{ rprintln!("Light blue"); cx.resources.led_red.set_low().ok(); cx.resources.led_green.set_high().ok(); cx.resources.led_blue.set_high().ok(); } else if no_toggled % 8 == 4{ rprintln!("Red"); cx.resources.led_red.set_high().ok(); cx.resources.led_green.set_low().ok(); cx.resources.led_blue.set_low().ok(); } else if no_toggled % 8 == 5{ rprintln!("Green"); cx.resources.led_red.set_low().ok(); cx.resources.led_green.set_high().ok(); cx.resources.led_blue.set_low().ok(); } else if no_toggled % 8 == 6{ rprintln!("Blue"); cx.resources.led_red.set_low().ok(); cx.resources.led_green.set_low().ok(); cx.resources.led_blue.set_high().ok(); } else { rprintln!("Off"); cx.resources.led_red.set_low().ok(); cx.resources.led_green.set_low().ok(); cx.resources.led_blue.set_low().ok(); } //*TOGGLE = !*TOGGLE; cx.schedule.toggle(cx.scheduled + OFFSET.cycles(),no_toggled).unwrap(); } extern "C" { fn EXTI0(); } }; fn _toggle_generic<E>(led: &mut dyn OutputPin<Error = E>, toggle: &mut bool) { if *toggle { led.set_high().ok(); } else { led.set_low().ok(); } *toggle = !*toggle; } fn _toggleable_generic<E>(led: &mut dyn ToggleableOutputPin<Error = E>) { led.toggle().ok(); } // 1. In this example you will use RTT. // // > cargo run --example rtic_bare7 // // Look in the generated documentation for `set_high`/`set_low`. // (You created documentation for your dependencies in previous exercise // so you can just search (press `S`) for `OutputPin`). // You will find that these methods are implemented for `Output` pins. // // Now change your code to use these functions instead of the low-level GPIO API. // // HINTS: // - A GPIOx peripheral can be `split` into individual PINs Px0..Px15). // - A Pxy, can be turned into an `Output` by `into_push_pull_output`. // - You may optionally set other pin properties as well (such as `speed`). // - An `Output` pin provides `set_low`/`set_high` // - Instead of passing `GPIO` resource to the `toggle` task pass the // `led: PA5<Output<PushPull>>` resource instead. // // Comment your code to explain the steps taken. // // Confirm that your implementation correctly toggles the LED as in // previous exercise. // // Commit your code (bare7_1) // // 2. Further generalizations: // // Now look at the documentation for `embedded_hal::digital::v2::OutputPin`. // // You see that the OutputPin trait defines `set_low`/`set_high` functions. // Your task is to alter the code to use the `set_low`/`set_high` API. // // The function `_toggle_generic` is generic to any object that // implements the `OutputPin<Error = E>` trait. // // Digging deeper we find the type parameter `E`, which in this case // is left generic (unbound). // // It will be instantiated with a concrete type argument when called. // // Our `PA5<Output<PushPull>>` implements `OutputPin` trait, thus // we can pass the `led` resource to `_toggle_generic`. // // The error type is given by the stm32f4xx-hal implementation: // where `core::convert::Infallible` is used to indicate // there are no errors to be expected (hence infallible). // // Additionally, `_toggle_generic` takes a mutable reference // `toggle: &mut bool`, so you need to pass your `TOGGLE` variable. // // As you see, `TOGGLE` holds the "state", switching between // `true` and `false` (to make your led blink). // // Change your code into using the `_toggle_generic` function. // (You may rename it to `toggle_generic` if wished.) // // Confirm that your implementation correctly toggles the LED as in // previous exercise. // // Commit your code (bare7_2) // // 3. What about the state? // // In your code `TOGGLE` holds the "state". However, the underlying // hardware ALSO holds the state (if the corresponding bit is set/cleared). // // What if we can leverage that, and guess what we can!!!! // // Look at the documentation for `embedded_hal::digital::v2::ToggleableOutputPin`, // and the implementation of: // // fn _toggleable_generic(led: &mut dyn ToggleableOutputPin<Error = Infallible>) { // led.toggle().ok(); // } // // The latter does not take any state variable, instead it directly `toggle()` // the `ToggleableOutputPin`. // // Now alter your code to leverage on the `_toggleable_generic` function. // (You should be able to remove the `TOGGLE` state variable altogether.) // // Confirm that your implementation correctly toggles the LED as in // previous exercise. // // Commit your code (bare7_3) // // 4. Discussion: // // In this exercise you have gone from a very hardware specific implementation, // to leveraging abstractions (batteries included). // // Your final code amounts to "configuration" rather than "coding". // // This reduces the risk of errors (as you let the libraries do the heavy lifting). // // This also improves code-re use. E.g., if you were to do something less // trivial then merely toggling you can do that in a generic manner, // breaking out functionality into "components" re-usable in other applications. // // Of course the example is trivial, you don't gain much here, but the principle // is the same behind drivers for USART communication, USB, PMW3389 etc. // // 5. More details: // // Looking closer at the implementation: // `led: &mut dyn OutputPin<Error = E>` // // You may ask what kind of mumbo jumbo is at play here. // // This is the way to express that we expect a mutable reference to a trait object // that implements the `OutputPin`. Since we will change the underlying object // (in this case an GPIOA pin 5) the reference needs to be mutable. // // Trait objects are further explained in the Rust book. // The `dyn` keyword indicates dynamic dispatch (through a VTABLE). // https://doc.rust-lang.org/std/keyword.dyn.html // // Notice: the Rust compiler (rustc + LLVM) is really smart. In many cases // it can analyse the call chain, and conclude the exact trait object type at hand. // In such cases the dynamic dispatch is turned into a static dispatch // and the VTABLE is gone, and we have a zero-cost abstraction. // // If the trait object is stored for e.g., in an array along with other // trait objects (of different concrete type), there is usually no telling // the concrete type of each element, and we will have dynamic dispatch. // Arguably, this is also a zero-cost abstraction, as there is no (obvious) // way to implement it more efficiently. Remember, zero-cost is not without cost // just that it is as good as it possibly gets (you can't make it better by hand). // // You can also force the compiler to deduce the type at compile time, by using // `impl` instead of `dyn`, if you are sure you don't want the compiler to // "fallback" to dynamic dispatch. // // You might find Rust to have long compile times. Yes you are right, // and this type of deep analysis done in release mode is part of the story. // On the other hand, the aggressive optimization allows us to code // in a generic high level fashion and still have excellent performing binaries.