initial commit

This commit is contained in:
Sebastian Hugentobler 2024-10-17 12:02:00 +02:00
commit c373ba5859
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
20 changed files with 56272 additions and 0 deletions

35
src/buttons.rs Normal file
View 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
View 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
View 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
View 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
View 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()));
}
}
}