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