Compare commits
3 commits
2677534c8e
...
9381de5039
| Author | SHA1 | Date | |
|---|---|---|---|
| 9381de5039 | |||
| 15c7b0ef1e | |||
| 4853df4cc9 |
8 changed files with 467 additions and 3 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -70,6 +70,16 @@ version = "2.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bitmask-enum"
|
||||
version = "2.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6cbbb8f56245b5a479b30a62cdc86d26e2f35c2b9f594bc4671654b03851380"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -101,6 +111,8 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||
name = "cardputer-bsc-nostd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitmask-enum",
|
||||
"embedded-hal-bus",
|
||||
"esp-hal",
|
||||
"mipidsi",
|
||||
|
|
@ -784,6 +796,16 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cardputer-bsc-nostd",
|
||||
"esp-bootloader-esp-idf",
|
||||
"esp-hal",
|
||||
"esp-println",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ debug = true # Symbols are nice and they don't increase the size on Flash
|
|||
opt-level = "z"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.9.1"
|
||||
bitmask-enum = "2.2.5"
|
||||
embedded-hal-bus = "0.3.0"
|
||||
esp-hal = {version = "=1.0.0-beta.1", features = ["esp32s3", "unstable"]}
|
||||
mipidsi = "0.9.0"
|
||||
|
|
|
|||
24
examples/keyboard_test/Cargo.toml
Normal file
24
examples/keyboard_test/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "keyboard_test"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "keyboard_test"
|
||||
harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
debug = true
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
debug = true # Symbols are nice and they don't increase the size on Flash
|
||||
opt-level = "z"
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
cardputer-bsc-nostd = { version = "0.1.0", path = "../.." }
|
||||
esp-hal = {version = "=1.0.0-beta.1", features = ["esp32s3", "unstable"]}
|
||||
esp-println = { version = "0.14.0", features = ["esp32s3"] }
|
||||
esp-bootloader-esp-idf = "0.1.0"
|
||||
52
examples/keyboard_test/build.rs
Normal file
52
examples/keyboard_test/build.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
fn main() {
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
11
examples/keyboard_test/readme.md
Normal file
11
examples/keyboard_test/readme.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# keyboard_test example
|
||||
|
||||
This is a simple test of the cardputer's keyboard. It logs key presses and releases over the USB serial.
|
||||
|
||||
## Run this example
|
||||
|
||||
With this repo cloned, run the following from either the root or this directory:
|
||||
|
||||
```shell
|
||||
cargo espflash flash --release --package keyboard_test --monitor
|
||||
```
|
||||
63
examples/keyboard_test/src/main.rs
Normal file
63
examples/keyboard_test/src/main.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
use esp_hal::main;
|
||||
use esp_println::println;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("{info}");
|
||||
esp_hal::system::software_reset()
|
||||
}
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
#[main]
|
||||
fn entrypoint() -> ! {
|
||||
_main();
|
||||
println!("main exited, entering infinite loop");
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[allow(static_mut_refs)]
|
||||
fn _main() {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let mut keyboard = cardputer_bsc_nostd::keyboard::Keyboard::new(
|
||||
peripherals.GPIO8,
|
||||
peripherals.GPIO9,
|
||||
peripherals.GPIO11,
|
||||
peripherals.GPIO13,
|
||||
peripherals.GPIO15,
|
||||
peripherals.GPIO3,
|
||||
peripherals.GPIO4,
|
||||
peripherals.GPIO5,
|
||||
peripherals.GPIO6,
|
||||
peripherals.GPIO7,
|
||||
);
|
||||
|
||||
loop {
|
||||
keyboard.scan();
|
||||
let key_set = keyboard.pressed_keys();
|
||||
if !key_set.is_none() {
|
||||
for &(key_name, key) in cardputer_bsc_nostd::keyboard::Key::flags() {
|
||||
if key_set.contains(key) {
|
||||
println!("Pressed {key_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboard.clear_pressed_keys();
|
||||
let key_set = keyboard.released_keys();
|
||||
if !key_set.is_none() {
|
||||
for &(key_name, key) in cardputer_bsc_nostd::keyboard::Key::flags() {
|
||||
if key_set.contains(key) {
|
||||
println!("Released {key_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboard.clear_released_keys();
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
//! Create and initialize ST7789 display driver
|
||||
// use display_interface_spi::SPIInterface;
|
||||
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
|
|
|
|||
293
src/keyboard.rs
293
src/keyboard.rs
|
|
@ -0,0 +1,293 @@
|
|||
use bitmask_enum::bitmask;
|
||||
|
||||
/// A set of physical keys on the cardputer's keyboard
|
||||
///
|
||||
/// This type is a bit flag (i.e. each bit corresponds to a different key).
|
||||
#[bitmask(u64)]
|
||||
#[bitmask_config(flags_iter)]
|
||||
pub enum Key {
|
||||
LeftOpt,
|
||||
Z,
|
||||
C,
|
||||
B,
|
||||
M,
|
||||
Period,
|
||||
Space,
|
||||
_Dummy0,
|
||||
LeftShift,
|
||||
S,
|
||||
F,
|
||||
H,
|
||||
K,
|
||||
SemiColon,
|
||||
Enter,
|
||||
_Dummy1,
|
||||
Q,
|
||||
E,
|
||||
T,
|
||||
U,
|
||||
O,
|
||||
OpenSquareBracket,
|
||||
Backslash,
|
||||
_Dummy2,
|
||||
One,
|
||||
Three,
|
||||
Five,
|
||||
Seven,
|
||||
Nine,
|
||||
Minus,
|
||||
Backspace,
|
||||
_Dummy3,
|
||||
LeftCtrl,
|
||||
LeftAlt,
|
||||
X,
|
||||
V,
|
||||
N,
|
||||
Comma,
|
||||
Slash,
|
||||
_Dummy4,
|
||||
LeftFn,
|
||||
A,
|
||||
D,
|
||||
G,
|
||||
J,
|
||||
L,
|
||||
Quote,
|
||||
_Dummy5,
|
||||
Tab,
|
||||
W,
|
||||
R,
|
||||
Y,
|
||||
I,
|
||||
P,
|
||||
CloseSquareBracket,
|
||||
_Dummy6,
|
||||
Backquote,
|
||||
Two,
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
Zero,
|
||||
Equal,
|
||||
_Dummy7,
|
||||
}
|
||||
|
||||
/// The type for controlling the cardputer's keyboard
|
||||
///
|
||||
/// `Keyboard` tracks and buffers key presses and releases,
|
||||
/// making it simple to react to only the changes in a key's state.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```rust
|
||||
/// let mut keyboard = cardputer_bsc_nostd::keyboard::Keyboard::new(
|
||||
/// peripherals.GPIO8,
|
||||
/// peripherals.GPIO9,
|
||||
/// peripherals.GPIO11,
|
||||
/// peripherals.GPIO13,
|
||||
/// peripherals.GPIO15,
|
||||
/// peripherals.GPIO3,
|
||||
/// peripherals.GPIO4,
|
||||
/// peripherals.GPIO5,
|
||||
/// peripherals.GPIO6,
|
||||
/// peripherals.GPIO7,
|
||||
/// );
|
||||
/// loop {
|
||||
/// keyboard.scan();
|
||||
/// let key_set = keyboard.pressed_keys();
|
||||
/// if !key_set.is_none() {
|
||||
/// for &(key_name, key) in cardputer_bsc_nostd::keyboard::Key::flags() {
|
||||
/// if key_set.contains(key) {
|
||||
/// println!("Pressed {key_name}");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// keyboard.clear_pressed_keys();
|
||||
/// let key_set = keyboard.released_keys();
|
||||
/// if !key_set.is_none() {
|
||||
/// for &(key_name, key) in cardputer_bsc_nostd::keyboard::Key::flags() {
|
||||
/// if key_set.contains(key) {
|
||||
/// println!("Released {key_name}");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// keyboard.clear_released_keys();
|
||||
/// esp_hal::delay::Delay::new().delay_millis(10);
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Keyboard<'a> {
|
||||
a0: esp_hal::gpio::Output<'a>,
|
||||
a1: esp_hal::gpio::Output<'a>,
|
||||
a2: esp_hal::gpio::Output<'a>,
|
||||
y0: esp_hal::gpio::Input<'a>,
|
||||
y1: esp_hal::gpio::Input<'a>,
|
||||
y2: esp_hal::gpio::Input<'a>,
|
||||
y3: esp_hal::gpio::Input<'a>,
|
||||
y4: esp_hal::gpio::Input<'a>,
|
||||
y5: esp_hal::gpio::Input<'a>,
|
||||
y6: esp_hal::gpio::Input<'a>,
|
||||
last_state: Key,
|
||||
pressed: Key,
|
||||
released: Key,
|
||||
}
|
||||
|
||||
impl<'a> Keyboard<'a> {
|
||||
/// Initialises the cardputer's keyboard.
|
||||
pub fn new(
|
||||
a0: esp_hal::peripherals::GPIO8<'a>,
|
||||
a1: esp_hal::peripherals::GPIO9<'a>,
|
||||
a2: esp_hal::peripherals::GPIO11<'a>,
|
||||
y0: esp_hal::peripherals::GPIO13<'a>,
|
||||
y1: esp_hal::peripherals::GPIO15<'a>,
|
||||
y2: esp_hal::peripherals::GPIO3<'a>,
|
||||
y3: esp_hal::peripherals::GPIO4<'a>,
|
||||
y4: esp_hal::peripherals::GPIO5<'a>,
|
||||
y5: esp_hal::peripherals::GPIO6<'a>,
|
||||
y6: esp_hal::peripherals::GPIO7<'a>,
|
||||
) -> Self {
|
||||
let a0 = esp_hal::gpio::Output::new(
|
||||
a0,
|
||||
esp_hal::gpio::Level::Low,
|
||||
esp_hal::gpio::OutputConfig::default()
|
||||
.with_drive_mode(esp_hal::gpio::DriveMode::PushPull),
|
||||
);
|
||||
let a1 = esp_hal::gpio::Output::new(
|
||||
a1,
|
||||
esp_hal::gpio::Level::Low,
|
||||
esp_hal::gpio::OutputConfig::default()
|
||||
.with_drive_mode(esp_hal::gpio::DriveMode::PushPull),
|
||||
);
|
||||
let a2 = esp_hal::gpio::Output::new(
|
||||
a2,
|
||||
esp_hal::gpio::Level::Low,
|
||||
esp_hal::gpio::OutputConfig::default()
|
||||
.with_drive_mode(esp_hal::gpio::DriveMode::PushPull),
|
||||
);
|
||||
let y0 = esp_hal::gpio::Input::new(
|
||||
y0,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y1 = esp_hal::gpio::Input::new(
|
||||
y1,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y2 = esp_hal::gpio::Input::new(
|
||||
y2,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y3 = esp_hal::gpio::Input::new(
|
||||
y3,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y4 = esp_hal::gpio::Input::new(
|
||||
y4,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y5 = esp_hal::gpio::Input::new(
|
||||
y5,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
let y6 = esp_hal::gpio::Input::new(
|
||||
y6,
|
||||
esp_hal::gpio::InputConfig::default().with_pull(esp_hal::gpio::Pull::Up),
|
||||
);
|
||||
|
||||
Self {
|
||||
a0,
|
||||
a1,
|
||||
a2,
|
||||
y0,
|
||||
y1,
|
||||
y2,
|
||||
y3,
|
||||
y4,
|
||||
y5,
|
||||
y6,
|
||||
last_state: Key::none(),
|
||||
pressed: Key::none(),
|
||||
released: Key::none(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the keyboard matrix and tracks which keys have been pressed and released.
|
||||
///
|
||||
/// This function should be called often (100Hz) otherwise it may miss key presses.
|
||||
pub fn scan(&mut self) {
|
||||
let mut key_flags = 0_u64;
|
||||
for i in 0..8 {
|
||||
self.a0
|
||||
.set_level(esp_hal::gpio::Level::from(i & 0b00000001 != 0));
|
||||
self.a1
|
||||
.set_level(esp_hal::gpio::Level::from(i & 0b00000010 != 0));
|
||||
self.a2
|
||||
.set_level(esp_hal::gpio::Level::from(i & 0b00000100 != 0));
|
||||
|
||||
let inputs: [esp_hal::gpio::Level; 7] = [
|
||||
self.y0.level(),
|
||||
self.y1.level(),
|
||||
self.y2.level(),
|
||||
self.y3.level(),
|
||||
self.y4.level(),
|
||||
self.y5.level(),
|
||||
self.y6.level(),
|
||||
];
|
||||
|
||||
let major_offset = i * 8;
|
||||
|
||||
for (minor_offset, decoded) in inputs.iter().enumerate() {
|
||||
let bit = match decoded {
|
||||
esp_hal::gpio::Level::Low => 1_u64,
|
||||
esp_hal::gpio::Level::High => 0_u64,
|
||||
};
|
||||
key_flags |= bit << (major_offset + minor_offset);
|
||||
}
|
||||
}
|
||||
let key = Key { bits: key_flags };
|
||||
|
||||
let changes = key.xor(self.last_state);
|
||||
|
||||
let new_presses = changes.and(key);
|
||||
self.pressed |= new_presses;
|
||||
|
||||
let new_releases = changes.and(!key);
|
||||
self.released |= new_releases;
|
||||
|
||||
self.last_state = key;
|
||||
}
|
||||
|
||||
/// Returns the keys that were held when [scan][Self::scan] was last called.
|
||||
pub fn held_keys(&self) -> Key {
|
||||
self.last_state
|
||||
}
|
||||
|
||||
/// Returns buffered key presses that haven't already been cleared with
|
||||
/// [clear_pressed_keys][Self::clear_pressed_keys] or [clear_some_pressed_keys][Self::clear_some_pressed_keys]
|
||||
pub fn pressed_keys(&self) -> Key {
|
||||
self.pressed
|
||||
}
|
||||
|
||||
/// Clears the key press buffer
|
||||
pub fn clear_pressed_keys(&mut self) {
|
||||
self.pressed = Key::none()
|
||||
}
|
||||
|
||||
/// Clears specific keys from the key press buffer
|
||||
pub fn clear_some_pressed_keys(&mut self, keys_to_clear: Key) {
|
||||
self.pressed &= !keys_to_clear;
|
||||
}
|
||||
|
||||
/// Returns buffered key releases that haven't already been cleared with
|
||||
/// [clear_released_keys][Self::clear_released_keys] or [clear_some_released_keys][Self::clear_some_released_keys]
|
||||
pub fn released_keys(&self) -> Key {
|
||||
self.released
|
||||
}
|
||||
|
||||
/// Clears the key release buffer
|
||||
pub fn clear_released_keys(&mut self) {
|
||||
self.released = Key::none()
|
||||
}
|
||||
|
||||
/// Clears specific keys from the key release buffer
|
||||
pub fn clear_some_released_keys(&mut self, keys_to_clear: Key) {
|
||||
self.released &= !keys_to_clear;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue