Skip to content

Add a nonblocking LED display driver #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ cortex-m = "0.5.8"
cortex-m-rt = "0.6.7"
nb = "0.1.1"
nrf51-hal = "0.6.0"
tiny-led-matrix = "0.1"

[dev-dependencies]
cortex-m-semihosting = "0.3.2"
Expand Down
117 changes: 117 additions & 0 deletions examples/led_nonblocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#![no_main]
#![no_std]

#[allow(unused)]
use panic_halt;

use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;

use microbit::display::image::GreyscaleImage;
use microbit::display::{self, Display, Frame, MicrobitFrame};
use microbit::hal::nrf51::{interrupt, GPIO, RTC0, TIMER1};

fn heart_image(inner_brightness: u8) -> GreyscaleImage {
let b = inner_brightness;
GreyscaleImage::new(&[
[0, 7, 0, 7, 0],
[7, b, 7, b, 7],
[7, b, b, b, 7],
[0, 7, b, 7, 0],
[0, 0, 7, 0, 0],
])
}

// We use TIMER1 to drive the display, and RTC0 to update the animation.
// We set the TIMER1 interrupt to a higher priority than RTC0.

static GPIO: Mutex<RefCell<Option<GPIO>>> = Mutex::new(RefCell::new(None));
static RTC0: Mutex<RefCell<Option<RTC0>>> = Mutex::new(RefCell::new(None));
static TIMER1: Mutex<RefCell<Option<TIMER1>>> = Mutex::new(RefCell::new(None));
static DISPLAY: Mutex<RefCell<Option<Display<MicrobitFrame>>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
if let Some(mut p) = microbit::Peripherals::take() {
// Starting the low-frequency clock (needed for RTC to work)
p.CLOCK.tasks_lfclkstart.write(|w| unsafe { w.bits(1) });
while p.CLOCK.events_lfclkstarted.read().bits() == 0 {}
p.CLOCK.events_lfclkstarted.write(|w| unsafe { w.bits(0) });

// 16Hz; 62.5ms period
p.RTC0.prescaler.write(|w| unsafe { w.bits(2047) });
p.RTC0.evtenset.write(|w| w.tick().set_bit());
p.RTC0.intenset.write(|w| w.tick().set_bit());
p.RTC0.tasks_start.write(|w| unsafe { w.bits(1) });

display::initialise_display(&mut p.TIMER1, &mut p.GPIO);

cortex_m::interrupt::free(move |cs| {
*GPIO.borrow(cs).borrow_mut() = Some(p.GPIO);
*RTC0.borrow(cs).borrow_mut() = Some(p.RTC0);
*TIMER1.borrow(cs).borrow_mut() = Some(p.TIMER1);
*DISPLAY.borrow(cs).borrow_mut() = Some(Display::new());
});

if let Some(mut cp) = Peripherals::take() {
unsafe {
cp.NVIC.set_priority(microbit::Interrupt::RTC0, 64);
cp.NVIC.set_priority(microbit::Interrupt::TIMER1, 128);
}
cp.NVIC.enable(microbit::Interrupt::RTC0);
cp.NVIC.enable(microbit::Interrupt::TIMER1);
microbit::NVIC::unpend(microbit::Interrupt::RTC0);
microbit::NVIC::unpend(microbit::Interrupt::TIMER1);
}
}

loop {
continue;
}
}

#[interrupt]
fn TIMER1() {
cortex_m::interrupt::free(|cs| {
if let Some(timer1) = TIMER1.borrow(cs).borrow_mut().as_mut() {
if let Some(gpio) = GPIO.borrow(cs).borrow_mut().as_mut() {
if let Some(d) = DISPLAY.borrow(cs).borrow_mut().as_mut() {
display::handle_display_event(d, timer1, gpio);
}
}
}
});
}

#[interrupt]
fn RTC0() {
static mut STEP: u8 = 0;
static mut FRAME: MicrobitFrame = MicrobitFrame::const_default();

cortex_m::interrupt::free(|cs| {
if let Some(rtc0) = RTC0.borrow(cs).borrow().as_ref() {
rtc0.events_tick.write(|w| unsafe { w.bits(0) });
}
});

let inner_brightness = match *STEP {
0..=8 => 9 - *STEP,
9..=12 => 0,
_ => unreachable!(),
};

FRAME.set(&mut heart_image(inner_brightness));

cortex_m::interrupt::free(|cs| {
if let Some(d) = DISPLAY.borrow(cs).borrow_mut().as_mut() {
d.set_frame(&FRAME);
}
});

*STEP += 1;
if *STEP == 13 {
*STEP = 0
};
}
98 changes: 98 additions & 0 deletions src/display/doc_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! A complete working example.
//!
//! This requires `cortex-m-rtfm` v0.4.1.
//!
//! It uses `TIMER1` to drive the display, and `RTC0` to update a simple
//! animated image.
//!
//! A version of this code which doesn't use `cortex-m-rtfm` is as
//! `examples/led_nonblocking.rs`.
//!
//! ```
//! #![no_main]
//! #![no_std]
//!
//! #[allow(unused)]
//! use panic_halt;
//!
//! use microbit::display::image::GreyscaleImage;
//! use microbit::display::{self, Display, Frame, MicrobitFrame};
//! use microbit::hal::nrf51;
//! use rtfm::app;
//!
//! fn heart_image(inner_brightness: u8) -> GreyscaleImage {
//! let b = inner_brightness;
//! GreyscaleImage::new(&[
//! [0, 7, 0, 7, 0],
//! [7, b, 7, b, 7],
//! [7, b, b, b, 7],
//! [0, 7, b, 7, 0],
//! [0, 0, 7, 0, 0],
//! ])
//! }
//!
//! #[app(device = microbit::hal::nrf51)]
//! const APP: () = {
//! static mut GPIO: nrf51::GPIO = ();
//! static mut TIMER1: nrf51::TIMER1 = ();
//! static mut RTC0: nrf51::RTC0 = ();
//! static mut DISPLAY: Display<MicrobitFrame> = ();
//!
//! #[init]
//! fn init() -> init::LateResources {
//! let mut p: nrf51::Peripherals = device;
//!
//! // Starting the low-frequency clock (needed for RTC to work)
//! p.CLOCK.tasks_lfclkstart.write(|w| unsafe { w.bits(1) });
//! while p.CLOCK.events_lfclkstarted.read().bits() == 0 {}
//! p.CLOCK.events_lfclkstarted.write(|w| unsafe { w.bits(0) });
//!
//! // 16Hz; 62.5ms period
//! p.RTC0.prescaler.write(|w| unsafe { w.bits(2047) });
//! p.RTC0.evtenset.write(|w| w.tick().set_bit());
//! p.RTC0.intenset.write(|w| w.tick().set_bit());
//! p.RTC0.tasks_start.write(|w| unsafe { w.bits(1) });
//!
//! display::initialise_display(&mut p.TIMER1, &mut p.GPIO);
//!
//! init::LateResources {
//! GPIO: p.GPIO,
//! TIMER1: p.TIMER1,
//! RTC0: p.RTC0,
//! DISPLAY: Display::new(),
//! }
//! }
//!
//! #[interrupt(priority = 2,
//! resources = [TIMER1, GPIO, DISPLAY])]
//! fn TIMER1() {
//! display::handle_display_event(&mut resources.DISPLAY, resources.TIMER1, resources.GPIO);
//! }
//!
//! #[interrupt(priority = 1,
//! resources = [RTC0, DISPLAY])]
//! fn RTC0() {
//! static mut FRAME: MicrobitFrame = MicrobitFrame::const_default();
//! static mut STEP: u8 = 0;
//!
//! let event_reg = &resources.RTC0.events_tick;
//! event_reg.write(|w| unsafe { w.bits(0) });
//!
//! let inner_brightness = match *STEP {
//! 0..=8 => 9 - *STEP,
//! 9..=12 => 0,
//! _ => unreachable!(),
//! };
//!
//! FRAME.set(&mut heart_image(inner_brightness));
//! resources.DISPLAY.lock(|display| {
//! display.set_frame(FRAME);
//! });
//!
//! *STEP += 1;
//! if *STEP == 13 {
//! *STEP = 0
//! };
//! }
//! };
//! ```
112 changes: 112 additions & 0 deletions src/display/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Static 5×5 greyscale and black-and-white images.

use tiny_led_matrix::{Render, MAX_BRIGHTNESS};

/// A 5×5 image supporting the full range of brightnesses for each LED.
///
/// Uses 25 bytes of storage.
#[derive(Copy, Clone, Debug)]
pub struct GreyscaleImage([[u8; 5]; 5]);

impl GreyscaleImage {
/// Constructs a GreyscaleImage from an array of brightnesses.
///
/// The data should be an array of 5 rows (top first), each of which is an
/// array of 5 brightness values (left first).
///
/// # Example
///
/// ```
/// const GREY_HEART: GreyscaleImage = GreyscaleImage::new(&[
/// [0, 9, 0, 9, 0],
/// [9, 5, 9, 5, 9],
/// [9, 5, 5, 5, 9],
/// [0, 9, 5, 9, 0],
/// [0, 0, 9, 0, 0],
/// ]);
/// ```
pub const fn new(data: &[[u8; 5]; 5]) -> GreyscaleImage {
GreyscaleImage(*data)
}

pub const fn blank() -> GreyscaleImage {
GreyscaleImage([[0; 5]; 5])
}
}

impl Render for GreyscaleImage {
fn brightness_at(&self, x: usize, y: usize) -> u8 {
self.0[y][x]
}
}

impl Render for &GreyscaleImage {
fn brightness_at(&self, x: usize, y: usize) -> u8 {
GreyscaleImage::brightness_at(self, x, y)
}
}

/// A 5×5 image supporting only two levels of brightness (on and off).
///
/// Uses 5 bytes of storage.
///
/// For display, each pixel is treated as having brightness either 0 or
/// MAX_BRIGHTNESS.
#[derive(Copy, Clone, Debug)]
pub struct BitImage([u8; 5]);

impl BitImage {
/// Constructs a BitImage from an array of brightnesses.
///
/// The data should be an array of 5 rows (top first), each of which is an
/// array of 5 values (left first). Each value should be either 0 or 1.
///
/// # Example
///
/// ```
/// const HEART: BitImage = BitImage::new(&[
/// [0, 1, 0, 1, 0],
/// [1, 0, 1, 0, 1],
/// [1, 0, 0, 0, 1],
/// [0, 1, 0, 1, 0],
/// [0, 0, 1, 0, 0],
/// ]);
/// ```
pub const fn new(im: &[[u8; 5]; 5]) -> BitImage {
// FIXME: can we reject values other than 0 or 1?
const fn row_byte(row: [u8; 5]) -> u8 {
row[0] | row[1] << 1 | row[2] << 2 | row[3] << 3 | row[4] << 4
};
BitImage([
row_byte(im[0]),
row_byte(im[1]),
row_byte(im[2]),
row_byte(im[3]),
row_byte(im[4]),
])
}

/// Returns a new blank BitImage.
///
/// All pixel values are 0.
pub const fn blank() -> BitImage {
BitImage([0; 5])
}
}

impl Render for BitImage {
fn brightness_at(&self, x: usize, y: usize) -> u8 {
let rowdata = self.0[y];
if rowdata & (1 << x) != 0 {
MAX_BRIGHTNESS as u8
} else {
0
}
}
}

impl Render for &BitImage {
fn brightness_at(&self, x: usize, y: usize) -> u8 {
BitImage::brightness_at(self, x, y)
}
}
Loading