This commit is contained in:
2023-06-12 17:39:07 +02:00
parent 59f91e2f6b
commit 9854e0c0a8
45 changed files with 4708 additions and 400 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
/target
target

1798
left/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
left/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "tangentbord1-left"
version = "0.1.0"
authors = ["Joakim Hulthe <joakim@hulthe.net>"]
description = "Keyboard firmware"
edition = "2021"
[dependencies.tangentbord1]
path = "../lib"
package = "tangentbord1-lib"
[dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
cortex-m-rt = "0.7"
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "unstable-traits", "unstable-pac", "time-driver", "critical-section-impl"] }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["arch-cortex-m", "log", "executor-thread", "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"] }
log = "0.4.17"
postcard = { version = "1.0.4", features = ["alloc"] }
[patch."https://git.nubo.sh/hulthe/tgnt.git"]
tgnt = { path = "../../tgnt" }
[build-dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
ron = "0.8.0"
postcard = { version = "1", features = ["use-std"] }

View File

@ -17,8 +17,7 @@ use tgnt::layer::Layer;
fn main() {
memory();
serialize_layout("./layers-left.ron", "./src/bin/layers-left.pc");
serialize_layout("./layers-right.ron", "./src/bin/layers-right.pc");
serialize_layout("./layers.ron", "./src/layers.pc");
}
fn memory() {
@ -27,7 +26,7 @@ fn memory() {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.write_all(include_bytes!("../memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
@ -35,7 +34,7 @@ fn memory() {
// 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:rerun-if-changed=../memory.x");
// --nmagic turns off page alignment of sections (which saves flash space)
println!("cargo:rustc-link-arg-bins=--nmagic");

View File

@ -25,7 +25,7 @@
// Thumbpad
Key(Backspace),
Key(Space),
NextLayer,
HoldLayer(1),
],
),
Layer(
@ -54,7 +54,7 @@
// Thumbpad
Key(Backspace),
Key(Space),
NextLayer,
HoldLayer(1),
],
),
Layer(
@ -83,7 +83,7 @@
// Thumbpad
Key(Backspace),
Key(Space),
NextLayer,
HoldLayer(1),
],
),
]

BIN
left/src/layers.pc Normal file

Binary file not shown.

View File

@ -3,6 +3,7 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![cfg(target_arch = "arm")]
extern crate alloc;
extern crate cortex_m_rt;
@ -12,12 +13,16 @@ use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output, Pin};
use embassy_time::{Duration, Timer};
use log::error;
use tangentbord1::board::Board;
use tangentbord1::keyboard::{Half, KeyboardConfig};
use tangentbord1::logger::Logger;
use tangentbord1::util::{stall, wheel};
use tangentbord1::ws2812::{Rgb, Ws2812};
use tangentbord1::{allocator, rtt, uart, usb};
use tangentbord1::{
board::Board,
event::Half,
keyboard::KeyboardConfig,
logger::Logger,
rgb::Rgb,
util::{stall, wheel},
ws2812::Ws2812,
{allocator, rtt, uart, usb},
};
use tgnt::layer::Layer;
#[embassy_executor::main]
@ -48,7 +53,7 @@ async fn main(_spawner: Spawner) {
neopixel.write(&[Rgb::new(0xFF, 0x00, 0x00)]).await;
let layers = include_bytes!("layers-left.pc");
let layers = include_bytes!("layers.pc");
let Ok(layers): Result<Vec<Layer>, _> = postcard::from_bytes(layers) else {
log::error!("Failed to deserialize layer config");
stall().await

1781
lib/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[package]
name = "tangentbord1"
name = "tangentbord1-lib"
version = "0.1.0"
authors = ["Joakim Hulthe <joakim@hulthe.net>"]
description = "Keyboard firmware"
@ -14,15 +14,14 @@ usb-device = "0.2.9"
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", "arch-cortex-m", "executor-thread", "nightly", "integrated-timers" ] }
futures = { version = "0.3", default-features = false, features = ["async-await"] }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "executor-thread", "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", "critical-section-impl"] }
log = "0.4.17"
pio = "0.2.1"
pio-proc = "0.2.1"
@ -39,8 +38,17 @@ crc-any = "2.4.3"
serde = { version = "1.0.163", default-features = false, features = ["derive"] }
bytemuck = { version = "1.13.1", features = ["derive"] }
#[patch."https://git.nubo.sh/hulthe/tgnt.git"]
#tgnt = { path = "../tgnt" }
[target.'cfg(target_arch = "x86_64")'.dependencies]
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["arch-std"] }
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", features = ["std", "generic-queue"] }
simple_logger = "4"
[target.thumbv6m-none-eabi.dependencies]
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "unstable-traits", "unstable-pac", "time-driver", "critical-section-impl"] }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["arch-cortex-m"] }
[patch."https://git.nubo.sh/hulthe/tgnt.git"]
tgnt = { path = "../../tgnt" }
[build-dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }

15
lib/memory.x Normal file
View File

@ -0,0 +1,15 @@
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
EXTERN(BOOT2_FIRMWARE)
SECTIONS {
/* ### Boot loader */
.boot2 ORIGIN(BOOT2) :
{
KEEP(*(.boot2));
} > BOOT2
} INSERT BEFORE .text;

60
lib/src/event.rs Normal file
View File

@ -0,0 +1,60 @@
use core::time::Duration;
use serde::{Deserialize, Serialize};
use tgnt::{button::Button, keys::Key};
pub mod switch {
use super::*;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
/// A switch was pressed or released
///
/// This event is triggered by tasks that monitor switches.
pub struct Event {
/// The keyboard half that triggered the event.
pub source: Half,
/// The index of the button that triggered the event.
pub source_button: usize,
pub kind: EventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum EventKind {
Press {
button: Button,
},
Release {
button: Button,
/// The duration that the button was held down for
after: Duration,
},
}
}
pub mod button {
use tgnt::button::Modifier;
use super::*;
/// A usb keyboard button was pressed or released.
///
/// This is a lower-level event than a [SwitchEvent], as things like ModTap and Compose are
/// converted to Presses and Releases.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
PressKey(Key),
ReleaseKey(Key),
PressMod(Modifier),
ReleaseMod(Modifier),
}
}
/// A keyboard half.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Half {
Left,
Right,
}

11
lib/src/interrupts.rs Normal file
View File

@ -0,0 +1,11 @@
use embassy_rp::{
bind_interrupts,
peripherals::{UART0, USB},
};
bind_interrupts! {
pub struct Irqs {
UART0_IRQ => embassy_rp::uart::BufferedInterruptHandler<UART0>;
USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<USB>;
}
}

View File

@ -9,18 +9,20 @@ use embassy_rp::{
peripherals::PIO1,
};
use embassy_sync::pubsub::{ImmediatePublisher, PubSubChannel, Subscriber};
use embassy_time::{Duration, Timer};
use futures::{select_biased, FutureExt};
use embassy_time::{Duration, Instant};
use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize};
use static_cell::StaticCell;
use tgnt::{
button::{Button, Modifier},
keys::Key,
layer::Layer,
};
use tgnt::{button::Button, layer::Layer};
use crate::{lights::Lights, util::CS, ws2812::Ws2812};
use crate::{
event::{
switch::{Event, EventKind},
Half,
},
lights::Lights,
util::CS,
ws2812::Ws2812,
};
pub struct KeyboardConfig {
/// Which board is this.
@ -43,41 +45,14 @@ struct State {
lights: Lights<PIO1, SWITCH_COUNT>,
}
/// A keyboard half.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Half {
Left,
Right,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Event {
/// The keyboard half that triggered the event.
pub source: Half,
/// The index of the button that triggered the event.
pub source_button: usize,
pub kind: EventKind,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EventKind {
PressKey(Key),
ReleaseKey(Key),
PressModifier(Modifier),
ReleaseModifier(Modifier),
SetLayer(u16),
}
pub const KB_SUBSCRIBERS: usize = 2;
const ACTUAL_KB_SUBSCRIBERS: usize = KB_SUBSCRIBERS + 2;
const KB_EVENT_CAP: usize = 128;
static KB_EVENTS: PubSubChannel<CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0> =
PubSubChannel::new();
pub struct KbEvents {
subscriber: Subscriber<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
publisher: ImmediatePublisher<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
pub subscriber: Subscriber<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
pub publisher: ImmediatePublisher<'static, CS, Event, KB_EVENT_CAP, ACTUAL_KB_SUBSCRIBERS, 0>,
}
pub struct KbEventsTx<'a> {
@ -183,8 +158,8 @@ impl KbEventsTx<'_> {
}
}
const MOD_TAP_TIME: Duration = Duration::from_millis(150);
const SWITCH_COUNT: usize = 18;
pub const MOD_TAP_TIME: Duration = Duration::from_millis(150);
pub const SWITCH_COUNT: usize = 18;
/// Task for monitoring a single switch pin, and handling button presses.
#[embassy_executor::task(pool_size = 18)]
@ -195,6 +170,7 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
loop {
// pins are pull-up, so when the switch is pressed they are brought low.
pin.wait_for_low().await;
let pressed_at = Instant::now();
// TODO: do we need debouncing?
@ -219,72 +195,54 @@ async fn switch_task(switch_num: usize, pin: AnyPin, state: &'static State) -> !
debug!("switch {switch_num} button {button:?} pressed");
let wait_for_release = async {
pin.wait_for_high().await;
debug!("switch {switch_num} button {button:?} released");
};
let ev = |kind| Event {
source: state.half,
source_button: switch_num,
kind,
};
use EventKind::*;
match button {
&Button::Key(key) => {
events.publish_immediate(ev(PressKey(key)));
wait_for_release.await;
events.publish_immediate(ev(ReleaseKey(key)));
continue;
}
&Button::Mod(modifier) => {
events.publish_immediate(ev(PressModifier(modifier)));
wait_for_release.await;
events.publish_immediate(ev(ReleaseModifier(modifier)));
continue;
}
&Button::ModTap(key, modifier) => {
select_biased! {
_ = Timer::after(MOD_TAP_TIME).fuse() => {
events.publish_immediate(ev(PressModifier(modifier)));
pin.wait_for_high().await;
events.publish_immediate(ev(ReleaseModifier(modifier)));
debug!("switch {switch_num} button {button:?} released");
continue;
}
_ = wait_for_release.fuse() => {
events.publish_immediate(ev(PressKey(key)));
Timer::after(Duration::from_millis(20)).await;
events.publish_immediate(ev(ReleaseKey(key)));
continue;
}
}
}
Button::NextLayer => {
let next_layer = (current_layer + 1) % layer_count;
events.publish_immediate(ev(SetLayer(next_layer)));
debug!("switched to layer {next_layer}");
}
Button::PrevLayer => {
let prev_layer = current_layer.checked_sub(1).unwrap_or(layer_count - 1);
events.publish_immediate(ev(SetLayer(prev_layer)));
debug!("switched to layer {prev_layer}");
}
Button::None => {}
}
events.publish_immediate(ev(EventKind::Press {
button: button.clone(),
}));
wait_for_release.await;
pin.wait_for_high().await;
let released_after = pressed_at.elapsed();
debug!("switch {switch_num} button {button:?} released");
events.publish_immediate(ev(EventKind::Release {
button: button.clone(),
after: released_after.into(),
}));
}
}
#[embassy_executor::task]
async fn layer_switch_task(mut events: KbEvents, state: &'static State) {
let layer_count = state.layers.len() as u16;
let Some(last_layer) = layer_count.checked_sub(1) else {
error!("no layers specified");
return;
};
loop {
let event = events.recv().await;
if let EventKind::SetLayer(new_layer) = event.kind {
let layer = state.current_layer.load(Ordering::Relaxed);
let new_layer = match event.kind {
EventKind::Press { button } => match button {
Button::NextLayer => layer.wrapping_add(1) % layer_count,
Button::PrevLayer => layer.checked_sub(1).unwrap_or(last_layer),
Button::HoldLayer(l) => layer.wrapping_add(l) % layer_count,
_ => continue,
},
EventKind::Release { button, .. } => match button {
Button::HoldLayer(l) => layer.checked_sub(l).unwrap_or(last_layer),
_ => continue,
},
};
state.current_layer.store(new_layer, Ordering::Relaxed);
}
debug!("switched to layer {new_layer}");
}
}

View File

@ -1,9 +1,12 @@
use core::cmp::min;
use atomic_polyfill::Ordering;
use embassy_futures::yield_now;
use embassy_time::{Duration, Instant, Timer};
use futures::{select_biased, FutureExt};
use tgnt::button::Button;
use crate::{util::wheel, ws2812::Rgb};
use crate::{rgb::Rgb, util::wheel};
use super::{Event, EventKind, KbEvents, State, SWITCH_COUNT};
@ -140,29 +143,15 @@ async fn handle_event(
state: &'static State,
lights: &mut [LightState; SWITCH_COUNT],
) {
match event.kind {
EventKind::PressKey(_) => {
if state.half != event.source {
return;
}
let Some(light) = lights.get_mut(event.source_button) else { return; };
*light = LightState::Solid(Rgb::new(0, 150, 0));
}
EventKind::PressModifier(_) => {
if state.half != event.source {
return;
}
let Some(light) = lights.get_mut(event.source_button) else { return; };
*light = LightState::Solid(Rgb::new(0, 0, 150));
}
EventKind::ReleaseKey(_) | EventKind::ReleaseModifier(_) => {
if state.half != event.source {
return;
}
let Some(light) = lights.get_mut(event.source_button) else { return; };
*light = LightState::FadeBy(0.85);
}
EventKind::SetLayer(layer) => {
let rgb = match event.kind {
EventKind::Press { button } => match button {
Button::Key(..) => LightState::Solid(Rgb::new(0, 150, 0)),
Button::Mod(..) => LightState::Solid(Rgb::new(0, 0, 150)),
Button::ModTap(..) => LightState::Solid(Rgb::new(0, 0, 150)),
Button::Compose(..) => LightState::Solid(Rgb::new(0, 100, 100)),
Button::NextLayer | Button::PrevLayer => {
yield_now().await; // dirty hack to make sure layer_switch_task gets to run first
let layer = state.current_layer.load(Ordering::Relaxed);
let layer = min(layer, state.layers.len().saturating_sub(1) as u16);
let buttons_to_light_up = if state.layers.len() <= 3 {
match layer {
@ -191,6 +180,17 @@ async fn handle_event(
fade_by: 0.85,
}
}
LightState::Solid(Rgb::new(100, 0, 100))
}
_ => LightState::Solid(Rgb::new(150, 0, 0)),
},
EventKind::Release { .. } => LightState::FadeBy(0.85),
};
if event.source != state.half {
return;
}
let Some(light) = lights.get_mut(event.source_button) else { return; };
*light = rgb;
}

397
lib/src/keypress_handler.rs Normal file
View File

@ -0,0 +1,397 @@
use core::future::pending;
use embassy_sync::pubsub::{publisher::Pub, subscriber::Sub, PubSubBehavior, WaitResult};
use embassy_time::{Duration, Instant, Timer};
use futures::FutureExt;
use heapless::Deque;
use log::{debug, error};
use tgnt::button::Button;
use crate::event::{button, switch, Half};
// TODO
const MOD_TAP_TIME: Duration = Duration::from_millis(150);
const SWITCH_COUNT: usize = 18;
/// This function perpetually converts between [switch::Event]s and [button::Event]s.
///
/// Call it from a dedicated task.
pub async fn keypress_handler(
input: &mut Sub<'_, impl PubSubBehavior<switch::Event>, switch::Event>,
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
) -> ! {
type SwitchIndex = usize;
#[derive(Debug)]
struct PressEvent {
source_button: SwitchIndex,
source_half: Half,
button: Button,
time: Instant,
}
async fn slow_press(
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
button: &Button,
) {
let event = match button {
&Button::Mod(m) | &Button::ModTap(_, m) => button::Event::PressMod(m),
&Button::Key(k) => button::Event::PressKey(k),
_ => return,
};
output.publish_immediate(event);
}
async fn slow_release(
output: &mut Pub<'_, impl PubSubBehavior<button::Event>, button::Event>,
button: &Button,
) {
let event = match button {
&Button::Mod(m) | &Button::ModTap(_, m) => button::Event::ReleaseMod(m),
&Button::Key(k) => button::Event::ReleaseKey(k),
_ => return,
};
output.publish_immediate(event);
}
// queue of button presses that are waiting for ModTap
let mut queue = Deque::<PressEvent, { 2 * SWITCH_COUNT }>::new();
let queue = &mut queue;
loop {
// create a future that waits for the next ModTap to time out
let modtap_timeout = async {
loop {
if let Some(event) = queue.front() {
let Button::ModTap(..) = event.button else {
error!("first element in queue wasn't a modtap, wtf?");
let _ = queue.pop_front();
continue;
};
let timeout = event.time + MOD_TAP_TIME;
Timer::at(timeout).await;
return queue.pop_front().unwrap();
} else {
// if the queue is empty, never return.
return pending().await;
}
}
};
let event = futures::select_biased! {
event = input.next_message().fuse() => event,
event = modtap_timeout.fuse() => {
// first element in queue timed out, and will be treated as a Mod
let &Button::ModTap(..) = &event.button else {
error!("first element in queue wasn't a modtap, wtf?");
continue;
};
slow_press(output, &event.button).await;
loop {
let Some(event) = queue.pop_front() else { break };
// resolve events until we encounter another ModTap,
// then put it back in the queue and stop
if let Button::ModTap(..) = &event.button {
queue.push_front(event).expect("we just popped, the queue can't be full");
break;
}
slow_press(output, &event.button).await;
}
continue;
},
};
let WaitResult::Message(event) = event else {
error!("lagged");
continue;
};
let time = Instant::now();
debug!("event: {:?}", event.kind);
match event.kind {
switch::EventKind::Press { button } => {
let insert = |queue: &mut Deque<_, 36>, button| {
if let Some(_queued) = queue
.iter()
.find(|queued: &&PressEvent| queued.source_button == event.source_button)
.filter(|queued| queued.source_half == event.source)
{
error!("tried to add PressEvent to queue twice");
} else if queue
.push_back(PressEvent {
source_half: event.source,
source_button: event.source_button,
button,
time,
})
.is_err()
{
error!("button queue full. this shouldn't happen.");
}
};
match button {
Button::ModTap(_, _) => {
debug!("adding modtap to queue");
// add event to queue
insert(queue, button);
}
Button::Mod(m) => {
// if events in queue, also add to queue maybe?
// TODO
}
Button::Key(_) => {
if queue.is_empty() {
debug!("sending key now");
// otherwise, send immediately
slow_press(output, &button).await;
} else {
debug!("adding key to queue");
// if events in queue, also add to queue
insert(queue, button);
}
}
Button::Compose(..) => {
if queue.is_empty() {
// otherwise, send immediately
// TODO
} else {
// if events in queue, also add to queue
insert(queue, button);
}
}
_ => {}
}
}
switch::EventKind::Release { button, .. } => {
let position_in_queue = queue
.iter()
.enumerate()
.find(|(_, queued)| queued.source_button == event.source_button)
.map(|(i, _)| i);
match button {
Button::ModTap(k, _) => {
// check if modtap in queue
if let Some(position_in_queue) = position_in_queue {
// If the modtap was still in the queue, it hasn't been resolved as a mod
// yet. Therefore, it is a Tap. Resolve all ModTaps before this one as Mods
debug!("modtap was still in queue");
for _ in 0..position_in_queue {
let prev_event = queue.pop_front().unwrap();
debug!("pressing earlier event {:?}", prev_event);
slow_press(output, &prev_event.button).await;
}
let _ = queue.pop_front();
debug!("pressing modtap as key");
slow_press(output, &Button::Key(k)).await;
slow_release(output, &Button::Key(k)).await;
} else {
// If the ModTap wasn't in the queue, it has already been resolved as a Mod.
debug!("modtap wasn't in queue, releasing");
slow_release(output, &button).await;
};
}
Button::Mod(m) => {
debug!("mod not implemented yet");
// TODO
}
Button::Key(_) => {
// if this press event was in queue, resolve all ModTaps before in queue as Mods
// otherwise, just resolve this
if let Some(position_in_queue) = position_in_queue {
debug!(
"key was in queue, pressing all events up to and including this"
);
for _ in 0..=position_in_queue {
let prev_event = queue.pop_front().unwrap();
debug!("pressing {prev_event:?}");
slow_press(output, &prev_event.button).await;
}
}
debug!("releasing key {button:?}");
slow_release(output, &button).await;
}
Button::Compose(..) => {
// if this press event was in queue, resolve all ModTaps before in queue as Mods
// otherwise, just resolve this
// TODO
}
_ => {}
}
}
}
}
}
#[cfg(test)]
mod tests {
use core::time;
use super::*;
use alloc::vec;
use alloc::vec::Vec;
use embassy_futures::{
join::join,
select::{select, Either},
};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::PubSubChannel};
use embassy_time::with_timeout;
use log::info;
use tgnt::{button::Modifier, keys::Key};
#[test]
fn test_modtap_timings() {
simple_logger::SimpleLogger::new().init().unwrap();
let switch_events = PubSubChannel::<NoopRawMutex, switch::Event, 10, 1, 1>::new();
let button_events = PubSubChannel::<NoopRawMutex, button::Event, 10, 1, 1>::new();
let mut button_sub = button_events.subscriber().unwrap();
let buttons = [
Button::ModTap(Key::A, Modifier::LShift),
Button::ModTap(Key::B, Modifier::LCtrl),
Button::Key(Key::C),
];
struct Test {
description: &'static str,
// button index, pressed, delay
input: Vec<(usize, bool, Duration)>,
expected: Vec<button::Event>,
}
let modtap_mod = Test {
description: "modtap mod",
input: vec![
(0, true, Duration::from_millis(25)),
(1, true, Duration::from_millis(25)),
(1, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::B),
button::Event::ReleaseKey(Key::B),
button::Event::ReleaseMod(Modifier::LShift),
],
};
let modtap_tap = Test {
description: "modtap tap",
input: vec![
(0, true, Duration::from_millis(25)),
(1, true, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
(1, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressKey(Key::A),
button::Event::ReleaseKey(Key::A),
button::Event::PressKey(Key::B),
button::Event::ReleaseKey(Key::B),
],
};
let modtap_tap_2x = Test {
description: "2x modtap tap",
input: vec![
(0, true, Duration::from_millis(25)),
(2, true, Duration::from_millis(25)),
(2, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
(0, true, Duration::from_millis(25)),
(2, true, Duration::from_millis(25)),
(2, false, Duration::from_millis(25)),
(0, false, Duration::from_millis(25)),
],
expected: vec![
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::C),
button::Event::ReleaseKey(Key::C),
button::Event::ReleaseMod(Modifier::LShift),
button::Event::PressMod(Modifier::LShift),
button::Event::PressKey(Key::C),
button::Event::ReleaseKey(Key::C),
button::Event::ReleaseMod(Modifier::LShift),
],
};
for test in [modtap_tap, modtap_mod, modtap_tap_2x] {
info!("running timing test test {:?}", test.description);
embassy_futures::block_on(async {
let r = select(
join(
async {
for &(i, pressed, delay) in &test.input {
let kind = if pressed {
switch::EventKind::Press {
button: buttons[i].clone(),
}
} else {
switch::EventKind::Release {
button: buttons[i].clone(),
after: time::Duration::ZERO, // ignore
}
};
let event = switch::Event {
source: Half::Left,
source_button: i,
kind,
};
switch_events.publish_immediate(event);
Timer::after(delay).await;
}
},
async {
let mut got = Vec::new();
for _expected in &test.expected {
let event = match with_timeout(
Duration::from_millis(200),
button_sub.next_message(),
)
.await
{
Ok(WaitResult::Message(event)) => event,
Ok(WaitResult::Lagged(_)) => return Err(("lagged", got)),
Err(_) => return Err(("timeout", got)),
};
got.push(event.clone());
}
for (event, expected) in got.iter().zip(test.expected.iter()) {
if event != expected {
return Err(("unexpected event", got));
}
}
Ok(())
},
),
convert_events(
&mut *switch_events.subscriber().unwrap(),
&mut *button_events.publisher().unwrap(),
),
)
.await;
match r {
Either::First(((), Ok(()))) => {}
Either::First(((), Err((msg, got)))) => panic!(
"timing test {:?} failed due to {msg}.\nexpected={:#?} got={:#?}",
test.description, test.expected, got
),
Either::Second(never) => never,
}
});
}
}
}

32
lib/src/lib.rs Normal file
View File

@ -0,0 +1,32 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![feature(split_array)]
extern crate alloc;
#[cfg(target_arch = "arm")]
pub mod allocator;
#[cfg(target_arch = "arm")]
pub mod board;
#[cfg(target_arch = "arm")]
pub mod interrupts;
#[cfg(target_arch = "arm")]
pub mod keyboard;
#[cfg(target_arch = "arm")]
pub mod lights;
#[cfg(target_arch = "arm")]
pub mod panic_handler;
#[cfg(target_arch = "arm")]
pub mod uart;
#[cfg(target_arch = "arm")]
pub mod usb;
#[cfg(target_arch = "arm")]
pub mod ws2812;
pub mod event;
pub mod logger;
pub mod neopixel;
pub mod rgb;
pub mod rtt;
pub mod util;
pub mod keypress_handler;

View File

@ -2,7 +2,7 @@ use crate::ws2812::Ws2812;
use embassy_rp::pio;
use embassy_sync::mutex::Mutex;
use crate::{util::CS, ws2812::Rgb};
use crate::{rgb::Rgb, util::CS};
pub struct Lights<P: pio::Instance + 'static, const N: usize> {
state: Mutex<CS, State<P, N>>,

View File

@ -24,8 +24,10 @@ impl Logger {
static LOGGER: StaticCell<Logger> = StaticCell::new();
let logger = LOGGER.init(self);
unsafe { log::set_logger_racy(logger).unwrap() };
log::set_max_level(log::LevelFilter::Debug);
unsafe {
log::set_logger_racy(logger).unwrap();
log::set_max_level_racy(log::LevelFilter::Debug);
}
}
}

58
lib/src/rgb.rs Normal file
View File

@ -0,0 +1,58 @@
use bytemuck::{cast_slice, Pod, Zeroable};
use core::{
fmt::{self, Debug},
ops::Div,
};
/// An Rgb value that can be safely transmuted to u32 for use with Ws2812.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
pub struct Rgb(u32);
impl Rgb {
#[inline(always)]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self(u32::from_be_bytes([g, r, b, 0]))
}
/// Get the red, green, and blue components of this Rgb.
#[inline(always)]
pub const fn components(&self) -> [u8; 3] {
let [g, r, b, _] = self.0.to_be_bytes();
[r, g, b]
}
#[inline(always)]
pub fn slice_as_u32s(rgbs: &[Rgb]) -> &[u32] {
cast_slice(rgbs)
}
}
impl Debug for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [r, g, b] = self.components();
f.debug_tuple("Rgb").field(&r).field(&g).field(&b).finish()
}
}
impl Div<u8> for Rgb {
type Output = Rgb;
fn div(self, d: u8) -> Self::Output {
let [r, g, b] = self.components();
Rgb::new(r / d, g / d, b / d)
}
}
#[cfg(all(target_arch = "x86_64", test))]
mod tests {
use super::*;
use bytemuck::cast;
#[test]
fn test_rgb_as_u32() {
let rgb = Rgb::new(0x11, 0x22, 0xCC);
let rgb_u32: u32 = cast(rgb);
assert_eq!(rgb_u32, 0x2211CC00);
}
}

View File

@ -11,12 +11,13 @@ use heapless::Vec;
use serde::{Deserialize, Serialize};
use static_cell::StaticCell;
use crate::keyboard::{self, Half, KbEvents};
use crate::Irqs;
use crate::event::{switch, Half};
use crate::interrupts::Irqs;
use crate::keyboard::KbEvents;
#[derive(Clone, Debug, Serialize, Deserialize)]
enum Message {
KeyboardEvent(keyboard::Event),
KeyboardEvent(switch::Event),
}
pub async fn start(tx: PIN_0, rx: PIN_1, uart: UART0, board: Half, events: KbEvents) {

View File

@ -3,7 +3,7 @@ use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_usb::{Builder, Config, UsbDevice};
use static_cell::StaticCell;
use crate::{keyboard::KbEvents, Irqs};
use crate::{interrupts::Irqs, keyboard::KbEvents};
pub mod keyboard;
pub mod logger;

View File

@ -1,8 +1,13 @@
pub mod report;
use embassy_executor::Spawner;
use embassy_futures::select::select;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::mutex::Mutex;
use embassy_sync::{
blocking_mutex::raw::NoopRawMutex,
mutex::Mutex,
pubsub::{PubSubChannel, WaitResult},
};
use embassy_time::{Duration, Timer};
use embassy_usb::{
class::hid::{self, HidReaderWriter, ReadError, ReportId, RequestHandler},
@ -10,13 +15,15 @@ use embassy_usb::{
Builder,
};
use embassy_usb_driver::EndpointError;
use log::error;
use static_cell::StaticCell;
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
use usbd_hid::descriptor::SerializedDescriptor;
use crate::{
keyboard::{Event, EventKind, KbEvents},
event::button,
keyboard::KbEvents,
usb::keyboard::report::{KeyboardReport, EMPTY_KEYBOARD_REPORT},
util::CS,
util::CS, keypress_handler::keypress_handler,
};
use super::MAX_PACKET_SIZE;
@ -25,7 +32,18 @@ struct Handler;
static CONTEXT: StaticCell<Context> = StaticCell::new();
pub static KB_REPORT: Mutex<CS, KeyboardReport> = Mutex::new(EMPTY_KEYBOARD_REPORT);
static KB_REPORT: Mutex<CS, Reports> = Mutex::new(Reports {
actual: EMPTY_KEYBOARD_REPORT,
unsent: EMPTY_KEYBOARD_REPORT,
});
struct Reports {
/// The report to be sent to the host machine.
actual: KeyboardReport,
/// Key presses which hasn't been sent yet.
unsent: KeyboardReport,
}
struct Context {
handler: Handler,
@ -86,20 +104,53 @@ type HidStream = HidReaderWriter<'static, Driver<'static, USB>, 256, 256>;
#[embassy_executor::task]
async fn listen_to_events(mut events: KbEvents) {
let button_events = PubSubChannel::<NoopRawMutex, button::Event, 10, 1, 1>::new();
let mut button_pub = button_events.publisher().unwrap();
let mut button_sub = button_events.subscriber().unwrap();
select(
async {
loop {
let event = events.recv().await;
report_event(event).await;
let WaitResult::Message(event) = button_sub.next_message().await else {
error!("lagged");
continue;
};
loop {
let mut r = KB_REPORT.lock().await;
match event {
button::Event::PressKey(k) => {
r.actual.press_key(k);
r.unsent.press_key(k);
}
button::Event::PressMod(m) => {
r.actual.press_modifier(m);
r.unsent.press_modifier(m);
}
pub async fn report_event(event: Event) {
match event.kind {
EventKind::PressKey(key) => KB_REPORT.lock().await.press_key(key),
EventKind::ReleaseKey(key) => KB_REPORT.lock().await.release_key(key),
EventKind::PressModifier(modifier) => KB_REPORT.lock().await.press_modifier(modifier),
EventKind::ReleaseModifier(modifier) => KB_REPORT.lock().await.release_modifier(modifier),
EventKind::SetLayer(_) => {}
// we got a key release, but if the key press hasn't been sent yet, we
// wait for a bit until it has.
button::Event::ReleaseKey(k) if r.unsent.key_pressed(k) => {
drop(r);
Timer::after(Duration::from_millis(1)).await;
continue;
}
button::Event::ReleaseMod(m) if r.unsent.modifier_pressed(m) => {
drop(r);
Timer::after(Duration::from_millis(1)).await;
continue;
}
button::Event::ReleaseKey(k) => r.actual.release_key(k),
button::Event::ReleaseMod(m) => r.actual.release_modifier(m),
}
break;
}
}
},
keypress_handler(&mut *events.subscriber, &mut *button_pub),
)
.await;
}
#[embassy_executor::task]
@ -107,9 +158,6 @@ async fn task(stream: HidStream, handler: &'static Handler) {
if let Err(e) = keyboard_report(stream, handler).await {
log::error!("keyboard error: {e:?}");
}
//if let Err(e) = mouse_wiggler(stream).await {
// log::error!("mouse wiggler: {e:?}");
//}
}
async fn keyboard_report(mut stream: HidStream, _handler: &'static Handler) -> Result<(), Error> {
@ -117,7 +165,12 @@ async fn keyboard_report(mut stream: HidStream, _handler: &'static Handler) -> R
loop {
Timer::after(Duration::from_millis(2)).await;
let report = KB_REPORT.lock().await.clone();
let report = {
let mut reports = KB_REPORT.lock().await;
reports.unsent = EMPTY_KEYBOARD_REPORT;
reports.actual.clone()
};
if report.keycodes != EMPTY_KEYBOARD_REPORT.keycodes {
log::trace!("keys: {:x?}", report.keycodes);
}
@ -130,48 +183,6 @@ async fn keyboard_report(mut stream: HidStream, _handler: &'static Handler) -> R
}
}
#[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),

View File

@ -5,7 +5,7 @@
/// keyboard LEDs.
///
/// Unlike usbd_hids KeyboardReport, this one supports N-key rollover.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[cfg(feature = "n-key-rollover")]
#[repr(C, packed)]
pub struct KeyboardReport {
@ -15,7 +15,8 @@ pub struct KeyboardReport {
pub keycodes: [u8; 27],
}
use core::mem::{size_of, transmute};
use bytemuck::{cast_ref, Pod, Zeroable};
use core::mem::size_of;
use tgnt::{button::Modifier, keys::Key};
#[cfg(not(feature = "n-key-rollover"))]
@ -35,15 +36,22 @@ pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
keycodes: [0; 6],
};
#[cfg(feature = "n-key-rollover")]
impl KeyboardReport {
#[inline(always)]
pub fn set_key(&mut self, key: Key, pressed: bool) {
/// Get the byte index, and the mask for that byte, in the keycode bitmap.
fn key_to_byte_mask(key: Key) -> (usize, u8) {
let keycode = u8::from(key);
let byte = keycode >> 3;
let bit = keycode & 0b111;
let mask = 1 << bit;
(usize::from(byte), mask)
}
#[cfg(feature = "n-key-rollover")]
impl KeyboardReport {
#[inline(always)]
pub fn set_key(&mut self, key: Key, pressed: bool) {
let (byte, mask) = key_to_byte_mask(key);
if let Some(k) = self.keycodes.get_mut(byte as usize) {
if pressed {
*k |= mask;
@ -51,7 +59,7 @@ impl KeyboardReport {
*k &= !mask;
}
} else {
log::warn!("Tried to set out-of-range keycode: 0x{keycode:x}");
log::warn!("Tried to set out-of-range keycode: 0x{:x}", u8::from(key));
}
}
@ -65,6 +73,17 @@ impl KeyboardReport {
self.set_key(key, false)
}
#[inline(always)]
pub fn key_pressed(&mut self, key: Key) -> bool {
let (byte, mask) = key_to_byte_mask(key);
if let Some(k) = self.keycodes.get_mut(byte as usize) {
(*k & mask) != 0
} else {
log::warn!("Tried to get out-of-range keycode: 0x{:x}", u8::from(key));
false
}
}
#[inline(always)]
pub fn set_modifier(&mut self, modifier: Modifier, pressed: bool) {
if pressed {
@ -84,10 +103,14 @@ impl KeyboardReport {
self.set_modifier(modifier, false)
}
#[inline(always)]
pub fn modifier_pressed(&mut self, modifier: Modifier) -> bool {
(self.modifier & u8::from(modifier)) != 0
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; size_of::<KeyboardReport>()] {
// SAFETY: KeyboardReport is repr(C, packed) and contains only u8s.
unsafe { transmute(self) }
cast_ref(self)
}
}

View File

@ -2,39 +2,17 @@ use crate::util::CS;
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::pipe::Pipe;
use embassy_time::Instant;
use embassy_usb::{
class::cdc_acm::{self, CdcAcmClass},
Builder,
};
use embassy_usb::class::cdc_acm::CdcAcmClass;
use log::{Metadata, Record};
use static_cell::StaticCell;
pub const BUFFER_SIZE: usize = 16 * 1024;
static BUFFER: Pipe<CS, BUFFER_SIZE> = Pipe::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;
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
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));
}
#[embassy_executor::task]
async fn log_task(mut class: CdcAcmClass<'static, Driver<'static, USB>>) {
let mut buf = [0u8; MAX_PACKET_SIZE as usize];

View File

@ -1,7 +1,7 @@
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Timer};
use crate::ws2812::Rgb;
use crate::rgb::Rgb;
pub type CS = CriticalSectionRawMutex;

View File

@ -1,23 +1,16 @@
use core::fmt::{self, Debug};
use core::mem::transmute;
use core::ops::Div;
use embassy_rp::dma::{self, AnyChannel};
use embassy_rp::pio::{self, FifoJoin, Instance, Pio, PioPin, ShiftConfig, ShiftDirection};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{Peripheral, PeripheralRef};
use fixed::FixedU32;
use crate::rgb::Rgb;
pub struct Ws2812<P: pio::Instance + 'static> {
sm: pio::StateMachine<'static, P, 0>,
dma: PeripheralRef<'static, AnyChannel>,
}
/// An Rgb value that can be safely transmuted to u32 for use with Ws2812.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Rgb(u32);
impl<P: Instance> Ws2812<P> {
pub fn new(
pio: impl Peripheral<P = P> + 'static,
@ -96,39 +89,3 @@ impl<P: Instance> Ws2812<P> {
self.sm.tx().dma_push(self.dma.reborrow(), colors).await;
}
}
impl Rgb {
#[inline(always)]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self(u32::from_be_bytes([g, r, b, 0]))
}
/// Get the red, green, and blue components of this Rgb.
#[inline(always)]
pub const fn components(&self) -> [u8; 3] {
let [g, r, b, _] = self.0.to_be_bytes();
[r, g, b]
}
#[inline(always)]
pub fn slice_as_u32s(rgbs: &[Rgb]) -> &[u32] {
// SAFETY: Rgb contains only a u32, and is #[repr(transparent)]
unsafe { transmute(rgbs) }
}
}
impl Debug for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [r, g, b] = self.components();
f.debug_tuple("Rgb").field(&r).field(&g).field(&b).finish()
}
}
impl Div<u8> for Rgb {
type Output = Rgb;
fn div(self, d: u8) -> Self::Output {
let [r, g, b] = self.components();
Rgb::new(r / d, g / d, b / d)
}
}

View File

@ -44,6 +44,17 @@ dependencies = [
"critical-section",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -115,7 +126,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
"syn 2.0.18",
]
[[package]]
@ -152,6 +163,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "cortex-m"
version = "0.7.7"
@ -282,7 +304,7 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "embassy-cortex-m"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"atomic-polyfill 1.0.2",
"cfg-if",
@ -297,8 +319,9 @@ dependencies = [
[[package]]
name = "embassy-embedded-hal"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"embassy-futures",
"embassy-sync",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0-alpha.10",
@ -311,7 +334,7 @@ dependencies = [
[[package]]
name = "embassy-executor"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"atomic-polyfill 1.0.2",
"cortex-m",
@ -326,7 +349,7 @@ dependencies = [
[[package]]
name = "embassy-futures"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"log",
]
@ -334,7 +357,7 @@ dependencies = [
[[package]]
name = "embassy-hal-common"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"num-traits",
]
@ -342,7 +365,7 @@ dependencies = [
[[package]]
name = "embassy-macros"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"darling",
"proc-macro2",
@ -353,12 +376,12 @@ dependencies = [
[[package]]
name = "embassy-net-driver"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
[[package]]
name = "embassy-net-driver-channel"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"embassy-futures",
"embassy-net-driver",
@ -368,7 +391,7 @@ dependencies = [
[[package]]
name = "embassy-rp"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"atomic-polyfill 1.0.2",
"cfg-if",
@ -404,7 +427,7 @@ dependencies = [
[[package]]
name = "embassy-sync"
version = "0.2.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"cfg-if",
"critical-section",
@ -417,7 +440,7 @@ dependencies = [
[[package]]
name = "embassy-time"
version = "0.1.1"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"atomic-polyfill 1.0.2",
"cfg-if",
@ -431,7 +454,7 @@ dependencies = [
[[package]]
name = "embassy-usb"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"embassy-futures",
"embassy-net-driver-channel",
@ -445,12 +468,12 @@ dependencies = [
[[package]]
name = "embassy-usb-driver"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
[[package]]
name = "embassy-usb-logger"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy.git#d55b9bc6e2de528e1351b992bb30f54ec6f76b6d"
source = "git+https://github.com/embassy-rs/embassy.git#d414f4e4f7f243b4e52e6550ca616c180d731774"
dependencies = [
"embassy-futures",
"embassy-sync",
@ -631,7 +654,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
"syn 2.0.18",
]
[[package]]
@ -709,6 +732,15 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
@ -733,11 +765,11 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
@ -748,7 +780,7 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys 0.48.0",
@ -763,6 +795,12 @@ dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "lalrpop"
version = "0.19.12"
@ -794,6 +832,12 @@ dependencies = [
"regex",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.144"
@ -824,12 +868,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.17"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
[[package]]
name = "memchr"
@ -888,10 +929,19 @@ dependencies = [
]
[[package]]
name = "once_cell"
version = "1.17.1"
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
[[package]]
name = "parking_lot"
@ -1036,18 +1086,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8"
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.27"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
dependencies = [
"proc-macro2",
]
@ -1080,13 +1130,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.8.1"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.1",
"regex-syntax 0.7.2",
]
[[package]]
@ -1097,9 +1147,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "rgb"
@ -1232,7 +1282,20 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
"syn 2.0.18",
]
[[package]]
name = "simple_logger"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d"
dependencies = [
"atty",
"colored",
"log",
"time",
"windows-sys 0.42.0",
]
[[package]]
@ -1292,9 +1355,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_cell"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c37c250d21f53fa7165e76e5401d7e6539c211a8d2cf449e3962956a5cc2ce"
checksum = "ed6a851e9c00ce152b2bad24e8545a6db0b8646988267a6ea966d95c6932e8a2"
dependencies = [
"atomic-polyfill 1.0.2",
]
@ -1331,9 +1394,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.16"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
dependencies = [
"proc-macro2",
"quote",
@ -1341,7 +1404,7 @@ dependencies = [
]
[[package]]
name = "tangentbord1"
name = "tangentbord1-lib"
version = "0.1.0"
dependencies = [
"atomic-polyfill 1.0.2",
@ -1372,6 +1435,7 @@ dependencies = [
"ron",
"rtt-target",
"serde",
"simple_logger",
"smart-leds",
"static_cell",
"tgnt",
@ -1379,6 +1443,23 @@ dependencies = [
"usbd-hid",
]
[[package]]
name = "tangentbord1-right"
version = "0.1.0"
dependencies = [
"cortex-m-rt",
"embassy-executor",
"embassy-futures",
"embassy-rp",
"embassy-sync",
"embassy-time",
"log",
"postcard",
"ron",
"tangentbord1-lib",
"tgnt",
]
[[package]]
name = "term"
version = "0.7.0"
@ -1402,7 +1483,6 @@ dependencies = [
[[package]]
name = "tgnt"
version = "0.1.0"
source = "git+https://git.nubo.sh/hulthe/tgnt.git#643f75cf0a33208ec18c38ea7d9ccf06f344ff35"
dependencies = [
"serde",
]
@ -1424,7 +1504,36 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
"syn 2.0.18",
]
[[package]]
name = "time"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc"
dependencies = [
"itoa",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
dependencies = [
"time-core",
]
[[package]]
@ -1450,9 +1559,9 @@ checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicode-ident"
version = "1.0.8"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "unicode-width"
@ -1571,6 +1680,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

29
right/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "tangentbord1-right"
version = "0.1.0"
authors = ["Joakim Hulthe <joakim@hulthe.net>"]
description = "Keyboard firmware"
edition = "2021"
[dependencies.tangentbord1]
path = "../lib"
package = "tangentbord1-lib"
[dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
cortex-m-rt = "0.7"
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "unstable-traits", "unstable-pac", "time-driver", "critical-section-impl"] }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["arch-cortex-m", "log", "executor-thread", "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"] }
log = "0.4.17"
postcard = { version = "1.0.4", features = ["alloc"] }
[patch."https://git.nubo.sh/hulthe/tgnt.git"]
tgnt = { path = "../../tgnt" }
[build-dependencies]
tgnt = { git = "https://git.nubo.sh/hulthe/tgnt.git", default-features = false }
ron = "0.8.0"
postcard = { version = "1", features = ["use-std"] }

59
right/build.rs Normal file
View File

@ -0,0 +1,59 @@
//! 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::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::{env, fs};
use tgnt::layer::Layer;
fn main() {
memory();
serialize_layout("./layers.ron", "./src/layers.pc");
}
fn memory() {
// 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");
}
fn serialize_layout(ron_path: &str, postcard_path: &str) {
println!("cargo:rerun-if-changed={ron_path}");
let layers = fs::read_to_string(ron_path).expect("Failed to read .ron");
let layers: Vec<Layer> = ron::from_str(&layers).expect("Failed to deserialize .ron");
let serialized = postcard::to_stdvec(&layers).expect("Failed to serialize layers");
File::create(postcard_path)
.expect("Failed to create .pc")
.write_all(&serialized)
.expect("Failed to write .pc");
}

View File

@ -23,7 +23,7 @@
Key(Z),
// Thumbpad
PrevLayer,
HoldLayer(1),
Key(Return),
Key(Delete),
],
@ -53,7 +53,7 @@
None,
// Thumbpad
PrevLayer,
HoldLayer(1),
Key(Return),
Key(Delete),
],
@ -82,7 +82,7 @@
None,
// Thumbpad
PrevLayer,
HoldLayer(1),
Key(Return),
Key(Delete),
],

BIN
right/src/layers.pc Normal file

Binary file not shown.

View File

@ -12,12 +12,16 @@ use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output, Pin};
use embassy_time::{Duration, Timer};
use log::error;
use tangentbord1::board::Board;
use tangentbord1::keyboard::{Half, KeyboardConfig};
use tangentbord1::logger::Logger;
use tangentbord1::util::{stall, wheel};
use tangentbord1::ws2812::{Rgb, Ws2812};
use tangentbord1::{allocator, rtt, uart, usb};
use tangentbord1::{
board::Board,
event::Half,
keyboard::KeyboardConfig,
logger::Logger,
rgb::Rgb,
util::{stall, wheel},
ws2812::Ws2812,
{allocator, rtt, uart, usb},
};
use tgnt::layer::Layer;
#[embassy_executor::main]
@ -50,7 +54,7 @@ async fn main(_spawner: Spawner) {
//Timer::after(Duration::from_millis(3000)).await;
let layers = include_bytes!("layers-right.pc");
let layers = include_bytes!("layers.pc");
let Ok(layers): Result<Vec<Layer>, _> = postcard::from_bytes(layers) else {
log::error!("Failed to deserialize layer config");
stall().await

1
src/bin/.gitignore vendored
View File

@ -1 +0,0 @@
*.pc

View File

@ -1,30 +0,0 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![feature(split_array)]
use embassy_rp::{
bind_interrupts,
peripherals::{UART0, USB},
};
extern crate alloc;
pub mod allocator;
pub mod board;
pub mod keyboard;
pub mod lights;
pub mod logger;
pub mod neopixel;
pub mod panic_handler;
pub mod rtt;
pub mod uart;
pub mod usb;
pub mod util;
pub mod ws2812;
bind_interrupts! {
pub struct Irqs {
UART0_IRQ => embassy_rp::uart::BufferedInterruptHandler<UART0>;
USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<USB>;
}
}