Move away from matrix-based key layout
This commit is contained in:
81
Cargo.lock
generated
81
Cargo.lock
generated
@ -70,6 +70,12 @@ dependencies = [
|
||||
"rustc_version 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
@ -115,6 +121,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@ -418,6 +430,16 @@ dependencies = [
|
||||
"usbd-hid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-alloc"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8931e47e33c5d3194fbcf9cc82df0919193bd2fa40008f388eb1d28fd9c9ea6b"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"linked_list_allocator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "0.2.7"
|
||||
@ -609,6 +631,7 @@ dependencies = [
|
||||
"atomic-polyfill 0.1.11",
|
||||
"hash32",
|
||||
"rustc_version 0.4.0",
|
||||
"serde",
|
||||
"spin",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
@ -661,14 +684,18 @@ dependencies = [
|
||||
"embassy-usb",
|
||||
"embassy-usb-driver",
|
||||
"embassy-usb-logger",
|
||||
"embedded-alloc",
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-io",
|
||||
"futures",
|
||||
"log",
|
||||
"pio",
|
||||
"pio-proc",
|
||||
"postcard",
|
||||
"ron",
|
||||
"smart-leds",
|
||||
"static_cell",
|
||||
"tgnt",
|
||||
"usb-device",
|
||||
"usbd-hid",
|
||||
]
|
||||
@ -711,6 +738,12 @@ version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "linked_list_allocator"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
@ -896,6 +929,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postcard"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfa512cd0d087cc9f99ad30a1bf64795b67871edbead083ffc3a4dfafa59aa00"
|
||||
dependencies = [
|
||||
"cobs",
|
||||
"heapless",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
@ -996,6 +1040,17 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rp2040-pac2"
|
||||
version = "0.1.0"
|
||||
@ -1058,9 +1113,23 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.154"
|
||||
version = "1.0.156"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
|
||||
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.156"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@ -1176,6 +1245,14 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tgnt"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.nubo.sh/hulthe/tgnt.git#e22588c2cb139864fcd3be1087350803343c622b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.39"
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@ -4,17 +4,16 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
#adafruit-itsy-bitsy-rp2040 = "0.6.0"
|
||||
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"
|
||||
#panic-halt = "0.2.0"
|
||||
usb-device = "*"
|
||||
usb-device = "0.2.9"
|
||||
usbd-hid = "0.6.1"
|
||||
static_cell = "1.0.0"
|
||||
embedded-io = { version = "*", features = ["async"] }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "integrated-timers" ] }
|
||||
embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly"] }
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", features = ["log"] }
|
||||
@ -22,12 +21,18 @@ embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", features
|
||||
embassy-usb = { git = "https://github.com/embassy-rs/embassy.git", features = ["usbd-hid"] }
|
||||
embassy-usb-logger = { git = "https://github.com/embassy-rs/embassy.git", features = [] }
|
||||
embassy-usb-driver = { git = "https://github.com/embassy-rs/embassy.git", features = [] }
|
||||
|
||||
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "unstable-traits", "unstable-pac", "time-driver", "pio", "critical-section-impl"] }
|
||||
log = "0.4.17"
|
||||
pio = "0.2.1"
|
||||
pio-proc = "0.2.1"
|
||||
smart-leds = "0.3.0"
|
||||
embedded-alloc = "0.5.0"
|
||||
postcard = { version = "1.0.4", features = ["alloc"] }
|
||||
|
||||
[build-dependencies]
|
||||
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
|
||||
ron = "0.8.0"
|
||||
postcard = { version = "1", features = ["use-std"] }
|
||||
|
||||
[features]
|
||||
default = ["n-key-rollover"]
|
||||
|
||||
23
build.rs
23
build.rs
@ -8,12 +8,19 @@
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
use tgnt::layer::Layer;
|
||||
|
||||
fn main() {
|
||||
memory();
|
||||
serialize_layout();
|
||||
}
|
||||
|
||||
fn memory() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
@ -36,3 +43,17 @@ fn main() {
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
|
||||
//println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||
}
|
||||
|
||||
fn serialize_layout() {
|
||||
println!("cargo:rerun-if-changed=layers.ron");
|
||||
|
||||
let layers = fs::read_to_string("layers.ron").expect("Failed to read layers.ron");
|
||||
let layers: Vec<Layer> = ron::from_str(&layers).expect("Failed to deserialize layers.ron");
|
||||
|
||||
let serialized = postcard::to_stdvec(&layers).expect("Failed to serialize layers");
|
||||
|
||||
File::create("./src/layers.pc")
|
||||
.expect("Failed to create layers.pc")
|
||||
.write_all(&serialized)
|
||||
.expect("Failed to write layers.pc");
|
||||
}
|
||||
|
||||
59
layers.ron
Normal file
59
layers.ron
Normal file
@ -0,0 +1,59 @@
|
||||
[
|
||||
Layer(
|
||||
buttons: [
|
||||
/* Left */
|
||||
// Row 1
|
||||
Key(Apostrophe),
|
||||
Key(Comma),
|
||||
Key(Period),
|
||||
Key(P),
|
||||
Key(Y),
|
||||
|
||||
// Row 2
|
||||
Key(A),
|
||||
Key(O),
|
||||
Key(E),
|
||||
Key(U),
|
||||
Key(I),
|
||||
|
||||
// Row 3
|
||||
Key(Colon),
|
||||
Key(Q),
|
||||
Key(J),
|
||||
Key(K),
|
||||
Key(X),
|
||||
|
||||
// Thumbpad
|
||||
Mod(LShift),
|
||||
Mod(LCtrl),
|
||||
NextLayer,
|
||||
|
||||
/* Right */
|
||||
// Row 1
|
||||
Key(F),
|
||||
Key(G),
|
||||
Key(C),
|
||||
Key(R),
|
||||
Key(L),
|
||||
|
||||
// Row 2
|
||||
Key(D),
|
||||
Key(H),
|
||||
Key(T),
|
||||
Key(N),
|
||||
Key(S),
|
||||
|
||||
// Row 3
|
||||
Key(B),
|
||||
Key(M),
|
||||
Key(W),
|
||||
Key(V),
|
||||
Key(Z),
|
||||
|
||||
// Thumbpad
|
||||
PrevLayer,
|
||||
Mod(RAlt),
|
||||
Mod(RMod),
|
||||
],
|
||||
)
|
||||
]
|
||||
1
src/.gitignore
vendored
Normal file
1
src/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
layers.pc
|
||||
14
src/allocator.rs
Normal file
14
src/allocator.rs
Normal file
@ -0,0 +1,14 @@
|
||||
extern crate alloc;
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use embedded_alloc::Heap;
|
||||
|
||||
#[global_allocator]
|
||||
static HEAP: Heap = Heap::empty();
|
||||
|
||||
pub fn init() {
|
||||
const HEAP_SIZE: usize = 2048;
|
||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
||||
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
|
||||
}
|
||||
304
src/keyboard.rs
304
src/keyboard.rs
@ -1,238 +1,154 @@
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
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_time::{Duration, Timer};
|
||||
use log::info;
|
||||
use log::{error, warn};
|
||||
use tgnt::{button::Button, layer::Layer};
|
||||
|
||||
pub const ROWS: usize = 3;
|
||||
pub const COLS: usize = 5;
|
||||
const NEW_SWITCH: Switch = Switch::new();
|
||||
const NEW_ROW: [Switch; COLS] = [NEW_SWITCH; COLS];
|
||||
pub static MATRIX: [[Switch; COLS]; ROWS] = [NEW_ROW; ROWS];
|
||||
use crate::usb::keyboard::KB_REPORT;
|
||||
|
||||
pub static TEST_KEYMAP: [[Button; COLS]; ROWS] = [
|
||||
[
|
||||
Button::KEY_A,
|
||||
Button::KEY_B,
|
||||
Button::KEY_C,
|
||||
Button::KEY_D,
|
||||
Button::KEY_E,
|
||||
],
|
||||
[
|
||||
Button::KEY_T,
|
||||
Button::KEY_R,
|
||||
Button::KEY_H,
|
||||
Button::KEY_I,
|
||||
Button::KEY_SPACE,
|
||||
],
|
||||
[
|
||||
Button::KEY_O,
|
||||
Button::KEY_L,
|
||||
Button::KEY_LSHIFT,
|
||||
Button::KEY_LCTRL,
|
||||
Button::KEY_LALT,
|
||||
],
|
||||
];
|
||||
static CURRENT_LAYER: AtomicU16 = AtomicU16::new(0);
|
||||
|
||||
pub struct Switch {
|
||||
state: AtomicBool,
|
||||
pub struct KeyboardConfig {
|
||||
pub pins: [AnyPin; SWITCH_COUNT],
|
||||
pub layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Switch {
|
||||
pub const fn new() -> Self {
|
||||
Switch {
|
||||
state: AtomicBool::new(false),
|
||||
impl KeyboardConfig {
|
||||
pub async fn create(self) {
|
||||
let spawner = Spawner::for_current_executor().await;
|
||||
|
||||
if self.layers.is_empty() {
|
||||
error!("no layers defined");
|
||||
return;
|
||||
}
|
||||
|
||||
let layers = Box::leak(self.layers.into_boxed_slice());
|
||||
for (i, layer) in layers.iter().enumerate() {
|
||||
if layer.buttons.len() != SWITCH_COUNT {
|
||||
warn!(
|
||||
"layer {i} defines {} buttons, but there are {SWITCH_COUNT} switches",
|
||||
layer.buttons.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn press(&self) {
|
||||
self.state.store(true, Ordering::Relaxed);
|
||||
for (i, pin) in self.pins.into_iter().enumerate() {
|
||||
if spawner.spawn(switch_task(i, pin, layers)).is_err() {
|
||||
error!("failed to spawn switch task, pool_size mismatch?");
|
||||
break;
|
||||
}
|
||||
|
||||
pub fn release(&self) {
|
||||
self.state.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn is_pressed(&self) -> bool {
|
||||
self.state.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Button {
|
||||
Key { keycode: u8 },
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = 20)]
|
||||
pub async fn monitor_switch(pin: AnyPin) -> ! {
|
||||
let pin_nr = pin.pin();
|
||||
const SWITCH_COUNT: usize = 18;
|
||||
#[embassy_executor::task(pool_size = 18)]
|
||||
async fn switch_task(switch_num: usize, pin: AnyPin, layers: &'static [Layer]) -> ! {
|
||||
let _pin_nr = pin.pin();
|
||||
let mut pin = Input::new(pin, Pull::Up);
|
||||
loop {
|
||||
// pins are pull-up, so when the switch is pressed they are brought low.
|
||||
pin.wait_for_low().await;
|
||||
info!("pin {pin_nr} low");
|
||||
let mut current_layer = CURRENT_LAYER.load(Ordering::Relaxed);
|
||||
let layer_count = 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 {
|
||||
error!("current layer was out of bounds for some reason ({current_layer})");
|
||||
CURRENT_LAYER.store(0, Ordering::Relaxed);
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(button) = buttons.get(switch_num) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match button {
|
||||
&Button::Key(key) => {
|
||||
KB_REPORT.lock().await.press_key(key);
|
||||
pin.wait_for_high().await;
|
||||
info!("pin {pin_nr} high");
|
||||
KB_REPORT.lock().await.release_key(key);
|
||||
continue;
|
||||
}
|
||||
Button::Mod(modifier) => {
|
||||
// TODO
|
||||
//KB_REPORT.lock().await.press_mod(modifier);
|
||||
//pin.wait_for_high().await;
|
||||
//KB_REPORT.lock().await.release_mod(modifier);
|
||||
//continue;
|
||||
}
|
||||
Button::ModTap { keycode, modifier } => {
|
||||
// TODO
|
||||
}
|
||||
Button::NextLayer => {
|
||||
let next_layer = (current_layer + 1) % layer_count;
|
||||
CURRENT_LAYER.store(next_layer, Ordering::Relaxed);
|
||||
}
|
||||
Button::PrevLayer => {
|
||||
let prev_layer = current_layer.checked_sub(1).unwrap_or(layer_count - 1);
|
||||
CURRENT_LAYER.store(prev_layer, Ordering::Relaxed);
|
||||
}
|
||||
Button::None => {}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Button {
|
||||
pub const fn key(keycode: u8) -> Self {
|
||||
Button::Key { keycode }
|
||||
pin.wait_for_high().await;
|
||||
}
|
||||
|
||||
// https://usb.org/sites/default/files/hut1_3_0.pdf
|
||||
pub const KEY_A: Button = Button::key(0x04);
|
||||
pub const KEY_B: Button = Button::key(0x05);
|
||||
pub const KEY_C: Button = Button::key(0x06);
|
||||
pub const KEY_D: Button = Button::key(0x07);
|
||||
pub const KEY_E: Button = Button::key(0x08);
|
||||
pub const KEY_F: Button = Button::key(0x09);
|
||||
pub const KEY_G: Button = Button::key(0x0A);
|
||||
pub const KEY_H: Button = Button::key(0x0B);
|
||||
pub const KEY_I: Button = Button::key(0x0C);
|
||||
pub const KEY_J: Button = Button::key(0x0D);
|
||||
pub const KEY_K: Button = Button::key(0x0E);
|
||||
pub const KEY_L: Button = Button::key(0x0F);
|
||||
pub const KEY_M: Button = Button::key(0x10);
|
||||
pub const KEY_N: Button = Button::key(0x11);
|
||||
pub const KEY_O: Button = Button::key(0x12);
|
||||
pub const KEY_P: Button = Button::key(0x13);
|
||||
pub const KEY_Q: Button = Button::key(0x14);
|
||||
pub const KEY_R: Button = Button::key(0x15);
|
||||
pub const KEY_S: Button = Button::key(0x16);
|
||||
pub const KEY_T: Button = Button::key(0x17);
|
||||
pub const KEY_U: Button = Button::key(0x18);
|
||||
pub const KEY_V: Button = Button::key(0x19);
|
||||
pub const KEY_W: Button = Button::key(0x1A);
|
||||
pub const KEY_X: Button = Button::key(0x1B);
|
||||
pub const KEY_Y: Button = Button::key(0x1C);
|
||||
pub const KEY_Z: Button = Button::key(0x1D);
|
||||
|
||||
pub const KEY_1: Button = Button::key(0x1E);
|
||||
pub const KEY_2: Button = Button::key(0x1F);
|
||||
pub const KEY_3: Button = Button::key(0x20);
|
||||
pub const KEY_4: Button = Button::key(0x21);
|
||||
pub const KEY_5: Button = Button::key(0x22);
|
||||
pub const KEY_6: Button = Button::key(0x23);
|
||||
pub const KEY_7: Button = Button::key(0x24);
|
||||
pub const KEY_8: Button = Button::key(0x25);
|
||||
pub const KEY_9: Button = Button::key(0x26);
|
||||
pub const KEY_0: Button = Button::key(0x27);
|
||||
|
||||
pub const KEY_SPACE: Button = Button::key(0x2C);
|
||||
pub const KEY_RETURN: Button = Button::key(0x28);
|
||||
|
||||
pub const KEY_LCTRL: Button = Button::key(0xE0);
|
||||
pub const KEY_RCTRL: Button = Button::key(0xE4);
|
||||
|
||||
pub const KEY_LSHIFT: Button = Button::key(0xE1);
|
||||
pub const KEY_RSHIFT: Button = Button::key(0xE5);
|
||||
|
||||
pub const KEY_LALT: Button = Button::key(0xE2);
|
||||
pub const KEY_RALT: Button = Button::key(0xE6);
|
||||
}
|
||||
|
||||
/// Random functions for testing
|
||||
#[allow(dead_code)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use tgnt::{button::Button, keys::Key};
|
||||
|
||||
pub fn letter_to_key(c: char) -> Option<Button> {
|
||||
pub fn letter_to_key(c: char) -> Button {
|
||||
if !c.is_ascii() {
|
||||
return None;
|
||||
return Button::None;
|
||||
}
|
||||
|
||||
let c = c.to_ascii_uppercase();
|
||||
|
||||
let key = match c {
|
||||
'A' => Button::KEY_A,
|
||||
'B' => Button::KEY_B,
|
||||
'C' => Button::KEY_C,
|
||||
'D' => Button::KEY_D,
|
||||
'E' => Button::KEY_E,
|
||||
'F' => Button::KEY_F,
|
||||
'G' => Button::KEY_G,
|
||||
'H' => Button::KEY_H,
|
||||
'I' => Button::KEY_I,
|
||||
'J' => Button::KEY_J,
|
||||
'K' => Button::KEY_K,
|
||||
'L' => Button::KEY_L,
|
||||
'M' => Button::KEY_M,
|
||||
'N' => Button::KEY_N,
|
||||
'O' => Button::KEY_O,
|
||||
'P' => Button::KEY_P,
|
||||
'Q' => Button::KEY_Q,
|
||||
'R' => Button::KEY_R,
|
||||
'S' => Button::KEY_S,
|
||||
'T' => Button::KEY_T,
|
||||
'U' => Button::KEY_U,
|
||||
'V' => Button::KEY_V,
|
||||
'W' => Button::KEY_W,
|
||||
'X' => Button::KEY_X,
|
||||
'Y' => Button::KEY_Y,
|
||||
'Z' => Button::KEY_Z,
|
||||
' ' => Button::KEY_SPACE,
|
||||
'\n' => Button::KEY_RETURN,
|
||||
'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 None;
|
||||
return Button::None;
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("char {c:?} -> {key:?}");
|
||||
|
||||
Some(key)
|
||||
}
|
||||
|
||||
pub async fn type_string(s: &str) {
|
||||
log::info!("typing {s:?}");
|
||||
|
||||
for c in s.chars() {
|
||||
let Some(key) = letter_to_key(c) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for col in 0..COLS {
|
||||
for row in 0..ROWS {
|
||||
if TEST_KEYMAP[row][col] == key {
|
||||
MATRIX[row][col].press();
|
||||
Timer::after(Duration::from_millis(20)).await;
|
||||
MATRIX[row][col].release();
|
||||
Timer::after(Duration::from_millis(5)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Press all keys at once
|
||||
pub async fn rollover<const N: usize>(letters: [char; N]) {
|
||||
log::info!("pressing all letters in {letters:?}");
|
||||
|
||||
let keys = letters.map(letter_to_key);
|
||||
|
||||
for key in &keys {
|
||||
for col in 0..COLS {
|
||||
for row in 0..ROWS {
|
||||
if Some(&TEST_KEYMAP[row][col]) == key.as_ref() {
|
||||
MATRIX[row][col].press();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer::after(Duration::from_millis(200)).await;
|
||||
|
||||
for key in &keys {
|
||||
for col in 0..COLS {
|
||||
for row in 0..ROWS {
|
||||
if Some(&TEST_KEYMAP[row][col]) == key.as_ref() {
|
||||
MATRIX[row][col].release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Button::Key(key)
|
||||
}
|
||||
}
|
||||
|
||||
71
src/main.rs
71
src/main.rs
@ -10,23 +10,32 @@
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate cortex_m_rt;
|
||||
|
||||
mod allocator;
|
||||
mod board;
|
||||
mod keyboard;
|
||||
mod neopixel;
|
||||
mod panic_handler;
|
||||
mod usb;
|
||||
mod util;
|
||||
mod ws2812;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use board::Board;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::gpio::{Level, Output, Pin};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use tgnt::layer::Layer;
|
||||
use ws2812::Rgb;
|
||||
|
||||
use crate::keyboard::KeyboardConfig;
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
allocator::init();
|
||||
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
let board = Board {
|
||||
@ -85,27 +94,42 @@ async fn main(spawner: Spawner) {
|
||||
|
||||
Timer::after(Duration::from_millis(3000)).await;
|
||||
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.a0.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.a1.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.a2.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.a3.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d2.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d3.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d4.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d7.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d9.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d10.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d11.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d12.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d24.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.d25.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.scl.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.sda.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.mosi.degrade()));
|
||||
spawner.must_spawn(keyboard::monitor_switch(board.miso.degrade()));
|
||||
let layers = include_bytes!("layers.pc");
|
||||
let Ok(layers): Result<Vec<Layer>, _> = postcard::from_bytes(layers) else {
|
||||
log::error!("Failed to deserialize layer config");
|
||||
loop_forever().await
|
||||
};
|
||||
|
||||
let keyboard = KeyboardConfig {
|
||||
layers,
|
||||
pins: [
|
||||
// row 1
|
||||
board.d24.degrade(),
|
||||
board.a3.degrade(),
|
||||
board.a2.degrade(),
|
||||
board.a1.degrade(),
|
||||
board.a0.degrade(),
|
||||
// row 2
|
||||
board.d25.degrade(),
|
||||
board.sck.degrade(),
|
||||
board.mosi.degrade(),
|
||||
board.miso.degrade(),
|
||||
board.d2.degrade(),
|
||||
// row 3
|
||||
board.d12.degrade(),
|
||||
board.d11.degrade(),
|
||||
board.d10.degrade(),
|
||||
board.d9.degrade(),
|
||||
board.d3.degrade(),
|
||||
// thumbpad
|
||||
board.d7.degrade(),
|
||||
board.scl.degrade(),
|
||||
board.sda.degrade(),
|
||||
],
|
||||
};
|
||||
|
||||
keyboard.create().await;
|
||||
|
||||
//keyboard::test::type_string("Hello there!\n").await;
|
||||
//keyboard::test::rollover(['h', 'e', 'l', 'o', 't', 'r', 'a', 'b', 'c', 'd', 'i']).await;
|
||||
for w in 0usize.. {
|
||||
neopixel.write(&[wheel(w as u8)]).await;
|
||||
neopixels_d5
|
||||
@ -117,7 +141,12 @@ async fn main(spawner: Spawner) {
|
||||
])
|
||||
.await;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
//Timer::after(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn loop_forever() -> ! {
|
||||
loop {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ pub mod report;
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::{peripherals::USB, usb::Driver};
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embassy_usb::{
|
||||
class::hid::{self, HidReaderWriter, ReadError, ReportId, RequestHandler},
|
||||
@ -13,8 +14,8 @@ use static_cell::StaticCell;
|
||||
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
|
||||
|
||||
use crate::{
|
||||
keyboard::{Button, COLS, MATRIX, ROWS, TEST_KEYMAP},
|
||||
usb::keyboard::report::{KeyboardReport, EMPTY_KEYBOARD_REPORT},
|
||||
util::CS,
|
||||
};
|
||||
|
||||
use super::MAX_PACKET_SIZE;
|
||||
@ -23,6 +24,8 @@ struct Handler;
|
||||
|
||||
static CONTEXT: StaticCell<Context> = StaticCell::new();
|
||||
|
||||
pub static KB_REPORT: Mutex<CS, KeyboardReport> = Mutex::new(EMPTY_KEYBOARD_REPORT);
|
||||
|
||||
struct Context {
|
||||
handler: Handler,
|
||||
state: hid::State<'static>,
|
||||
@ -94,36 +97,7 @@ async fn keyboard_test(mut stream: HidStream, _handler: &'static Handler) -> Res
|
||||
loop {
|
||||
Timer::after(Duration::from_millis(2)).await;
|
||||
|
||||
let keymap = &TEST_KEYMAP;
|
||||
|
||||
let mut report = EMPTY_KEYBOARD_REPORT;
|
||||
#[cfg(not(feature = "n-key-rollover"))]
|
||||
let mut i = 0;
|
||||
|
||||
#[allow(unused_labels)]
|
||||
'keyscan: for col in 0..COLS {
|
||||
for row in 0..ROWS {
|
||||
if !MATRIX[row][col].is_pressed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let &Button::Key { keycode } = &keymap[row][col];
|
||||
// else { continue; };
|
||||
|
||||
#[cfg(feature = "n-key-rollover")]
|
||||
report.set_key(keycode);
|
||||
|
||||
#[cfg(not(feature = "n-key-rollover"))]
|
||||
{
|
||||
report.keycodes[i] = keycode;
|
||||
i += 1;
|
||||
if i >= report.keycodes.len() {
|
||||
break 'keyscan;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let report = KB_REPORT.lock().await.clone();
|
||||
if report.keycodes != EMPTY_KEYBOARD_REPORT.keycodes {
|
||||
log::debug!("keys: {:x?}", report.keycodes);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
/// keyboard LEDs.
|
||||
///
|
||||
/// Unlike usbd_hids KeyboardReport, this one supports N-key rollover.
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg(feature = "n-key-rollover")]
|
||||
pub struct KeyboardReport {
|
||||
pub modifier: u8,
|
||||
@ -14,6 +14,7 @@ pub struct KeyboardReport {
|
||||
pub keycodes: [u8; 13],
|
||||
}
|
||||
|
||||
use tgnt::keys::Key;
|
||||
#[cfg(not(feature = "n-key-rollover"))]
|
||||
pub use usbd_hid::descriptor::KeyboardReport;
|
||||
|
||||
@ -33,19 +34,32 @@ pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
|
||||
|
||||
#[cfg(feature = "n-key-rollover")]
|
||||
impl KeyboardReport {
|
||||
pub fn set_key(&mut self, keycode: u8) {
|
||||
log::info!("setting keycode: {keycode}");
|
||||
pub fn set_key(&mut self, key: Key, pressed: bool) {
|
||||
let keycode = u8::from(key);
|
||||
log::debug!("setting key: {key:?} ({keycode:x})");
|
||||
let byte = keycode >> 3;
|
||||
let bit = keycode & 0b111;
|
||||
let mask = 1 << bit;
|
||||
|
||||
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: {keycode:x}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn press_key(&mut self, key: Key) {
|
||||
self.set_key(key, true)
|
||||
}
|
||||
|
||||
pub fn release_key(&mut self, key: Key) {
|
||||
self.set_key(key, false)
|
||||
}
|
||||
|
||||
pub fn serialized(&self) -> [u8; 14] {
|
||||
let [a, b, c, d, e, f, g, h, i, j, k, l, m] = self.keycodes;
|
||||
[self.modifier, a, b, c, d, e, f, g, h, i, j, k, l, m]
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
use crate::util::CS;
|
||||
|
||||
use super::MAX_PACKET_SIZE;
|
||||
use core::fmt::Write as WriteFmt;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::{peripherals::USB, usb::Driver};
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
|
||||
use embassy_sync::pipe::Pipe;
|
||||
use embassy_time::Instant;
|
||||
use embassy_usb::{
|
||||
class::cdc_acm::{self, CdcAcmClass},
|
||||
@ -12,7 +14,7 @@ use log::{Metadata, Record};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
pub const BUFFER_SIZE: usize = 16 * 1024;
|
||||
static BUFFER: Pipe<CriticalSectionRawMutex, BUFFER_SIZE> = Pipe::new();
|
||||
static BUFFER: Pipe<CS, BUFFER_SIZE> = Pipe::new();
|
||||
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
|
||||
|
||||
struct UsbLogger;
|
||||
@ -62,7 +64,7 @@ impl log::Log for UsbLogger {
|
||||
let s = now.as_secs();
|
||||
let ms = now.as_millis() % 1000;
|
||||
let level = record.metadata().level();
|
||||
let _ = write!(w, "[{s}.{ms:04}] ({level}) {}\n", record.args());
|
||||
let _ = writeln!(w, "[{s}.{ms:04}] ({level}) {}", record.args());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
src/util.rs
Normal file
3
src/util.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
|
||||
pub type CS = CriticalSectionRawMutex;
|
||||
Reference in New Issue
Block a user