This commit is contained in:
2024-03-24 16:29:24 +01:00
parent 84f8222b30
commit 4a528eb4b7
44 changed files with 5438 additions and 328 deletions

2
lib/.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[build]
target = "thumbv6m-none-eabi"

View File

@ -6,12 +6,10 @@ description = "Keyboard firmware"
edition = "2021"
[dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
#tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
cortex-m = "0.7.6"
cortex-m-rt = "0.7"
embedded-hal = "0.2.5"
usb-device = "0.2.9"
usbd-hid = "0.6.1"
static_cell = "1.0.0"
embedded-io-async = "*"
futures = { version = "0.3", default-features = false, features = ["async-await"] }
@ -19,9 +17,6 @@ embassy-executor = { version = "0.5.0", features = ["nightly", "nightly", "execu
embassy-sync = "0.5.0"
embassy-time = "0.3.0"
embassy-futures = "0.1.1"
embassy-usb = { version = "0.1.0", features = ["usbd-hid"] }
embassy-usb-logger = "0.1.0"
embassy-usb-driver = "0.1.0"
log = "0.4.17"
pio = "0.2.1"
pio-proc = "0.2.1"
@ -32,13 +27,15 @@ fixed = "1.23.1"
rtt-target = "0.4.0"
heapless = "0.7.16"
once_cell = { version = "1.17.1", default-features = false }
atomic-polyfill = "1.0.2"
critical-section = "1.1.1"
crc-any = "2.4.3"
serde = { version = "1.0.163", default-features = false, features = ["derive"] }
bytemuck = { version = "1.13.1", features = ["derive"] }
libm = "0.2.8"
glam = { version = "0.25.0", default-features = false, features = ["libm"] }
msgpck = { version = "0.2.8", features = ["alloc"] }
#atomic-polyfill = "1.0.2"
portable-atomic = { version = "1.6.0", default-features = false }
[target.'cfg(target_arch = "x86_64")'.dependencies]
embassy-executor = { version = "0.5.0", features = ["arch-std"] }
@ -48,12 +45,18 @@ simple_logger = "4"
[target.thumbv6m-none-eabi.dependencies]
embassy-rp = { version = "0.1.0", features = ["time-driver", "critical-section-impl"] }
embassy-executor = { version = "0.5.0", features = ["arch-cortex-m"] }
embassy-usb = { version = "0.1.0", features = ["usbd-hid"] }
embassy-usb-logger = "0.1.0"
embassy-usb-driver = "0.1.0"
usb-device = "0.2.9"
usbd-hid = "0.6.1"
[build-dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
#tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
ron = "0.8.0"
postcard = { version = "1", features = ["use-std"] }
[features]
std = []
default = ["n-key-rollover"]
n-key-rollover=[]

View File

@ -1,4 +1,4 @@
use atomic_polyfill::{AtomicU32, Ordering};
use portable_atomic::{AtomicU32, Ordering};
pub struct AtomicCoord {
inner: AtomicU32,

107
lib/src/button.rs Normal file
View File

@ -0,0 +1,107 @@
use core::fmt::{self, Debug, Display};
use msgpck::{MsgPack, MsgUnpack};
use serde::{Deserialize, Serialize};
use crate::keys::Key;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack)]
pub enum Modifier {
LCtrl = 0x01,
LShift = 0x02,
LAlt = 0x04,
LMod = 0x08,
RCtrl = 0x10,
RShift = 0x20,
RAlt = 0x40,
RMod = 0x80,
}
impl From<Modifier> for u8 {
fn from(modifier: Modifier) -> Self {
modifier as u8
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack)]
#[non_exhaustive]
pub enum Button {
Mod(Modifier),
Key(Key),
ModTap(Key, Modifier),
Compose2(CompShift, Key, CompShift, Key),
Compose3(CompShift, Key, CompShift, Key, CompShift, Key),
Layer(LayerShift, LayerDir, u16),
None,
}
/// Whether a key should be shift modified as part of a compose chain.
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack,
)]
pub enum CompShift {
/// Do not shift the key.
#[default]
Lower,
/// Shift the key.
Upper,
/// Shift the key if shift is being held down.
Variable,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack)]
pub enum LayerDir {
Left,
Right,
Up,
Down,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack)]
pub enum LayerShift {
Move,
Peek,
}
impl Display for Button {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cs = |cs: &CompShift| match cs {
CompShift::Lower => "",
CompShift::Upper => "",
CompShift::Variable => "⇧?",
};
match self {
Button::Mod(modifier) => Debug::fmt(&modifier, f),
Button::Key(key) => write!(f, "{key:?}"),
Button::ModTap(key, modifier) => write!(f, "{key:?}/{modifier}"),
Button::Compose2(cs1, k1, cs2, k2) => {
write!(f, "⎄ {}{k1:?} {}{k2:?}", cs(cs1), cs(cs2))
}
Button::Compose3(cs1, k1, cs2, k2, cs3, k3) => {
write!(f, "⎄ {}{k1:?} {}{k2:?} {}{k3:?}", cs(cs1), cs(cs2), cs(cs3))
}
Button::Layer(..) => write!(f, "Lr"),
Button::None => write!(f, "Ø"),
}
}
}
impl Display for Modifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Modifier::LCtrl => "",
Modifier::LShift => "",
Modifier::LAlt => "",
Modifier::LMod => "",
Modifier::RCtrl => "",
Modifier::RShift => "",
Modifier::RAlt => "",
Modifier::RMod => "",
};
write!(f, "{s}")
}
}

View File

@ -1,7 +1,6 @@
use crate::{button::Button, keys::Key};
use core::time::Duration;
use serde::{Deserialize, Serialize};
use tgnt::{button::Button, keys::Key};
pub mod switch {
use super::*;
@ -35,9 +34,8 @@ pub mod switch {
}
pub mod button {
use tgnt::button::Modifier;
use super::*;
use crate::button::Modifier;
/// A usb keyboard button was pressed or released.
///

View File

@ -2,7 +2,7 @@ mod lights;
use core::sync::atomic::Ordering;
use alloc::{boxed::Box, vec::Vec};
use alloc::vec::Vec;
use embassy_executor::Spawner;
use embassy_rp::{
gpio::{AnyPin, Input, Pin, Pull},
@ -12,19 +12,22 @@ use embassy_sync::pubsub::{ImmediatePublisher, PubSubChannel, Subscriber};
use embassy_time::{Duration, Instant, Timer};
use log::{debug, error, info, warn};
use static_cell::StaticCell;
use tgnt::{
button::{Button, LayerDir, LayerShift},
layer::Layer,
};
use crate::{
atomics::AtomicCoord,
button::{Button, LayerDir, LayerShift},
event::{
switch::{Event, EventKind},
Half,
},
layer::{Layer, Layers},
lights::Lights,
util::CS,
serial_proto::{
borrowed::DeviceMsg,
owned::{ChangeLayer, SwitchPress, SwitchRelease},
},
usb::serial::serial_send,
util::{SwapCell, SwapCellRead, CS},
ws2812::Ws2812,
};
@ -45,7 +48,8 @@ struct State {
half: Half,
current_layer: AtomicCoord,
layer_cols: usize,
layers: &'static [Vec<Layer>],
layer_rows: usize,
layers: SwapCellRead<Layers>,
/// Array of LED indices of each switch
led_map: [usize; SWITCH_COUNT],
lights: Lights<PIO1, SWITCH_COUNT>,
@ -89,17 +93,7 @@ impl KeyboardConfig {
self.layers.len()
);
static STATE: StaticCell<State> = StaticCell::new();
let state = STATE.init_with(|| State {
half: self.half,
current_layer: AtomicCoord::new(),
layer_cols: self.layers.iter().map(|row| row.len()).max().unwrap_or(0),
layers: Box::leak(self.layers.into_boxed_slice()),
lights: Lights::new(self.led_driver),
led_map: self.led_map,
});
for (y, row) in state.layers.iter().enumerate() {
for (y, row) in self.layers.iter().enumerate() {
for (x, layer) in row.iter().enumerate() {
if layer.buttons.len() != SWITCH_COUNT {
warn!(
@ -110,6 +104,24 @@ impl KeyboardConfig {
}
}
let layer_cols = self.layers.iter().map(|row| row.len()).max().unwrap_or(0);
let layer_rows = self.layers.len();
static LAYERS: StaticCell<SwapCell<Layers>> = StaticCell::new();
let layers = LAYERS.init_with(|| SwapCell::new(self.layers));
let (layers_read, layers_write) = layers.split();
static STATE: StaticCell<State> = StaticCell::new();
let state = STATE.init_with(|| State {
half: self.half,
current_layer: AtomicCoord::new(),
layer_cols,
layer_rows,
layers: layers_read,
lights: Lights::new(self.led_driver),
led_map: self.led_map,
});
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?");
@ -200,8 +212,8 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
// get current layer
let (x, y) = state.current_layer.load(Ordering::Relaxed);
let Some(Layer { buttons }) = state
.layers
let layers = state.layers.read();
let Some(Layer { buttons }) = layers
.get(usize::from(y))
.and_then(|row| row.get(usize::from(x)))
else {
@ -210,14 +222,18 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
continue;
};
let button = buttons.get(switch_num).cloned();
drop(layers);
// and current button
let Some(button) = buttons.get(switch_num) else {
let Some(button) = button else {
warn!("no button defined for switch {switch_num}");
pin.wait_for_high().await;
continue;
};
debug!("switch {switch_num} button {button:?} pressed");
serial_send(&DeviceMsg::SwitchPress(SwitchPress(switch_num as u16)));
let ev = |kind| Event {
source: state.half,
@ -234,6 +250,7 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
let released_after = pressed_at.elapsed();
debug!("switch {switch_num} button {button:?} released");
serial_send(&DeviceMsg::SwitchRelease(SwitchRelease(switch_num as u16)));
events.publish_immediate(ev(EventKind::Release {
button: button.clone(),
@ -247,7 +264,7 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
#[embassy_executor::task]
async fn layer_switch_task(mut events: KbEvents, state: &'static State) {
let col_count = state.layer_cols as u16;
let row_count = state.layers.len() as u16;
let row_count = state.layer_rows as u16;
let Some(last_row) = row_count.checked_sub(1) else {
error!("no layers specified");
return;
@ -282,6 +299,7 @@ async fn layer_switch_task(mut events: KbEvents, state: &'static State) {
};
state.current_layer.store(nx, ny, Ordering::Relaxed);
serial_send(&DeviceMsg::ChangeLayer(ChangeLayer { x: nx, y: ny }));
debug!("switched to layer ({nx}, {ny})");
}
}
@ -289,7 +307,7 @@ async fn layer_switch_task(mut events: KbEvents, state: &'static State) {
/// Random functions for testing
#[allow(dead_code)]
pub mod test {
use tgnt::{button::Button, keys::Key};
use crate::{button::Button, keys::Key};
pub fn letter_to_key(c: char) -> Button {
if !c.is_ascii() {

View File

@ -2,9 +2,9 @@ use core::future::pending;
use embassy_time::{Duration, Instant, Timer};
use futures::{select_biased, FutureExt};
use tgnt::button::Button;
use crate::{
button::Button,
lights::shaders::{PowerOffAnim, PowerOnAnim, Shader, Shaders},
rgb::Rgb,
usb::{UsbEvent, USB_EVENTS},

View File

@ -1,13 +1,15 @@
use core::future::pending;
use crate::{
button::Button,
event::{button, switch, Half},
keys::Key,
};
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, keys::Key};
use crate::event::{button, switch, Half};
/// The time a ModTap button takes to resolve as a Mod while being held down.
const MOD_TAP_TIME: Duration = Duration::from_millis(150);

269
lib/src/keys.rs Normal file
View File

@ -0,0 +1,269 @@
use msgpck::{MsgPack, MsgUnpack};
use serde::{Deserialize, Serialize};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, MsgPack, MsgUnpack)]
#[non_exhaustive]
// https://usb.org/sites/default/files/hut1_3_0.pdf
pub enum Key {
A = 0x04,
B = 0x05,
C = 0x06,
D = 0x07,
E = 0x08,
F = 0x09,
G = 0x0A,
H = 0x0B,
I = 0x0C,
J = 0x0D,
K = 0x0E,
L = 0x0F,
M = 0x10,
N = 0x11,
O = 0x12,
P = 0x13,
Q = 0x14,
R = 0x15,
S = 0x16,
T = 0x17,
U = 0x18,
V = 0x19,
W = 0x1A,
X = 0x1B,
Y = 0x1C,
Z = 0x1D,
/// Keyboard 1 and !
D1 = 0x1E,
/// Keyboard 2 and @
D2 = 0x1F,
/// Keyboard 3 and #
D3 = 0x20,
/// Keyboard 4 and $
D4 = 0x21,
/// Keyboard 5 and %
D5 = 0x22,
/// Keyboard 6 and ∧
D6 = 0x23,
/// Keyboard 7 and &
D7 = 0x24,
/// Keyboard 8 and *
D8 = 0x25,
/// Keyboard 9 and (
D9 = 0x26,
/// Keyboard 0 and )
D0 = 0x27,
Return = 0x28,
Escape = 0x29,
/// Keyboard DELETE (Backspace)
Backspace = 0x2A,
Tab = 0x2B,
/// Keyboard Spacebar
Space = 0x2C,
/// Keyboard - and (underscore)
Dash = 0x2D,
/// Keyboard = and +2
Equal = 0x2E,
/// Keyboard [ and {
LBracket = 0x2F,
/// Keyboard ] and }
RBracket = 0x30,
/// Keyboard \and |
BackslashPipe = 0x31,
/// Keyboard Non-US # and ̃
Pound = 0x32,
/// Keyboard ; and :
Colon = 0x33,
/// Keyboard and “
Apostrophe = 0x34,
/// Keyboard Grave Accent and Tilde
Accent = 0x35,
/// Keyboard , and <
Comma = 0x36,
/// Keyboard . and >
Period = 0x37,
/// Keyboard / and ?
Slash = 0x38,
CapsLock = 0x39,
F1 = 0x3A,
F2 = 0x3B,
F3 = 0x3C,
F4 = 0x3D,
F5 = 0x3E,
F6 = 0x3F,
F7 = 0x40,
F8 = 0x41,
F9 = 0x42,
F10 = 0x43,
F11 = 0x44,
F12 = 0x45,
/// Keyboard PrintScreen
PrintScreen = 0x46,
/// Keyboard Scroll Lock
ScrollLock = 0x47,
/// Keyboard Pause
Pause = 0x48,
/// Keyboard Insert
Insert = 0x49,
/// Keyboard Home
Home = 0x4A,
/// Keyboard PageUp
PageUp = 0x4B,
/// Keyboard Delete Forward
Delete = 0x4C,
/// KeyboardEnd7
End = 0x4D,
/// Keyboard PageDown7
PageDown7 = 0x4E,
/// Keyboard RightArrow7
RightArrow7 = 0x4F,
/// Keyboard LeftArrow7
LeftArrow7 = 0x50,
/// Keyboard DownArrow7
DownArrow7 = 0x51,
/// Keyboard UpArrow7
UpArrow7 = 0x52,
/// Keypad Num Lock and Clear6
KeypadNumLock = 0x53,
/// Keypad /
KeypadSlash = 0x54,
/// Keypad *
KeypadAsterisk = 0x55,
/// Keypad -
KeypadMinus = 0x56,
/// Keypad +
KeypadPlus = 0x57,
/// KeypadEnter
KeypadEnter = 0x58,
/// Keypad 1 and End
Keypad1 = 0x59,
/// Keypad 2 and Down Arrow
Keypad2 = 0x5A,
/// Keypad 3 and PageDn
Keypad3 = 0x5B,
/// Keypad 4 and Left Arrow
Keypad4 = 0x5C,
/// Keypad 5
Keypad5 = 0x5D,
/// Keypad 6 and Right Arrow
Keypad6 = 0x5E,
/// Keypad 7 and Home
Keypad7 = 0x5F,
/// Keypad 8 and Up Arrow
Keypad8 = 0x60,
/// Keypad 9 and PageUp
Keypad9 = 0x61,
/// Keypad 0 and Insert
Keypad0 = 0x62,
/// Keypad . and Delete
KbPeriod = 0x63,
/// Keyboard Non-US \ and |
NonUsBackslashPipe = 0x64,
Application = 0x65,
Power = 0x66,
/// Keypad =
KpEqual = 0x67,
F13 = 0x68,
F14 = 0x69,
F15 = 0x6A,
F16 = 0x6B,
F17 = 0x6C,
F18 = 0x6D,
F19 = 0x6E,
F20 = 0x6F,
F21 = 0x70,
F22 = 0x71,
F23 = 0x72,
F24 = 0x73,
Execute = 0x74,
Help = 0x75,
Menu = 0x76,
Select = 0x77,
Stop = 0x78,
Again = 0x79,
Undo = 0x7A,
Cut = 0x7B,
Copy = 0x7C,
Paste = 0x7D,
Find = 0x7E,
Mute = 0x7F,
VolumeUp = 0x80,
VolumeDown = 0x81,
/// Keyboard Locking Caps Lock
LockingCapsLock = 0x82,
/// Keyboard Locking Num Lock
LockingNumLock = 0x83,
/// Keyboard Locking Scroll Lock
LockingScrollLock = 0x84,
/// Keypad ,
KpComma = 0x85,
/// Keypad Equal Sign
KpEqualSIgn = 0x86,
International1 = 0x87,
International2 = 0x88,
International3 = 0x89,
International4 = 0x8A,
International5 = 0x8B,
International6 = 0x8C,
International7 = 0x8D,
International8 = 0x8E,
International9 = 0x8F,
Lang1 = 0x90,
Lang2 = 0x91,
Lang3 = 0x92,
Lang4 = 0x93,
Lang5 = 0x94,
Lang6 = 0x95,
Lang7 = 0x96,
Lang8 = 0x97,
Lang9 = 0x98,
/// Keyboard Alternative Erase
AltErase = 0x99,
/// Keyboard SysReq/Attention
SysReq = 0x9A,
Cancel = 0x9B,
Clear = 0x9C,
Prior = 0x9D,
/// Keyboard Return
Return2 = 0x9E,
/// Keyboard Separator
Separator = 0x9F,
/// Keyboard Out
Out = 0xA0,
/// Keyboard Oper
Oper = 0xA1,
/// Keyboard Clear/Again
ClearAgain = 0xA2,
/// Keyboard CrSel/Props
CrSel = 0xA3,
/// Keyboard ExSel
ExSel = 0xA4,
//
// NOTE: the usb keyboard/keypad page allegedly har characters in the rage above A4,
// like the ones below, but they don't seem to work
///// Keypad {
//LCurly = 0xB8,
///// Keypad }
//RCurly = 0xB9,
//
// NOTE: Not sure if these are required
//LCtrl = 0xE0,
//LShift = 0xE1,
//LAlt = 0xE2,
//RCtrl = 0xE4,
//RShift = 0xE5,
//RAlt = 0xE6,
}
impl From<Key> for u8 {
fn from(key: Key) -> u8 {
key as u8
}
}

12
lib/src/layer.rs Normal file
View File

@ -0,0 +1,12 @@
use alloc::vec::Vec;
use msgpck::{MsgPack, MsgUnpack};
use serde::{Deserialize, Serialize};
use crate::button::Button;
#[derive(Default, Debug, Serialize, Deserialize, Clone, MsgPack, MsgUnpack)]
pub struct Layer {
pub buttons: Vec<Button>,
}
pub type Layers = Vec<Vec<Layer>>;

36
lib/src/layout.rs Normal file
View File

@ -0,0 +1,36 @@
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Layout {
pub buttons: Vec<Rect>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Rect {
pub x: f32,
pub y: f32,
#[serde(default = "default_width")]
pub w: f32,
#[serde(default = "default_height")]
pub h: f32,
}
impl Default for Rect {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
w: default_width(),
h: default_height(),
}
}
}
fn default_width() -> f32 {
1.0
}
fn default_height() -> f32 {
1.0
}

View File

@ -1,4 +1,4 @@
#![no_std]
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(type_alias_impl_trait)]
#![feature(split_array)]
@ -24,10 +24,15 @@ pub mod usb;
pub mod ws2812;
pub mod atomics;
pub mod button;
pub mod event;
pub mod keypress_handler;
pub mod keys;
pub mod layer;
pub mod layout;
pub mod logger;
pub mod neopixel;
pub mod rgb;
pub mod rtt;
pub mod serial_proto;
pub mod util;

View File

@ -1,17 +1,25 @@
use core::{fmt::Write, sync::atomic::Ordering};
use core::{fmt::Arguments, sync::atomic::Ordering};
use atomic_polyfill::AtomicBool;
use embassy_time::Instant;
use log::{Metadata, Record};
use log::{Level, Metadata, Record};
use portable_atomic::AtomicBool;
use static_cell::StaticCell;
pub const LOGGER_OUTPUTS: usize = 1;
pub const LOGGER_OUTPUTS: usize = 2;
pub struct Logger {
pub outputs: [fn(&[u8]); LOGGER_OUTPUTS],
/// A logger which timestamps logs and forwards them to mutiple other loggers.
pub struct LogMultiplexer {
pub outputs: [&'static dyn LogOutput; LOGGER_OUTPUTS],
}
impl Logger {
pub struct TimestampedRecord<'a> {
pub record: Record<'a>,
/// Timestamp
pub timestamp: Instant,
}
impl LogMultiplexer {
/// Set this as the global logger.
///
/// Calling this function more than once does nothing.
@ -22,7 +30,7 @@ impl Logger {
return;
}
static LOGGER: StaticCell<Logger> = StaticCell::new();
static LOGGER: StaticCell<LogMultiplexer> = StaticCell::new();
let logger = LOGGER.init(self);
unsafe {
log::set_logger_racy(logger).unwrap();
@ -31,32 +39,43 @@ impl Logger {
}
}
impl log::Log for Logger {
impl log::Log for LogMultiplexer {
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());
let timestamp = Instant::now();
let record = TimestampedRecord {
timestamp,
record: record.clone(),
};
for output in &self.outputs {
output.log(&record);
}
}
}
fn flush(&self) {}
}
struct Writer<'a>(&'a Logger);
pub trait LogOutput: Send + Sync {
fn log(&self, record: &TimestampedRecord);
}
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(())
impl TimestampedRecord<'_> {
pub fn args(&self) -> &Arguments<'_> {
self.record.args()
}
pub fn level(&self) -> Level {
self.metadata().level()
}
pub fn metadata(&self) -> &Metadata<'_> {
self.record.metadata()
}
}

View File

@ -23,7 +23,7 @@ struct Writer;
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
rtt_write(s.as_bytes());
rtt_write(s);
Ok(())
}
}

View File

@ -1,23 +1,25 @@
use core::cell::RefCell;
use core::{cell::RefCell, fmt::Write};
use critical_section::Mutex;
use rtt_target::{rtt_init, UpChannel};
pub type RttWriteFn = fn(&[u8]);
use crate::logger::{LogOutput, TimestampedRecord};
pub type RttWriteFn = fn(&str);
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]) {
pub fn rtt_write(s: &str) {
critical_section::with(|cs| {
let mut slot = CHANNEL.borrow_ref_mut(cs);
if let Some(channel) = slot.as_mut() {
channel.write(bytes);
channel.write(s.as_bytes());
};
});
}
pub fn init_rtt_logger() -> RttWriteFn {
pub fn init_rtt_logger() -> &'static RttLogger {
let channels = rtt_init! {
up: {
0: {
@ -31,12 +33,32 @@ pub fn init_rtt_logger() -> RttWriteFn {
critical_section::with(|cs| {
let mut slot = CHANNEL.borrow_ref_mut(cs);
if slot.is_some() {
return rtt_write;
if slot.is_none() {
*slot = Some(channels.up.0);
}
*slot = Some(channels.up.0);
rtt_write
static RTT_LOGGER: RttLogger = RttLogger;
&RTT_LOGGER
})
}
pub struct RttLogger;
impl LogOutput for RttLogger {
fn log(&self, record: &TimestampedRecord) {
let s = record.timestamp.as_secs();
let ms = record.timestamp.as_millis() % 1000;
let level = record.level();
let mut w = &mut Writer;
let _ = writeln!(&mut w, "[{s}.{ms:04}] ({level}) {}", record.args());
}
}
struct Writer;
impl Write for Writer {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
rtt_write(s);
Ok(())
}
}

71
lib/src/serial_proto.rs Normal file
View File

@ -0,0 +1,71 @@
pub mod owned {
use alloc::string::String;
use msgpck::{MsgPack, MsgUnpack};
use crate::layer::Layers;
#[derive(Debug, MsgPack, MsgUnpack)]
pub struct LogRecord {
/// Milliseconds since boot
pub timestamp: u64,
pub level: String,
pub message: String,
}
/// Press the switch with the provided index.
#[derive(Debug, MsgPack, MsgUnpack)]
pub struct SwitchPress(pub u16);
/// Release the switch with the provided index.
#[derive(Debug, MsgPack, MsgUnpack)]
pub struct SwitchRelease(pub u16);
/// Change to the layer at the provided coordinates in the layer matrix.
#[derive(Debug, MsgPack, MsgUnpack)]
pub struct ChangeLayer {
pub x: u16,
pub y: u16,
}
#[derive(Debug, MsgPack, MsgUnpack)]
pub enum DeviceMsg {
Log(LogRecord),
SwitchPress(SwitchPress),
SwitchRelease(SwitchRelease),
ChangeLayer(ChangeLayer),
Layers(Layers),
}
#[derive(Debug, MsgPack, MsgUnpack)]
pub enum HostMsg {
GetLayers,
}
}
pub mod borrowed {
use msgpck::MsgPack;
use super::owned::*;
use crate::{layer::Layers, util::DisplayPack};
#[derive(Debug, MsgPack)]
pub struct LogRecord<'a> {
/// Milliseconds since boot
pub timestamp: u64,
pub level: DisplayPack<log::Level>,
pub message: DisplayPack<&'a core::fmt::Arguments<'a>>,
}
#[derive(Debug, MsgPack)]
pub enum DeviceMsg<'a> {
Log(LogRecord<'a>),
SwitchPress(SwitchPress),
SwitchRelease(SwitchRelease),
ChangeLayer(ChangeLayer),
Layers(&'a Layers),
}
}

View File

@ -7,8 +7,10 @@ use static_cell::StaticCell;
use crate::{interrupts::Irqs, keyboard::KbEvents, uart::UART_USB_EVENTS_OUT, util::CS};
use self::serial::UsbSerial;
pub mod keyboard;
pub mod logger;
pub mod serial;
pub const MAX_PACKET_SIZE: u8 = 64;
@ -35,17 +37,18 @@ struct State {
static STATE: StaticCell<State> = StaticCell::new();
pub async fn setup_logger_and_keyboard(usb: USB, events: KbEvents) {
pub async fn setup_logger_and_keyboard(usb: USB, events: KbEvents) -> &'static UsbSerial {
let mut builder = builder(usb);
//logger::setup(&mut builder).await;
let usb_serial = serial::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));
usb_serial
}
pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {

View File

@ -218,6 +218,7 @@ async fn write_reports(mut stream: HidStream, ctx: &'static Context) -> Result<(
}
#[derive(Debug)]
#[allow(dead_code)]
enum Error {
Read(ReadError),
Endpoint(EndpointError),

View File

@ -1,5 +1,9 @@
#![allow(dead_code)]
use crate::{button::Modifier, keys::Key};
use bytemuck::{cast_ref, Pod, Zeroable};
use core::mem::size_of;
/// 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.
@ -15,10 +19,6 @@ pub struct KeyboardReport {
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;

View File

@ -1,55 +0,0 @@
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(())
}
}

219
lib/src/usb/serial.rs Normal file
View File

@ -0,0 +1,219 @@
use crate::{
logger::{LogOutput, TimestampedRecord},
serial_proto::borrowed::{DeviceMsg, LogRecord},
serial_proto::owned::HostMsg,
util::{DisplayPack, CS},
};
use super::MAX_PACKET_SIZE;
use core::future::pending;
use embassy_executor::Spawner;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::{channel::Channel, pipe::Pipe};
use embassy_time::{Duration, Timer};
use embassy_usb::{
class::cdc_acm::{self, CdcAcmClass},
Builder,
};
use embassy_usb_driver::EndpointError;
use futures::{select_biased, FutureExt};
use msgpck::{MsgPack, MsgUnpack, PackErr, UnpackErr};
use static_cell::StaticCell;
pub const BUFFER_SIZE: usize = 16 * 1024;
static OUT: Pipe<CS, BUFFER_SIZE> = Pipe::new();
pub static IN: Channel<CS, HostMsg, 16> = Channel::new(); // TODO: read from this guy
#[derive(Clone)]
pub struct UsbSerial;
pub async fn setup(usb_builder: &mut Builder<'static, Driver<'static, USB>>) -> &'static UsbSerial {
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);
spawner.must_spawn(serial_task(class));
static USB_SERIAL: UsbSerial = UsbSerial;
&USB_SERIAL
}
async fn wait_connection(class: &mut CdcAcmClass<'static, Driver<'static, USB>>) {
class.wait_connection().await;
OUT.clear();
}
#[embassy_executor::task]
async fn serial_task(mut class: CdcAcmClass<'static, Driver<'static, USB>>) {
let mut write_buf = [0u8; MAX_PACKET_SIZE as usize];
let mut read_buf = [0u8; 1024 as usize];
let mut message_parser = MessageParser::new(&mut read_buf);
class.wait_connection().await;
loop {
select_biased! {
n = OUT.read(&mut write_buf).fuse() => {
let write = async {
class.write_packet(&write_buf[..n]).await?;
// if we send a packet containing exactly MAX_PACKET_SIZE bytes, we need to send another
// packet to "flush" the buffer.
if OUT.len() == 0 && n == usize::from(MAX_PACKET_SIZE) {
let _ = class.write_packet(&[]).await?;
}
Ok(())
};
match write.await {
Ok(()) => {}
Err(EndpointError::Disabled) => wait_connection(&mut class).await,
Err(EndpointError::BufferOverflow) => {
// not much we can do if this happens, just ignore the error
}
}
}
//r = class.read_packet(&mut read_buf).fuse() => {
r = message_parser.read(&mut class).fuse() => {
match r {
Ok(Some(message)) => {
log::info!("Got message!!!: {:?}", message);
if IN.try_send(message).is_err() {
log::error!("USB serial in buffer is full");
}
}
Ok(None) |
Err(EndpointError::Disabled) => wait_connection(&mut class).await,
Err(EndpointError::BufferOverflow) => {
// wtf, this shouldn't happen?
panic!("usb serial buffer overflow on read");
}
}
}
}
}
}
/// Send a [DeviceMsg] over the serial connection.
///
/// If the OUT buffer is full, the message may be partially dropped.
///
/// If no serial connection is active, the message may not be ever be sent.
pub fn serial_send(message: &DeviceMsg<'_>) {
struct UsbWriter;
impl msgpck::Write for UsbWriter {
fn write_all(&mut self, bytes: &[u8]) -> Result<(), PackErr> {
OUT.try_write(bytes).map_err(|_| PackErr::BufferOverflow)?;
Ok(())
}
}
let _ = message.pack_with_writer(&mut UsbWriter);
}
impl LogOutput for UsbSerial {
fn log(&self, record: &TimestampedRecord) {
let ms = record.timestamp.as_millis();
let level = record.metadata().level();
let record = LogRecord {
timestamp: ms,
level: DisplayPack(level),
message: DisplayPack(record.args()),
};
serial_send(&DeviceMsg::Log(record));
}
}
pub struct MessageParser<'a> {
pub buf: &'a mut [u8],
pub len: usize,
}
impl<'buf> MessageParser<'buf> {
pub fn new(buf: &'buf mut [u8]) -> Self {
Self { buf, len: 0 }
}
pub async fn read(
&mut self,
class: &mut CdcAcmClass<'static, Driver<'static, USB>>,
) -> Result<Option<HostMsg>, EndpointError> {
loop {
// try to parse messages from the buffer
if self.len > 0 {
log::debug!("buf: {:x?}", &self.buf[..self.len]);
}
let mut reader = &mut &self.buf[..self.len];
match HostMsg::unpack(&mut reader) {
Ok(r) => {
// remove the decoded bytes from buf
if reader.is_empty() {
self.len = 0;
} else {
let bytes_read = self.len - reader.len();
self.buf.rotate_left(bytes_read);
self.len -= bytes_read;
}
log::debug!("received message: {r:?}");
return Ok(Some(r));
}
// we probably have not gotten the entire message yet, go back to reading bytes.
// if the message is corrupted, we will eventually hit MESSAGE_TIMEOUT or
// max buffer size.
Err(UnpackErr::UnexpectedEof) => {}
// on any other error, the message is corrupt. clear the buffer.
Err(_e) => {
log::error!("{:?}", _e);
self.len = 0;
}
};
// if buffer is not empty, this future will sleep until the pending message times out
let buf_is_empty = self.len == 0;
let timeout = async {
if buf_is_empty {
pending().await
} else {
const MESSAGE_TIMEOUT: Duration = Duration::from_millis(30);
Timer::after(MESSAGE_TIMEOUT).await
}
};
// try to read some bytes from the file
let n = select_biased! {
n = class.read_packet(&mut self.buf[self.len..]).fuse() => n?,
_ = timeout.fuse() => {
log::debug!("clearing buffer");
self.len = 0;
continue;
}
};
// make sure we're not just reading garbage forever
if self.len >= self.buf.len() {
log::debug!("max message size exceeded");
self.len = 0;
continue;
}
self.len += n;
log::info!("read {} bytes, buf: {:x?}", n, &self.buf[..self.len]);
// exit on eof
if n == 0 {
log::error!("read 0 bytes, fuck.");
return Ok(None);
}
}
}
}

View File

@ -1,10 +1,130 @@
use core::{
cell::UnsafeCell,
fmt::{self, Debug, Display, Write},
iter,
mem::MaybeUninit,
ops::Deref,
};
use embassy_futures::yield_now;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Timer};
use msgpck::{Marker, MsgPack, PackErr, Piece};
use portable_atomic::{AtomicU32, Ordering};
use crate::rgb::Rgb;
pub type CS = CriticalSectionRawMutex;
#[derive()]
pub struct SwapCell<T> {
// active_slot: 1 bit
// readers: 31 bits
entries: AtomicU32,
exits: AtomicU32,
slot0: UnsafeCell<MaybeUninit<T>>,
slot1: UnsafeCell<MaybeUninit<T>>,
}
pub struct SwapCellWrite<T: 'static> {
cell: &'static SwapCell<T>,
}
#[derive(Clone)]
pub struct SwapCellRead<T: 'static> {
cell: &'static SwapCell<T>,
}
pub struct SwapCellGuard<'a, T> {
cell: &'a SwapCell<T>,
slot: &'a T,
}
const SLOT_MASK: u32 = 0x80000000;
const ENTRIES_MASK: u32 = 0x7FFFFFFF;
impl<T> SwapCell<T> {
pub const fn new(initial: T) -> Self {
SwapCell {
entries: AtomicU32::new(0),
exits: AtomicU32::new(0),
slot0: UnsafeCell::new(MaybeUninit::new(initial)),
slot1: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn split(&'static mut self) -> (SwapCellRead<T>, SwapCellWrite<T>) {
(SwapCellRead { cell: self }, SwapCellWrite { cell: self })
}
}
impl<T> SwapCellWrite<T> {
pub async fn write(&mut self, t: T) {
let x = self.cell.entries.load(Ordering::SeqCst);
let active_slot = (x & SLOT_MASK).rotate_left(1) as usize;
let mut slots = [&self.cell.slot0, &self.cell.slot1];
slots.rotate_left(active_slot);
let [active_slot, inactive_slot] = slots;
// SAFETY: no one elase is allowed to touch the inactive slot.
unsafe { inactive_slot.get().write(MaybeUninit::new(t)) };
// swap active/inactive slots
let x = self.cell.entries.fetch_xor(SLOT_MASK, Ordering::SeqCst);
let (_active_slot, inactive_slot) = (inactive_slot, active_slot);
// wait until there are no more readers in the previously active slot
let entries = x & ENTRIES_MASK;
loop {
let exits = self.cell.exits.load(Ordering::SeqCst);
if exits >= entries {
break;
}
yield_now().await;
}
// drop the now inactive slot
// SAFETY: we waited until everyone else stopped using this slot.
unsafe { inactive_slot.get().drop_in_place() };
}
}
impl<T> SwapCellRead<T> {
pub fn read(&self) -> SwapCellGuard<'_, T> {
let x = self.cell.entries.fetch_add(1, Ordering::SeqCst);
let entries = x & ENTRIES_MASK;
debug_assert!(entries < ENTRIES_MASK, "SwapCell overflowed");
let slot = (x & SLOT_MASK).rotate_left(1) as usize;
let slot = [&self.cell.slot0, &self.cell.slot1][slot];
let slot = unsafe { &*slot.get() };
let slot = unsafe { slot.assume_init_ref() };
SwapCellGuard {
cell: self.cell,
slot,
}
}
}
impl<T> Drop for SwapCellGuard<'_, T> {
fn drop(&mut self) {
self.cell.exits.fetch_add(1, Ordering::SeqCst);
}
}
impl<T> Deref for SwapCellGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.slot
}
}
pub async fn stall() -> ! {
loop {
Timer::after(Duration::from_secs(1)).await;
@ -25,3 +145,70 @@ pub fn wheel(mut wheel_pos: u8) -> Rgb {
Rgb::new(wheel_pos * 3, 255 - wheel_pos * 3, 0)
}
}
/// Calculate the length of the formatted string of a type that impls Display.
pub fn display_len(t: &impl Display) -> usize {
// impl fmt::Write for a dummy struct that just increments a length
struct Write<'a>(&'a mut usize);
impl fmt::Write for Write<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
*self.0 += s.len();
Ok(())
}
}
let mut n = 0;
let mut w = Write(&mut n);
write!(&mut w, "{}", t).expect("Write impl is infallible");
n
}
/// Wrapper type that impls [MsgPack] for `T: Display`, by serializing `T` as a msgpck string.
pub struct DisplayPack<T>(pub T);
impl<T: Display> MsgPack for DisplayPack<T> {
fn pack(&self) -> impl Iterator<Item = msgpck::Piece<'_>> {
let len = display_len(&self.0) as u32;
[msgpck::Marker::Str32.into(), len.into()]
.into_iter()
.chain(iter::from_fn(move || None)) // TODO
}
fn pack_with_writer(&self, w: &mut dyn msgpck::Write) -> Result<usize, PackErr> {
let mut n = 0;
let str_len = display_len(&self.0);
for bytes in [
Piece::from(Marker::Str32).as_bytes(),
Piece::from(str_len as u32).as_bytes(),
] {
w.write_all(bytes)?;
n += bytes.len();
}
// this is advanced stupid.
struct Write<'a>(&'a mut dyn msgpck::Write);
impl fmt::Write for Write<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0
.write_all(s.as_bytes())
.map_err(|_| fmt::Error::default())?;
Ok(())
}
}
write!(&mut Write(w), "{}", self.0).map_err(|_| PackErr::BufferOverflow)?;
n += str_len;
Ok(n)
}
}
impl<T: Debug> Debug for DisplayPack<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}