1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
//! Non-blocking support for the 5×5 LED display. //! //! # Scope //! //! Together with [`tiny-led-matrix`](tiny_led_matrix), this module provides: //! - support for driving the LED display from a timer interrupt //! - ten levels of brightness for each LED //! - simple 5×5 greyscale and black-and-white image types. //! //! The module doesn't define interrupt handlers directly; instead it provides //! a function to be called from a timer interrupt. It knows how to program //! one of the micro:bit's timers to provide that interrupt. //! //! # Example //! //! This shows general usage but is not a working example. //! For a working exaple see `examples/led_nonblocking.rs`. //! //! ```no_run //! // in your main function //! { //! let p = pac::Peripherals::take().unwrap(); //! let mut timer = MicrobitDisplayTimer::new(p.TIMER1); //! let p0parts = P0Parts::new(p.GPIO); //! let mut pins = display_pins!(p0parts); //! display::initialise_display(&mut timer, &mut pins); //! let display = Display::new(); //! //! static mut FRAME: MicrobitFrame = MicrobitFrame::const_default(); //! //! loop { //! FRAME.set(GreyscaleImage::new(&[ //! [0, 7, 0, 7, 0], //! [7, 0, 7, 0, 7], //! [7, 0, 0, 0, 7], //! [0, 7, 0, 7, 0], //! [0, 0, 7, 0, 0], //! ])); //! display.set_frame(&FRAME); //! timer2.delay_ms(1000); //! //! FRAME.set(GreyscaleImage::new(&[ //! [0, 0, 0, 0, 0], //! [0, 0, 0, 0, 0], //! [0, 0, 0, 0, 0], //! [0, 0, 0, 0, 0], //! [0, 0, 0, 0, 0], //! ])); //! display.set_frame(&FRAME); //! timer2.delay_ms(1000); //! } //! } //! //! // in a timer interrupt //! { //! display::handle_display_event(display, timer, pins); //! } //! ``` //! //! # Coordinate system //! //! The LEDs are identified using (x,y) coordinates as follows: //! //! ```text //! (0,0) ... (4,0) //! ... ... ... //! (0,4) ... (4,4) //! ``` //! //! # Greyscale model //! //! LED brightness levels are described using a scale from 0 (off) to 9 //! (brightest) inclusive. //! //! These are converted to time slices using the same timings as used by the //! [micro:bit MicroPython port][micropython] (this is different to the 0 to //! 255 scale used by the [micro:bit runtime][dal]). //! //! The time slice for each level above 1 is approximately 1.9× the slice for //! the previous level. //! //! An LED with brightness 9 is lit for one third of the time (because //! internally there are three 'rows' of LEDs which have to be addressed one //! at a time). //! //! # Images and Render //! //! The [`Render`] trait defines the interface that an image-like type needs //! to provide in order to be displayed. //! //! It contains a single function: [`brightness_at(x, y)`](Render::brightness_at), //! returning a brightness level. //! //! The [`image`] submodule provides two static image types implementing //! `Render`: //! - [`GreyscaleImage`](image::GreyscaleImage), allowing all 9 levels (using one byte for each LED) //! - [`BitImage`](image::BitImage), allowing only 'on' and 'off' (using five bytes) //! //! # Display //! //! A [`Display`] instance controls the LEDs and programs a timer. There //! should normally be a single `Display` instance in the program. //! //! # Frames //! //! Types implementing [`Render`] aren't used directly with the [`Display`]; //! instead they're used to update a [`MicrobitFrame`] instance which is in //! turn passed to the `Display`. //! //! A `MicrobitFrame` instance is a 'compiled' representation of a 5×5 //! greyscale image, in a form that's more directly usable by the display //! code. //! //! This is exposed in the public API so that you can construct the //! `MicrobitFrame` representation in code running at a low priority. Then //! only [`Display::set_frame()`] has to be called in code that can't be //! interrupted by the display timer. //! //! # Timer integration //! //! The [`Display`] expects to control a single timer. It can use the //! micro:bit's `TIMER0`, `TIMER1`, or `TIMER2`. //! //! This uses a 6ms period to light each of the three internal LED rows, so //! that the entire display is updated every 18ms. //! //! When rendering greyscale images, the `Display` requests extra interrupts //! within each 6ms period. It only requests interrupts for the greyscale //! levels which are actually required for what's currently being displayed. //! //! ## Technical details //! //! The timer is set to 16-bit mode, using a 62.5kHz clock (16 µs ticks). It //! resets every 375 ticks. //! //! # Usage //! //! Choose a timer to drive the display from (`TIMER0`, `TIMER1`, or //! `TIMER2`). //! //! When your program starts: //! * create a [`MicrobitDisplayTimer`] struct, passing the timer you chose to //! [`MicrobitDisplayTimer::new()`] //! * call [`initialise_display()`], passing it the `MicrobitDisplayTimer` and the //! [`crate::gpio::DisplayPins`] //! * create a [`Display`] struct (a `Display<MicrobitFrame>`). //! //! In an interrupt handler for the timer, call [`handle_display_event()`]. //! //! To change what's displayed: create a [`MicrobitFrame`] instance, use //! [`.set()`](`Frame::set()`) to put an image (something implementing //! [`Render`]) in it, then call [`Display::set_frame()`]. Note you'll have to //! `use microbit::display::Frame` to make `set()` available. //! //! You can call `set_frame()` at any time, so long as you're not //! interrupting, or interruptable by, `handle_display_event()`. //! //! Once you've called `set_frame()`, you are free to reuse the //! `MicrobitFrame`. //! //! See [`led_rtfm`](https://github.com/therealprof/microbit/blob/master/examples/led_rtfm.rs) example for a complete working example. //! //! [dal]: https://lancaster-university.github.io/microbit-docs/ //! [micropython]: https://microbit-micropython.readthedocs.io/ #[doc(no_inline)] pub use tiny_led_matrix::{Display, Frame, Render, MAX_BRIGHTNESS}; mod control; mod matrix; mod timer; pub mod image; pub use matrix::MicrobitFrame; pub use timer::MicrobitDisplayTimer; use crate::{gpio::DisplayPins, hal::timer::Instance}; use control::MicrobitGpio; /// Initialises the micro:bit hardware to use the display driver. /// /// Assumes the GPIO port is in the state it would have after system reset. /// /// # Example /// /// ```ignore /// let mut p: nrf51::Peripherals = _; /// let mut timer = microbit::display::MicrobitDisplayTimer::new(p.TIMER1); /// microbit::display::initialise_display(&mut timer, &mut p.GPIO); /// ``` pub fn initialise_display<T: Instance>( timer: &mut MicrobitDisplayTimer<T>, _pins: &mut DisplayPins, ) { tiny_led_matrix::initialise_control(&mut MicrobitGpio {}); tiny_led_matrix::initialise_timer(timer); } /// Updates the LEDs and timer state during a timer interrupt. /// /// The timer parameter must be the same `MicrobitDisplayTimer` you used for /// [`initialise_display()`]. /// /// Call this in an interrupt handler for the timer you're using. /// /// Takes care of clearing the timer's event registers. /// /// See [`Display::handle_event()`] for details. /// /// # Example /// /// In the style of `cortex-m-rtfm` v0.5: /// /// ```ignore /// #[task(binds = TIMER1, priority = 2, /// resources = [display_timer, gpio, display])] /// fn timer1(mut cx: timer1::Context) { /// microbit::display::handle_display_event( /// &mut cx.resources.display, /// cx.resources.display_timer, /// cx.resources.gpio, /// ); /// } /// ``` pub fn handle_display_event<T: Instance>( display: &mut Display<MicrobitFrame>, timer: &mut MicrobitDisplayTimer<T>, _pins: &mut DisplayPins, ) { display.handle_event(timer, &mut MicrobitGpio {}); }