basic keyboard driver
This commit is contained in:
9
.cargo/config.toml
Normal file
9
.cargo/config.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
runner = "probe-rs run --chip RP2040"
|
||||||
|
# runner = "elf2uf2-rs -d"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||||
|
|
||||||
|
[env]
|
||||||
|
DEFMT_LOG = "info"
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
3299
Cargo.lock
generated
Normal file
3299
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
Cargo.toml
Normal file
70
Cargo.toml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[package]
|
||||||
|
name = "picocalc-os-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
reqwest = { version = "0.12.20", features = ["blocking"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["rp2040"]
|
||||||
|
rp2040 = ["embassy-rp/rp2040"]
|
||||||
|
rp2350 = ["embassy-rp/_rp235x"]
|
||||||
|
trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"]
|
||||||
|
defmt = [
|
||||||
|
"dep:defmt",
|
||||||
|
"panic-probe/print-defmt",
|
||||||
|
"embassy-executor/defmt",
|
||||||
|
"embassy-time/defmt",
|
||||||
|
"embassy-time/defmt-timestamp-uptime",
|
||||||
|
"embassy-rp/defmt",
|
||||||
|
"embassy-sync/defmt",
|
||||||
|
"embedded-graphics/defmt",
|
||||||
|
"embedded-sdmmc/defmt-log",
|
||||||
|
# "bt-hci/defmt",
|
||||||
|
# "cyw43/defmt",
|
||||||
|
# "cyw43-pio/defmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embassy-executor = { version = "0.7", features = [
|
||||||
|
"arch-cortex-m",
|
||||||
|
"executor-interrupt",
|
||||||
|
"executor-thread",
|
||||||
|
"nightly",
|
||||||
|
] }
|
||||||
|
embassy-rp = { version = "0.4.0", features = [
|
||||||
|
"critical-section-impl",
|
||||||
|
"time-driver",
|
||||||
|
"intrinsics",
|
||||||
|
] }
|
||||||
|
embassy-futures = "0.1.0"
|
||||||
|
embassy-time = "0.4.0"
|
||||||
|
embassy-embedded-hal = "0.3.0"
|
||||||
|
embassy-sync = { version = "0.6" }
|
||||||
|
trouble-host = { version = "0.1", features = [
|
||||||
|
"derive",
|
||||||
|
"scan",
|
||||||
|
], optional = true }
|
||||||
|
bt-hci = { version = "0.2", default-features = false, optional = true }
|
||||||
|
cyw43 = { version = "0.3.0", features = [
|
||||||
|
"firmware-logs",
|
||||||
|
"bluetooth",
|
||||||
|
], optional = true }
|
||||||
|
cyw43-pio = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
|
embedded-hal-bus = { version = "0.3.0", features = ["async"] }
|
||||||
|
embedded-hal = "0.2.7"
|
||||||
|
embedded-hal-async = "1.0.0"
|
||||||
|
cortex-m = { version = "0.7.7" }
|
||||||
|
cortex-m-rt = "0.7.5"
|
||||||
|
panic-probe = "0.3"
|
||||||
|
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
||||||
|
|
||||||
|
defmt = { version = "0.3", optional = true }
|
||||||
|
defmt-rtt = "0.4.2"
|
||||||
|
|
||||||
|
embedded-graphics = { version = "0.8.1" }
|
||||||
|
embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false }
|
||||||
|
|
||||||
|
static_cell = "2.1.0"
|
||||||
39
Embed.toml
Normal file
39
Embed.toml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[default.probe]
|
||||||
|
protocol = "Swd"
|
||||||
|
speed = 20000
|
||||||
|
# If you only have one probe cargo embed will pick automatically
|
||||||
|
# Otherwise: add your probe's VID/PID/serial to filter
|
||||||
|
|
||||||
|
## rust-dap
|
||||||
|
# usb_vid = "6666"
|
||||||
|
# usb_pid = "4444"
|
||||||
|
# serial = "test"
|
||||||
|
|
||||||
|
|
||||||
|
[default.flashing]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[default.reset]
|
||||||
|
enabled = true
|
||||||
|
halt_afterwards = false
|
||||||
|
|
||||||
|
[default.general]
|
||||||
|
chip = "RP2040"
|
||||||
|
log_level = "WARN"
|
||||||
|
# RP2040 does not support connect_under_reset
|
||||||
|
connect_under_reset = false
|
||||||
|
|
||||||
|
[default.rtt]
|
||||||
|
enabled = true
|
||||||
|
up_mode = "NoBlockSkip"
|
||||||
|
channels = [
|
||||||
|
{ up = 0, down = 0, name = "name", up_mode = "NoBlockSkip", format = "Defmt" },
|
||||||
|
]
|
||||||
|
timeout = 3000
|
||||||
|
show_timestamps = true
|
||||||
|
log_enabled = false
|
||||||
|
log_path = "./logs"
|
||||||
|
|
||||||
|
[default.gdb]
|
||||||
|
enabled = false
|
||||||
|
gdb_connection_string = "127.0.0.1:2345"
|
||||||
36
build.rs
Normal file
36
build.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//! 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");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
17
memory.x
Normal file
17
memory.x
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
MEMORY {
|
||||||
|
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||||
|
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
|
||||||
|
|
||||||
|
/* Pick one of the two options for RAM layout */
|
||||||
|
|
||||||
|
/* OPTION A: Use all RAM banks as one big block */
|
||||||
|
/* Reasonable, unless you are doing something */
|
||||||
|
/* really particular with DMA or other concurrent */
|
||||||
|
/* access that would benefit from striping */
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 264K
|
||||||
|
|
||||||
|
/* OPTION B: Keep the unstriped sections separate */
|
||||||
|
/* RAM: ORIGIN = 0x20000000, LENGTH = 256K */
|
||||||
|
/* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */
|
||||||
|
/* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */
|
||||||
|
}
|
||||||
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2025-06-18"
|
||||||
|
components = ["rust-src", "rustfmt", "rust-lld"]
|
||||||
|
targets = ["thumbv6m-none-eabi"]
|
||||||
161
src/keyboard.rs
Normal file
161
src/keyboard.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use embassy_rp::{
|
||||||
|
i2c::{Async, I2c},
|
||||||
|
peripherals::I2C1,
|
||||||
|
};
|
||||||
|
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Sender};
|
||||||
|
|
||||||
|
const KEYBOARD_ADDR: u8 = 0x1F;
|
||||||
|
|
||||||
|
pub struct KeyEvent {
|
||||||
|
key: KeyCode,
|
||||||
|
state: KeyState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn keyboard(
|
||||||
|
mut i2c: I2c<'static, I2C1, Async>,
|
||||||
|
channel: Sender<'static, NoopRawMutex, KeyEvent, 10>,
|
||||||
|
) {
|
||||||
|
embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
|
let mut res = [0_u8; 2];
|
||||||
|
if i2c.read_async(KEYBOARD_ADDR, &mut res).await.is_ok() {
|
||||||
|
if let Ok(state) = KeyState::try_from(res[0]) {
|
||||||
|
if let Ok(key) = KeyCode::try_from(res[1]) {
|
||||||
|
let _ = channel.try_send(KeyEvent { key, state });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KeyState {
|
||||||
|
Idle = 0,
|
||||||
|
Pressed,
|
||||||
|
Hold,
|
||||||
|
Released,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for KeyState {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(KeyState::Idle),
|
||||||
|
1 => Ok(KeyState::Pressed),
|
||||||
|
2 => Ok(KeyState::Hold),
|
||||||
|
3 => Ok(KeyState::Released),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KeyCode {
|
||||||
|
// Joystick
|
||||||
|
JoyUp = 0x01,
|
||||||
|
JoyDown = 0x02,
|
||||||
|
JoyLeft = 0x03,
|
||||||
|
JoyRight = 0x04,
|
||||||
|
JoyCenter = 0x05,
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
BtnLeft1 = 0x06,
|
||||||
|
BtnRight1 = 0x07,
|
||||||
|
BtnLeft2 = 0x11,
|
||||||
|
BtnRight2 = 0x12,
|
||||||
|
|
||||||
|
// Basic Keys
|
||||||
|
Backspace = 0x08,
|
||||||
|
Tab = 0x09,
|
||||||
|
Enter = 0x0A,
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
ModAlt = 0xA1,
|
||||||
|
ModShiftLeft = 0xA2,
|
||||||
|
ModShiftRight = 0xA3,
|
||||||
|
ModSym = 0xA4,
|
||||||
|
ModCtrl = 0xA5,
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
Esc = 0xB1,
|
||||||
|
Left = 0xB4,
|
||||||
|
Up = 0xB5,
|
||||||
|
Down = 0xB6,
|
||||||
|
Right = 0xB7,
|
||||||
|
|
||||||
|
// Specials
|
||||||
|
Break = 0xD0,
|
||||||
|
Insert = 0xD1,
|
||||||
|
Home = 0xD2,
|
||||||
|
Del = 0xD4,
|
||||||
|
End = 0xD5,
|
||||||
|
PageUp = 0xD6,
|
||||||
|
PageDown = 0xD7,
|
||||||
|
|
||||||
|
// Locks
|
||||||
|
CapsLock = 0xC1,
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
F1 = 0x81,
|
||||||
|
F2 = 0x82,
|
||||||
|
F3 = 0x83,
|
||||||
|
F4 = 0x84,
|
||||||
|
F5 = 0x85,
|
||||||
|
F6 = 0x86,
|
||||||
|
F7 = 0x87,
|
||||||
|
F8 = 0x88,
|
||||||
|
F9 = 0x89,
|
||||||
|
F10 = 0x90,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for KeyCode {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
use KeyCode::*;
|
||||||
|
match value {
|
||||||
|
0x01 => Ok(JoyUp),
|
||||||
|
0x02 => Ok(JoyDown),
|
||||||
|
0x03 => Ok(JoyLeft),
|
||||||
|
0x04 => Ok(JoyRight),
|
||||||
|
0x05 => Ok(JoyCenter),
|
||||||
|
0x06 => Ok(BtnLeft1),
|
||||||
|
0x07 => Ok(BtnRight1),
|
||||||
|
0x08 => Ok(Backspace),
|
||||||
|
0x09 => Ok(Tab),
|
||||||
|
0x0A => Ok(Enter),
|
||||||
|
0x11 => Ok(BtnLeft2),
|
||||||
|
0x12 => Ok(BtnRight2),
|
||||||
|
0xA1 => Ok(ModAlt),
|
||||||
|
0xA2 => Ok(ModShiftLeft),
|
||||||
|
0xA3 => Ok(ModShiftRight),
|
||||||
|
0xA4 => Ok(ModSym),
|
||||||
|
0xA5 => Ok(ModCtrl),
|
||||||
|
0xB1 => Ok(Esc),
|
||||||
|
0xB4 => Ok(Left),
|
||||||
|
0xB5 => Ok(Up),
|
||||||
|
0xB6 => Ok(Down),
|
||||||
|
0xB7 => Ok(Right),
|
||||||
|
0xC1 => Ok(CapsLock),
|
||||||
|
0xD0 => Ok(Break),
|
||||||
|
0xD1 => Ok(Insert),
|
||||||
|
0xD2 => Ok(Home),
|
||||||
|
0xD4 => Ok(Del),
|
||||||
|
0xD5 => Ok(End),
|
||||||
|
0xD6 => Ok(PageUp),
|
||||||
|
0xD7 => Ok(PageDown),
|
||||||
|
0x81 => Ok(F1),
|
||||||
|
0x82 => Ok(F2),
|
||||||
|
0x83 => Ok(F3),
|
||||||
|
0x84 => Ok(F4),
|
||||||
|
0x85 => Ok(F5),
|
||||||
|
0x86 => Ok(F6),
|
||||||
|
0x87 => Ok(F7),
|
||||||
|
0x88 => Ok(F8),
|
||||||
|
0x89 => Ok(F9),
|
||||||
|
0x90 => Ok(F10),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main.rs
Normal file
41
src/main.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
use defmt::*;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_rp::peripherals::I2C1;
|
||||||
|
use embassy_rp::spi::{self, Spi};
|
||||||
|
use embassy_rp::{bind_interrupts, i2c};
|
||||||
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
|
use embassy_sync::channel::Channel;
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
|
use embedded_sdmmc::asynchronous::{File, SdCard, ShortFileName, VolumeIdx, VolumeManager};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
mod keyboard;
|
||||||
|
use keyboard::KeyEvent;
|
||||||
|
|
||||||
|
embassy_rp::bind_interrupts!(struct Irqs {
|
||||||
|
I2C1_IRQ => i2c::InterruptHandler<I2C1>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
|
static KEYBOARD_EVENTS: StaticCell<Channel<NoopRawMutex, KeyEvent, 10>> = StaticCell::new();
|
||||||
|
let keyboard_events = KEYBOARD_EVENTS.init(Channel::new());
|
||||||
|
|
||||||
|
// configure keyboard event handler
|
||||||
|
let config = embassy_rp::i2c::Config::default();
|
||||||
|
let bus = embassy_rp::i2c::I2c::new_async(p.I2C1, p.PIN_27, p.PIN_26, Irqs, config);
|
||||||
|
spawner
|
||||||
|
.spawn(keyboard::keyboard(bus, keyboard_events.sender()))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user