tiny_led_matrix/
display.rs

1//! The display driver and Frame trait.
2
3use crate::control::DisplayControl;
4use crate::timer::DisplayTimer;
5use crate::render::{Render, BRIGHTNESSES, MAX_BRIGHTNESS};
6
7
8/// A set of matrix column indices.
9///
10/// Supports maximum index 15.
11#[derive(Copy, Clone, Debug)]
12struct ColumnSet (u16);
13
14impl ColumnSet {
15
16    /// Returns a new empty set.
17    const fn empty() -> ColumnSet {
18        ColumnSet(0)
19    }
20
21    /// Adds column index 'col' to the set.
22    fn set(&mut self, col: usize) {
23        self.0 |= 1<<col;
24    }
25
26    /// Returns the set as a bitmap in a u32 (LSB is index 0).
27    fn as_u32(&self) -> u32 {
28        self.0 as u32
29    }
30
31    /// Says whether the set is empty.
32    fn is_empty(&self) -> bool {
33        self.0 == 0
34    }
35}
36
37
38/// A 'compiled' representation of the part of an image displayed on a single
39/// matrix row.
40///
41/// RowPlans are created and contained by [`Frame`]s.
42// This is effectively a map brightness -> set of columns
43#[derive(Copy, Clone, Debug)]
44pub struct RowPlan (
45    [ColumnSet; BRIGHTNESSES],
46);
47
48impl RowPlan {
49
50    /// Returns a new RowPlan with all LEDs brightness 0.
51    pub const fn default() -> RowPlan {
52        RowPlan([ColumnSet::empty(); BRIGHTNESSES])
53    }
54
55    /// Resets all LEDs to brightness 0.
56    fn clear(&mut self) {
57        self.0 = RowPlan::default().0;
58    }
59
60    /// Says which LEDs have the specified brightness.
61    fn lit_cols(&self, brightness: u8) -> ColumnSet {
62        self.0[brightness as usize]
63    }
64
65    /// Sets a single LED to the specified brightness.
66    fn light_col(&mut self, brightness: u8, col: usize) {
67        self.0[brightness as usize].set(col);
68    }
69
70}
71
72
73/// Description of a device's LED layout.
74///
75/// This describes the correspondence between the visible layout of LEDs and
76/// the pins controlling them.
77///
78/// # Example implementation
79///
80/// ```
81/// # use tiny_led_matrix::Matrix;
82/// struct SimpleMatrix ();
83///
84/// impl Matrix for SimpleMatrix {
85///     const MATRIX_COLS: usize = 2;
86///     const MATRIX_ROWS: usize = 3;
87///     const IMAGE_COLS: usize = 3;
88///     const IMAGE_ROWS: usize = 2;
89///     fn image_coordinates(col: usize, row: usize) -> Option<(usize, usize)> {
90///         Some((row, col))
91///     }
92/// }
93/// ```
94pub trait Matrix {
95    /// The number of pins connected to LED columns.
96    ///
97    /// At present this can be at most 16.
98    const MATRIX_COLS: usize;
99
100    /// The number of pins connected to LED rows.
101    ///
102    /// This should normally be a small number (eg 3).
103    const MATRIX_ROWS: usize;
104
105    // Note that nothing uses IMAGE_COLS and IMAGE_ROWS directly; having these
106    // constants allows us to document them.
107
108    /// The number of visible LED columns.
109    const IMAGE_COLS: usize;
110
111    /// The number of visible LED rows.
112    const IMAGE_ROWS: usize;
113
114    /// Returns the image coordinates (x, y) to use for the LED at (col, row).
115    ///
116    /// Returns None if (col, row) doesn't control an LED.
117    ///
118    /// Otherwise the return value is in (0..IMAGE_COLS, 0..IMAGE_ROWS), with
119    /// (0, 0) representing the top left.
120    ///
121    /// # Panics
122    ///
123    /// Panics if the provided col and row are out of range 0..MATRIX_COLS and
124    /// 0..MATRIX_ROWS.
125
126    fn image_coordinates(col: usize, row: usize) -> Option<(usize, usize)>;
127}
128
129
130/// A 'Compiled' representation of an image to be displayed.
131///
132/// `Frame`s are populated from images implementing [`Render`], then passed on
133/// to [`Display::set_frame()`].
134///
135/// # Implementing `Frame`
136///
137/// Implementations of `Frame` do two things:
138///
139/// - specify the [`Matrix`] used to convert between image and matrix
140///   coordinates
141/// - act like an array of [`RowPlan`]s, one for each matrix row.
142///
143/// Note that implementations of `Frame` must also implement `Copy` and
144/// `Default`.
145///
146/// # Example implementation
147///
148/// ```
149/// # use tiny_led_matrix::{Matrix,Frame,RowPlan};
150/// # struct SimpleMatrix ();
151/// # impl Matrix for SimpleMatrix {
152/// #     const MATRIX_COLS: usize = 2;
153/// #     const MATRIX_ROWS: usize = 3;
154/// #     const IMAGE_COLS: usize = 3;
155/// #     const IMAGE_ROWS: usize = 2;
156/// #     fn image_coordinates(col: usize, row: usize) ->
157/// #                         Option<(usize, usize)> {
158/// #         Some((row, col))
159/// #     }
160/// # }
161/// #
162/// #[derive(Copy, Clone)]
163/// struct SimpleFrame (
164///     [RowPlan; 3]
165/// );
166///
167/// impl Default for SimpleFrame {
168///     fn default() -> SimpleFrame {
169///         SimpleFrame([RowPlan::default(); SimpleFrame::ROWS])
170///     }
171/// }
172///
173/// impl Frame for SimpleFrame {
174///     type Mtx = SimpleMatrix;
175///
176///     fn row_plan(&self, row: usize) -> &RowPlan {
177///         &self.0[row]
178///     }
179///
180///     fn row_plan_mut(&mut self, row: usize) -> &mut RowPlan {
181///         &mut self.0[row]
182///     }
183/// }
184/// ```
185pub trait Frame: Copy + Default {
186
187    /// The Matrix used to convert between image and matrix coordinates.
188    type Mtx: Matrix;
189
190    /// The number of pins connected to LED columns.
191    const COLS: usize = Self::Mtx::MATRIX_COLS;
192
193    /// The number of pins connected to LED rows.
194    const ROWS: usize = Self::Mtx::MATRIX_ROWS;
195
196    /// Returns a reference to the RowPlan for a row of LEDs.
197    ///
198    /// # Panics
199    ///
200    /// Panics if `row` is not in the range 0..ROWS
201    fn row_plan(&self, row: usize) -> &RowPlan;
202
203    /// Returns a mutable reference to the RowPlan for a row of LEDs.
204    ///
205    /// # Panics
206    ///
207    /// Panics if `row` is not in the range 0..ROWS
208    fn row_plan_mut(&mut self, row: usize) -> &mut RowPlan;
209
210
211    /// Stores a new image into the frame.
212    ///
213    /// Example:
214    ///
215    /// ```ignore
216    /// frame.set(GreyscaleImage::blank());
217    /// ```
218    fn set<T>(&mut self, image: &T) where T: Render + ?Sized {
219        for row in 0..Self::ROWS {
220            let plan = self.row_plan_mut(row);
221            plan.clear();
222            for col in 0..Self::COLS {
223                if let Some((x, y)) = Self::Mtx::image_coordinates(col, row) {
224                    let brightness = image.brightness_at(x, y);
225                    plan.light_col(brightness, col);
226                }
227            }
228        }
229    }
230}
231
232
233// With a 16µs period, 375 ticks is 6ms
234const CYCLE_TICKS: u16 = 375;
235
236const GREYSCALE_TIMINGS: [u16; BRIGHTNESSES-2] = [
237//   Delay,   Bright, Ticks, Duration, Relative power
238//   375,  //   0,      0,      0µs,    ---
239     373,  //   1,      2,     32µs,    inf
240     371,  //   2,      4,     64µs,   200%
241     367,  //   3,      8,    128µs,   200%
242     360,  //   4,     15,    240µs,   187%
243     347,  //   5,     28,    448µs,   187%
244     322,  //   6,     53,    848µs,   189%
245     273,  //   7,    102,   1632µs,   192%
246     176,  //   8,    199,   3184µs,   195%
247//     0,  //   9,    375,   6000µs,   188%
248];
249
250
251/// The reason for a display-timer interrupt.
252///
253/// This is the return value from [`handle_event()`].
254///
255/// [`handle_event()`]: Display::handle_event
256#[derive(PartialEq, Eq, Debug)]
257pub enum Event {
258    /// The display has switched to lighting a new row.
259    SwitchedRow,
260    /// The display has changed the LEDs in the current row.
261    UpdatedRow,
262    /// Neither a new primary cycle nor a secondary alarm has occurred.
263    Unknown,
264}
265
266impl Event {
267    /// Checks whether this event is `SwitchedRow`.
268    ///
269    /// This is provided for convenience in the common case where you want to
270    /// perform some action based on the display timer's primary cycle.
271    pub fn is_new_row(self) -> bool {
272        self == Event::SwitchedRow
273    }
274}
275
276
277/// Starts the timer you plan to use with a [`Display`].
278///
279/// Call this once before using a [`Display`].
280///
281/// This calls the timer's
282/// [`initialise_cycle()`][DisplayTimer::initialise_cycle] implementation.
283pub fn initialise_timer(timer: &mut impl DisplayTimer) {
284    timer.initialise_cycle(CYCLE_TICKS);
285}
286
287
288/// Initialises the display hardware you plan to use with a [`Display`].
289///
290/// Call this once before using a [`Display`].
291///
292/// This calls the [`DisplayControl`]'s
293/// [`initialise_for_display()`][DisplayControl::initialise_for_display]
294/// implementation.
295pub fn initialise_control(control: &mut impl DisplayControl) {
296    control.initialise_for_display();
297}
298
299
300/// Manages a small LED display.
301///
302/// There should normally be a single `Display` instance for a single piece of
303/// display hardware.
304///
305/// Display is generic over a [`Frame`] type, which holds image data suitable
306/// for display on a particular piece of hardware.
307///
308/// Call [`initialise_control()`] and [`initialise_timer()`] before using a
309/// `Display`.
310///
311/// # Example
312///
313/// Using `cortex-m-rtfm` v0.5:
314/// ```ignore
315/// #[app(device = nrf51, peripherals = true)]
316/// const APP: () = {
317///
318///     struct Resources {
319///         gpio: nrf51::GPIO,
320///         timer1: nrf51::TIMER1,
321///         display: Display<MyFrame>,
322///     }
323///
324///     #[init]
325///     fn init(cx: init::Context) -> init::LateResources {
326///         let mut p: nrf51::Peripherals = cx.device;
327///         display::initialise_control(&mut MyDisplayControl(&mut p.GPIO));
328///         display::initialise_timer(&mut MyDisplayTimer(&mut p.TIMER1));
329///         init::LateResources {
330///             gpio : p.GPIO,
331///             timer1 : p.TIMER1,
332///             display : Display::new(),
333///         }
334///     }
335/// }
336/// ```
337
338pub struct Display<F: Frame> {
339    // index (0..F::ROWS) of the row being displayed
340    row_strobe      : usize,
341    // brightness level (0..=MAX_BRIGHTNESS) to process next
342    next_brightness : u8,
343    frame           : F,
344    current_plan    : RowPlan
345}
346
347impl<F: Frame> Display<F> {
348
349    /// Creates a Display instance, initially holding a blank image.
350    pub fn new() -> Display<F> {
351        Display {
352            row_strobe: 0,
353            next_brightness: 0,
354            frame: F::default(),
355            current_plan: RowPlan::default(),
356        }
357    }
358
359    /// Accepts a new image to be displayed.
360    ///
361    /// The code that calls this method must not be interrupting, or
362    /// interruptable by, [`handle_event()`][Display::handle_event].
363    ///
364    /// After calling this, it's safe to modify the frame again (its data is
365    /// copied into the `Display`).
366    ///
367    /// # Example
368    ///
369    /// In the style of `cortex-m-rtfm` v0.5:
370    ///
371    /// ```ignore
372    /// #[task(binds = RTC0, priority = 1, resources = [rtc0, display])]
373    /// fn rtc0(mut cx: rtc0::Context) {
374    ///     static mut FRAME: MyFrame = MyFrame::const_default();
375    ///     &cx.resources.rtc0.clear_tick_event();
376    ///     FRAME.set(GreyscaleImage::blank());
377    ///     cx.resources.display.lock(|display| {
378    ///         display.set_frame(FRAME);
379    ///     });
380    /// }
381    /// ```
382    pub fn set_frame(&mut self, frame: &F) {
383        self.frame = *frame;
384    }
385
386    /// Updates the display for the start of a new primary cycle.
387    ///
388    /// Leaves the timer's secondary alarm enabled iff there are any
389    /// intermediate brightnesses in the current image.
390    fn render_row(&mut self,
391                  control: &mut impl DisplayControl,
392                  timer: &mut impl DisplayTimer) {
393        assert! (self.row_strobe < F::ROWS);
394        self.row_strobe += 1;
395        if self.row_strobe == F::ROWS {self.row_strobe = 0};
396
397        let plan = self.frame.row_plan(self.row_strobe);
398
399        let lit_cols = plan.lit_cols(MAX_BRIGHTNESS);
400        control.display_row_leds(self.row_strobe, lit_cols.as_u32());
401
402        // We copy this so that we'll continue using it for the rest of this
403        // 'tick' even if set_frame() is called part way through
404        self.current_plan = *plan;
405        self.next_brightness = MAX_BRIGHTNESS;
406        self.program_next_brightness(timer);
407        if self.next_brightness != 0 {
408            timer.enable_secondary();
409        }
410    }
411
412    /// Updates the display to represent an intermediate brightness.
413    ///
414    /// This is called after an interrupt from the secondary alarm.
415    fn render_subrow(&mut self,
416                     control: &mut impl DisplayControl,
417                     timer: &mut impl DisplayTimer) {
418        // When this method is called, next_brightness is an intermediate
419        // brightness in the range 1..8 (the one that it's time to display).
420
421        let additional_cols = self.current_plan.lit_cols(self.next_brightness);
422        control.light_current_row_leds(additional_cols.as_u32());
423
424        self.program_next_brightness(timer);
425    }
426
427    /// Updates next_brightness to the next (dimmer) brightness that needs
428    /// displaying, and program the timer's secondary alarm correspondingly.
429    ///
430    /// If no further brightness needs displaying for this row, this means
431    /// disabling the secondary alarm.
432    fn program_next_brightness(&mut self, timer: &mut impl DisplayTimer) {
433        loop {
434            self.next_brightness -= 1;
435            if self.next_brightness == 0 {
436                timer.disable_secondary();
437                break;
438            }
439            if !self.current_plan.lit_cols(self.next_brightness).is_empty() {
440                timer.program_secondary(
441                    GREYSCALE_TIMINGS[(self.next_brightness-1) as usize]
442                );
443                break;
444            }
445        }
446    }
447
448    /// Updates the LEDs and timer state during a timer interrupt.
449    ///
450    /// You should call this each time the timer's interrupt is signalled.
451    ///
452    /// The `timer` parameter must represent the same device each time you
453    /// call this method, and the same as originally passed to
454    /// [`initialise_timer()`].
455    ///
456    /// The `control` parameter must represent the same device each time you
457    /// call this method, and the same as originally passed to
458    /// [`initialise_control()`].
459    ///
460    /// This method always calls the timer's
461    /// [`check_primary()`][DisplayTimer::check_primary] and
462    /// [`check_secondary()`][DisplayTimer::check_secondary] methods.
463    ///
464    /// As well as updating the LED state by calling [`DisplayControl`]
465    /// methods, it may update the timer state by calling the timer's
466    /// [`program_secondary()`][DisplayTimer::program_secondary],
467    /// [`enable_secondary()`][DisplayTimer::enable_secondary], and/or
468    /// [`disable_secondary()`][DisplayTimer::disable_secondary] methods.
469    ///
470    /// Returns a value indicating the reason for the interrupt. You can check
471    /// this if you wish to perform some other action once per primary cycle.
472    ///
473    /// # Example
474    ///
475    /// In the style of `cortex-m-rtfm` v0.5:
476    ///
477    /// ```ignore
478    /// #[task(binds = TIMER1, priority = 2,
479    ///        resources = [timer1, gpio, display])]
480    /// fn timer1(cx: timer1::Context) {
481    ///     let display_event = cx.resources.display.handle_event(
482    ///         &mut MyDisplayControl(&mut cx.resources.timer1),
483    ///         &mut MyDisplayControl(&mut cx.resources.gpio),
484    ///     );
485    ///     if display_event.is_new_row() {
486    ///         ...
487    ///     }
488    /// }
489    /// ```
490    pub fn handle_event(&mut self,
491                        timer: &mut impl DisplayTimer,
492                        control: &mut impl DisplayControl) -> Event {
493        let row_timer_fired = timer.check_primary();
494        let brightness_timer_fired = timer.check_secondary();
495        if row_timer_fired {
496            self.render_row(control, timer);
497            Event::SwitchedRow
498        } else if brightness_timer_fired {
499            self.render_subrow(control, timer);
500            Event::UpdatedRow
501        } else {
502            Event::Unknown
503        }
504    }
505
506}
507