Skip to content

Add a nonblocking LED display driver (draft 2) #16

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 6 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.6.1"
cortex-m-rt = "0.6.10"
nb = "0.1.2"
nrf51-hal = "0.7.0"
tiny-led-matrix = "1.0"

[dev-dependencies]
cortex-m-semihosting = "0.3.5"
Expand Down
119 changes: 119 additions & 0 deletions examples/led_nonblocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#![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, MicrobitDisplayTimer, MicrobitFrame};
use microbit::hal::lo_res_timer::{LoResTimer, FREQ_16HZ};
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 ANIM_TIMER: Mutex<RefCell<Option<LoResTimer<RTC0>>>> = Mutex::new(RefCell::new(None));
static DISPLAY_TIMER: Mutex<RefCell<Option<MicrobitDisplayTimer<TIMER1>>>> = Mutex::new(RefCell::new(None));
static DISPLAY: Mutex<RefCell<Option<Display<MicrobitFrame>>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
if let Some(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.reset();

cortex_m::interrupt::free(move |cs| {
let mut rtc0 = LoResTimer::new(p.RTC0);
// 62.5ms period
rtc0.set_frequency(FREQ_16HZ);
rtc0.enable_tick_event();
rtc0.enable_tick_interrupt();
rtc0.start();

let mut timer = MicrobitDisplayTimer::new(p.TIMER1);
let mut gpio = p.GPIO;
display::initialise_display(&mut timer, &mut gpio);
*GPIO.borrow(cs).borrow_mut() = Some(gpio);
*ANIM_TIMER.borrow(cs).borrow_mut() = Some(rtc0);
*DISPLAY_TIMER.borrow(cs).borrow_mut() = Some(timer);
*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(timer) = DISPLAY_TIMER.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, timer, gpio);
}
}
}
});
}

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

cortex_m::interrupt::free(|cs| {
if let Some(rtc) = ANIM_TIMER.borrow(cs).borrow_mut().as_mut() {
rtc.clear_tick_event();
}
});

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
};
}
77 changes: 77 additions & 0 deletions src/display/control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Implementation of [`DisplayControl`] for the micro:bit's GPIO peripheral.
//!
//! This controls the micro:bit's 5×5 LED display.
//!
//! [`DisplayControl`]: tiny_led_matrix::DisplayControl

use crate::hal::nrf51;
use tiny_led_matrix::DisplayControl;

const fn bit_range(lo: usize, count: usize) -> u32 {
return ((1 << count) - 1) << lo;
}

pub(crate) const MATRIX_COLS: usize = 9;
const FIRST_COL_PIN: usize = 4;
const LAST_COL_PIN: usize = FIRST_COL_PIN + MATRIX_COLS - 1;
const COL_BITS: u32 = bit_range(FIRST_COL_PIN, MATRIX_COLS);

pub(crate) const MATRIX_ROWS: usize = 3;
const FIRST_ROW_PIN: usize = 13;
const LAST_ROW_PIN: usize = FIRST_ROW_PIN + MATRIX_ROWS - 1;
const ROW_BITS: u32 = bit_range(FIRST_ROW_PIN, MATRIX_ROWS);

/// Wrapper for `nrf51::GPIO` for passing to the display code.
///
/// This implements the `DisplayControl` trait.
///
/// [`DisplayControl`]: tiny_led_matrix::DisplayControl
pub(crate) struct MicrobitGpio<'a>(pub &'a nrf51::GPIO);

/// Returns the GPIO pin numbers corresponding to the columns in a ColumnSet.
fn column_pins(cols: u32) -> u32 {
cols << FIRST_COL_PIN
}

/// Implementation of [`DisplayControl`] for the micro:bit's GPIO peripheral.
///
/// This controls the micro:bit's 5×5 LED display.
///
/// The `initialise_for display` implementation assumes the port is in the
/// state it would have after system reset.
///
/// [`DisplayControl`]: tiny_led_matrix::DisplayControl
impl DisplayControl for MicrobitGpio<'_> {
fn initialise_for_display(&mut self) {
let gpio = &self.0;
for ii in FIRST_COL_PIN..=LAST_COL_PIN {
gpio.pin_cnf[ii].write(|w| w.dir().output());
}
for ii in FIRST_ROW_PIN..=LAST_ROW_PIN {
gpio.pin_cnf[ii].write(|w| w.dir().output());
}

// Set all cols high.
gpio.outset
.write(|w| unsafe { w.bits((FIRST_COL_PIN..=LAST_COL_PIN).map(|pin| 1 << pin).sum()) });
}

fn display_row_leds(&mut self, row: usize, cols: u32) {
let gpio = &self.0;
// To light an LED, we set the row bit and clear the col bit.
let rows_to_set = 1 << (FIRST_ROW_PIN + row);
let rows_to_clear = ROW_BITS ^ rows_to_set;
let cols_to_clear = column_pins(cols);
let cols_to_set = COL_BITS ^ cols_to_clear;

gpio.outset
.write(|w| unsafe { w.bits(rows_to_set | cols_to_set) });
gpio.outclr
.write(|w| unsafe { w.bits(rows_to_clear | cols_to_clear) });
}

fn light_current_row_leds(&mut self, cols: u32) {
let gpio = &self.0;
gpio.outclr.write(|w| unsafe { w.bits(column_pins(cols)) });
}
}
104 changes: 104 additions & 0 deletions src/display/doc_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! 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 available 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, MicrobitDisplayTimer, MicrobitFrame};
//! use microbit::hal::lo_res_timer::{LoResTimer, FREQ_16HZ};
//! 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 DISPLAY_TIMER: MicrobitDisplayTimer<nrf51::TIMER1> = ();
//! static mut ANIM_TIMER: LoResTimer<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.reset();
//!
//! let mut rtc0 = LoResTimer::new(p.RTC0);
//! // 16Hz; 62.5ms period
//! rtc0.set_frequency(FREQ_16HZ);
//! rtc0.enable_tick_event();
//! rtc0.enable_tick_interrupt();
//! rtc0.start();
//!
//! let mut timer = MicrobitDisplayTimer::new(p.TIMER1);
//! display::initialise_display(&mut timer, &mut p.GPIO);
//!
//! init::LateResources {
//! GPIO: p.GPIO,
//! DISPLAY_TIMER: timer,
//! ANIM_TIMER: rtc0,
//! DISPLAY: Display::new(),
//! }
//! }
//!
//! #[interrupt(priority = 2,
//! resources = [DISPLAY_TIMER, GPIO, DISPLAY])]
//! fn TIMER1() {
//! display::handle_display_event(
//! &mut resources.DISPLAY,
//! resources.DISPLAY_TIMER,
//! resources.GPIO,
//! );
//! }
//!
//! #[interrupt(priority = 1,
//! resources = [ANIM_TIMER, DISPLAY])]
//! fn RTC0() {
//! static mut FRAME: MicrobitFrame = MicrobitFrame::const_default();
//! static mut STEP: u8 = 0;
//!
//! &resources.ANIM_TIMER.clear_tick_event();
//!
//! 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
//! };
//! }
//! };
//! ```
Loading