microbit_common/display/nonblocking/
mod.rs

1//! Non-blocking support for the 5×5 LED display.
2//!
3//! Together with [`tiny-led-matrix`](tiny_led_matrix), this module provides:
4//! - support for driving the LED display from a timer interrupt
5//! - ten levels of brightness for each LED
6//! - simple 5×5 greyscale and black-and-white image types.
7//!
8//! The module doesn't define interrupt handlers directly; instead it provides
9//! a function to be called from a timer interrupt. It knows how to program
10//! one of the micro:bit's timers to provide that interrupt.
11//!
12//! ## Example
13//!
14//! This shows general usage but is not a working example.
15//! For a working exaple see
16//! [`display_nonblocking`](https://github.com/nrf-rs/microbit/tree/main/examples/display-nonblocking).
17//!
18//! ```no_run
19//! # use microbit_common as microbit;
20//! use microbit::{
21//!     Board,
22//!     hal,
23//!     display::nonblocking::{Display, GreyscaleImage},
24//! };
25//! use embedded_hal::delay::DelayNs;
26//!
27//! let board = Board::take().unwrap();
28//!
29//! let mut display = Display::new(board.TIMER1, board.display_pins);
30//!
31//! // in your main function
32//! {
33//!     let mut timer2 = hal::Timer::new(board.TIMER0);
34//!     loop {
35//!         display.show(&GreyscaleImage::new(&[
36//!             [0, 7, 0, 7, 0],
37//!             [7, 0, 7, 0, 7],
38//!             [7, 0, 0, 0, 7],
39//!             [0, 7, 0, 7, 0],
40//!             [0, 0, 7, 0, 0],
41//!         ]));
42//!         timer2.delay_ms(1000);
43//!
44//!         display.clear();
45//!         timer2.delay_ms(1000);
46//!     }
47//! }
48//!
49//! // in a timer interrupt
50//! {
51//!     display.handle_display_event();
52//! }
53//! ```
54//!
55//! ## Coordinate system
56//!
57//! The LEDs are identified using (x,y) coordinates as follows:
58//!
59//! ```text
60//! (0,0) ... (4,0)
61//!  ...  ...  ...
62//! (0,4) ... (4,4)
63//! ```
64//!
65//! where the 'bottom' (x,4) of the board is the edge connector.
66//!
67//! ## Greyscale model
68//!
69//! LED brightness levels are described using a scale from 0 (off) to 9
70//! (brightest) inclusive.
71//!
72//! These are converted to time slices using the same timings as used by the
73//! [micro:bit MicroPython port][micropython] (this is different to the 0 to
74//! 255 scale used by the [micro:bit runtime][dal]).
75//!
76//! The time slice for each level above 1 is approximately 1.9× the slice for
77//! the previous level.
78//!
79#![cfg_attr(
80    feature = "v1",
81    doc = "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)."
82)]
83#![cfg_attr(
84    feature = "v2",
85    doc = "An LED with brightness 9 is lit for one fifth of the time."
86)]
87//!
88//! ## Images
89//!
90//! An image is a type that implements the [`tiny_led_matrix::Render`] trait. Two image types are provided:
91//! - [`GreyscaleImage`](image::GreyscaleImage), allowing all 9 levels (using one byte for each LED)
92//! - [`BitImage`](image::BitImage), allowing only 'on' and 'off' (using five bytes)
93//!
94//! ## Display
95//!
96//! A [`Display`] instance controls the LEDs and programs a timer. There
97//! should normally be a single `Display` instance in the program. It is a wrapper
98//! around [`tiny_led_matrix::Display`] to expose an API similar to the blocking API.
99//!
100//! ## Frames
101//!
102//! Internally types implementing [`Render`](tiny_led_matrix::Render) aren't used directly with the [`Display`];
103//! instead they're used to update a [`MicrobitFrame`] instance which is in
104//! turn passed to the `tiny_led_matrix::Display`.
105//!
106//! A `MicrobitFrame` instance is a 'compiled' representation of a 5×5
107//! greyscale image, in a form that's more directly usable by the display
108//! code.
109//!
110//! This is exposed in the public API so that you can construct the
111//! `MicrobitFrame` representation in code running at a low priority. Then
112//! only [`Display::show_frame()`] has to be called in code that can't be
113//! interrupted by the display timer.
114//!
115//! ## Timer integration
116//!
117//! The [`Display`] expects to control a single timer. It can use the
118//! micro:bit's `TIMER0`, `TIMER1`, or `TIMER2`.
119//!
120//! For the micro:bit v1 this uses a 6ms period to light each of the three
121//! internal LED rows, so that the entire display is updated every 18ms.
122//!
123//! For the micro:bit v2 this uses a 3ms period to light each of the five
124//! internal LED rows, so that the entire display is updated every 15ms.
125//!
126//! When rendering greyscale images, the `Display` requests extra interrupts
127//! within each 6ms or 3ms period. It only requests interrupts for the
128//! greyscale levels which are actually required for what's currently being
129//! displayed.
130//!
131//! ### Technical details
132//!
133//! The timer is set to 16-bit mode, using a 62.5kHz or 135Khz clock (16 µs or
134//! 8µs ticks). It resets every 375 ticks.
135//!
136//! ## Usage
137//!
138//! Choose a timer to drive the display from (`TIMER0`, `TIMER1`, or `TIMER2`).
139//!
140//! When your program starts:
141//! - create a [`Display`] struct passing the timer and
142//! [`gpio::DisplayPins`](crate::gpio::DisplayPins) to [`Display::new()`].
143//!
144//! In an interrupt handler for the timer call [`.handle_display_event()`](Display::handle_display_event)
145//!
146//! To change what's displayed; pass an image ([`GreyscaleImage`] or [`BitImage`]) to [`Display::show`].
147//!
148//! You can call `show()` at any time, so long as you're not interrupting, or interruptable by,
149//! [`Display::handle_display_event()`].
150//!
151//! See [`display_rtic`](https://github.com/nrf-rs/microbit/blob/master/examples/display_rtic) or
152//! [`display_nonblocking`](https://github.com/nrf-rs/microbit/blob/master/examples/display_nonblocking)
153//! example for a complete working example.
154//!
155//! [dal]: https://lancaster-university.github.io/microbit-docs/
156//! [micropython]: https://microbit-micropython.readthedocs.io/
157
158use tiny_led_matrix;
159#[doc(no_inline)]
160pub use tiny_led_matrix::{Frame, MAX_BRIGHTNESS};
161
162mod control;
163mod image;
164mod matrix;
165mod timer;
166
167pub use image::{BitImage, GreyscaleImage};
168pub use matrix::MicrobitFrame;
169use timer::MicrobitDisplayTimer;
170
171use crate::{gpio::DisplayPins, hal::timer::Instance};
172
173use control::MicrobitGpio;
174
175/// Non-blocking interface to the on board 5x5 LED display
176pub struct Display<T: Instance> {
177    display: tiny_led_matrix::Display<MicrobitFrame>,
178    timer: MicrobitDisplayTimer<T>,
179    pins: DisplayPins,
180    frame: MicrobitFrame,
181}
182
183impl<T: Instance> Display<T> {
184    /// Create and initialise the display driver
185    ///
186    /// [`DisplayPins`] can be used from [`Board::display_pins`](crate::Board::display_pins)
187    /// or the [`display_pins!`](crate::display_pins) macro can be used is manually.
188    pub fn new(timer: T, pins: DisplayPins) -> Self {
189        let mut display = Self {
190            display: tiny_led_matrix::Display::new(),
191            timer: MicrobitDisplayTimer::new(timer),
192            pins,
193            frame: MicrobitFrame::default(),
194        };
195        display.initialise();
196        display
197    }
198
199    /// Release the timer and pins
200    pub fn free(self) -> (T, DisplayPins) {
201        (self.timer.free(), self.pins)
202    }
203
204    /// Initialise the display
205    ///
206    /// This is usually called immediately after creating the display driver.
207    /// It does not need to be called in a critical section.
208    fn initialise(&mut self) {
209        tiny_led_matrix::initialise_control(&mut MicrobitGpio {});
210        tiny_led_matrix::initialise_timer(&mut self.timer);
211    }
212
213    /// Update the LED display and timer state
214    ///
215    /// Call this in an interrupt handler for the timer you're using. This method
216    /// takes care of updating the LED display and clearing the timer's event registers
217    ///
218    /// This may be called at any time, so long as the code calling it is not interrupting, or
219    /// interruptable by `tiny_led_matrix::Display::handle_event()`. Within safe code, the borrow
220    /// checker ensures that this requirement is fulfilled. When writing unsafe code, this method
221    /// should be called from within a [critical
222    /// section](https://docs.rs/cortex-m/0.7.2/cortex_m/interrupt/fn.free.html).
223    pub fn handle_display_event(&mut self) {
224        self.display
225            .handle_event(&mut self.timer, &mut MicrobitGpio {});
226    }
227
228    /// Show a new image
229    ///
230    /// This may be called at any time, so long as the code calling it is not interrupting, or
231    /// interruptable by `tiny_led_matrix::Display::handle_event()`. Within safe code, the borrow
232    /// checker ensures that this requirement is fulfilled. When writing unsafe code, this method
233    /// should be called from within a [critical
234    /// section](https://docs.rs/cortex-m/0.7.2/cortex_m/interrupt/fn.free.html).
235    ///
236    /// ## Example
237    ///
238    /// ```ignore
239    /// display.show(&GreyscaleImage::new(&[
240    ///     [0, 7, 0, 7, 0],
241    ///     [7, 0, 7, 0, 7],
242    ///     [7, 0, 0, 0, 7],
243    ///     [0, 7, 0, 7, 0],
244    ///     [0, 0, 7, 0, 0],
245    /// ]));
246    /// ```
247    pub fn show<R: tiny_led_matrix::Render>(&mut self, image: &R) {
248        self.frame.set(image);
249        self.display.set_frame(&self.frame);
250    }
251
252    /// Clear the display
253    ///
254    /// This may be called at any time, so long as the code calling it is not interrupting, or
255    /// interruptable by `tiny_led_matrix::Display::handle_event()`. Within safe code, the borrow
256    /// checker ensures that this requirement is fulfilled. When writing unsafe code, this method
257    /// should be called from within a [critical
258    /// section](https://docs.rs/cortex-m/0.7.2/cortex_m/interrupt/fn.free.html).
259    pub fn clear(&mut self) {
260        self.display.set_frame(&MicrobitFrame::default());
261    }
262
263    /// Show a new frame
264    ///
265    /// This is similar to [`show`](Display::show) but accepts a [`MicrobitFrame`] instead.
266    /// This may be useful if performance is a concern as calling `set` on the frame
267    /// can be done outside the critical section.
268    ///
269    /// This may be called at any time, so long as the code calling it is not interrupting, or
270    /// interruptable by `tiny_led_matrix::Display::handle_event()`. Within safe code, the borrow
271    /// checker ensures that this requirement is fulfilled. When writing unsafe code, this method
272    /// should be called from within a [critical
273    /// section](https://docs.rs/cortex-m/0.7.2/cortex_m/interrupt/fn.free.html).
274    ///
275    /// ## Example
276    ///
277    /// ```ignore
278    /// FRAME = MicrobitFrame::default();
279    /// FRAME.set(&GreyscaleImage::new(&[
280    ///     [0, 7, 0, 7, 0],
281    ///     [7, 0, 7, 0, 7],
282    ///     [7, 0, 0, 0, 7],
283    ///     [0, 7, 0, 7, 0],
284    ///     [0, 0, 7, 0, 0],
285    /// ]));
286    ///
287    /// // only this needs to be in a critical section
288    /// display.show_frame(&FRAME);
289    /// ```
290    pub fn show_frame(&mut self, frame: &MicrobitFrame) {
291        self.display.set_frame(frame);
292    }
293}