feat(firmware): read keycodes from a USB HID boot keyboard
Spike 4. Drive the ESP-IDF USB Host Library directly through the raw esp-idf-sys bindings (the convenience HID class driver is a managed component not vendored in mainline, and a boot keyboard doesn't need it). On attach, src/usb_kbd.rs enumerates the device and dumps its descriptors, claims the boot-keyboard interface, sends SET_PROTOCOL(boot) and SET_IDLE(0), then polls the interrupt-IN endpoint and decodes each 8-byte report into modifiers + keycodes logged over UART. main.rs becomes the Spike 4 harness; the epd module is kept compiled (allow(dead_code)) so it doesn't bit-rot. Verified on hardware with a 19f5:3255 keyboard: letters, digits, modifiers, and rollover all decode.
This commit is contained in:
@@ -1,22 +1,10 @@
|
||||
// epd is proven (Spike 2) but unused by the Spike 4 harness; keep it compiled
|
||||
// so it doesn't bit-rot while USB host bring-up is in progress.
|
||||
#[allow(dead_code)]
|
||||
mod epd;
|
||||
mod usb_kbd;
|
||||
|
||||
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
||||
use embedded_graphics::mono_font::MonoTextStyle;
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics::primitives::{Circle, PrimitiveStyle};
|
||||
use embedded_graphics::text::{Alignment, Text};
|
||||
use esp_idf_svc::hal::delay::FreeRtos;
|
||||
use esp_idf_svc::hal::gpio::{AnyIOPin, PinDriver, Pull};
|
||||
use esp_idf_svc::hal::peripherals::Peripherals;
|
||||
use esp_idf_svc::hal::spi::config::{Config, DriverConfig};
|
||||
use esp_idf_svc::hal::spi::{Dma, SpiBusDriver, SpiDriver};
|
||||
use esp_idf_svc::hal::units::FromValueType;
|
||||
|
||||
use epd::Epd;
|
||||
|
||||
/// Injected by build.rs so serial output and the panel itself identify the
|
||||
/// exact build being diagnosed.
|
||||
/// Injected by build.rs so serial output identifies the exact build.
|
||||
const BUILD_TAG: &str = concat!("build ", env!("BUILD_TIME"), " @", env!("BUILD_GIT"));
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
@@ -25,77 +13,11 @@ fn main() -> anyhow::Result<()> {
|
||||
esp_idf_svc::sys::link_patches();
|
||||
esp_idf_svc::log::EspLogger::initialize_default();
|
||||
|
||||
let peripherals = Peripherals::take()?;
|
||||
let pins = peripherals.pins;
|
||||
log::info!("Typoena Spike 4 — USB host keyboard, {BUILD_TAG}");
|
||||
log::info!("Plug the keyboard into the native USB port, then type.");
|
||||
|
||||
// GDEY0579T93 wiring on S3-safe GPIOs (clear of flash 26–32, octal PSRAM
|
||||
// 33–37, strapping 0/3/45/46, USB 19/20, RGB LED 38/48). See
|
||||
// docs/v0.1-mvp-technical.md (Spike 2):
|
||||
// SCK 12 · DIN/MOSI 11 · CS 7 · DC 6 · RST 5 · BUSY 4
|
||||
let spi = SpiDriver::new(
|
||||
peripherals.spi2,
|
||||
pins.gpio12, // SCK
|
||||
pins.gpio11, // SDO / MOSI (DIN)
|
||||
None::<AnyIOPin>, // SDI / MISO — unused (write-only panel)
|
||||
&DriverConfig::new().dma(Dma::Auto(4096)),
|
||||
)?;
|
||||
// 4 MHz — GxEPD2's default for this controller. Verified clean on the
|
||||
// breadboard rig; a loose CS jumper (not clock speed) was behind the
|
||||
// early bring-up noise.
|
||||
let bus = SpiBusDriver::new(spi, &Config::new().baudrate(4.MHz().into()))?;
|
||||
|
||||
let cs = PinDriver::output(pins.gpio7)?;
|
||||
let dc = PinDriver::output(pins.gpio6)?;
|
||||
let rst = PinDriver::output(pins.gpio5)?;
|
||||
let busy = PinDriver::input(pins.gpio4, Pull::Down)?;
|
||||
|
||||
let mut epd = Epd::new(bus, dc, rst, cs, busy);
|
||||
|
||||
log::info!("Typoena Spike 2b — GDEY0579T93 text test, {BUILD_TAG}");
|
||||
log::info!("hardware reset…");
|
||||
epd.reset()?;
|
||||
log::info!("init…");
|
||||
epd.init()?;
|
||||
epd.clear_screen(0xFF)?; // initial clean slate, per GxEPD2
|
||||
|
||||
// Alternate normal and inverted frames: circle on the master/slave seam
|
||||
// (proves the split-and-mirror blit), "Typoena" inside it, and the build
|
||||
// tag at the bottom so the panel identifies the running build.
|
||||
let frames = [make_frame(false), make_frame(true)];
|
||||
let mut i = 0;
|
||||
loop {
|
||||
log::info!("frame → {}", if i == 0 { "black on WHITE" } else { "white on BLACK" });
|
||||
epd.display_frame(frames[i].bytes())?;
|
||||
log::info!("refresh done; holding 3 s");
|
||||
FreeRtos::delay_ms(3000);
|
||||
i = 1 - i;
|
||||
}
|
||||
}
|
||||
|
||||
/// Circle centered on the controller seam, "Typoena" inside, build tag at
|
||||
/// the bottom edge. `inverted` swaps ink and paper.
|
||||
fn make_frame(inverted: bool) -> epd::Frame {
|
||||
let (mut frame, ink) = if inverted {
|
||||
(epd::Frame::new_black(), BinaryColor::Off)
|
||||
} else {
|
||||
(epd::Frame::new_white(), BinaryColor::On)
|
||||
};
|
||||
let center = Point::new(epd::WIDTH as i32 / 2, epd::HEIGHT as i32 / 2);
|
||||
let style = MonoTextStyle::new(&FONT_10X20, ink);
|
||||
Circle::with_center(center, 200)
|
||||
.into_styled(PrimitiveStyle::with_stroke(ink, 6))
|
||||
.draw(&mut frame)
|
||||
.unwrap();
|
||||
Text::with_alignment("Typoena", center + Point::new(0, 7), style, Alignment::Center)
|
||||
.draw(&mut frame)
|
||||
.unwrap();
|
||||
Text::with_alignment(
|
||||
BUILD_TAG,
|
||||
Point::new(center.x, epd::HEIGHT as i32 - 10),
|
||||
style,
|
||||
Alignment::Center,
|
||||
)
|
||||
.draw(&mut frame)
|
||||
.unwrap();
|
||||
frame
|
||||
// Runs forever: installs the USB Host Library and logs descriptors of any
|
||||
// device that attaches. Returns only on a fatal host-library error.
|
||||
usb_kbd::run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user