implement basic keyboard firmware for my 40% ortho prototype, along with debug display info
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
# Cargo build artifacts
|
# Cargo build artifacts
|
||||||
/target/
|
/target/
|
||||||
**/.idea
|
**/.idea
|
||||||
|
*.uf2
|
||||||
159
Cargo.lock
generated
159
Cargo.lock
generated
@@ -116,6 +116,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte-slice-cast"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.24.0"
|
version = "1.24.0"
|
||||||
@@ -318,6 +324,35 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "display-interface"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "display-interface-i2c"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d964fa85bbbb5a6ecd06e58699407ac5dc3e3ad72dac0ab7e6b0d00a1cd262d"
|
||||||
|
dependencies = [
|
||||||
|
"display-interface",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
"embedded-hal-async",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "display-interface-spi"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f86b9ec30048b1955da2038fcc3c017f419ab21bb0001879d16c0a3749dc6b7a"
|
||||||
|
dependencies = [
|
||||||
|
"byte-slice-cast",
|
||||||
|
"display-interface",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
"embedded-hal-async",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "document-features"
|
name = "document-features"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@@ -565,6 +600,29 @@ dependencies = [
|
|||||||
"embedded-io-async",
|
"embedded-io-async",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
"embedded-graphics-core",
|
||||||
|
"float-cmp",
|
||||||
|
"micromath",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics-core"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-hal"
|
name = "embedded-hal"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
@@ -669,6 +727,15 @@ version = "0.5.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -799,12 +866,17 @@ dependencies = [
|
|||||||
"embassy-sync 0.6.2",
|
"embassy-sync 0.6.2",
|
||||||
"embassy-time",
|
"embassy-time",
|
||||||
"embassy-usb",
|
"embassy-usb",
|
||||||
|
"embedded-graphics",
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
"embedded-hal-async",
|
"embedded-hal-async",
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
"embedded-io-async",
|
"embedded-io-async",
|
||||||
"embedded-storage",
|
"embedded-storage",
|
||||||
|
"heapless",
|
||||||
"panic-probe",
|
"panic-probe",
|
||||||
|
"portable-atomic",
|
||||||
|
"ssd1306",
|
||||||
|
"static_cell",
|
||||||
"usbd-hid",
|
"usbd-hid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -876,12 +948,31 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-async-cfg"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"pulldown-cmark",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "micromath"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@@ -1065,9 +1156,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "precomputed-hash"
|
name = "precomputed-hash"
|
||||||
@@ -1075,6 +1166,30 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error-attr2"
|
name = "proc-macro-error-attr2"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -1106,6 +1221,17 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulldown-cmark"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"memchr",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.42"
|
version = "1.0.42"
|
||||||
@@ -1304,6 +1430,20 @@ dependencies = [
|
|||||||
"rgb",
|
"rgb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssd1306"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ea6aac2d078bbc71d9b8ac3f657335311f3b6625e9a1a96ccc29f5abfa77c56"
|
||||||
|
dependencies = [
|
||||||
|
"display-interface",
|
||||||
|
"display-interface-i2c",
|
||||||
|
"display-interface-spi",
|
||||||
|
"embedded-graphics-core",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
"maybe-async-cfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ssmarshal"
|
name = "ssmarshal"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1320,6 +1460,15 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_cell"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -1404,6 +1553,12 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ embassy-rp = { version = "0.4", features = ["defmt", "unstable-pac", "time-drive
|
|||||||
embassy-usb = "0.4.0"
|
embassy-usb = "0.4.0"
|
||||||
usbd-hid = "0.8.2"
|
usbd-hid = "0.8.2"
|
||||||
embassy-futures = "0.1.2"
|
embassy-futures = "0.1.2"
|
||||||
|
ssd1306 = "0.10.0"
|
||||||
|
embedded-graphics = "0.8.1"
|
||||||
|
heapless = "0.8"
|
||||||
|
static_cell = "2.1.1"
|
||||||
|
portable-atomic = { version = "1.13.0", features = ["unsafe-assume-single-core"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 2
|
debug = 2
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
85
src/display.rs
Normal file
85
src/display.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use embedded_graphics::{
|
||||||
|
mono_font::{MonoTextStyleBuilder, ascii::FONT_6X10},
|
||||||
|
pixelcolor::BinaryColor,
|
||||||
|
prelude::Point,
|
||||||
|
text::{Baseline, Text},
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
Drawable,
|
||||||
|
};
|
||||||
|
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306, mode::BufferedGraphicsMode};
|
||||||
|
use embassy_rp::i2c::I2c;
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
/// Display type encapsulating the I2C interface, its size and graphics mode
|
||||||
|
pub type Display = Ssd1306<
|
||||||
|
I2CInterface<I2c<'static, embassy_rp::peripherals::I2C0, embassy_rp::i2c::Async>>,
|
||||||
|
DisplaySize128x32,
|
||||||
|
BufferedGraphicsMode<DisplaySize128x32>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Function that initializes the display with given settings
|
||||||
|
pub fn init_display(bus: I2c<'static, embassy_rp::peripherals::I2C0, embassy_rp::i2c::Async>) -> Option<Display> {
|
||||||
|
let interface = I2CDisplayInterface::new(bus);
|
||||||
|
let mut display = Ssd1306::new(
|
||||||
|
interface,
|
||||||
|
DisplaySize128x32,
|
||||||
|
DisplayRotation::Rotate180,
|
||||||
|
)
|
||||||
|
.into_buffered_graphics_mode();
|
||||||
|
|
||||||
|
match display.init() {
|
||||||
|
Ok(_) => Some(display),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temp function which updates the display with currently pressed keys
|
||||||
|
pub fn update_display(
|
||||||
|
display: &mut Option<Display>,
|
||||||
|
pressed_keys: &[(usize, usize, crate::keymap::KeyCode)],
|
||||||
|
) -> Option<()> {
|
||||||
|
if let Some(display) = display {
|
||||||
|
display.clear(BinaryColor::Off).ok()?;
|
||||||
|
|
||||||
|
let text_style = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_6X10)
|
||||||
|
.text_color(BinaryColor::On)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if pressed_keys.is_empty() {
|
||||||
|
Text::with_baseline("No keys", Point::zero(), text_style, Baseline::Top)
|
||||||
|
.draw(display)
|
||||||
|
.ok()?;
|
||||||
|
} else {
|
||||||
|
let mut y_pos = 0;
|
||||||
|
for (row_idx, col_idx, keycode) in pressed_keys.iter().take(3) {
|
||||||
|
let mut msg = heapless::String::<32>::new();
|
||||||
|
write!(&mut msg, "R{}C{}: 0x{:02X}", row_idx, col_idx, keycode.as_u8()).ok();
|
||||||
|
|
||||||
|
Text::with_baseline(&msg, Point::new(0, y_pos), text_style, Baseline::Top)
|
||||||
|
.draw(display)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
y_pos += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display.flush().ok()?;
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to clear the display
|
||||||
|
pub fn clear_display(
|
||||||
|
display: &mut Option<Display>,
|
||||||
|
) -> Option<()> {
|
||||||
|
if let Some(display) = display {
|
||||||
|
display.clear(BinaryColor::Off).ok()?;
|
||||||
|
display.flush().ok()?;
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/keymap.rs
Normal file
172
src/keymap.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/// USB HID Keyboard scancodes
|
||||||
|
/// Reference: https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
|
||||||
|
/// Section 10: Keyboard/Keypad Page (0x07)
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KeyCode {
|
||||||
|
No = 0x00,
|
||||||
|
|
||||||
|
// Letters
|
||||||
|
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,
|
||||||
|
|
||||||
|
// Numbers
|
||||||
|
Kb1 = 0x1E,
|
||||||
|
Kb2 = 0x1F,
|
||||||
|
Kb3 = 0x20,
|
||||||
|
Kb4 = 0x21,
|
||||||
|
Kb5 = 0x22,
|
||||||
|
Kb6 = 0x23,
|
||||||
|
Kb7 = 0x24,
|
||||||
|
Kb8 = 0x25,
|
||||||
|
Kb9 = 0x26,
|
||||||
|
Kb0 = 0x27,
|
||||||
|
|
||||||
|
// Special keys
|
||||||
|
Enter = 0x28,
|
||||||
|
Escape = 0x29,
|
||||||
|
Backspace = 0x2A,
|
||||||
|
Tab = 0x2B,
|
||||||
|
Space = 0x2C,
|
||||||
|
Minus = 0x2D,
|
||||||
|
Equal = 0x2E,
|
||||||
|
LeftBracket = 0x2F,
|
||||||
|
RightBracket = 0x30,
|
||||||
|
Backslash = 0x31,
|
||||||
|
Semicolon = 0x33,
|
||||||
|
Quote = 0x34,
|
||||||
|
Grave = 0x35,
|
||||||
|
Comma = 0x36,
|
||||||
|
Dot = 0x37,
|
||||||
|
Slash = 0x38,
|
||||||
|
CapsLock = 0x39,
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
F1 = 0x3A,
|
||||||
|
F2 = 0x3B,
|
||||||
|
F3 = 0x3C,
|
||||||
|
F4 = 0x3D,
|
||||||
|
F5 = 0x3E,
|
||||||
|
F6 = 0x3F,
|
||||||
|
F7 = 0x40,
|
||||||
|
F8 = 0x41,
|
||||||
|
F9 = 0x42,
|
||||||
|
F10 = 0x43,
|
||||||
|
F11 = 0x44,
|
||||||
|
F12 = 0x45,
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
PrintScreen = 0x46,
|
||||||
|
ScrollLock = 0x47,
|
||||||
|
Pause = 0x48,
|
||||||
|
Insert = 0x49,
|
||||||
|
Home = 0x4A,
|
||||||
|
PageUp = 0x4B,
|
||||||
|
Delete = 0x4C,
|
||||||
|
End = 0x4D,
|
||||||
|
PageDown = 0x4E,
|
||||||
|
Right = 0x4F,
|
||||||
|
Left = 0x50,
|
||||||
|
Down = 0x51,
|
||||||
|
Up = 0x52,
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
LeftCtrl = 0xE0,
|
||||||
|
LeftShift = 0xE1,
|
||||||
|
LeftAlt = 0xE2,
|
||||||
|
LeftGui = 0xE3,
|
||||||
|
RightCtrl = 0xE4,
|
||||||
|
RightShift = 0xE5,
|
||||||
|
RightAlt = 0xE6, // AltGr
|
||||||
|
RightGui = 0xE7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyCode {
|
||||||
|
pub const fn as_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_modifier(self) -> bool {
|
||||||
|
matches!(self,
|
||||||
|
KeyCode::LeftCtrl | KeyCode::LeftShift | KeyCode::LeftAlt | KeyCode::LeftGui |
|
||||||
|
KeyCode::RightCtrl | KeyCode::RightShift | KeyCode::RightAlt | KeyCode::RightGui
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn modifier_bit(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
KeyCode::LeftCtrl => 0x01,
|
||||||
|
KeyCode::LeftShift => 0x02,
|
||||||
|
KeyCode::LeftAlt => 0x04,
|
||||||
|
KeyCode::LeftGui => 0x08,
|
||||||
|
KeyCode::RightCtrl => 0x10,
|
||||||
|
KeyCode::RightShift => 0x20,
|
||||||
|
KeyCode::RightAlt => 0x40, // AltGr
|
||||||
|
KeyCode::RightGui => 0x80,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keymap for my 4x12 ortholinear keyboard
|
||||||
|
/// Each position [row][col] maps to a USB HID scancode
|
||||||
|
pub const KEYMAP: [[KeyCode; 12]; 4] = [
|
||||||
|
[
|
||||||
|
KeyCode::Escape, KeyCode::Q, KeyCode::W, KeyCode::E,
|
||||||
|
KeyCode::R, KeyCode::T, KeyCode::Y, KeyCode::U,
|
||||||
|
KeyCode::I, KeyCode::O, KeyCode::P, KeyCode::Backspace,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
KeyCode::Tab, KeyCode::A, KeyCode::S, KeyCode::D,
|
||||||
|
KeyCode::F, KeyCode::G, KeyCode::H, KeyCode::J,
|
||||||
|
KeyCode::K, KeyCode::L, KeyCode::Quote, KeyCode::Enter,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
KeyCode::LeftShift, KeyCode::Z, KeyCode::X, KeyCode::C,
|
||||||
|
KeyCode::V, KeyCode::B, KeyCode::N, KeyCode::M,
|
||||||
|
KeyCode::Comma, KeyCode::Dot, KeyCode::Up, KeyCode::Slash,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
KeyCode::LeftCtrl, KeyCode::No, KeyCode::No, KeyCode::LeftAlt,
|
||||||
|
KeyCode::No, KeyCode::Space, KeyCode::No, KeyCode::RightAlt,
|
||||||
|
KeyCode::No, KeyCode::Left, KeyCode::Down, KeyCode::Right,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Get keycode from given row and column
|
||||||
|
pub fn get_keycode(row: usize, col: usize) -> Option<KeyCode> {
|
||||||
|
if row < 4 && col < 12 {
|
||||||
|
let keycode = KEYMAP[row][col];
|
||||||
|
if keycode != KeyCode::No {
|
||||||
|
return Some(keycode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
295
src/main.rs
295
src/main.rs
@@ -1,222 +1,141 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
mod matrix;
|
||||||
|
mod keymap;
|
||||||
|
mod display;
|
||||||
|
mod usb;
|
||||||
|
|
||||||
use defmt::{info, warn};
|
use defmt::{info, warn};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
use embassy_rp::{bind_interrupts, gpio::{Input, Level, Output, Pull}, peripherals::USB, usb::{Driver, InterruptHandler}};
|
use embassy_rp::{
|
||||||
|
bind_interrupts,
|
||||||
|
i2c::{Config as I2cConfig, I2c, InterruptHandler as I2cInterruptHandler},
|
||||||
|
peripherals::USB,
|
||||||
|
usb::{Driver, InterruptHandler as UsbInterruptHandler},
|
||||||
|
};
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embassy_usb::{Builder, Config, Handler, class::hid::{HidReaderWriter, ReportId, RequestHandler, State}, control::OutResponse};
|
use usbd_hid::descriptor::KeyboardReport;
|
||||||
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
pub struct Debouncer<'a> {
|
use keymap::get_keycode;
|
||||||
input: Input<'a>,
|
|
||||||
debounce: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Debouncer<'a> {
|
|
||||||
pub fn new(input: Input<'a>, debounce: Duration) -> Self {
|
|
||||||
Self { input, debounce }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn debounce_low(&mut self) -> Level {
|
|
||||||
loop {
|
|
||||||
let l1 = self.input.get_level();
|
|
||||||
|
|
||||||
self.input.wait_for_low().await;
|
|
||||||
|
|
||||||
Timer::after(self.debounce).await;
|
|
||||||
|
|
||||||
let l2 = self.input.get_level();
|
|
||||||
if l1 != l2 {
|
|
||||||
break l2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn debounce_high(&mut self) -> Level {
|
|
||||||
loop {
|
|
||||||
let l1 = self.input.get_level();
|
|
||||||
|
|
||||||
self.input.wait_for_high().await;
|
|
||||||
|
|
||||||
Timer::after(self.debounce).await;
|
|
||||||
|
|
||||||
let l2 = self.input.get_level();
|
|
||||||
if l1 != l2 {
|
|
||||||
break l2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
USBCTRL_IRQ => InterruptHandler<USB>;
|
I2C0_IRQ => I2cInterruptHandler<embassy_rp::peripherals::I2C0>;
|
||||||
|
USBCTRL_IRQ => UsbInterruptHandler<USB>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) -> () {
|
async fn main(_spawner: Spawner) -> () {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
let driver = Driver::new(p.USB, Irqs);
|
info!("Firmware starting");
|
||||||
|
|
||||||
let mut config = Config::new(0x1234, 0xabcd);
|
// Setup USB HID
|
||||||
config.manufacturer = Some("Lukrecja");
|
let usb_driver = Driver::new(p.USB, Irqs);
|
||||||
config.product = Some("kb-prototype");
|
let mut usb_keyboard = usb::setup_usb(usb_driver);
|
||||||
config.serial_number = Some("00000000");
|
let usb_fut = usb_keyboard.usb.run();
|
||||||
config.composite_with_iads = false;
|
info!("USB configured");
|
||||||
config.device_class = 0x00;
|
|
||||||
config.device_sub_class = 0x00;
|
|
||||||
config.device_protocol = 0x00;
|
|
||||||
|
|
||||||
let mut config_descriptor = [0; 256];
|
// Setup I2C display
|
||||||
let mut bos_descriptor = [0; 256];
|
info!("Initializing display...");
|
||||||
let mut msos_descriptor = [0; 256];
|
let i2c_config = I2cConfig::default();
|
||||||
let mut control_buf = [0; 64];
|
let bus = I2c::new_async(p.I2C0, p.PIN_17, p.PIN_16, Irqs, i2c_config);
|
||||||
let mut request_handler = KbRequestHandler {};
|
let mut display = display::init_display(bus);
|
||||||
let mut device_handler = KbDeviceHandler::new();
|
match display {
|
||||||
|
Some(_) => info!("Display initialized"),
|
||||||
|
None => warn!("Can't initialize display"),
|
||||||
|
}
|
||||||
|
|
||||||
let mut state = State::new();
|
// Setup matrix
|
||||||
|
info!("Initializing matrix...");
|
||||||
let mut builder = Builder::new(
|
let mut matrix = matrix_4x12!(p,
|
||||||
driver,
|
rows: [PIN_0, PIN_1, PIN_2, PIN_3],
|
||||||
config,
|
cols: [PIN_4, PIN_5, PIN_6, PIN_7, PIN_8, PIN_9, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15]
|
||||||
&mut config_descriptor,
|
|
||||||
&mut bos_descriptor,
|
|
||||||
&mut msos_descriptor,
|
|
||||||
&mut control_buf,
|
|
||||||
);
|
);
|
||||||
|
info!("Matrix initialized");
|
||||||
|
|
||||||
builder.handler(&mut device_handler);
|
let mut writer = usb_keyboard.writer;
|
||||||
|
let reader = usb_keyboard.reader;
|
||||||
|
let request_handler = usb_keyboard.request_handler;
|
||||||
|
info!("Starting main loop...");
|
||||||
|
|
||||||
let config = embassy_usb::class::hid::Config {
|
display::clear_display(&mut display);
|
||||||
report_descriptor: KeyboardReport::desc(),
|
|
||||||
request_handler: None,
|
|
||||||
poll_ms: 60,
|
|
||||||
max_packet_size: 64,
|
|
||||||
};
|
|
||||||
|
|
||||||
let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config);
|
|
||||||
|
|
||||||
let mut usb = builder.build();
|
|
||||||
|
|
||||||
let usb_fut = usb.run();
|
|
||||||
|
|
||||||
let mut led = Output::new(p.PIN_25, Level::Low);
|
|
||||||
let mut switch_input = Debouncer::new(Input::new(p.PIN_0, Pull::Up), Duration::from_millis(20));
|
|
||||||
switch_input.input.set_schmitt(true);
|
|
||||||
|
|
||||||
let (reader, mut writer) = hid.split();
|
|
||||||
|
|
||||||
|
// Keyboard scanning task
|
||||||
let in_fut = async {
|
let in_fut = async {
|
||||||
|
let mut previous_keycodes: [u8; 6] = [0; 6];
|
||||||
|
let mut previous_modifier: u8 = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
defmt::info!("waiting for low");
|
let scan_result = matrix.scan().await;
|
||||||
led.set_low();
|
|
||||||
|
// Collect all pressed keys and their scancodes
|
||||||
switch_input.debounce_low().await;
|
let mut pressed_keys: heapless::Vec<(usize, usize, keymap::KeyCode), 48> = heapless::Vec::new();
|
||||||
|
|
||||||
info!("got low");
|
for (row_idx, row) in scan_result.iter().enumerate() {
|
||||||
led.set_high();
|
for (col_idx, &is_pressed) in row.iter().enumerate() {
|
||||||
|
if is_pressed {
|
||||||
let report = KeyboardReport {
|
if let Some(keycode) = get_keycode(row_idx, col_idx) {
|
||||||
keycodes: [4, 0, 0, 0, 0, 0],
|
let _ = pressed_keys.push((row_idx, col_idx, keycode));
|
||||||
leds: 0,
|
}
|
||||||
modifier: 0,
|
}
|
||||||
reserved: 0,
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
match writer.write_serialize(&report).await {
|
// Build modifier byte and keycodes array
|
||||||
Ok(()) => info!("report sent"),
|
let mut modifier: u8 = 0;
|
||||||
Err(e) => warn!("failed to send: {:?}", e),
|
let mut keycodes = [0u8; 6];
|
||||||
};
|
let mut keycode_index = 0;
|
||||||
|
|
||||||
switch_input.debounce_high().await;
|
for (_, _, keycode) in pressed_keys.iter() {
|
||||||
info!("got high");
|
if keycode.is_modifier() {
|
||||||
|
// Add to modifier byte
|
||||||
let report = KeyboardReport {
|
modifier |= keycode.modifier_bit();
|
||||||
keycodes: [0, 0, 0, 0, 0, 0],
|
} else if keycode_index < 6 {
|
||||||
leds: 0,
|
// Add to keycodes array (max 6 regular keys)
|
||||||
modifier: 0,
|
keycodes[keycode_index] = keycode.as_u8();
|
||||||
reserved: 0,
|
keycode_index += 1;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
match writer.write_serialize(&report).await {
|
|
||||||
Ok(()) => info!("report sent"),
|
// Update display
|
||||||
Err(e) => warn!("failed to send: {:?}", e),
|
display::update_display(&mut display, &pressed_keys);
|
||||||
};
|
|
||||||
|
// Send HID report if keycodes or modifiers changed
|
||||||
|
if keycodes != previous_keycodes || modifier != previous_modifier {
|
||||||
|
if keycodes != [0; 6] || modifier != 0 {
|
||||||
|
info!("Keys: {:?}, Mods: 0x{:02X}", keycodes, modifier);
|
||||||
|
} else {
|
||||||
|
info!("All keys released");
|
||||||
|
}
|
||||||
|
|
||||||
|
let report = KeyboardReport {
|
||||||
|
keycodes,
|
||||||
|
leds: 0,
|
||||||
|
modifier,
|
||||||
|
reserved: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
match writer.write_serialize(&report).await {
|
||||||
|
Ok(()) => {},
|
||||||
|
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
previous_keycodes = keycodes;
|
||||||
|
previous_modifier = modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer::after(Duration::from_millis(10)).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// USB reader task
|
||||||
let out_fut = async {
|
let out_fut = async {
|
||||||
reader.run(false, &mut request_handler).await;
|
reader.run(false, request_handler).await;
|
||||||
};
|
};
|
||||||
|
|
||||||
join(usb_fut, join(in_fut, out_fut)).await;
|
join(usb_fut, join(in_fut, out_fut)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KbRequestHandler {}
|
|
||||||
|
|
||||||
impl RequestHandler for KbRequestHandler {
|
|
||||||
fn get_report(&mut self, _id: ReportId, _buf: &mut [u8]) -> Option<usize> {
|
|
||||||
info!("get report");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_report(&mut self, _id: ReportId, data: &[u8]) -> OutResponse {
|
|
||||||
info!("set report: {=[u8]}", data);
|
|
||||||
OutResponse::Accepted
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_idle_ms(&mut self, _id: Option<ReportId>, dur: u32) {
|
|
||||||
info!("set idle rate to {}", dur);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_idle_ms(&mut self, _id: Option<ReportId>) -> Option<u32> {
|
|
||||||
info!("get idle rate");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KbDeviceHandler {
|
|
||||||
configured: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KbDeviceHandler {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
configured: AtomicBool::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler for KbDeviceHandler {
|
|
||||||
fn enabled(&mut self, enabled: bool) {
|
|
||||||
self.configured.store(false, Ordering::Relaxed);
|
|
||||||
if enabled {
|
|
||||||
info!("device enabled");
|
|
||||||
} else {
|
|
||||||
info!("device disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) {
|
|
||||||
self.configured.store(false, Ordering::Relaxed);
|
|
||||||
info!("bus reset, Vbus current limit is 100mA");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addressed(&mut self, addr: u8) {
|
|
||||||
self.configured.store(false, Ordering::Relaxed);
|
|
||||||
info!("USB address set to: {}", addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configured(&mut self, configured: bool) {
|
|
||||||
self.configured.store(configured, Ordering::Relaxed);
|
|
||||||
if configured {
|
|
||||||
info!("device configured")
|
|
||||||
} else {
|
|
||||||
info!("device no longer configured");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
78
src/matrix.rs
Normal file
78
src/matrix.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use embassy_rp::gpio::{Input, Output};
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
|
||||||
|
/// Matrix struct with row output pins, column input pins and the key mask
|
||||||
|
pub struct Matrix<const R: usize, const C: usize> {
|
||||||
|
rows: [Output<'static>; R],
|
||||||
|
cols: [Input<'static>; C],
|
||||||
|
key_mask: [[bool; C]; R],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const R: usize, const C: usize> Matrix<R, C> {
|
||||||
|
/// Create a new `Matrix` with a given mask
|
||||||
|
pub const fn new_with_mask(
|
||||||
|
rows: [Output<'static>; R],
|
||||||
|
cols: [Input<'static>; C],
|
||||||
|
key_mask: [[bool; C]; R],
|
||||||
|
) -> Self {
|
||||||
|
Self { rows, cols, key_mask }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan all the keys in the `Matrix` and output current state
|
||||||
|
pub async fn scan(&mut self) -> [[bool; C]; R] {
|
||||||
|
let mut state = [[false; C]; R];
|
||||||
|
|
||||||
|
for (row_idx, row_pin) in self.rows.iter_mut().enumerate() {
|
||||||
|
row_pin.set_low();
|
||||||
|
Timer::after(Duration::from_micros(1)).await;
|
||||||
|
|
||||||
|
for col_idx in 0..C {
|
||||||
|
if !self.key_mask[row_idx][col_idx] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
state[row_idx][col_idx] = self.cols[col_idx].is_low();
|
||||||
|
}
|
||||||
|
|
||||||
|
row_pin.set_high();
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12x4 mask
|
||||||
|
pub const MASK: [[bool; 12]; 4] = [
|
||||||
|
[true; 12],
|
||||||
|
[true; 12],
|
||||||
|
[true; 12],
|
||||||
|
[true; 12],
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Generic macro to create a matrix with any pins
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_matrix {
|
||||||
|
(
|
||||||
|
$p:expr,
|
||||||
|
rows: [$($row:tt),+ $(,)?],
|
||||||
|
cols: [$($col:tt),+ $(,)?],
|
||||||
|
mask: $mask:expr
|
||||||
|
) => {{
|
||||||
|
use embassy_rp::gpio::{Input, Level, Output, Pull};
|
||||||
|
let rows = [
|
||||||
|
$(Output::new($p.$row, Level::High)),+
|
||||||
|
];
|
||||||
|
let cols = [
|
||||||
|
$(Input::new($p.$col, Pull::Up)),+
|
||||||
|
];
|
||||||
|
$crate::matrix::Matrix::new_with_mask(rows, cols, $mask)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience macro for my 4x12 ortholinear matrix
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! matrix_4x12 {
|
||||||
|
($p:expr, rows: [$($row:tt),+ $(,)?], cols: [$($col:tt),+ $(,)?]) => {
|
||||||
|
$crate::create_matrix!($p, rows: [$($row),+], cols: [$($col),+], mask: $crate::matrix::MASK)
|
||||||
|
};
|
||||||
|
}
|
||||||
138
src/usb.rs
Normal file
138
src/usb.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/// Reference: https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/usb_hid_keyboard.rs
|
||||||
|
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
use defmt::info;
|
||||||
|
use embassy_rp::peripherals::USB;
|
||||||
|
use embassy_rp::usb::Driver;
|
||||||
|
use embassy_usb::{
|
||||||
|
Builder, Config, Handler,
|
||||||
|
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
||||||
|
control::OutResponse,
|
||||||
|
};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
|
||||||
|
|
||||||
|
pub struct UsbKeyboard {
|
||||||
|
pub usb: embassy_usb::UsbDevice<'static, Driver<'static, USB>>,
|
||||||
|
pub reader: embassy_usb::class::hid::HidReader<'static, Driver<'static, USB>, 1>,
|
||||||
|
pub writer: embassy_usb::class::hid::HidWriter<'static, Driver<'static, USB>, 8>,
|
||||||
|
pub request_handler: &'static mut KbRequestHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new();
|
||||||
|
static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
|
||||||
|
static MSOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
|
||||||
|
static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
|
||||||
|
static REQUEST_HANDLER: StaticCell<KbRequestHandler> = StaticCell::new();
|
||||||
|
static DEVICE_HANDLER: StaticCell<KbDeviceHandler> = StaticCell::new();
|
||||||
|
static STATE: StaticCell<State> = StaticCell::new();
|
||||||
|
|
||||||
|
pub fn setup_usb(driver: Driver<'static, USB>) -> UsbKeyboard {
|
||||||
|
let mut usb_config = Config::new(0x1209, 0x0001);
|
||||||
|
usb_config.manufacturer = Some("Lukrecja");
|
||||||
|
usb_config.product = Some("kb-ortho-40");
|
||||||
|
usb_config.serial_number = Some("00000000");
|
||||||
|
usb_config.composite_with_iads = false;
|
||||||
|
usb_config.device_class = 0x00;
|
||||||
|
usb_config.device_sub_class = 0x00;
|
||||||
|
usb_config.device_protocol = 0x00;
|
||||||
|
|
||||||
|
let config_descriptor = CONFIG_DESC.init([0; 256]);
|
||||||
|
let bos_descriptor = BOS_DESC.init([0; 256]);
|
||||||
|
let msos_descriptor = MSOS_DESC.init([0; 256]);
|
||||||
|
let control_buf = CONTROL_BUF.init([0; 64]);
|
||||||
|
let request_handler = REQUEST_HANDLER.init(KbRequestHandler {});
|
||||||
|
let device_handler = DEVICE_HANDLER.init(KbDeviceHandler::new());
|
||||||
|
let state = STATE.init(State::new());
|
||||||
|
|
||||||
|
let mut builder = Builder::new(
|
||||||
|
driver,
|
||||||
|
usb_config,
|
||||||
|
config_descriptor,
|
||||||
|
bos_descriptor,
|
||||||
|
msos_descriptor,
|
||||||
|
control_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.handler(device_handler);
|
||||||
|
|
||||||
|
let hid_config = embassy_usb::class::hid::Config {
|
||||||
|
report_descriptor: KeyboardReport::desc(),
|
||||||
|
request_handler: None,
|
||||||
|
poll_ms: 60,
|
||||||
|
max_packet_size: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, state, hid_config);
|
||||||
|
|
||||||
|
let usb = builder.build();
|
||||||
|
let (reader, writer) = hid.split();
|
||||||
|
|
||||||
|
UsbKeyboard { usb, reader, writer, request_handler }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KbRequestHandler {}
|
||||||
|
|
||||||
|
impl RequestHandler for KbRequestHandler {
|
||||||
|
fn get_report(&mut self, _id: ReportId, _buf: &mut [u8]) -> Option<usize> {
|
||||||
|
info!("get report");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_report(&mut self, _id: ReportId, data: &[u8]) -> OutResponse {
|
||||||
|
info!("set report: {=[u8]}", data);
|
||||||
|
OutResponse::Accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_idle_ms(&mut self, _id: Option<ReportId>, dur: u32) {
|
||||||
|
info!("set idle rate to {}", dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_idle_ms(&mut self, _id: Option<ReportId>) -> Option<u32> {
|
||||||
|
info!("get idle rate");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KbDeviceHandler {
|
||||||
|
configured: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KbDeviceHandler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
configured: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for KbDeviceHandler {
|
||||||
|
fn enabled(&mut self, enabled: bool) {
|
||||||
|
self.configured.store(false, Ordering::Relaxed);
|
||||||
|
if enabled {
|
||||||
|
info!("device enabled");
|
||||||
|
} else {
|
||||||
|
info!("device disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.configured.store(false, Ordering::Relaxed);
|
||||||
|
info!("bus reset, Vbus current limit is 100mA");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addressed(&mut self, addr: u8) {
|
||||||
|
self.configured.store(false, Ordering::Relaxed);
|
||||||
|
info!("USB address set to: {}", addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configured(&mut self, configured: bool) {
|
||||||
|
self.configured.store(configured, Ordering::Relaxed);
|
||||||
|
if configured {
|
||||||
|
info!("device configured")
|
||||||
|
} else {
|
||||||
|
info!("device no longer configured");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user