microbit_bsp/display/
mod.rs

1//! Driver a NxM LED matrix display
2//!
3//! * Can display 5x5 bitmaps from raw data or characters
4//! * Methods for scrolling text across LED matrix or displaying a bitmap for a duration
5use embassy_time::{block_for, Duration, Instant, Timer};
6use embedded_hal::digital::OutputPin;
7
8pub mod fonts;
9
10mod types;
11pub use types::*;
12
13const REFRESH_INTERVAL: Duration = Duration::from_micros(500);
14
15/// Led matrix driver supporting arbitrary sized led matrixes.
16///
17/// NOTE: Currently restricted by 8 bits width
18pub struct LedMatrix<P, const ROWS: usize, const COLS: usize>
19where
20    P: OutputPin + 'static,
21{
22    pin_rows: [P; ROWS],
23    pin_cols: [P; COLS],
24    frame_buffer: Frame<COLS, ROWS>,
25    row_p: usize,
26    brightness: Brightness,
27}
28
29impl<P, const ROWS: usize, const COLS: usize> LedMatrix<P, ROWS, COLS>
30where
31    P: OutputPin,
32{
33    /// Create a new instance of an LED matrix using the provided pins
34    pub fn new(pin_rows: [P; ROWS], pin_cols: [P; COLS]) -> Self {
35        LedMatrix {
36            pin_rows,
37            pin_cols,
38            frame_buffer: Frame::empty(),
39            row_p: 0,
40            brightness: Default::default(),
41        }
42    }
43
44    /// Clear all LEDs
45    pub fn clear(&mut self) {
46        self.frame_buffer.clear();
47        for row in self.pin_rows.iter_mut() {
48            row.set_high().ok();
49        }
50
51        for col in self.pin_cols.iter_mut() {
52            col.set_high().ok();
53        }
54    }
55
56    /// Turn on point (x,y) in the frame buffer
57    pub fn on(&mut self, x: usize, y: usize) {
58        self.frame_buffer.set(x, y);
59    }
60
61    /// Turn off point (x,y) in the frame buffer
62    pub fn off(&mut self, x: usize, y: usize) {
63        self.frame_buffer.unset(x, y);
64    }
65
66    /// Apply the provided frame onto the frame buffer
67    pub fn apply(&mut self, frame: Frame<COLS, ROWS>) {
68        self.frame_buffer = frame;
69    }
70
71    /// Adjust the brightness level
72    pub fn set_brightness(&mut self, brightness: Brightness) {
73        self.brightness = brightness;
74    }
75
76    /// Increase brightness relative to current setting
77    pub fn increase_brightness(&mut self) {
78        self.brightness += 1;
79    }
80
81    /// Decrease brightness relative to current setting
82    pub fn decrease_brightness(&mut self) {
83        self.brightness -= 1;
84    }
85
86    /// Perform a full refresh of the display based on the current frame buffer
87    pub fn render(&mut self) {
88        for row in self.pin_rows.iter_mut() {
89            row.set_low().ok();
90        }
91
92        for (cid, col) in self.pin_cols.iter_mut().enumerate() {
93            if self.frame_buffer.is_set(cid, self.row_p) {
94                col.set_low().ok();
95            } else {
96                col.set_high().ok();
97            }
98        }
99
100        // Adjust interval will impact brightness of the LEDs
101        block_for(Duration::from_micros(
102            ((Brightness::MAX.level() - self.brightness.level()) as u64) * 6000 / Brightness::MAX.level() as u64,
103        ));
104
105        self.pin_rows[self.row_p].set_high().ok();
106
107        self.row_p = (self.row_p + 1) % self.pin_rows.len();
108    }
109
110    /// Display the provided frame for the duration. Handles screen refresh
111    /// in an async display loop.
112    pub async fn display(&mut self, frame: Frame<COLS, ROWS>, length: Duration) {
113        self.apply(frame);
114        let end = Instant::now() + length;
115        while Instant::now() < end {
116            self.render();
117            Timer::after(REFRESH_INTERVAL).await;
118        }
119        self.clear();
120    }
121
122    /// Scroll the provided text across the LED display using default duration based on text length
123    pub async fn scroll(&mut self, text: &str) {
124        self.scroll_with_speed(text, Duration::from_secs((text.len() / 2) as u64))
125            .await;
126    }
127
128    /// Scroll the provided text across the screen within the provided duration
129    pub async fn scroll_with_speed(&mut self, text: &str, speed: Duration) {
130        self.animate(text.as_bytes(), AnimationEffect::Slide, speed).await;
131    }
132
133    /// Apply animation based on data with the given effect during the provided duration
134    pub async fn animate(&mut self, data: &[u8], effect: AnimationEffect, duration: Duration) {
135        let mut animation: Animation<'_, COLS, ROWS> =
136            Animation::new(AnimationData::Bytes(data), effect, duration).unwrap();
137        loop {
138            match animation.next(Instant::now()) {
139                AnimationState::Apply(f) => {
140                    self.apply(f);
141                }
142                AnimationState::Wait => {}
143                AnimationState::Done => {
144                    break;
145                }
146            }
147            self.render();
148            Timer::after(REFRESH_INTERVAL).await;
149        }
150        self.clear();
151    }
152
153    /// Animate a slice of frames using the provided effect during the provided duration
154    pub async fn animate_frames(&mut self, data: &[Frame<COLS, ROWS>], effect: AnimationEffect, duration: Duration) {
155        let mut animation: Animation<'_, COLS, ROWS> =
156            Animation::new(AnimationData::Frames(data), effect, duration).unwrap();
157        loop {
158            match animation.next(Instant::now()) {
159                AnimationState::Apply(f) => {
160                    self.apply(f);
161                }
162                AnimationState::Wait => {}
163                AnimationState::Done => {
164                    break;
165                }
166            }
167            self.render();
168            Timer::after(REFRESH_INTERVAL).await;
169        }
170        self.clear();
171    }
172
173    /// Disassemble the `LedMatrix` and return the pins, as
174    /// an array of row pins and an array of column pins.
175    pub fn into_inner(self) -> ([P; ROWS], [P;COLS]) {
176        (self.pin_rows, self.pin_cols)
177    }
178}
179
180/// An effect filter to apply for an animation
181#[derive(Clone, Copy)]
182pub enum AnimationEffect {
183    /// No effect
184    None,
185    /// Sliding effect
186    Slide,
187}
188
189enum AnimationData<'a, const XSIZE: usize, const YSIZE: usize> {
190    Frames(&'a [Frame<XSIZE, YSIZE>]),
191    Bytes(&'a [u8]),
192}
193
194impl<'a, const XSIZE: usize, const YSIZE: usize> AnimationData<'a, XSIZE, YSIZE> {
195    fn len(&self) -> usize {
196        match self {
197            AnimationData::Frames(f) => f.len(),
198            AnimationData::Bytes(f) => f.len(),
199        }
200    }
201
202    fn frame(&self, idx: usize) -> Frame<XSIZE, YSIZE> {
203        match self {
204            AnimationData::Frames(f) => f[idx],
205            AnimationData::Bytes(f) => f[idx].into(),
206        }
207    }
208}
209
210struct Animation<'a, const XSIZE: usize, const YSIZE: usize> {
211    frames: AnimationData<'a, XSIZE, YSIZE>,
212    sequence: usize,
213    frame_index: usize,
214    index: usize,
215    length: usize,
216    effect: AnimationEffect,
217    wait: Duration,
218    next: Instant,
219}
220
221#[derive(PartialEq, Debug)]
222enum AnimationState<const XSIZE: usize, const YSIZE: usize> {
223    Wait,
224    Apply(Frame<XSIZE, YSIZE>),
225    Done,
226}
227
228impl<'a, const XSIZE: usize, const YSIZE: usize> Animation<'a, XSIZE, YSIZE> {
229    pub fn new(
230        frames: AnimationData<'a, XSIZE, YSIZE>,
231        effect: AnimationEffect,
232        duration: Duration,
233    ) -> Result<Self, AnimationError> {
234        assert!(frames.len() > 0);
235        let length = match effect {
236            AnimationEffect::Slide => frames.len() * XSIZE,
237            AnimationEffect::None => frames.len(),
238        };
239
240        if let Some(wait) = duration.checked_div(length as u32) {
241            Ok(Self {
242                frames,
243                frame_index: 0,
244                sequence: 0,
245                index: 0,
246                length,
247                effect,
248                wait,
249                next: Instant::now(),
250            })
251        } else {
252            Err(AnimationError::TooFast)
253        }
254    }
255    fn current(&self) -> Frame<XSIZE, YSIZE> {
256        let mut current = self.frames.frame(self.frame_index);
257
258        let mut next = if self.frame_index < self.frames.len() - 1 {
259            self.frames.frame(self.frame_index + 1)
260        } else {
261            Frame::empty()
262        };
263
264        current.shift_left(self.sequence);
265        next.shift_right(XSIZE - self.sequence);
266
267        current.or(&next);
268        current
269    }
270
271    fn next(&mut self, now: Instant) -> AnimationState<XSIZE, YSIZE> {
272        if self.next <= now {
273            if self.index < self.length {
274                let current = self.current();
275                if self.sequence >= XSIZE - 1 {
276                    self.sequence = match self.effect {
277                        AnimationEffect::None => XSIZE,
278                        AnimationEffect::Slide => 0,
279                    };
280                    self.frame_index += 1;
281                } else {
282                    self.sequence += 1;
283                }
284
285                self.index += 1;
286                self.next += self.wait;
287                AnimationState::Apply(current)
288            } else {
289                AnimationState::Done
290            }
291        } else {
292            AnimationState::Wait
293        }
294    }
295}
296
297#[derive(Debug, Clone, Copy)]
298#[cfg_attr(feature = "defmt", derive(defmt::Format))]
299/// Errors produced when running animations
300pub enum AnimationError {
301    /// Animation scroll is too fast to keep up with the refresh rate
302    TooFast,
303}
304
305/*
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_animation() {
312        let mut animation: Animation<5, 5> = Animation::new(
313            AnimationData::Bytes(b"12"),
314            AnimationEffect::Slide,
315            Duration::from_secs(1),
316        )
317        .unwrap();
318
319        let expected = animation.length;
320        let mut n = 0;
321        while n < expected {
322            if let AnimationState::Apply(c) =
323                animation.next(Instant::now() + Duration::from_secs(1))
324            {
325                println!("C ({}): \n{:#?}", n, c);
326                n += 1;
327            } else {
328                break;
329            }
330        }
331        assert!(animation.next(Instant::now() + Duration::from_secs(1)) == AnimationState::Done);
332    }
333
334    #[test]
335    fn test_animation_length() {
336        let animation: Animation<5, 5> = Animation::new(
337            AnimationData::Bytes(b"12"),
338            AnimationEffect::Slide,
339            Duration::from_secs(1),
340        )
341        .unwrap();
342
343        assert_eq!(animation.length, 10);
344
345        let animation: Animation<5, 5> = Animation::new(
346            AnimationData::Bytes(b"123"),
347            AnimationEffect::Slide,
348            Duration::from_secs(1),
349        )
350        .unwrap();
351
352        assert_eq!(animation.length, 15);
353
354        let animation: Animation<5, 5> = Animation::new(
355            AnimationData::Bytes(b"1234"),
356            AnimationEffect::Slide,
357            Duration::from_secs(1),
358        )
359        .unwrap();
360
361        assert_eq!(animation.length, 20);
362    }
363}
364*/