initial commit
This commit is contained in:
commit
c373ba5859
20 changed files with 56272 additions and 0 deletions
35
src/buttons.rs
Normal file
35
src/buttons.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
//! micro:bit buttons to switch patterns.
|
||||
|
||||
use embassy_executor::{SpawnError, Spawner};
|
||||
use embassy_nrf::gpio::{AnyPin, Input, Pull};
|
||||
|
||||
use crate::state;
|
||||
|
||||
/// Wait for a button press on the provided pin and then publish the provided message.
|
||||
#[embassy_executor::task(pool_size = 2)]
|
||||
async fn pattern_switch(button: AnyPin, pattern_move: state::StateChange) {
|
||||
let mut button = Input::new(button, Pull::Up);
|
||||
let state_pub = state::STATE.publisher().unwrap();
|
||||
|
||||
loop {
|
||||
button.wait_for_low().await;
|
||||
button.wait_for_high().await;
|
||||
state_pub.publish_immediate(pattern_move);
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn tasks for the left and right button.
|
||||
/// The left button sends a PatternLeft message, the right button a PatternRight.
|
||||
pub fn spawn(
|
||||
spawner: Spawner,
|
||||
button_left: AnyPin,
|
||||
button_right: AnyPin,
|
||||
) -> Result<(), SpawnError> {
|
||||
spawner.spawn(pattern_switch(button_left, state::StateChange::PatternLeft))?;
|
||||
spawner.spawn(pattern_switch(
|
||||
button_right,
|
||||
state::StateChange::PatternRight,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
84
src/leds.rs
Normal file
84
src/leds.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
//! Driver for the micro:bit led display.
|
||||
|
||||
use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive};
|
||||
use embassy_time::Timer;
|
||||
|
||||
use crate::{patterns::PATTERN_START, state};
|
||||
|
||||
pub const ROWS: usize = 5;
|
||||
pub const COLS: usize = 5;
|
||||
|
||||
pub type Matrix = [[u8; COLS]; ROWS];
|
||||
|
||||
pub struct Display<'a> {
|
||||
rows: [Output<'a>; ROWS],
|
||||
cols: [Output<'a>; COLS],
|
||||
matrix: Matrix,
|
||||
}
|
||||
|
||||
/// Create a low Output from an AnyPin instance.
|
||||
fn pin_to_output<'a>(x: AnyPin) -> Output<'a> {
|
||||
Output::new(x, Level::Low, OutputDrive::Standard)
|
||||
}
|
||||
|
||||
impl<'a> Display<'a> {
|
||||
/// Initialize the display with the provided pins.
|
||||
pub fn new(rows: [AnyPin; ROWS], cols: [AnyPin; COLS]) -> Self {
|
||||
let rows = rows.map(pin_to_output);
|
||||
let cols = cols.map(pin_to_output);
|
||||
|
||||
Self {
|
||||
rows,
|
||||
cols,
|
||||
matrix: [[0; COLS]; ROWS],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set which leds are active.
|
||||
pub fn set(&mut self, matrix: Matrix) {
|
||||
self.matrix = matrix;
|
||||
}
|
||||
|
||||
/// Show the defined pattern on the leds.
|
||||
pub async fn show(&mut self) {
|
||||
for (i, row) in self.matrix.iter().enumerate() {
|
||||
self.rows[i].set_high();
|
||||
|
||||
for (j, cell) in row.iter().enumerate() {
|
||||
if *cell > 0 {
|
||||
self.cols[j].set_low();
|
||||
}
|
||||
}
|
||||
// TODO: instead of waiting for a set time, try
|
||||
// implementing some kind of constant refresh rate
|
||||
Timer::after_micros(10).await;
|
||||
|
||||
for col in &mut self.cols {
|
||||
col.set_high();
|
||||
}
|
||||
|
||||
self.rows[i].set_low();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the active pattern on the led screen.
|
||||
/// React on new pattern messages by showing the new pattern.
|
||||
#[embassy_executor::task]
|
||||
pub async fn task(rows: [AnyPin; ROWS], cols: [AnyPin; COLS]) {
|
||||
let mut state_sub = state::STATE.subscriber().unwrap();
|
||||
|
||||
let mut display = Display::new(rows, cols);
|
||||
display.set(PATTERN_START);
|
||||
|
||||
loop {
|
||||
if let Some(msg) = state_sub.try_next_message_pure() {
|
||||
if let state::StateChange::Pattern(new_pattern) = msg {
|
||||
display.set(new_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
display.show().await;
|
||||
Timer::after_micros(10).await;
|
||||
}
|
||||
}
|
45
src/main.rs
Normal file
45
src/main.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//! Show predefined patterns on the micro:bit led display and switch between them using its
|
||||
//! buttons.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_nrf::gpio::Pin;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
mod buttons;
|
||||
mod leds;
|
||||
mod patterns;
|
||||
mod state;
|
||||
|
||||
/// Setup the necessary micro:bit hardware and spawn the worker tasks.
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = embassy_nrf::init(Default::default());
|
||||
let rows = [
|
||||
p.P0_21.degrade(),
|
||||
p.P0_22.degrade(),
|
||||
p.P0_15.degrade(),
|
||||
p.P0_24.degrade(),
|
||||
p.P0_19.degrade(),
|
||||
];
|
||||
let cols = [
|
||||
p.P0_28.degrade(),
|
||||
p.P0_11.degrade(),
|
||||
p.P0_31.degrade(),
|
||||
p.P1_05.degrade(),
|
||||
p.P0_30.degrade(),
|
||||
];
|
||||
|
||||
let button_left = p.P0_14.degrade();
|
||||
let button_right = p.P0_23.degrade();
|
||||
|
||||
spawner
|
||||
.spawn(state::task())
|
||||
.expect("state task failed to start");
|
||||
spawner
|
||||
.spawn(leds::task(rows, cols))
|
||||
.expect("led task failed to start");
|
||||
buttons::spawn(spawner, button_left, button_right).expect("button task failed to start");
|
||||
}
|
31
src/patterns.rs
Normal file
31
src/patterns.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
//! Predefined patterns for the led display.
|
||||
|
||||
use crate::leds::Matrix;
|
||||
|
||||
pub const HEART: Matrix = [
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
];
|
||||
|
||||
pub const CIRCLE: Matrix = [
|
||||
[0, 1, 1, 1, 0],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
];
|
||||
|
||||
pub const CROSS: Matrix = [
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
];
|
||||
|
||||
pub const PATTERN_COUNT: usize = 3;
|
||||
pub const PATTERN_START: Matrix = HEART;
|
||||
pub const PATTERNS: [Matrix; PATTERN_COUNT] = [HEART, CIRCLE, CROSS];
|
87
src/state.rs
Normal file
87
src/state.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
//! Bare bones state with pubsub access.
|
||||
//! For more ergonomic access it might be nice to allow clients to subscribe to only the parts they
|
||||
//! are interested in instead of the whole thing.
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pubsub::PubSubChannel};
|
||||
|
||||
use crate::{
|
||||
leds::Matrix,
|
||||
patterns::{PATTERNS, PATTERN_COUNT},
|
||||
};
|
||||
|
||||
pub struct State {
|
||||
pattern_idx: usize,
|
||||
patterns: [Matrix; PATTERN_COUNT],
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pattern_idx: Default::default(),
|
||||
patterns: PATTERNS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Switch to the next pattern on the left.
|
||||
/// Wrap around to the last if it is already the first one.
|
||||
pub fn pattern_left(&mut self) {
|
||||
self.pattern_idx = if self.pattern_idx == 0 {
|
||||
self.patterns.len() - 1
|
||||
} else {
|
||||
self.pattern_idx - 1
|
||||
};
|
||||
}
|
||||
|
||||
/// Switch to the next pattern on the right.
|
||||
/// Wrap around to the first if it is already the last one.
|
||||
pub fn pattern_right(&mut self) {
|
||||
self.pattern_idx = if self.pattern_idx == self.patterns.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
self.pattern_idx + 1
|
||||
};
|
||||
}
|
||||
|
||||
/// Return the active patern.
|
||||
pub fn pattern(&self) -> Matrix {
|
||||
self.patterns[self.pattern_idx]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum StateChange {
|
||||
/// Switch to the paatternon the left of the active one.
|
||||
PatternLeft,
|
||||
/// Switch to the paatternon the right of the active one.
|
||||
PatternRight,
|
||||
/// Publish a new active pattern.
|
||||
Pattern(Matrix),
|
||||
}
|
||||
|
||||
/// PubSub channel to listen to state changes and publish them as well.
|
||||
pub static STATE: PubSubChannel<CriticalSectionRawMutex, StateChange, 4, 4, 4> =
|
||||
PubSubChannel::new();
|
||||
|
||||
/// Subscribe to state change messages and update the state accordingly.
|
||||
/// Publish when a new pattern is active.
|
||||
#[embassy_executor::task]
|
||||
pub async fn task() {
|
||||
let mut state = State::default();
|
||||
let state_pub = STATE.publisher().unwrap();
|
||||
let mut state_sub = STATE.subscriber().unwrap();
|
||||
|
||||
loop {
|
||||
let old_pattern = state.pattern();
|
||||
match state_sub.next_message_pure().await {
|
||||
StateChange::PatternLeft => state.pattern_left(),
|
||||
StateChange::PatternRight => state.pattern_right(),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if old_pattern != state.pattern() {
|
||||
state_pub.publish_immediate(StateChange::Pattern(state.pattern()));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue