Make keys light up when pressed
This commit is contained in:
@ -29,7 +29,7 @@ async fn main(_spawner: Spawner) {
|
||||
let _neopixel_power = Output::new(board.neopixel_power, Level::High);
|
||||
|
||||
let mut neopixel = Ws2812::new(board.PIO0, board.DMA_CH0, board.neopixel.degrade());
|
||||
let mut neopixels_d5 = Ws2812::new(board.PIO1, board.DMA_CH1, board.d5.degrade());
|
||||
let neopixels_d5 = Ws2812::new(board.PIO1, board.DMA_CH1, board.d5.degrade());
|
||||
|
||||
neopixel.write(&[Rgb::new(0xFF, 0x00, 0x00)]).await;
|
||||
usb::setup_logger_and_keyboard(board.USB).await;
|
||||
@ -44,7 +44,6 @@ async fn main(_spawner: Spawner) {
|
||||
};
|
||||
|
||||
let keyboard = KeyboardConfig {
|
||||
layers,
|
||||
pins: [
|
||||
// row 1
|
||||
board.d24.degrade(),
|
||||
@ -69,20 +68,17 @@ async fn main(_spawner: Spawner) {
|
||||
board.scl.degrade(),
|
||||
board.sda.degrade(),
|
||||
],
|
||||
// the index of the LEDs is different than the switch index.
|
||||
// each number is the index of the LED for the switch of that index.
|
||||
led_map: [4, 3, 2, 1, 0, 5, 6, 7, 8, 9, 14, 13, 12, 11, 10, 15, 16, 17],
|
||||
led_driver: neopixels_d5,
|
||||
layers,
|
||||
};
|
||||
|
||||
keyboard.create().await;
|
||||
|
||||
for w in 0usize.. {
|
||||
neopixel.write(&[wheel(w as u8)]).await;
|
||||
neopixels_d5
|
||||
.write(&[
|
||||
wheel((w + 50) as u8),
|
||||
wheel((w + 100) as u8),
|
||||
wheel((w + 150) as u8),
|
||||
wheel((w + 200) as u8),
|
||||
])
|
||||
.await;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,21 +2,39 @@ use core::sync::atomic::{AtomicU16, Ordering};
|
||||
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::gpio::{AnyPin, Input, Pin, Pull};
|
||||
use embassy_rp::{
|
||||
gpio::{AnyPin, Input, Pin, Pull},
|
||||
pio::PioInstanceBase,
|
||||
};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use futures::{select_biased, FutureExt};
|
||||
use log::{debug, error, info, warn};
|
||||
use static_cell::StaticCell;
|
||||
use tgnt::{button::Button, layer::Layer};
|
||||
|
||||
use crate::usb::keyboard::KB_REPORT;
|
||||
|
||||
static CURRENT_LAYER: AtomicU16 = AtomicU16::new(0);
|
||||
use crate::{
|
||||
lights::Lights,
|
||||
usb::keyboard::KB_REPORT,
|
||||
ws2812::{Rgb, Ws2812},
|
||||
};
|
||||
|
||||
pub struct KeyboardConfig {
|
||||
/// Array of input pins of each switch
|
||||
pub pins: [AnyPin; SWITCH_COUNT],
|
||||
/// Array of LED indices of each switch
|
||||
pub led_map: [usize; SWITCH_COUNT],
|
||||
pub led_driver: Ws2812<PioInstanceBase<1>>,
|
||||
pub layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
current_layer: AtomicU16,
|
||||
layers: &'static [Layer],
|
||||
/// Array of LED indices of each switch
|
||||
led_map: [usize; SWITCH_COUNT],
|
||||
lights: Lights<PioInstanceBase<1>, SWITCH_COUNT>,
|
||||
}
|
||||
|
||||
impl KeyboardConfig {
|
||||
pub async fn create(self) {
|
||||
let spawner = Spawner::for_current_executor().await;
|
||||
@ -31,8 +49,15 @@ impl KeyboardConfig {
|
||||
self.layers.len()
|
||||
);
|
||||
|
||||
let layers = Box::leak(self.layers.into_boxed_slice());
|
||||
for (i, layer) in layers.iter().enumerate() {
|
||||
static STATE: StaticCell<State> = StaticCell::new();
|
||||
let state = STATE.init_with(|| State {
|
||||
current_layer: AtomicU16::new(0),
|
||||
layers: Box::leak(self.layers.into_boxed_slice()),
|
||||
lights: Lights::new(self.led_driver),
|
||||
led_map: self.led_map,
|
||||
});
|
||||
|
||||
for (i, layer) in state.layers.iter().enumerate() {
|
||||
if layer.buttons.len() != SWITCH_COUNT {
|
||||
warn!(
|
||||
"layer {i} defines {} buttons, but there are {SWITCH_COUNT} switches",
|
||||
@ -42,7 +67,7 @@ impl KeyboardConfig {
|
||||
}
|
||||
|
||||
for (i, pin) in self.pins.into_iter().enumerate() {
|
||||
if spawner.spawn(switch_task(i, pin, layers)).is_err() {
|
||||
if spawner.spawn(switch_task(i, pin, state)).is_err() {
|
||||
error!("failed to spawn switch task, pool_size mismatch?");
|
||||
break;
|
||||
}
|
||||
@ -50,12 +75,12 @@ impl KeyboardConfig {
|
||||
}
|
||||
}
|
||||
|
||||
const MOD_TAP_TIME: Duration = Duration::from_millis(100);
|
||||
const MOD_TAP_TIME: Duration = Duration::from_millis(150);
|
||||
const SWITCH_COUNT: usize = 18;
|
||||
|
||||
/// Task for monitoring a single switch pin, and handling button presses.
|
||||
#[embassy_executor::task(pool_size = 18)]
|
||||
async fn switch_task(switch_num: usize, pin: AnyPin, layers: &'static [Layer]) -> ! {
|
||||
async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> ! {
|
||||
let _pin_nr = pin.pin();
|
||||
let mut pin = Input::new(pin, Pull::Up);
|
||||
loop {
|
||||
@ -65,15 +90,15 @@ async fn switch_task(switch_num: usize, pin: AnyPin, layers: &'static [Layer]) -
|
||||
// TODO: do we need debouncing?
|
||||
|
||||
// get current layer
|
||||
let mut current_layer = CURRENT_LAYER.load(Ordering::Relaxed);
|
||||
let layer_count = layers.len() as u16;
|
||||
let mut current_layer = state.current_layer.load(Ordering::Relaxed);
|
||||
let layer_count = state.layers.len() as u16;
|
||||
if current_layer >= layer_count {
|
||||
error!("current layer was out of bounds for some reason ({current_layer})");
|
||||
current_layer = 0;
|
||||
}
|
||||
let Some(Layer { buttons }) = layers.get(usize::from(current_layer)) else {
|
||||
let Some(Layer { buttons }) = state.layers.get(usize::from(current_layer)) else {
|
||||
error!("current layer was out of bounds for some reason ({current_layer})");
|
||||
CURRENT_LAYER.store(0, Ordering::Relaxed);
|
||||
state.current_layer.store(0, Ordering::Relaxed);
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -90,50 +115,69 @@ async fn switch_task(switch_num: usize, pin: AnyPin, layers: &'static [Layer]) -
|
||||
debug!("switch {switch_num} button {button:?} released");
|
||||
};
|
||||
|
||||
let set_led = |color: Rgb| {
|
||||
let led_num = state.led_map.get(switch_num).copied();
|
||||
move |leds: &mut [Rgb; SWITCH_COUNT]| {
|
||||
if let Some(led) = led_num.and_then(|i| leds.get_mut(i)) {
|
||||
*led = color;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match button {
|
||||
&Button::Key(key) => {
|
||||
KB_REPORT.lock().await.press_key(key);
|
||||
state.lights.update(set_led(Rgb::new(0, 150, 0))).await;
|
||||
wait_for_release.await;
|
||||
KB_REPORT.lock().await.release_key(key);
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 0))).await;
|
||||
continue;
|
||||
}
|
||||
&Button::Mod(modifier) => {
|
||||
KB_REPORT.lock().await.press_modifier(modifier);
|
||||
state.lights.update(set_led(Rgb::new(100, 100, 0))).await;
|
||||
wait_for_release.await;
|
||||
KB_REPORT.lock().await.release_modifier(modifier);
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 0))).await;
|
||||
continue;
|
||||
}
|
||||
&Button::ModTap { keycode, modifier } => {
|
||||
&Button::ModTap(key, modifier) => {
|
||||
state.lights.update(set_led(Rgb::new(100, 100, 0))).await;
|
||||
select_biased! {
|
||||
_ = Timer::after(MOD_TAP_TIME).fuse() => {
|
||||
KB_REPORT.lock().await.press_modifier(modifier);
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 150))).await;
|
||||
pin.wait_for_high().await;
|
||||
KB_REPORT.lock().await.release_modifier(modifier);
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 0))).await;
|
||||
debug!("switch {switch_num} button {button:?} released");
|
||||
continue;
|
||||
}
|
||||
_ = wait_for_release.fuse() => {
|
||||
KB_REPORT.lock().await.press_key(keycode);
|
||||
KB_REPORT.lock().await.press_key(key);
|
||||
state.lights.update(set_led(Rgb::new(0, 150, 0))).await;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
KB_REPORT.lock().await.release_key(keycode);
|
||||
KB_REPORT.lock().await.release_key(key);
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 0))).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Button::NextLayer => {
|
||||
let next_layer = (current_layer + 1) % layer_count;
|
||||
CURRENT_LAYER.store(next_layer, Ordering::Relaxed);
|
||||
state.current_layer.store(next_layer, Ordering::Relaxed);
|
||||
debug!("switched to layer {next_layer}");
|
||||
}
|
||||
Button::PrevLayer => {
|
||||
let prev_layer = current_layer.checked_sub(1).unwrap_or(layer_count - 1);
|
||||
CURRENT_LAYER.store(prev_layer, Ordering::Relaxed);
|
||||
state.current_layer.store(prev_layer, Ordering::Relaxed);
|
||||
debug!("switched to layer {prev_layer}");
|
||||
}
|
||||
Button::None => {}
|
||||
}
|
||||
|
||||
wait_for_release.await;
|
||||
state.lights.update(set_led(Rgb::new(0, 0, 0))).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ extern crate alloc;
|
||||
pub mod allocator;
|
||||
pub mod board;
|
||||
pub mod keyboard;
|
||||
pub mod lights;
|
||||
pub mod neopixel;
|
||||
pub mod panic_handler;
|
||||
pub mod usb;
|
||||
|
||||
41
src/lights.rs
Normal file
41
src/lights.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::ws2812::Ws2812;
|
||||
use embassy_rp::pio::PioInstance;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
|
||||
use crate::{util::CS, ws2812::Rgb};
|
||||
|
||||
pub struct Lights<P: PioInstance, const N: usize> {
|
||||
state: Mutex<CS, State<P, N>>,
|
||||
}
|
||||
|
||||
struct State<P: PioInstance, const N: usize> {
|
||||
colors: [Rgb; N],
|
||||
driver: Ws2812<P>,
|
||||
}
|
||||
|
||||
impl<P: PioInstance, const N: usize> Lights<P, N> {
|
||||
pub const fn new(driver: Ws2812<P>) -> Self {
|
||||
Lights {
|
||||
state: Mutex::new(State {
|
||||
colors: [Rgb::new(0, 0, 0); N],
|
||||
driver,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn colors_mut(&mut self) -> &[Rgb; N] {
|
||||
&self.state.get_mut().colors
|
||||
}
|
||||
|
||||
/// Run a function to update the colors, and then immediately refresh the LEDs.
|
||||
pub async fn update(&self, f: impl FnOnce(&mut [Rgb; N])) {
|
||||
let State { colors, driver } = &mut *self.state.lock().await;
|
||||
f(colors);
|
||||
driver.write(colors).await
|
||||
}
|
||||
|
||||
/// Update the LEDs with the currently set colors.
|
||||
pub async fn refresh(&self) {
|
||||
self.update(|_| ()).await;
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,9 @@ pub async fn setup_logger_and_keyboard(usb: USB) {
|
||||
}
|
||||
|
||||
pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {
|
||||
let state = STATE.init(State {
|
||||
// calling init here can't panic because this function can't be called
|
||||
// twice since we are taking ownership of the only USB peripheral.
|
||||
let state = STATE.init_with(|| State {
|
||||
device_descriptor: [0; 256],
|
||||
config_descriptor: [0; 256],
|
||||
bos_descriptor: [0; 256],
|
||||
|
||||
@ -15,7 +15,6 @@ use static_cell::StaticCell;
|
||||
|
||||
pub const BUFFER_SIZE: usize = 16 * 1024;
|
||||
static BUFFER: Pipe<CS, BUFFER_SIZE> = Pipe::new();
|
||||
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
|
||||
|
||||
struct UsbLogger;
|
||||
|
||||
@ -23,11 +22,12 @@ pub async fn setup(usb_builder: &mut Builder<'static, Driver<'static, USB>>) {
|
||||
unsafe {
|
||||
static LOGGER: UsbLogger = UsbLogger;
|
||||
log::set_logger_racy(&LOGGER).unwrap();
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
}
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
|
||||
let spawner = Spawner::for_current_executor().await;
|
||||
|
||||
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
|
||||
let state = STATE.init(cdc_acm::State::new());
|
||||
|
||||
let class = CdcAcmClass::new(usb_builder, state, MAX_PACKET_SIZE as u16);
|
||||
@ -35,10 +35,6 @@ pub async fn setup(usb_builder: &mut Builder<'static, Driver<'static, USB>>) {
|
||||
spawner.must_spawn(log_task(class));
|
||||
}
|
||||
|
||||
//pub async fn print(s: &str) {
|
||||
// BUFFER.writer().write_all(s.as_bytes()).await.ok(/* infallible */);
|
||||
//}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn log_task(mut class: CdcAcmClass<'static, Driver<'static, USB>>) {
|
||||
let mut buf = [0u8; MAX_PACKET_SIZE as usize];
|
||||
|
||||
20
src/util.rs
20
src/util.rs
@ -15,13 +15,17 @@ pub async fn stall() -> ! {
|
||||
// The colours are a transition r - g - b - back to r.
|
||||
pub fn wheel(mut wheel_pos: u8) -> Rgb {
|
||||
wheel_pos = 255 - wheel_pos;
|
||||
if wheel_pos < 85 {
|
||||
return Rgb::new(255 - wheel_pos * 3, 0, wheel_pos * 3);
|
||||
}
|
||||
if wheel_pos < 170 {
|
||||
let rgb = if wheel_pos < 85 {
|
||||
Rgb::new(255 - wheel_pos * 3, 0, wheel_pos * 3)
|
||||
} else if wheel_pos < 170 {
|
||||
wheel_pos -= 85;
|
||||
return Rgb::new(0, wheel_pos * 3, 255 - wheel_pos * 3);
|
||||
}
|
||||
wheel_pos -= 170;
|
||||
Rgb::new(wheel_pos * 3, 255 - wheel_pos * 3, 0)
|
||||
Rgb::new(0, wheel_pos * 3, 255 - wheel_pos * 3)
|
||||
} else {
|
||||
wheel_pos -= 170;
|
||||
Rgb::new(wheel_pos * 3, 255 - wheel_pos * 3, 0)
|
||||
};
|
||||
|
||||
// tone the brightness down a bit, sheesh.
|
||||
//rgb / 4
|
||||
rgb
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use core::fmt::{self, Debug};
|
||||
use core::mem::transmute;
|
||||
use core::ops::Div;
|
||||
|
||||
use embassy_rp::dma::{self, AnyChannel};
|
||||
use embassy_rp::pio::{
|
||||
@ -128,3 +129,12 @@ impl Debug for Rgb {
|
||||
f.debug_tuple("Rgb").field(&r).field(&g).field(&b).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<u8> for Rgb {
|
||||
type Output = Rgb;
|
||||
|
||||
fn div(self, d: u8) -> Self::Output {
|
||||
let (r, g, b) = self.components();
|
||||
Rgb::new(r / d, g / d, b / d)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user