Get a dummy keyboard working
This commit is contained in:
@ -17,18 +17,11 @@ target = "thumbv6m-none-eabi"
|
|||||||
[target.thumbv6m-none-eabi]
|
[target.thumbv6m-none-eabi]
|
||||||
# Pass some extra options to rustc, some of which get passed on to the linker.
|
# Pass some extra options to rustc, some of which get passed on to the linker.
|
||||||
#
|
#
|
||||||
# * linker argument --nmagic turns off page alignment of sections (which saves
|
|
||||||
# flash space)
|
|
||||||
# * linker argument -Tlink.x tells the linker to use link.x as the linker
|
|
||||||
# script. This is usually provided by the cortex-m-rt crate, and by default
|
|
||||||
# the version in that crate will include a file called `memory.x` which
|
|
||||||
# describes the particular memory layout for your specific chip.
|
|
||||||
# * inline-threshold=5 makes the compiler more aggressive and inlining functions
|
# * inline-threshold=5 makes the compiler more aggressive and inlining functions
|
||||||
# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't
|
# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't
|
||||||
# have SIMD)
|
# have SIMD)
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=--nmagic",
|
"-C", "linker=flip-link",
|
||||||
"-C", "link-arg=-Tlink.x",
|
|
||||||
"-C", "inline-threshold=5",
|
"-C", "inline-threshold=5",
|
||||||
"-C", "no-vectorize-loops",
|
"-C", "no-vectorize-loops",
|
||||||
]
|
]
|
||||||
|
|||||||
1154
Cargo.lock
generated
1154
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -3,11 +3,25 @@ name = "itsybitsy_rp2040_keyboard"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adafruit-itsy-bitsy-rp2040 = "0.6.0"
|
#adafruit-itsy-bitsy-rp2040 = "0.6.0"
|
||||||
cortex-m = "0.7.2"
|
cortex-m = "0.7.6"
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embedded-hal ="0.2.5"
|
embedded-hal ="0.2.5"
|
||||||
panic-halt= "0.2.0"
|
panic-halt= "0.2.0"
|
||||||
|
usb-device = "*"
|
||||||
|
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"] }
|
||||||
|
embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", features = ["log"] }
|
||||||
|
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"
|
||||||
|
|||||||
38
build.rs
Normal file
38
build.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//! This build script copies the `memory.x` file from the crate root into
|
||||||
|
//! a directory where the linker can always find it at build time.
|
||||||
|
//! For many projects this is optional, as the linker always searches the
|
||||||
|
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||||
|
//! are using a workspace or have a more complicated build setup, this
|
||||||
|
//! build script becomes required. Additionally, by requesting that
|
||||||
|
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||||
|
//! 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;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// 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());
|
||||||
|
File::create(out.join("memory.x"))
|
||||||
|
.unwrap()
|
||||||
|
.write_all(include_bytes!("memory.x"))
|
||||||
|
.unwrap();
|
||||||
|
println!("cargo:rustc-link-search={}", out.display());
|
||||||
|
|
||||||
|
// By default, Cargo will re-run a build script whenever
|
||||||
|
// any file in the project changes. By specifying `memory.x`
|
||||||
|
// here, we ensure the build script is only re-run when
|
||||||
|
// `memory.x` is changed.
|
||||||
|
println!("cargo:rerun-if-changed=memory.x");
|
||||||
|
|
||||||
|
// --nmagic turns off page alignment of sections (which saves flash space)
|
||||||
|
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
|
||||||
|
//println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||||
|
}
|
||||||
19
run
Executable file
19
run
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
printf "waiting for serial log"
|
||||||
|
|
||||||
|
SERIAL=/dev/ttyACM0
|
||||||
|
while [ ! -e $SERIAL ]
|
||||||
|
do
|
||||||
|
printf "."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
cat $SERIAL
|
||||||
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2023-02-01"
|
||||||
|
components = ["rust-std", "rust-src", "rustfmt", "cargo", "clippy"]
|
||||||
|
targets = ["thumbv6m-none-eabi"]
|
||||||
31
src/board.rs
Normal file
31
src/board.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use embassy_rp::peripherals::*;
|
||||||
|
|
||||||
|
/// Pinouts for the ItsyBitsy
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Board {
|
||||||
|
pub a0: PIN_26,
|
||||||
|
pub a1: PIN_27,
|
||||||
|
pub a2: PIN_28,
|
||||||
|
pub a3: PIN_29,
|
||||||
|
pub d24: PIN_24,
|
||||||
|
pub d25: PIN_25,
|
||||||
|
pub sck: PIN_18,
|
||||||
|
pub mosi: PIN_19,
|
||||||
|
pub miso: PIN_20,
|
||||||
|
pub d2: PIN_12,
|
||||||
|
pub d3: PIN_5,
|
||||||
|
pub d4: PIN_4,
|
||||||
|
pub rx: PIN_1,
|
||||||
|
pub tx: PIN_0,
|
||||||
|
pub sda: PIN_2,
|
||||||
|
pub scl: PIN_3,
|
||||||
|
pub d5: PIN_14,
|
||||||
|
pub d7: PIN_6,
|
||||||
|
pub d9: PIN_7,
|
||||||
|
pub d10: PIN_8,
|
||||||
|
pub d11: PIN_9,
|
||||||
|
pub d12: PIN_10,
|
||||||
|
pub d13: PIN_11,
|
||||||
|
pub neopixel: PIN_17,
|
||||||
|
pub neopixel_power: PIN_16,
|
||||||
|
}
|
||||||
189
src/keyboard.rs
Normal file
189
src/keyboard.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct Switch {
|
||||||
|
state: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Switch {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Switch {
|
||||||
|
state: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn press(&self) {
|
||||||
|
self.state.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Button {
|
||||||
|
pub const fn key(keycode: u8) -> Self {
|
||||||
|
Button::Key { keycode }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn letter_to_key(c: char) -> Option<Button> {
|
||||||
|
if !c.is_ascii() {
|
||||||
|
return 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,
|
||||||
|
_ => {
|
||||||
|
log::info!("char {c:?} -> None");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("char {c:?} -> {key:?}");
|
||||||
|
|
||||||
|
Some(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_type(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/led.rs
Normal file
25
src/led.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use adafruit_itsy_bitsy_rp2040::hal::gpio::{Output, Pin, PinId, PushPull};
|
||||||
|
use cortex_m::delay::Delay;
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
|
||||||
|
pub struct Led<I: PinId> {
|
||||||
|
pub pin: Pin<I, Output<PushPull>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Blink {
|
||||||
|
ms: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SHORT: Blink = Blink { ms: 200 };
|
||||||
|
pub const LONG: Blink = Blink { ms: 600 };
|
||||||
|
|
||||||
|
impl<I: PinId> Led<I> {
|
||||||
|
pub fn dance(&mut self, delay: &mut Delay, moves: &[Blink]) {
|
||||||
|
for m in moves {
|
||||||
|
self.pin.set_high().unwrap();
|
||||||
|
delay.delay_ms(m.ms);
|
||||||
|
self.pin.set_low().unwrap();
|
||||||
|
delay.delay_ms(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/main.rs
126
src/main.rs
@ -8,90 +8,76 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
// The macro for our start-up function
|
extern crate cortex_m_rt;
|
||||||
use adafruit_itsy_bitsy_rp2040::{
|
extern crate panic_halt;
|
||||||
entry,
|
|
||||||
hal::{clocks::ClocksManager, usb},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure we halt the program on panic (if we don't mention this crate it won't
|
mod board;
|
||||||
// be linked)
|
mod keyboard;
|
||||||
use panic_halt as _;
|
mod usb;
|
||||||
|
|
||||||
// Some traits we need
|
use board::Board;
|
||||||
use embedded_hal::digital::v2::OutputPin;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
|
||||||
use adafruit_itsy_bitsy_rp2040::{
|
#[embassy_executor::main]
|
||||||
hal::{
|
async fn main(spawner: Spawner) {
|
||||||
clocks::{init_clocks_and_plls, Clock},
|
let p = embassy_rp::init(Default::default());
|
||||||
pac,
|
|
||||||
sio::Sio,
|
|
||||||
watchdog::Watchdog,
|
|
||||||
},
|
|
||||||
Pins, XOSC_CRYSTAL_FREQ,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cortex_m::delay::Delay;
|
let board = Board {
|
||||||
|
a0: p.PIN_26,
|
||||||
|
a1: p.PIN_27,
|
||||||
|
a2: p.PIN_28,
|
||||||
|
a3: p.PIN_29,
|
||||||
|
d24: p.PIN_24,
|
||||||
|
d25: p.PIN_25,
|
||||||
|
sck: p.PIN_18,
|
||||||
|
mosi: p.PIN_19,
|
||||||
|
miso: p.PIN_20,
|
||||||
|
d2: p.PIN_12,
|
||||||
|
d3: p.PIN_5,
|
||||||
|
d4: p.PIN_4,
|
||||||
|
rx: p.PIN_1,
|
||||||
|
tx: p.PIN_0,
|
||||||
|
sda: p.PIN_2,
|
||||||
|
scl: p.PIN_3,
|
||||||
|
d5: p.PIN_14,
|
||||||
|
d7: p.PIN_6,
|
||||||
|
d9: p.PIN_7,
|
||||||
|
d10: p.PIN_8,
|
||||||
|
d11: p.PIN_9,
|
||||||
|
d12: p.PIN_10,
|
||||||
|
d13: p.PIN_11,
|
||||||
|
neopixel: p.PIN_17,
|
||||||
|
neopixel_power: p.PIN_16,
|
||||||
|
};
|
||||||
|
|
||||||
/// Entry point to our bare-metal application.
|
let mut led = Output::new(board.d13, Level::High);
|
||||||
///
|
|
||||||
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
|
|
||||||
/// as soon as all global variables are initialised.
|
|
||||||
///
|
|
||||||
/// The function configures the RP2040 peripherals, then toggles a GPIO pin in
|
|
||||||
/// an infinite loop. If there is an LED connected to that pin, it will blink.
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
// Grab our singleton objects
|
|
||||||
let mut pac = pac::Peripherals::take().unwrap();
|
|
||||||
let core = pac::CorePeripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Set up the watchdog driver - needed by the clock setup code
|
let mut builder = usb::builder(p.USB);
|
||||||
let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
|
||||||
|
|
||||||
// Configure the clocks
|
usb::logger::setup(&mut builder).await;
|
||||||
let clocks = init_clocks_and_plls(
|
|
||||||
XOSC_CRYSTAL_FREQ,
|
|
||||||
pac.XOSC,
|
|
||||||
pac.CLOCKS,
|
|
||||||
pac.PLL_SYS,
|
|
||||||
pac.PLL_USB,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
&mut watchdog,
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let usb = usb::UsbBus::new(
|
log::error!("log_level: error");
|
||||||
pac.USBCTRL_REGS,
|
log::warn!("log_level: warn");
|
||||||
pac.USBCTRL_DPRAM,
|
log::info!("log_level: info");
|
||||||
clocks.usb_clock,
|
log::debug!("log_level: debug");
|
||||||
false,
|
log::trace!("log_level: trace");
|
||||||
&mut pac.RESETS,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: how to keyboard
|
usb::keyboard::setup(&mut builder).await;
|
||||||
|
|
||||||
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
let usb = builder.build();
|
||||||
|
|
||||||
// The single-cycle I/O block controls our GPIO pins
|
spawner.must_spawn(usb::run(usb));
|
||||||
let sio = Sio::new(pac.SIO);
|
|
||||||
|
|
||||||
let pins = Pins::new(
|
Timer::after(Duration::from_millis(1000)).await;
|
||||||
pac.IO_BANK0,
|
|
||||||
pac.PADS_BANK0,
|
crate::keyboard::test_type("Hello there!\n").await;
|
||||||
sio.gpio_bank0,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
);
|
|
||||||
let mut led_pin = pins.d13.into_push_pull_output();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
led_pin.set_high().unwrap();
|
Timer::after(Duration::from_millis(500)).await;
|
||||||
delay.delay_ms(500);
|
led.toggle();
|
||||||
led_pin.set_low().unwrap();
|
|
||||||
delay.delay_ms(500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of file
|
|
||||||
|
|||||||
57
src/usb.rs
Normal file
57
src/usb.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use embassy_rp::{interrupt, peripherals::USB, usb::Driver};
|
||||||
|
use embassy_usb::{Builder, Config, UsbDevice};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod logger;
|
||||||
|
|
||||||
|
pub const MAX_PACKET_SIZE: u8 = 64;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
device_descriptor: [u8; 256],
|
||||||
|
config_descriptor: [u8; 256],
|
||||||
|
bos_descriptor: [u8; 256],
|
||||||
|
control_buf: [u8; 64],
|
||||||
|
}
|
||||||
|
|
||||||
|
static STATE: StaticCell<State> = StaticCell::new();
|
||||||
|
|
||||||
|
pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {
|
||||||
|
let state = STATE.init(State {
|
||||||
|
device_descriptor: [0; 256],
|
||||||
|
config_descriptor: [0; 256],
|
||||||
|
bos_descriptor: [0; 256],
|
||||||
|
control_buf: [0; 64],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create embassy-usb Config
|
||||||
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
|
config.manufacturer = Some("Tux");
|
||||||
|
config.product = Some("Tangentbord1");
|
||||||
|
config.serial_number = Some("42069");
|
||||||
|
config.max_power = 100;
|
||||||
|
config.max_packet_size_0 = MAX_PACKET_SIZE;
|
||||||
|
|
||||||
|
// Required for windows compatiblity.
|
||||||
|
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
|
||||||
|
config.device_class = 0xEF;
|
||||||
|
config.device_sub_class = 0x02;
|
||||||
|
config.device_protocol = 0x01;
|
||||||
|
config.composite_with_iads = true;
|
||||||
|
|
||||||
|
let driver = Driver::new(usb, interrupt::take!(USBCTRL_IRQ));
|
||||||
|
|
||||||
|
Builder::new(
|
||||||
|
driver,
|
||||||
|
config,
|
||||||
|
&mut state.device_descriptor,
|
||||||
|
&mut state.config_descriptor,
|
||||||
|
&mut state.bos_descriptor,
|
||||||
|
&mut state.control_buf,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn run(mut device: UsbDevice<'static, Driver<'static, USB>>) {
|
||||||
|
device.run().await
|
||||||
|
}
|
||||||
185
src/usb/keyboard.rs
Normal file
185
src/usb/keyboard.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::{peripherals::USB, usb::Driver};
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
use embassy_usb::{
|
||||||
|
class::hid::{self, HidReaderWriter, ReadError, ReportId, RequestHandler},
|
||||||
|
control::OutResponse,
|
||||||
|
Builder,
|
||||||
|
};
|
||||||
|
use embassy_usb_driver::EndpointError;
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
use usbd_hid::descriptor::{KeyboardReport, MouseReport, SerializedDescriptor};
|
||||||
|
|
||||||
|
use crate::keyboard::{Button, COLS, MATRIX, ROWS, TEST_KEYMAP};
|
||||||
|
|
||||||
|
use super::MAX_PACKET_SIZE;
|
||||||
|
|
||||||
|
struct Handler;
|
||||||
|
|
||||||
|
static CONTEXT: StaticCell<Context> = StaticCell::new();
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
handler: Handler,
|
||||||
|
state: hid::State<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn setup(builder: &mut Builder<'static, Driver<'static, USB>>) {
|
||||||
|
log::info!("setting up usb hid");
|
||||||
|
|
||||||
|
let context = CONTEXT.init(Context {
|
||||||
|
handler: Handler,
|
||||||
|
state: hid::State::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let config = hid::Config {
|
||||||
|
//report_descriptor: MouseReport::desc(),
|
||||||
|
report_descriptor: KeyboardReport::desc(),
|
||||||
|
request_handler: Some(&context.handler),
|
||||||
|
poll_ms: 2,
|
||||||
|
max_packet_size: MAX_PACKET_SIZE as u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = HidStream::new(builder, &mut context.state, config);
|
||||||
|
|
||||||
|
let spawner = Spawner::for_current_executor().await;
|
||||||
|
|
||||||
|
spawner.must_spawn(task(stream, &context.handler));
|
||||||
|
|
||||||
|
log::info!("done setting up usb keyboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestHandler for Handler {
|
||||||
|
fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
|
||||||
|
log::info!("get_report({id:?}, {buf:?})");
|
||||||
|
let _ = (id, buf);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_report(&self, id: ReportId, data: &[u8]) -> embassy_usb::control::OutResponse {
|
||||||
|
log::info!("set_report({id:?}, {data:?})");
|
||||||
|
let _ = (id, data);
|
||||||
|
OutResponse::Rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
|
||||||
|
log::info!("get_idle_ms({id:?})");
|
||||||
|
let _ = id;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_idle_ms(&self, id: Option<ReportId>, duration_ms: u32) {
|
||||||
|
log::info!("set_idle_ms({id:?}, {duration_ms})");
|
||||||
|
let _ = (id, duration_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type HidStream = HidReaderWriter<'static, Driver<'static, USB>, 256, 256>;
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn task(stream: HidStream, handler: &'static Handler) {
|
||||||
|
if let Err(e) = keyboard_test(stream, handler).await {
|
||||||
|
log::error!("keyboard error: {e:?}");
|
||||||
|
}
|
||||||
|
//if let Err(e) = mouse_wiggler(stream).await {
|
||||||
|
// log::error!("mouse wiggler: {e:?}");
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn keyboard_test(mut stream: HidStream, handler: &'static Handler) -> Result<(), Error> {
|
||||||
|
stream.ready().await;
|
||||||
|
loop {
|
||||||
|
Timer::after(Duration::from_millis(2)).await;
|
||||||
|
|
||||||
|
let keymap = &TEST_KEYMAP;
|
||||||
|
|
||||||
|
let mut keycodes = [0u8; 6];
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
'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; };
|
||||||
|
|
||||||
|
keycodes[i] = *keycode;
|
||||||
|
i += 1;
|
||||||
|
if i >= keycodes.len() {
|
||||||
|
break 'keyscan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycodes.iter().any(|&b| b != 0) {
|
||||||
|
log::info!("keycodes: {keycodes:?}");
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
.write_serialize(&KeyboardReport {
|
||||||
|
modifier: 0,
|
||||||
|
reserved: 0,
|
||||||
|
leds: 0,
|
||||||
|
keycodes,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn mouse_wiggler(mut stream: HidStream) -> Result<(), Error> {
|
||||||
|
stream.ready().await;
|
||||||
|
|
||||||
|
let (_r, mut w) = stream.split();
|
||||||
|
|
||||||
|
let write_fut = async move {
|
||||||
|
let mut x = 1;
|
||||||
|
loop {
|
||||||
|
for _ in 0..100 {
|
||||||
|
Timer::after(Duration::from_millis(10)).await;
|
||||||
|
log::info!("sending mouse report");
|
||||||
|
//w.ready().await;
|
||||||
|
w.write_serialize(&MouseReport {
|
||||||
|
x,
|
||||||
|
y: 0,
|
||||||
|
buttons: 0,
|
||||||
|
wheel: 0,
|
||||||
|
pan: 0,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
x = -x;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//let read_fut = async move {
|
||||||
|
// let mut buf = [0u8; MAX_PACKET_SIZE as usize];
|
||||||
|
// loop {
|
||||||
|
// Timer::after(Duration::from_millis(30)).await;
|
||||||
|
// let n = r.read(&mut buf).await?;
|
||||||
|
// log::info!("got packet: {:?}", &buf[..n]);
|
||||||
|
// }
|
||||||
|
//};
|
||||||
|
|
||||||
|
//let r: Result<((), ()), Error> = try_join(write_fut, read_fut).await;
|
||||||
|
let r: Result<(), Error> = write_fut.await;
|
||||||
|
r?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error {
|
||||||
|
Read(ReadError),
|
||||||
|
Endpoint(EndpointError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReadError> for Error {
|
||||||
|
fn from(value: ReadError) -> Self {
|
||||||
|
Error::Read(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<EndpointError> for Error {
|
||||||
|
fn from(value: EndpointError) -> Self {
|
||||||
|
Error::Endpoint(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/usb/logger.rs
Normal file
79
src/usb/logger.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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_time::Instant;
|
||||||
|
use embassy_usb::{
|
||||||
|
class::cdc_acm::{self, CdcAcmClass},
|
||||||
|
Builder,
|
||||||
|
};
|
||||||
|
use log::{Metadata, Record};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
pub const BUFFER_SIZE: usize = 16 * 1024;
|
||||||
|
static BUFFER: Pipe<CriticalSectionRawMutex, BUFFER_SIZE> = Pipe::new();
|
||||||
|
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
|
||||||
|
|
||||||
|
struct UsbLogger;
|
||||||
|
|
||||||
|
pub async fn setup(usb_builder: &mut Builder<'static, Driver<'static, USB>>) {
|
||||||
|
unsafe {
|
||||||
|
static LOGGER: UsbLogger = UsbLogger;
|
||||||
|
log::set_logger_racy(&LOGGER).unwrap();
|
||||||
|
log::set_max_level(log::LevelFilter::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spawner = Spawner::for_current_executor().await;
|
||||||
|
|
||||||
|
let state = STATE.init(cdc_acm::State::new());
|
||||||
|
|
||||||
|
let class = CdcAcmClass::new(usb_builder, state, MAX_PACKET_SIZE as u16);
|
||||||
|
|
||||||
|
spawner.must_spawn(log_task(class));
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub async fn print(s: &str) {
|
||||||
|
// BUFFER.writer().write_all(s.as_bytes()).await.ok(/* infallible */);
|
||||||
|
//}
|
||||||
|
|
||||||
|
#[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 _ = write!(w, "[{s}.{ms:04}] ({level}) {}\n", 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user