This commit is contained in:
2023-06-12 17:39:07 +02:00
parent 59f91e2f6b
commit 9854e0c0a8
45 changed files with 4708 additions and 400 deletions

1
lib/src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
layers.pc

15
lib/src/allocator.rs Normal file
View File

@ -0,0 +1,15 @@
extern crate alloc;
use core::mem::MaybeUninit;
use embedded_alloc::Heap;
pub const HEAP_SIZE: usize = 4096;
#[global_allocator]
static HEAP: Heap = Heap::empty();
pub fn init() {
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}

99
lib/src/board.rs Normal file
View File

@ -0,0 +1,99 @@
use embassy_rp::{peripherals::*, Peripherals};
/// Pinouts for the ItsyBitsy
#[allow(dead_code, non_snake_case)]
pub struct Board {
pub USB: USB,
pub UART0: UART0,
pub UART1: UART1,
pub PIO0: PIO0,
pub PIO1: PIO1,
pub DMA_CH0: DMA_CH0,
pub DMA_CH1: DMA_CH1,
pub DMA_CH2: DMA_CH2,
pub DMA_CH3: DMA_CH3,
pub DMA_CH4: DMA_CH4,
pub DMA_CH5: DMA_CH5,
pub DMA_CH6: DMA_CH6,
pub DMA_CH7: DMA_CH7,
pub DMA_CH8: DMA_CH8,
pub DMA_CH9: DMA_CH9,
pub DMA_CH10: DMA_CH10,
// pins
pub a0: PIN_26,
pub a1: PIN_27,
pub a2: PIN_28,
pub a3: PIN_29,
pub d24: PIN_24,
pub d25: PIN_25,
pub sck: PIN_18,
pub mosi: PIN_19,
pub miso: PIN_20,
pub d2: PIN_12,
pub d3: PIN_5,
pub d4: PIN_4,
pub rx: PIN_1,
pub tx: PIN_0,
pub sda: PIN_2,
pub scl: PIN_3,
pub d5: PIN_14,
pub d7: PIN_6,
pub d9: PIN_7,
pub d10: PIN_8,
pub d11: PIN_9,
pub d12: PIN_10,
pub d13: PIN_11,
pub neopixel: PIN_17,
pub neopixel_power: PIN_16,
}
impl From<Peripherals> for Board {
fn from(p: Peripherals) -> Self {
Board {
USB: p.USB,
UART0: p.UART0,
UART1: p.UART1,
PIO0: p.PIO0,
PIO1: p.PIO1,
DMA_CH0: p.DMA_CH0,
DMA_CH1: p.DMA_CH1,
DMA_CH2: p.DMA_CH2,
DMA_CH3: p.DMA_CH3,
DMA_CH4: p.DMA_CH4,
DMA_CH5: p.DMA_CH5,
DMA_CH6: p.DMA_CH6,
DMA_CH7: p.DMA_CH7,
DMA_CH8: p.DMA_CH8,
DMA_CH9: p.DMA_CH9,
DMA_CH10: p.DMA_CH10,
// pins
a0: p.PIN_26,
a1: p.PIN_27,
a2: p.PIN_28,
a3: p.PIN_29,
d24: p.PIN_24,
d25: p.PIN_25,
sck: p.PIN_18,
mosi: p.PIN_19,
miso: p.PIN_20,
d2: p.PIN_12,
d3: p.PIN_5,
d4: p.PIN_4,
rx: p.PIN_1,
tx: p.PIN_0,
sda: p.PIN_2,
scl: p.PIN_3,
d5: p.PIN_14,
d7: p.PIN_6,
d9: p.PIN_7,
d10: p.PIN_8,
d11: p.PIN_9,
d12: p.PIN_10,
d13: p.PIN_11,
neopixel: p.PIN_17,
neopixel_power: p.PIN_16,
}
}
}

0
lib/src/defmt.rs Normal file
View File

60
lib/src/event.rs Normal file
View File

@ -0,0 +1,60 @@
use core::time::Duration;
use serde::{Deserialize, Serialize};
use tgnt::{button::Button, keys::Key};
pub mod switch {
use super::*;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
/// A switch was pressed or released
///
/// This event is triggered by tasks that monitor switches.
pub struct Event {
/// The keyboard half that triggered the event.
pub source: Half,
/// The index of the button that triggered the event.
pub source_button: usize,
pub kind: EventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum EventKind {
Press {
button: Button,
},
Release {
button: Button,
/// The duration that the button was held down for
after: Duration,
},
}
}
pub mod button {
use tgnt::button::Modifier;
use super::*;
/// A usb keyboard button was pressed or released.
///
/// This is a lower-level event than a [SwitchEvent], as things like ModTap and Compose are
/// converted to Presses and Releases.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
PressKey(Key),
ReleaseKey(Key),
PressMod(Modifier),
ReleaseMod(Modifier),
}
}
/// A keyboard half.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Half {
Left,
Right,
}

11
lib/src/interrupts.rs Normal file
View File

@ -0,0 +1,11 @@
use embassy_rp::{
bind_interrupts,
peripherals::{UART0, USB},
};
bind_interrupts! {
pub struct Irqs {
UART0_IRQ => embassy_rp::uart::BufferedInterruptHandler<UART0>;
USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<USB>;
}
}

300
lib/src/keyboard.rs Normal file
View File

@ -0,0 +1,300 @@
mod lights;
use core::sync::atomic::{AtomicU16, Ordering};
use alloc::{boxed::Box, vec::Vec};
use embassy_executor::Spawner;
use embassy_rp::{
gpio::{AnyPin, Input, Pin, Pull},
peripherals::PIO1,
};
use embassy_sync::pubsub::{ImmediatePublisher, PubSubChannel, Subscriber};
use embassy_time::{Duration, Instant};
use log::{debug, error, info, warn};
use static_cell::StaticCell;
use tgnt::{button::Button, layer::Layer};
use crate::{
event::{
switch::{Event, EventKind},
Half,
},
lights::Lights,
util::CS,
ws2812::Ws2812,
};
pub struct KeyboardConfig {
/// Which board is this.
pub half: Half,
/// 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<PIO1>,
pub layers: Vec<Layer>,
}
struct State {
/// Which board is this.
half: Half,
current_layer: AtomicU16,
layers: &'static [Layer],
/// Array of LED indices of each switch
led_map: [usize; SWITCH_COUNT],
lights: Lights<PIO1, SWITCH_COUNT>,
}
pub const KB_SUBSCRIBERS: usize = 2;
const ACTUAL_KB_SUBSCRIBERS: usize = KB_SUBSCRIBERS + 2;
const KB_EVENT_CAP: usize = 128;
static KB_EVENTS: PubSubChannel<CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0> =
PubSubChannel::new();
pub struct KbEvents {
pub subscriber: Subscriber<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
pub publisher: ImmediatePublisher<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
}
pub struct KbEventsTx<'a> {
publisher:
&'a mut ImmediatePublisher<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
}
pub struct KbEventsRx<'a> {
subscriber: &'a mut Subscriber<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
}
impl KeyboardConfig {
pub async fn create(self) -> Option<[KbEvents; KB_SUBSCRIBERS]> {
let spawner = Spawner::for_current_executor().await;
if self.layers.is_empty() {
error!("no layers defined");
return None;
}
info!(
"setting up keyboard layout with {} layer(s)",
self.layers.len()
);
static STATE: StaticCell<State> = StaticCell::new();
let state = STATE.init_with(|| State {
half: self.half,
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",
layer.buttons.len(),
)
}
}
for (i, pin) in self.pins.into_iter().enumerate() {
if spawner.spawn(switch_task(i, pin, state)).is_err() {
error!("failed to spawn switch task, pool_size mismatch?");
break;
}
}
spawner.must_spawn(layer_switch_task(
KbEvents {
publisher: KB_EVENTS.immediate_publisher(),
subscriber: KB_EVENTS.subscriber().unwrap(),
},
state,
));
spawner.must_spawn(lights::task(
KbEvents {
publisher: KB_EVENTS.immediate_publisher(),
subscriber: KB_EVENTS.subscriber().unwrap(),
},
state,
));
Some([(); KB_SUBSCRIBERS].map(|_| KbEvents {
publisher: KB_EVENTS.immediate_publisher(),
subscriber: KB_EVENTS.subscriber().unwrap(),
}))
}
}
impl KbEvents {
pub async fn send(&mut self, event: Event) {
self.publisher.publish_immediate(event);
}
pub async fn recv(&mut self) -> Event {
self.subscriber.next_message_pure().await
}
pub fn split(&mut self) -> (KbEventsRx, KbEventsTx) {
let tx = KbEventsTx {
publisher: &mut self.publisher,
};
let rx = KbEventsRx {
subscriber: &mut self.subscriber,
};
(rx, tx)
}
}
impl KbEventsRx<'_> {
pub async fn recv(&mut self) -> Event {
self.subscriber.next_message_pure().await
}
}
impl KbEventsTx<'_> {
pub fn send(&mut self, event: Event) {
self.publisher.publish_immediate(event);
}
}
pub const MOD_TAP_TIME: Duration = Duration::from_millis(150);
pub 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, state: &'static State) -> ! {
let _pin_nr = pin.pin();
let mut pin = Input::new(pin, Pull::Up);
let events = KB_EVENTS.immediate_publisher();
loop {
// pins are pull-up, so when the switch is pressed they are brought low.
pin.wait_for_low().await;
let pressed_at = Instant::now();
// TODO: do we need debouncing?
// get current layer
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 }) = state.layers.get(usize::from(current_layer)) else {
error!("current layer was out of bounds for some reason ({current_layer})");
state.current_layer.store(0, Ordering::Relaxed);
continue;
};
// and current button
let Some(button) = buttons.get(switch_num) else {
warn!("no button defined for switch {switch_num}");
continue;
};
debug!("switch {switch_num} button {button:?} pressed");
let ev = |kind| Event {
source: state.half,
source_button: switch_num,
kind,
};
events.publish_immediate(ev(EventKind::Press {
button: button.clone(),
}));
pin.wait_for_high().await;
let released_after = pressed_at.elapsed();
debug!("switch {switch_num} button {button:?} released");
events.publish_immediate(ev(EventKind::Release {
button: button.clone(),
after: released_after.into(),
}));
}
}
#[embassy_executor::task]
async fn layer_switch_task(mut events: KbEvents, state: &'static State) {
let layer_count = state.layers.len() as u16;
let Some(last_layer) = layer_count.checked_sub(1) else {
error!("no layers specified");
return;
};
loop {
let event = events.recv().await;
let layer = state.current_layer.load(Ordering::Relaxed);
let new_layer = match event.kind {
EventKind::Press { button } => match button {
Button::NextLayer => layer.wrapping_add(1) % layer_count,
Button::PrevLayer => layer.checked_sub(1).unwrap_or(last_layer),
Button::HoldLayer(l) => layer.wrapping_add(l) % layer_count,
_ => continue,
},
EventKind::Release { button, .. } => match button {
Button::HoldLayer(l) => layer.checked_sub(l).unwrap_or(last_layer),
_ => continue,
},
};
state.current_layer.store(new_layer, Ordering::Relaxed);
debug!("switched to layer {new_layer}");
}
}
/// Random functions for testing
#[allow(dead_code)]
pub mod test {
use tgnt::{button::Button, keys::Key};
pub fn letter_to_key(c: char) -> Button {
if !c.is_ascii() {
return Button::None;
}
let c = c.to_ascii_uppercase();
let key = match c {
'A' => Key::A,
'B' => Key::B,
'C' => Key::C,
'D' => Key::D,
'E' => Key::E,
'F' => Key::F,
'G' => Key::G,
'H' => Key::H,
'I' => Key::I,
'J' => Key::J,
'K' => Key::K,
'L' => Key::L,
'M' => Key::M,
'N' => Key::N,
'O' => Key::O,
'P' => Key::P,
'Q' => Key::Q,
'R' => Key::R,
'S' => Key::S,
'T' => Key::T,
'U' => Key::U,
'V' => Key::V,
'W' => Key::W,
'X' => Key::X,
'Y' => Key::Y,
'Z' => Key::Z,
' ' => Key::Space,
'\n' => Key::Return,
_ => {
log::info!("char {c:?} -> None");
return Button::None;
}
};
log::info!("char {c:?} -> {key:?}");
Button::Key(key)
}
}

196
lib/src/keyboard/lights.rs Normal file
View File

@ -0,0 +1,196 @@
use core::cmp::min;
use atomic_polyfill::Ordering;
use embassy_futures::yield_now;
use embassy_time::{Duration, Instant, Timer};
use futures::{select_biased, FutureExt};
use tgnt::button::Button;
use crate::{rgb::Rgb, util::wheel};
use super::{Event, EventKind, KbEvents, State, SWITCH_COUNT};
/// Duration until the keyboard starts the idle animation
const UNTIL_IDLE: Duration = Duration::from_secs(30);
///// Duration from idle until the keyboard goes to sleep
//const UNTIL_SLEEP: Duration = Duration::from_secs(10);
const IDLE_ANIMATION_SPEED: u64 = 3;
const IDLE_ANIMATION_KEY_OFFSET: u64 = 10;
const IDLE_BRIGHTNESS_RAMPUP: Duration = Duration::from_secs(120);
const MAX_IDLE_BRIGHTESS: f32 = 1.0;
const MIN_IDLE_BRIGHTESS: f32 = 0.05;
#[derive(Clone, Copy)]
enum LightState {
Solid(Rgb),
SolidThenFade {
color: Rgb,
solid_until: Instant,
fade_by: f32,
},
FadeBy(f32),
None,
}
#[embassy_executor::task]
pub(super) async fn task(mut events: KbEvents, state: &'static State) {
let mut lights: [LightState; SWITCH_COUNT] = [LightState::None; SWITCH_COUNT];
let mut next_frame = Instant::now();
let mut idle_at = Instant::now() + UNTIL_IDLE;
loop {
select_biased! {
event = events.recv().fuse() => {
handle_event(event, state, &mut lights).await;
idle_at = Instant::now() + UNTIL_IDLE;
}
_ = Timer::at(next_frame).fuse() => {
tick(state, &mut lights).await;
next_frame = Instant::now() + Duration::from_millis(16);
}
_ = Timer::at(idle_at).fuse() => {
select_biased! {
event = events.recv().fuse() => {
state.lights.update(|lights| {
lights.iter_mut().for_each(|rgb| *rgb = Rgb::new(0, 0, 0));
}).await;
handle_event(event, state, &mut lights).await;
idle_at = Instant::now() + UNTIL_IDLE;
}
_ = idle_animation(state).fuse() => {}
}
}
}
}
}
async fn tick(state: &'static State, lights: &mut [LightState; SWITCH_COUNT]) {
let now = Instant::now();
state
.lights
.update(|rgbs| {
for (button, light) in lights.iter_mut().enumerate() {
let Some(&led_id) = state.led_map.get(button) else { continue; };
let Some(rgb) = rgbs.get_mut(led_id) else { continue; };
match &*light {
LightState::None => {}
LightState::FadeBy(fade) => {
let [r, g, b] = rgb
.components()
.map(|c| ((c as f32) * fade.clamp(0.0, 1.0)) as u8);
*rgb = Rgb::new(r, g, b);
if *rgb == Rgb::new(0, 0, 0) {
*light = LightState::None;
}
}
&LightState::Solid(color) => *rgb = color,
&LightState::SolidThenFade {
color,
solid_until,
fade_by,
} => {
*rgb = color;
if now >= solid_until {
*light = LightState::FadeBy(fade_by);
}
}
}
}
})
.await;
}
async fn idle_animation(state: &'static State) {
for tick in 0.. {
const FRAMETIME: Duration = Duration::from_millis(16);
state
.lights
.update(|lights| {
const N_MAX: u64 = IDLE_BRIGHTNESS_RAMPUP.as_millis() / FRAMETIME.as_millis();
let brightness = if tick >= N_MAX {
MAX_IDLE_BRIGHTESS
} else {
((tick as f32) / N_MAX as f32).clamp(MIN_IDLE_BRIGHTESS, MAX_IDLE_BRIGHTESS)
};
for (n, &i) in state.led_map.iter().enumerate() {
let Some(light) = lights.get_mut(i) else { continue };
let [r, g, b] = wheel(
(n as u64 * IDLE_ANIMATION_KEY_OFFSET + tick * IDLE_ANIMATION_SPEED) as u8,
)
.components();
let rgb = Rgb::new(
((r as f32) * brightness) as u8,
((g as f32) * brightness) as u8,
((b as f32) * brightness) as u8,
);
*light = rgb;
}
})
.await;
Timer::after(FRAMETIME).await;
}
}
async fn handle_event(
event: Event,
state: &'static State,
lights: &mut [LightState; SWITCH_COUNT],
) {
let rgb = match event.kind {
EventKind::Press { button } => match button {
Button::Key(..) => LightState::Solid(Rgb::new(0, 150, 0)),
Button::Mod(..) => LightState::Solid(Rgb::new(0, 0, 150)),
Button::ModTap(..) => LightState::Solid(Rgb::new(0, 0, 150)),
Button::Compose(..) => LightState::Solid(Rgb::new(0, 100, 100)),
Button::NextLayer | Button::PrevLayer => {
yield_now().await; // dirty hack to make sure layer_switch_task gets to run first
let layer = state.current_layer.load(Ordering::Relaxed);
let layer = min(layer, state.layers.len().saturating_sub(1) as u16);
let buttons_to_light_up = if state.layers.len() <= 3 {
match layer {
0 => [0, 1, 2, 3, 4].as_ref(),
1 => &[5, 6, 7, 8, 9],
2 => &[10, 11, 12, 13, 14],
_ => &[],
}
} else {
match layer {
0 => [0, 5, 10].as_ref(),
1 => &[1, 6, 11],
2 => &[2, 7, 12],
3 => &[3, 8, 13],
4 => &[4, 9, 14],
_ => &[],
}
};
let solid_until = Instant::now() + Duration::from_millis(200);
for &button in buttons_to_light_up {
let Some(light) = lights.get_mut(button) else { continue; };
*light = LightState::SolidThenFade {
color: Rgb::new(120, 0, 120),
solid_until,
fade_by: 0.85,
}
}
LightState::Solid(Rgb::new(100, 0, 100))
}
_ => LightState::Solid(Rgb::new(150, 0, 0)),
},
EventKind::Release { .. } => LightState::FadeBy(0.85),
};
if event.source != state.half {
return;
}
let Some(light) = lights.get_mut(event.source_button) else { return; };
*light = rgb;
}

397
lib/src/keypress_handler.rs Normal file
View File

@ -0,0 +1,397 @@
use core::future::pending;
use embassy_sync::pubsub::{publisher::Pub, subscriber::Sub, PubSubBehavior, WaitResult};
use embassy_time::{Duration, Instant, Timer};
use futures::FutureExt;
use heapless::Deque;
use log::{debug, error};
use tgnt::button::Button;
use crate::event::{button, switch, Half};
// TODO
const MOD_TAP_TIME: Duration = Duration::from_millis(150);
const SWITCH_COUNT: usize = 18;
/// This function perpetually converts between [switch::Event]s and [button::Event]s.
///
/// Call it from a dedicated task.
pub async fn keypress_handler(
input: &mut Sub<'_, impl PubSubBehavior<switch::Event>, switch::Event>,
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
) -> ! {
type SwitchIndex = usize;
#[derive(Debug)]
struct PressEvent {
source_button: SwitchIndex,
source_half: Half,
button: Button,
time: Instant,
}
async fn slow_press(
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
button: &Button,
) {
let event = match button {
&Button::Mod(m) | &Button::ModTap(_, m) => button::Event::PressMod(m),
&Button::Key(k) => button::Event::PressKey(k),
_ => return,
};
output.publish_immediate(event);
}
async fn slow_release(
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
button: &Button,
) {
let event = match button {
&Button::Mod(m) | &Button::ModTap(_, m) => button::Event::ReleaseMod(m),
&Button::Key(k) => button::Event::ReleaseKey(k),
_ => return,
};
output.publish_immediate(event);
}
// queue of button presses that are waiting for ModTap
let mut queue = Deque::<PressEvent, { 2 * SWITCH_COUNT }>::new();
let queue = &mut queue;
loop {
// create a future that waits for the next ModTap to time out
let modtap_timeout = async {
loop {
if let Some(event) = queue.front() {
let Button::ModTap(..) = event.button else {
error!("first element in queue wasn't a modtap, wtf?");
let _ = queue.pop_front();
continue;
};
let timeout = event.time + MOD_TAP_TIME;
Timer::at(timeout).await;
return queue.pop_front().unwrap();
} else {
// if the queue is empty, never return.
return pending().await;
}
}
};
let event = futures::select_biased! {
event = input.next_message().fuse() => event,
event = modtap_timeout.fuse() => {
// first element in queue timed out, and will be treated as a Mod
let &Button::ModTap(..) = &event.button else {
error!("first element in queue wasn't a modtap, wtf?");
continue;
};
slow_press(output, &event.button).await;
loop {
let Some(event) = queue.pop_front() else { break };
// resolve events until we encounter another ModTap,
// then put it back in the queue and stop
if let Button::ModTap(..) = &event.button {
queue.push_front(event).expect("we just popped, the queue can't be full");
break;
}
slow_press(output, &event.button).await;
}
continue;
},
};
let WaitResult::Message(event) = event else {
error!("lagged");
continue;
};
let time = Instant::now();
debug!("event: {:?}", event.kind);
match event.kind {
switch::EventKind::Press { button } => {
let insert = |queue: &mut Deque<_, 36>, button| {
if let Some(_queued) = queue
.iter()
.find(|queued: &&PressEvent| queued.source_button == event.source_button)
.filter(|queued| queued.source_half == event.source)
{
error!("tried to add PressEvent to queue twice");
} else if queue
.push_back(PressEvent {
source_half: event.source,
source_button: event.source_button,
button,
time,
})
.is_err()
{
error!("button queue full. this shouldn't happen.");
}
};
match button {
Button::ModTap(_, _) => {
debug!("adding modtap to queue");
// add event to queue
insert(queue, button);
}
Button::Mod(m) => {
// if events in queue, also add to queue maybe?
// TODO
}
Button::Key(_) => {
if queue.is_empty() {
debug!("sending key now");
// otherwise, send immediately
slow_press(output, &button).await;
} else {
debug!("adding key to queue");
// if events in queue, also add to queue
insert(queue, button);
}
}
Button::Compose(..) => {
if queue.is_empty() {
// otherwise, send immediately
// TODO
} else {
// if events in queue, also add to queue
insert(queue, button);
}
}
_ => {}
}
}
switch::EventKind::Release { button, .. } => {
let position_in_queue = queue
.iter()
.enumerate()
.find(|(_, queued)| queued.source_button == event.source_button)
.map(|(i, _)| i);
match button {
Button::ModTap(k, _) => {
// check if modtap in queue
if let Some(position_in_queue) = position_in_queue {
// If the modtap was still in the queue, it hasn't been resolved as a mod
// yet. Therefore, it is a Tap. Resolve all ModTaps before this one as Mods
debug!("modtap was still in queue");
for _ in 0..position_in_queue {
let prev_event = queue.pop_front().unwrap();
debug!("pressing earlier event {:?}", prev_event);
slow_press(output, &prev_event.button).await;
}
let _ = queue.pop_front();
debug!("pressing modtap as key");
slow_press(output, &Button::Key(k)).await;
slow_release(output, &Button::Key(k)).await;
} else {
// If the ModTap wasn't in the queue, it has already been resolved as a Mod.
debug!("modtap wasn't in queue, releasing");
slow_release(output, &button).await;
};
}
Button::Mod(m) => {
debug!("mod not implemented yet");
// TODO
}
Button::Key(_) => {
// if this press event was in queue, resolve all ModTaps before in queue as Mods
// otherwise, just resolve this
if let Some(position_in_queue) = position_in_queue {
debug!(
"key was in queue, pressing all events up to and including this"
);
for _ in 0..=position_in_queue {
let prev_event = queue.pop_front().unwrap();
debug!("pressing {prev_event:?}");
slow_press(output, &prev_event.button).await;
}
}
debug!("releasing key {button:?}");
slow_release(output, &button).await;
}
Button::Compose(..) => {
// if this press event was in queue, resolve all ModTaps before in queue as Mods
// otherwise, just resolve this
// TODO
}
_ => {}
}
}
}
}
}
#[cfg(test)]
mod tests {
use core::time;
use super::*;
use alloc::vec;
use alloc::vec::Vec;
use embassy_futures::{
join::join,
select::{select, Either},
};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::PubSubChannel};
use embassy_time::with_timeout;
use log::info;
use tgnt::{button::Modifier, keys::Key};
#[test]
fn test_modtap_timings() {
simple_logger::SimpleLogger::new().init().unwrap();
let switch_events = PubSubChannel::<NoopRawMutex, switch::Event, 10, 1, 1>::new();
let button_events = PubSubChannel::<NoopRawMutex, button::Event, 10, 1, 1>::new();
let mut button_sub = button_events.subscriber().unwrap();
let buttons = [
Button::ModTap(Key::A, Modifier::LShift),
Button::ModTap(Key::B, Modifier::LCtrl),
Button::Key(Key::C),
];
struct Test {
description: &'static str,
// button index, pressed, delay
input: Vec<(usize, bool, Duration)>,
expected: Vec<button::Event>,
}
let modtap_mod = Test {
description: "modtap mod",
input: vec![
(0, true, Duration::from_millis(25)),
(1, true, Duration::from_millis(25)),
(1, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::B),
button::Event::ReleaseKey(Key::B),
button::Event::ReleaseMod(Modifier::LShift),
],
};
let modtap_tap = Test {
description: "modtap tap",
input: vec![
(0, true, Duration::from_millis(25)),
(1, true, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
(1, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressKey(Key::A),
button::Event::ReleaseKey(Key::A),
button::Event::PressKey(Key::B),
button::Event::ReleaseKey(Key::B),
],
};
let modtap_tap_2x = Test {
description: "2x modtap tap",
input: vec![
(0, true, Duration::from_millis(25)),
(2, true, Duration::from_millis(25)),
(2, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
(0, true, Duration::from_millis(25)),
(2, true, Duration::from_millis(25)),
(2, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::C),
button::Event::ReleaseKey(Key::C),
button::Event::ReleaseMod(Modifier::LShift),
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::C),
button::Event::ReleaseKey(Key::C),
button::Event::ReleaseMod(Modifier::LShift),
],
};
for test in [modtap_tap, modtap_mod, modtap_tap_2x] {
info!("running timing test test {:?}", test.description);
embassy_futures::block_on(async {
let r = select(
join(
async {
for &(i, pressed, delay) in &test.input {
let kind = if pressed {
switch::EventKind::Press {
button: buttons[i].clone(),
}
} else {
switch::EventKind::Release {
button: buttons[i].clone(),
after: time::Duration::ZERO, // ignore
}
};
let event = switch::Event {
source: Half::Left,
source_button: i,
kind,
};
switch_events.publish_immediate(event);
Timer::after(delay).await;
}
},
async {
let mut got = Vec::new();
for _expected in &test.expected {
let event = match with_timeout(
Duration::from_millis(200),
button_sub.next_message(),
)
.await
{
Ok(WaitResult::Message(event)) => event,
Ok(WaitResult::Lagged(_)) => return Err(("lagged", got)),
Err(_) => return Err(("timeout", got)),
};
got.push(event.clone());
}
for (event, expected) in got.iter().zip(test.expected.iter()) {
if event != expected {
return Err(("unexpected event", got));
}
}
Ok(())
},
),
convert_events(
&mut *switch_events.subscriber().unwrap(),
&mut *button_events.publisher().unwrap(),
),
)
.await;
match r {
Either::First(((), Ok(()))) => {}
Either::First(((), Err((msg, got)))) => panic!(
"timing test {:?} failed due to {msg}.\nexpected={:#?} got={:#?}",
test.description, test.expected, got
),
Either::Second(never) => never,
}
});
}
}
}

25
lib/src/led.rs Normal file
View File

@ -0,0 +1,25 @@
use adafruit_itsy_bitsy_rp2040::hal::gpio::{Output, Pin, PinId, PushPull};
use cortex_m::delay::Delay;
use embedded_hal::digital::v2::OutputPin;
pub struct Led<I: PinId> {
pub pin: Pin<I, Output<PushPull>>,
}
pub struct Blink {
ms: u32,
}
pub const SHORT: Blink = Blink { ms: 200 };
pub const LONG: Blink = Blink { ms: 600 };
impl<I: PinId> Led<I> {
pub fn dance(&mut self, delay: &mut Delay, moves: &[Blink]) {
for m in moves {
self.pin.set_high().unwrap();
delay.delay_ms(m.ms);
self.pin.set_low().unwrap();
delay.delay_ms(100);
}
}
}

32
lib/src/lib.rs Normal file
View File

@ -0,0 +1,32 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![feature(split_array)]
extern crate alloc;
#[cfg(target_arch = "arm")]
pub mod allocator;
#[cfg(target_arch = "arm")]
pub mod board;
#[cfg(target_arch = "arm")]
pub mod interrupts;
#[cfg(target_arch = "arm")]
pub mod keyboard;
#[cfg(target_arch = "arm")]
pub mod lights;
#[cfg(target_arch = "arm")]
pub mod panic_handler;
#[cfg(target_arch = "arm")]
pub mod uart;
#[cfg(target_arch = "arm")]
pub mod usb;
#[cfg(target_arch = "arm")]
pub mod ws2812;
pub mod event;
pub mod logger;
pub mod neopixel;
pub mod rgb;
pub mod rtt;
pub mod util;
pub mod keypress_handler;

41
lib/src/lights.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::ws2812::Ws2812;
use embassy_rp::pio;
use embassy_sync::mutex::Mutex;
use crate::{rgb::Rgb, util::CS};
pub struct Lights<P: pio::Instance + 'static, const N: usize> {
state: Mutex<CS, State<P, N>>,
}
struct State<P: pio::Instance + 'static, const N: usize> {
colors: [Rgb; N],
driver: Ws2812<P>,
}
impl<P: pio::Instance, 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;
}
}

62
lib/src/logger.rs Normal file
View File

@ -0,0 +1,62 @@
use core::{fmt::Write, sync::atomic::Ordering};
use atomic_polyfill::AtomicBool;
use embassy_time::Instant;
use log::{Metadata, Record};
use static_cell::StaticCell;
pub const LOGGER_OUTPUTS: usize = 1;
pub struct Logger {
pub outputs: [fn(&[u8]); LOGGER_OUTPUTS],
}
impl Logger {
/// Set this as the global logger.
///
/// Calling this function more than once does nothing.
pub fn init(self) {
// guard against calling this multiple times
static INITIALIZED: AtomicBool = AtomicBool::new(false);
if INITIALIZED.fetch_or(true, Ordering::SeqCst) {
return;
}
static LOGGER: StaticCell<Logger> = StaticCell::new();
let logger = LOGGER.init(self);
unsafe {
log::set_logger_racy(logger).unwrap();
log::set_max_level_racy(log::LevelFilter::Debug);
}
}
}
impl log::Log for Logger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let now = Instant::now();
let s = now.as_secs();
let ms = now.as_millis() % 1000;
let level = record.metadata().level();
let mut w = &mut Writer(self);
let _ = writeln!(&mut w, "[{s}.{ms:04}] ({level}) {}", record.args());
}
}
fn flush(&self) {}
}
struct Writer<'a>(&'a Logger);
impl Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
for output in &self.0.outputs {
output(s.as_bytes());
}
Ok(())
}
}

11
lib/src/neopixel.pio Normal file
View File

@ -0,0 +1,11 @@
.program ws2812
.side_set 1
.wrap_target
bitloop:
out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls.
jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high.
do_one:
jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse)
do_zero:
nop side 0 [4]; Or drive low, for a zero (short pulse)
.wrap

122
lib/src/neopixel.rs Normal file
View File

@ -0,0 +1,122 @@
/*
use embassy_rp::{
gpio::{AnyPin, Drive, SlewRate},
peripherals::{DMA_CH0, PIO0},
pio::{FifoJoin, PioPeripheral, PioStateMachine, ShiftDirection},
pio_instr_util,
relocate::RelocatedProgram,
PeripheralRef,
};
use embassy_time::{Duration, Timer};
#[embassy_executor::task]
pub async fn test(pio: PIO0, pin: AnyPin, dma: DMA_CH0) {
let (_common, mut sm, ..) = pio.split();
let mut dma = PeripheralRef::new(dma);
let pio_program = pio_proc::pio_file!("src/neopixel.pio");
let relocated = RelocatedProgram::new(&pio_program.program);
sm.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
let pin = sm.make_pio_pin(pin);
sm.set_set_pins(&[&pin]);
sm.set_sideset_base_pin(&pin);
sm.set_sideset_count(1);
// Clock config
// TODO CLOCK_FREQ should come from embassy_rp
const CLOCK_FREQ: u32 = 125_000_000;
const WS2812_FREQ: u32 = 800_000;
const CYCLES_PER_BIT: u32 = 16;
let bit_freq = WS2812_FREQ * CYCLES_PER_BIT;
let mut int = CLOCK_FREQ / bit_freq;
let rem = CLOCK_FREQ - (int * bit_freq);
let frac = (rem * 256) / bit_freq;
// 65536.0 is represented as 0 in the pio's clock divider
if int == 65536 {
int = 0;
}
sm.set_clkdiv((int << 8) | frac);
let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target);
sm.set_autopull(true);
sm.set_fifo_join(FifoJoin::TxOnly);
sm.set_pull_threshold(8); // 24?
sm.set_out_shift_dir(ShiftDirection::Left);
sm.set_enable(true);
log::info!("wrap: {:?}", sm.get_wrap());
log::info!("addr: {:?}", sm.get_addr());
log::info!("sideset_base: {:?}", sm.get_sideset_base());
log::info!("sideset_count: {:?}", sm.get_sideset_count());
log::info!("in_base: {:?}", sm.get_in_base());
log::info!("jmp_pin: {:?}", sm.get_jmp_pin());
log::info!("set_range: {:?}", sm.get_set_range());
log::info!("out_range: {:?}", sm.get_out_range());
log::info!("pull_threshold: {:?}", sm.get_pull_threshold());
log::info!("push_threshold: {:?}", sm.get_push_threshold());
//sm = rp2pio.StateMachine(
// assembled,
// frequency=12_800_000, # to get appropriate sub-bit times in PIO program
// first_sideset_pin=NEOPIXEL,
// auto_pull=True,
// out_shift_right=False,
// pull_threshold=8,
//)
loop {
log::info!("sending dma");
sm.dma_push(dma.reborrow(), &[0x0a, 0x00, 0x00]).await;
Timer::after(Duration::from_millis(500)).await;
sm.dma_push(dma.reborrow(), &[0x00, 0x0a, 0x00]).await;
Timer::after(Duration::from_millis(500)).await;
sm.dma_push(dma.reborrow(), &[0x00, 0x00, 0x0a]).await;
Timer::after(Duration::from_millis(500)).await;
//sm0.set_enable(true);
}
}
#[embassy_executor::task]
pub async fn test_blink(pio: PIO0, pin: AnyPin) {
log::info!("test blink hehe");
let (_, mut sm, ..) = pio.split();
// Setup sm2
// blink
let prg = pio_proc::pio_file!("src/blink.pio");
let relocated = RelocatedProgram::new(&prg.program);
let out_pin = sm.make_pio_pin(pin);
let pio_pins = [&out_pin];
sm.set_set_pins(&pio_pins);
sm.set_set_range(25, 1);
sm.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
// sm.set_clkdiv((65535 << 8) + 255 as u32);
// sm.set_clkdiv(0);
let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target);
// sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32);
sm.set_enable(true);
// sm.wait_push().await as i32;
// sm.push_tx(1);
sm.wait_push(125_000_000).await;
log::info!("started");
loop {
sm.wait_irq(3).await;
log::info!("did it!");
}
}
*/

8
lib/src/neopixel2.pio Normal file
View File

@ -0,0 +1,8 @@
.program ws2812
.origin 0
.wrap_target
out x 1
set pins,1 [1]
mov pins,x [1]
set pins,0
.wrap

29
lib/src/panic_handler.rs Normal file
View File

@ -0,0 +1,29 @@
use core::{fmt::Write, panic::PanicInfo};
use cortex_m::asm;
use embassy_rp::gpio::{Level, Output};
use crate::rtt::rtt_write;
#[panic_handler]
fn panic_blink(info: &PanicInfo) -> ! {
cortex_m::interrupt::disable();
let _ = write!(&mut Writer, "{info}");
// SAFETY: we panicked, so no other code will be running.
let p = unsafe { embassy_rp::Peripherals::steal() };
let _led = Output::new(p.PIN_11, Level::High);
asm::udf()
}
/// Write to RTT
struct Writer;
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
rtt_write(s.as_bytes());
Ok(())
}
}

58
lib/src/rgb.rs Normal file
View File

@ -0,0 +1,58 @@
use bytemuck::{cast_slice, Pod, Zeroable};
use core::{
fmt::{self, Debug},
ops::Div,
};
/// An Rgb value that can be safely transmuted to u32 for use with Ws2812.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
pub struct Rgb(u32);
impl Rgb {
#[inline(always)]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self(u32::from_be_bytes([g, r, b, 0]))
}
/// Get the red, green, and blue components of this Rgb.
#[inline(always)]
pub const fn components(&self) -> [u8; 3] {
let [g, r, b, _] = self.0.to_be_bytes();
[r, g, b]
}
#[inline(always)]
pub fn slice_as_u32s(rgbs: &[Rgb]) -> &[u32] {
cast_slice(rgbs)
}
}
impl Debug for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [r, g, b] = self.components();
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)
}
}
#[cfg(all(target_arch = "x86_64", test))]
mod tests {
use super::*;
use bytemuck::cast;
#[test]
fn test_rgb_as_u32() {
let rgb = Rgb::new(0x11, 0x22, 0xCC);
let rgb_u32: u32 = cast(rgb);
assert_eq!(rgb_u32, 0x2211CC00);
}
}

42
lib/src/rtt.rs Normal file
View File

@ -0,0 +1,42 @@
use core::cell::RefCell;
use critical_section::Mutex;
use rtt_target::{rtt_init, UpChannel};
pub type RttWriteFn = fn(&[u8]);
static CHANNEL: Mutex<RefCell<Option<UpChannel>>> = Mutex::new(RefCell::new(None));
/// Write directly to the rtt output. Must call [init_rtt_logger] first.
pub fn rtt_write(bytes: &[u8]) {
critical_section::with(|cs| {
let mut slot = CHANNEL.borrow_ref_mut(cs);
if let Some(channel) = slot.as_mut() {
channel.write(bytes);
};
});
}
pub fn init_rtt_logger() -> RttWriteFn {
let channels = rtt_init! {
up: {
0: {
size: 1024
mode: NoBlockSkip
name: "Terminal"
}
}
};
critical_section::with(|cs| {
let mut slot = CHANNEL.borrow_ref_mut(cs);
if slot.is_some() {
return rtt_write;
}
*slot = Some(channels.up.0);
rtt_write
})
}

171
lib/src/uart.rs Normal file
View File

@ -0,0 +1,171 @@
use core::mem::size_of;
use bytemuck::{cast, AnyBitPattern, NoUninit};
use crc_any::CRCu16;
use embassy_executor::Spawner;
use embassy_rp::peripherals::{PIN_0, PIN_1, UART0};
use embassy_rp::uart::{self, BufferedUart, DataBits, Parity, StopBits};
use embedded_io::asynch::{Read, Write};
use futures::{select_biased, FutureExt};
use heapless::Vec;
use serde::{Deserialize, Serialize};
use static_cell::StaticCell;
use crate::event::{switch, Half};
use crate::interrupts::Irqs;
use crate::keyboard::KbEvents;
#[derive(Clone, Debug, Serialize, Deserialize)]
enum Message {
KeyboardEvent(switch::Event),
}
pub async fn start(tx: PIN_0, rx: PIN_1, uart: UART0, board: Half, events: KbEvents) {
static TX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
static RX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
let mut config = uart::Config::default();
config.baudrate = 115200;
config.data_bits = DataBits::DataBits8;
config.stop_bits = StopBits::STOP1;
config.parity = Parity::ParityNone;
let uart = embassy_rp::uart::BufferedUart::new(
uart,
Irqs,
tx,
rx,
TX_BUF.init_with(|| [0u8; 256]),
RX_BUF.init_with(|| [0u8; 256]),
config,
);
Spawner::for_current_executor()
.await
.must_spawn(uart_task(uart, board, events))
}
#[embassy_executor::task]
async fn uart_task(uart: BufferedUart<'static, UART0>, this_half: Half, mut events: KbEvents) {
let (mut rx, mut tx) = uart.split();
let (mut events_rx, mut events_tx) = events.split();
#[repr(C)]
#[derive(Clone, Copy, Debug, NoUninit, AnyBitPattern)]
struct Header {
/// The length of the payload.
len: u8,
/// An arbitrary value to feed the crc, should be different for each message.
random: u8,
/// A little-endian crc16.
crc: [u8; 2],
}
const HEADER_LEN: usize = size_of::<Header>();
let rx_task = async {
let mut buf: heapless::Vec<u8, 1024> = Vec::new();
loop {
if buf.len() >= HEADER_LEN {
let (&header, rest) = buf.split_array_ref::<HEADER_LEN>();
let header: Header = cast(header);
let crc = u16::from_le_bytes(header.crc);
let mut calculated_crc = CRCu16::crc16();
calculated_crc.digest(&[header.len, header.random]);
let calculated_crc = calculated_crc.get_crc();
if calculated_crc != crc {
log::error!("invalid uart header crc: {header:x?}");
buf.remove(0); // pop the first byte and hope we find a good packet header
continue;
}
log::debug!("got uart header {header:x?}");
let len = usize::from(header.len);
if rest.len() >= len {
let r = postcard::from_bytes(&rest[..len]);
// drop packet from buffer
buf.rotate_left(len + HEADER_LEN);
buf.truncate(buf.len() - len - HEADER_LEN);
let message: Message = match r {
Ok(v) => v,
Err(e) => {
log::error!("failed to deserialize message: {e}");
continue;
}
};
match &message {
Message::KeyboardEvent(event) => events_tx.send(event.clone()),
}
log::info!("got msg: {:?}", message);
}
}
let mut chunk = [0u8; 128];
let n = match rx.read(&mut chunk).await {
Ok(n) => n,
Err(e) => {
log::error!("uart error: {:?}", e);
continue;
}
};
if buf.extend_from_slice(&chunk[..n]).is_err() {
log::error!("uart buffer full");
buf.clear();
}
}
};
let tx_task = async {
let mut buf = [0u8; 256 + HEADER_LEN];
let mut counter = 0u8;
loop {
// forward messages to the other keyboard half
let event = events_rx.recv().await;
if event.source != this_half {
continue; // do not forward messages from the other half back to it
}
let message = Message::KeyboardEvent(event);
let (buf_header, body) = buf.split_array_mut();
let serialized = match postcard::to_slice(&message, body) {
Ok(s) => s,
Err(e) => {
log::error!("failed to serialize uart message: {e}");
continue;
}
};
// add a "random" value to feed the crc
counter = counter.wrapping_add(1);
let random = counter;
let len = serialized.len() as u8;
let mut crc = CRCu16::crc16();
crc.digest(&[len, random]);
let header = Header {
len: serialized.len() as u8,
random,
crc: crc.get_crc().to_le_bytes(),
};
let header: [u8; HEADER_LEN] = cast(header);
*buf_header = header;
let package = &buf[..HEADER_LEN + usize::from(len)];
tx.write_all(package).await.ok();
}
};
select_biased! {
_ = tx_task.fuse() => log::error!("uart tx_task exited"),
_ = rx_task.fuse() => log::error!("eart rx_task exited"),
}
}

75
lib/src/usb.rs Normal file
View File

@ -0,0 +1,75 @@
use embassy_executor::Spawner;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_usb::{Builder, Config, UsbDevice};
use static_cell::StaticCell;
use crate::{interrupts::Irqs, keyboard::KbEvents};
pub mod keyboard;
pub mod logger;
pub const MAX_PACKET_SIZE: u8 = 64;
struct State {
device_descriptor: [u8; 256],
config_descriptor: [u8; 256],
bos_descriptor: [u8; 256],
control_buf: [u8; 64],
}
static STATE: StaticCell<State> = StaticCell::new();
pub async fn setup_logger_and_keyboard(usb: USB, events: KbEvents) {
let mut builder = builder(usb);
//logger::setup(&mut builder).await;
keyboard::setup(&mut builder, events).await;
log::info!("building usb device");
let usb = builder.build();
log::info!("spawning usb task");
Spawner::for_current_executor().await.must_spawn(run(usb));
}
pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {
// 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],
control_buf: [0; 64],
});
// Create embassy-usb Config
let mut config = Config::new(0xb00b, 0x1355);
config.manufacturer = Some("Tux");
config.product = Some("Tangentbord1");
config.serial_number = Some("42069");
config.max_power = 100;
config.max_packet_size_0 = MAX_PACKET_SIZE;
// Required for windows compatiblity.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;
let driver = Driver::new(usb, Irqs);
Builder::new(
driver,
config,
&mut state.device_descriptor,
&mut state.config_descriptor,
&mut state.bos_descriptor,
&mut state.control_buf,
)
}
#[embassy_executor::task]
pub async fn run(mut device: UsbDevice<'static, Driver<'static, USB>>) {
log::info!("running usb device");
device.run().await
}

201
lib/src/usb/keyboard.rs Normal file
View File

@ -0,0 +1,201 @@
pub mod report;
use embassy_executor::Spawner;
use embassy_futures::select::select;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::{
blocking_mutex::raw::NoopRawMutex,
mutex::Mutex,
pubsub::{PubSubChannel, WaitResult},
};
use embassy_time::{Duration, Timer};
use embassy_usb::{
class::hid::{self, HidReaderWriter, ReadError, ReportId, RequestHandler},
control::OutResponse,
Builder,
};
use embassy_usb_driver::EndpointError;
use log::error;
use static_cell::StaticCell;
use usbd_hid::descriptor::SerializedDescriptor;
use crate::{
event::button,
keyboard::KbEvents,
usb::keyboard::report::{KeyboardReport, EMPTY_KEYBOARD_REPORT},
util::CS, keypress_handler::keypress_handler,
};
use super::MAX_PACKET_SIZE;
struct Handler;
static CONTEXT: StaticCell<Context> = StaticCell::new();
static KB_REPORT: Mutex<CS, Reports> = Mutex::new(Reports {
actual: EMPTY_KEYBOARD_REPORT,
unsent: EMPTY_KEYBOARD_REPORT,
});
struct Reports {
/// The report to be sent to the host machine.
actual: KeyboardReport,
/// Key presses which hasn't been sent yet.
unsent: KeyboardReport,
}
struct Context {
handler: Handler,
state: hid::State<'static>,
}
pub async fn setup(builder: &mut Builder<'static, Driver<'static, USB>>, events: KbEvents) {
log::info!("setting up usb hid");
let context = CONTEXT.init(Context {
handler: Handler,
state: hid::State::new(),
});
let config = hid::Config {
//report_descriptor: MouseReport::desc(),
report_descriptor: KeyboardReport::desc(),
request_handler: Some(&context.handler),
poll_ms: 2,
max_packet_size: MAX_PACKET_SIZE as u16,
};
let stream = HidStream::new(builder, &mut context.state, config);
let spawner = Spawner::for_current_executor().await;
spawner.must_spawn(task(stream, &context.handler));
spawner.must_spawn(listen_to_events(events));
log::info!("done setting up usb keyboard");
}
impl RequestHandler for Handler {
fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
log::info!("get_report({id:?}, {buf:?})");
let _ = (id, buf);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> embassy_usb::control::OutResponse {
log::info!("set_report({id:?}, {data:?})");
let _ = (id, data);
OutResponse::Rejected
}
fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
log::info!("get_idle_ms({id:?})");
let _ = id;
None
}
fn set_idle_ms(&self, id: Option<ReportId>, duration_ms: u32) {
log::info!("set_idle_ms({id:?}, {duration_ms})");
let _ = (id, duration_ms);
}
}
type HidStream = HidReaderWriter<'static, Driver<'static, USB>, 256, 256>;
#[embassy_executor::task]
async fn listen_to_events(mut events: KbEvents) {
let button_events = PubSubChannel::<NoopRawMutex, button::Event, 10, 1, 1>::new();
let mut button_pub = button_events.publisher().unwrap();
let mut button_sub = button_events.subscriber().unwrap();
select(
async {
loop {
let WaitResult::Message(event) = button_sub.next_message().await else {
error!("lagged");
continue;
};
loop {
let mut r = KB_REPORT.lock().await;
match event {
button::Event::PressKey(k) => {
r.actual.press_key(k);
r.unsent.press_key(k);
}
button::Event::PressMod(m) => {
r.actual.press_modifier(m);
r.unsent.press_modifier(m);
}
// we got a key release, but if the key press hasn't been sent yet, we
// wait for a bit until it has.
button::Event::ReleaseKey(k) if r.unsent.key_pressed(k) => {
drop(r);
Timer::after(Duration::from_millis(1)).await;
continue;
}
button::Event::ReleaseMod(m) if r.unsent.modifier_pressed(m) => {
drop(r);
Timer::after(Duration::from_millis(1)).await;
continue;
}
button::Event::ReleaseKey(k) => r.actual.release_key(k),
button::Event::ReleaseMod(m) => r.actual.release_modifier(m),
}
break;
}
}
},
keypress_handler(&mut *events.subscriber, &mut *button_pub),
)
.await;
}
#[embassy_executor::task]
async fn task(stream: HidStream, handler: &'static Handler) {
if let Err(e) = keyboard_report(stream, handler).await {
log::error!("keyboard error: {e:?}");
}
}
async fn keyboard_report(mut stream: HidStream, _handler: &'static Handler) -> Result<(), Error> {
stream.ready().await;
loop {
Timer::after(Duration::from_millis(2)).await;
let report = {
let mut reports = KB_REPORT.lock().await;
reports.unsent = EMPTY_KEYBOARD_REPORT;
reports.actual.clone()
};
if report.keycodes != EMPTY_KEYBOARD_REPORT.keycodes {
log::trace!("keys: {:x?}", report.keycodes);
}
#[cfg(feature = "n-key-rollover")]
stream.write(report.as_bytes()).await?;
#[cfg(not(feature = "n-key-rollover"))]
stream.write_serialize(&report).await?;
}
}
#[derive(Debug)]
enum Error {
Read(ReadError),
Endpoint(EndpointError),
}
impl From<ReadError> for Error {
fn from(value: ReadError) -> Self {
Error::Read(value)
}
}
impl From<EndpointError> for Error {
fn from(value: EndpointError) -> Self {
Error::Endpoint(value)
}
}

View File

@ -0,0 +1,160 @@
#![allow(dead_code)]
/// KeyboardReport describes a report and its companion descriptor that can be
/// used to send keyboard button presses to a host and receive the status of the
/// keyboard LEDs.
///
/// Unlike usbd_hids KeyboardReport, this one supports N-key rollover.
#[derive(Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[cfg(feature = "n-key-rollover")]
#[repr(C, packed)]
pub struct KeyboardReport {
pub modifier: u8,
/// Bitmap representing all keycodes from 0 to 215
pub keycodes: [u8; 27],
}
use bytemuck::{cast_ref, Pod, Zeroable};
use core::mem::size_of;
use tgnt::{button::Modifier, keys::Key};
#[cfg(not(feature = "n-key-rollover"))]
pub use ::usbd_hid::descriptor::KeyboardReport;
#[cfg(feature = "n-key-rollover")]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
modifier: 0,
keycodes: [0; 27],
};
#[cfg(not(feature = "n-key-rollover"))]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
modifier: 0,
leds: 0,
reserved: 0,
keycodes: [0; 6],
};
/// Get the byte index, and the mask for that byte, in the keycode bitmap.
fn key_to_byte_mask(key: Key) -> (usize, u8) {
let keycode = u8::from(key);
let byte = keycode >> 3;
let bit = keycode & 0b111;
let mask = 1 << bit;
(usize::from(byte), mask)
}
#[cfg(feature = "n-key-rollover")]
impl KeyboardReport {
#[inline(always)]
pub fn set_key(&mut self, key: Key, pressed: bool) {
let (byte, mask) = key_to_byte_mask(key);
if let Some(k) = self.keycodes.get_mut(byte as usize) {
if pressed {
*k |= mask;
} else {
*k &= !mask;
}
} else {
log::warn!("Tried to set out-of-range keycode: 0x{:x}", u8::from(key));
}
}
#[inline(always)]
pub fn press_key(&mut self, key: Key) {
self.set_key(key, true)
}
#[inline(always)]
pub fn release_key(&mut self, key: Key) {
self.set_key(key, false)
}
#[inline(always)]
pub fn key_pressed(&mut self, key: Key) -> bool {
let (byte, mask) = key_to_byte_mask(key);
if let Some(k) = self.keycodes.get_mut(byte as usize) {
(*k & mask) != 0
} else {
log::warn!("Tried to get out-of-range keycode: 0x{:x}", u8::from(key));
false
}
}
#[inline(always)]
pub fn set_modifier(&mut self, modifier: Modifier, pressed: bool) {
if pressed {
self.modifier |= u8::from(modifier);
} else {
self.modifier &= !u8::from(modifier);
}
}
#[inline(always)]
pub fn press_modifier(&mut self, modifier: Modifier) {
self.set_modifier(modifier, true)
}
#[inline(always)]
pub fn release_modifier(&mut self, modifier: Modifier) {
self.set_modifier(modifier, false)
}
#[inline(always)]
pub fn modifier_pressed(&mut self, modifier: Modifier) -> bool {
(self.modifier & u8::from(modifier)) != 0
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; size_of::<KeyboardReport>()] {
cast_ref(self)
}
}
// bitmasks for the `modifier` field
#[cfg(feature = "n-key-rollover")]
impl usbd_hid::descriptor::SerializedDescriptor for KeyboardReport {
fn desc() -> &'static [u8] {
// Manually define the descriptor since I can't figure out how to get
// gen_hid_descriptor to generate the correct one.
&[
0x05, 0x01, // usage page 1 (generic desktop)
0x09, 0x06, // usage (keyboard)
0xa1, 0x01, // collection (application)
//
0x05, 0x07, // usage page 7 (keyboard/keypad)
0x19, 0xe0, // local usage minimum
0x29, 0xe7, // local usage maximum
0x15, 0x00, // local minimum
0x25, 0x01, // local maximum
0x75, 0x01, // report size (1 bit)
0x95, 0x08, // report count (8)
0x81, 0x02, // input (variable)
//
0x19, 0x00, // local usage minimum
//0x29, 0x67, // local usage maximum (0x67)
//0x95, 0x68, // report count (0x68)
0x29, 215, // local usage maximum (215)
0x95, 216, // report count (216)
0x81, 0x02, // input (variable)
//
0x05, 0x08, // usage page 8 (led page)
0x19, 0x01, // local usage minimum
0x29, 0x05, // local usage maximum
0x15, 0x00, // logical min
0x25, 0x01, // logical max
0x75, 0x01, // report size
0x95, 0x05, // report count
0x91, 0x02, // output (variable)
//
0x75, 0x03, // report size
0x95, 0x01, // report count
0x91, 0x01, // output (constant)
0xc0, // end collection
]
}
}

55
lib/src/usb/logger.rs Normal file
View File

@ -0,0 +1,55 @@
use crate::util::CS;
use super::MAX_PACKET_SIZE;
use core::fmt::Write as WriteFmt;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::pipe::Pipe;
use embassy_time::Instant;
use embassy_usb::class::cdc_acm::CdcAcmClass;
use log::{Metadata, Record};
pub const BUFFER_SIZE: usize = 16 * 1024;
static BUFFER: Pipe<CS, BUFFER_SIZE> = Pipe::new();
struct UsbLogger;
#[embassy_executor::task]
async fn log_task(mut class: CdcAcmClass<'static, Driver<'static, USB>>) {
let mut buf = [0u8; MAX_PACKET_SIZE as usize];
class.wait_connection().await;
loop {
let n = BUFFER.read(&mut buf).await;
// not much we can do if this fails, just ignore the error
let _ = class.write_packet(&buf[..n]).await;
}
}
impl log::Log for UsbLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut w = Writer;
let now = Instant::now();
let s = now.as_secs();
let ms = now.as_millis() % 1000;
let level = record.metadata().level();
let _ = writeln!(w, "[{s}.{ms:04}] ({level}) {}", record.args());
}
}
fn flush(&self) {}
}
struct Writer;
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
let _ = BUFFER.try_write(s.as_bytes());
Ok(())
}
}

27
lib/src/util.rs Normal file
View File

@ -0,0 +1,27 @@
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Timer};
use crate::rgb::Rgb;
pub type CS = CriticalSectionRawMutex;
pub async fn stall() -> ! {
loop {
Timer::after(Duration::from_secs(1)).await;
}
}
/// Input a value 0 to 255 to get a color value
// 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 {
Rgb::new(255 - wheel_pos * 3, 0, wheel_pos * 3)
} else if wheel_pos < 170 {
wheel_pos -= 85;
Rgb::new(0, wheel_pos * 3, 255 - wheel_pos * 3)
} else {
wheel_pos -= 170;
Rgb::new(wheel_pos * 3, 255 - wheel_pos * 3, 0)
}
}

91
lib/src/ws2812.rs Normal file
View File

@ -0,0 +1,91 @@
use embassy_rp::dma::{self, AnyChannel};
use embassy_rp::pio::{self, FifoJoin, Instance, Pio, PioPin, ShiftConfig, ShiftDirection};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{Peripheral, PeripheralRef};
use fixed::FixedU32;
use crate::rgb::Rgb;
pub struct Ws2812<P: pio::Instance + 'static> {
sm: pio::StateMachine<'static, P, 0>,
dma: PeripheralRef<'static, AnyChannel>,
}
impl<P: Instance> Ws2812<P> {
pub fn new(
pio: impl Peripheral<P = P> + 'static,
dma: impl dma::Channel,
pin: impl PioPin,
) -> Self {
let mut pio = Pio::new(pio);
let mut sm = pio.sm0;
// prepare the PIO program
let side_set = ::pio::SideSet::new(false, 1, false);
let mut a: ::pio::Assembler<32> = ::pio::Assembler::new_with_side_set(side_set);
const T1: u8 = 2; // start bit
const T2: u8 = 5; // data bit
const T3: u8 = 3; // stop bit
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
let mut wrap_target = a.label();
let mut wrap_source = a.label();
let mut do_zero = a.label();
a.set_with_side_set(::pio::SetDestination::PINDIRS, 1, 0);
a.bind(&mut wrap_target);
// Do stop bit
a.out_with_delay_and_side_set(::pio::OutDestination::X, 1, T3 - 1, 0);
// Do start bit
a.jmp_with_delay_and_side_set(::pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
// Do data bit = 1
a.jmp_with_delay_and_side_set(::pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
a.bind(&mut do_zero);
// Do data bit = 0
a.nop_with_delay_and_side_set(T2 - 1, 0);
a.bind(&mut wrap_source);
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
let relocated_prg = RelocatedProgram::new(&prg);
let loaded_prg = pio.common.load_program(&relocated_prg);
// Clock config
// TODO CLOCK_FREQ should come from embassy_rp
const CLOCK_FREQ: u32 = 125_000_000;
const WS2812_FREQ: u32 = 800_000;
let bit_freq = WS2812_FREQ * CYCLES_PER_BIT;
let mut int = CLOCK_FREQ / bit_freq;
let rem = CLOCK_FREQ - (int * bit_freq);
let frac = (rem * 256) / bit_freq;
// 65536.0 is represented as 0 in the pio's clock divider
if int == 65536 {
int = 0;
}
let mut config = pio::Config::default();
config.clock_divider = FixedU32::from_bits((int << 8) | frac);
config.fifo_join = FifoJoin::TxOnly;
config.shift_out = ShiftConfig {
threshold: 24,
direction: ShiftDirection::Left,
auto_fill: true,
};
let out_pin = pio.common.make_pio_pin(pin);
config.set_set_pins(&[&out_pin]);
config.use_program(&loaded_prg, &[&out_pin]);
sm.set_config(&config);
sm.set_enable(true);
Self {
sm,
dma: PeripheralRef::new(dma.degrade()),
}
}
pub async fn write(&mut self, colors: &[Rgb]) {
let colors = Rgb::slice_as_u32s(colors);
self.sm.tx().dma_push(self.dma.reborrow(), colors).await;
}
}