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
//! Support for the 5×5 LED display.
//!
//! # Scope
//!
//! This module supports driving the LED display from a timer interrupt,
//! providing ten levels of brightness for each LED.
//!
//! Much of the implementation is in the separate `tiny-led-matrix` crate.
//!
//! The driver 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.
//!
//! # 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 [`graphics`] module provides a number of implementations of `Render`.
//!
//! # MicrobitDisplay
//!
//! A [`MicrobitDisplay`] instance controls the LEDs and programs a timer.
//! There should normally be a single `MicrobitDisplay` instance in the
//! program.
//!
//! # Frames
//!
//! Types implementing [`Render`] aren't used directly with the
//! [`MicrobitDisplay`]; 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 [`MicrobitDisplay::set_frame()`][set_frame] has to be called in code
//! that can't be interrupted by the display timer.
//!
//! # Timer integration
//!
//! The `MicrobitDisplay` owns a single timer peripheral. It can use the
//! micro:bit's `TIMER0`, `TIMER1`, or `TIMER2`.
//!
//! It 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 `MicrobitDisplay` 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.
//!
//! The function called from the timer interrupt returns a value indicating
//! which kind of interrupt has occurred, so that it's possible to rely on the
//! same timer to perform other tasks every 6ms.
//!
//! ## Technical details
//!
//! The timer is set to 16-bit mode, using a 62.5kHz clock (16 µs ticks). It
//! resets every 375 ticks.
//!
//! # Usage
//!
//! `use rmicrobit::prelude::*` to make trait methods available.
//!
//! Choose a timer to drive the display from (`TIMER0`, `TIMER1`, or
//! `TIMER2`).
//!
//! When your program starts:
//! * use [`GPIO.split_by_kind()`] to get a [`DisplayPins`] struct
//! * create a [`DisplayPort`] by passing that to [`DisplayPort::new()`]
//! * create a [`MicrobitDisplay`] by passing the `DisplayPort` and the
//! timer you chose to [`MicrobitDisplay::new()`]
//!
//! In an interrupt handler for the same timer, call the `MicrobitDisplay`'s
//! [`handle_event()`] method.
//!
//! To change what's displayed: create a [`MicrobitFrame`] instance, use
//! [`.set()`](`Frame::set()`) to put an image (something
//! implementing [`Render`]) in it, then call the `MicrobitDisplay`'s
//! [`set_frame()`][set_frame] method.
//!
//! You can call `set_frame()` at any time, so long as you're not
//! interrupting, or interruptable by, `handle_event()`.
//!
//! Once you've called `set_frame()`, you are free to reuse the
//! `MicrobitFrame`.
//!
//! See [`doc_example`] for a complete working example.
//!
//! [dal]: https://lancaster-university.github.io/microbit-docs/
//! [micropython]: https://microbit-micropython.readthedocs.io/
//!
//! [`DisplayPins`]: crate::gpio::DisplayPins
//! [`GPIO.split_by_kind()`]: crate::gpio::MicrobitGpioExt::split_by_kind
//! [`graphics`]: crate::graphics
//! [set_frame]: MicrobitDisplay::set_frame
//! [`handle_event()`]: MicrobitDisplay::handle_event
//!

#[doc(no_inline)]
pub use tiny_led_matrix::{
    Render,
    MAX_BRIGHTNESS,
    Frame,
    Event as DisplayEvent,
};

mod display_port;
mod microbit_display;
mod matrix;
mod timer;

pub mod doc_example;

pub use display_port::{pin_constants, DisplayPort};
pub use matrix::MicrobitFrame;
pub use microbit_display::MicrobitDisplay;