Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions firmware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ patched version with:
cargo install --git https://github.com/StripedMonkey/elf2uf2-rs.git#c1638b9
```

Note: if you soldered your microcontroller face down, uncomment the
`matrix.upside_down();` line in `main.rs`.

## ZMK

[There is a ZMK Configuration on a dedicated repo][2].
Expand Down
14 changes: 7 additions & 7 deletions firmware/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ const LAYER_COUNT: usize = 6; // adjust to the number of layers defined below
pub const COLS: usize = 12;
pub const ROWS: usize = 4;

use keyberon::layout;
use keyberon::action::{m, Action};
use keyberon::action::{Action, m};
use keyberon::key_code::KeyCode;
use keyberon::layout;

pub type QuackenLayout = layout::Layout<COLS, ROWS, LAYER_COUNT, ()>;

// common shortcuts -- adapt to your OS layout if necessary, e.g. for Ergol:
// const CLOSE: Action<()> = m(&[KeyCode::LCtrl, KeyCode::T].as_slice());
// const COPY: Action<()> = m(&[KeyCode::LCtrl, KeyCode::W].as_slice());
const CMD: KeyCode = KeyCode::LCtrl; // LGui for macOS
const UNDO: Action<()> = m(&[CMD, KeyCode::Z].as_slice());
const CUT: Action<()> = m(&[CMD, KeyCode::X].as_slice());
const COPY: Action<()> = m(&[CMD, KeyCode::C].as_slice());
const UNDO: Action<()> = m(&[CMD, KeyCode::Z].as_slice());
const CUT: Action<()> = m(&[CMD, KeyCode::X].as_slice());
const COPY: Action<()> = m(&[CMD, KeyCode::C].as_slice());
const PASTE: Action<()> = m(&[CMD, KeyCode::V].as_slice());
const ALL: Action<()> = m(&[CMD, KeyCode::A].as_slice());
const SAVE: Action<()> = m(&[CMD, KeyCode::S].as_slice());
const ALL: Action<()> = m(&[CMD, KeyCode::A].as_slice());
const SAVE: Action<()> = m(&[CMD, KeyCode::S].as_slice());
const CLOSE: Action<()> = m(&[CMD, KeyCode::W].as_slice());

// other shortcuts
Expand Down
38 changes: 20 additions & 18 deletions firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![no_main]

mod layout; // 3*6 key layout
mod zero; // QuackenZero-specific matrix scanning
mod zero; // QuackenZero-specific matrix scanning

// set the panic handler
use panic_halt as _;
Expand All @@ -25,29 +25,24 @@ mod app {
use cortex_m::delay::Delay;

use rp2040_hal::{
self,
Clock,
self, Clock,
clocks::init_clocks_and_plls,
fugit::MicrosDurationU32,
pac::CorePeripherals,
gpio::Pins,
pac::CorePeripherals,
sio::Sio,
timer::{ Alarm, Alarm0, Timer },
timer::{Alarm, Alarm0, Timer},
usb::UsbBus,
watchdog::Watchdog,
};

use keyberon::{
debounce::Debouncer,
key_code::KbHidReport,
layout::Layout,
};
use keyberon::{debounce::Debouncer, key_code::KbHidReport, layout::Layout};

use usb_device::{
prelude::UsbDeviceState,
class_prelude::UsbBusAllocator,
// HACK: import the UsbClass trait, but still allow to use its name for a type later
class::UsbClass as _,
class_prelude::UsbBusAllocator,
prelude::UsbDeviceState,
};

type UsbClass = keyberon::Class<'static, UsbBus, ()>;
Expand All @@ -67,8 +62,8 @@ mod app {

// Fun fact: the keyboard is invisible to `lsusb`
// if the scan time is set to 10_000 us or above.
const SCAN_TIME_US: u32 = 1_000;
const WATCHDOG_TIME_US: u32 = 10_000;
const SCAN_TIME_US: u32 = 1_000;
const WATCHDOG_TIME_US: u32 = 10_000;
const EXTERNAL_XTAL_FREQ_HZ: u32 = 12_000_000;

#[shared]
Expand Down Expand Up @@ -141,7 +136,10 @@ mod app {

watchdog.start(MicrosDurationU32::micros(WATCHDOG_TIME_US));

let Ok(matrix) = QuackenZeroMatrix::new_sparkfun_rp2040(pins);
#[allow(unused_mut)]
let Ok(mut matrix) = QuackenZeroMatrix::new_sparkfun_rp2040(pins);
//// Uncomment if you soldered your microcontroller face down:
// matrix.upside_down();

(
Shared {
Expand All @@ -156,7 +154,7 @@ mod app {
debouncer: Debouncer::new(
[[false; kb_layout::COLS]; kb_layout::ROWS],
[[false; kb_layout::COLS]; kb_layout::ROWS],
5
5,
),
delay,
timer,
Expand Down Expand Up @@ -197,8 +195,12 @@ mod app {

c.shared.watchdog.feed();

let delay_1us = || { c.local.delay.delay_us(1) };
for event in c.local.debouncer.events(c.local.matrix.get_with_delay(delay_1us).get()) {
let delay_1us = || c.local.delay.delay_us(1);
for event in c
.local
.debouncer
.events(c.local.matrix.get_with_delay(delay_1us).get())
{
c.local.layout.event(event);
}

Expand Down
31 changes: 20 additions & 11 deletions firmware/src/zero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ Besides, the ProMicro GPIO pinout used by Ergogen and ZMK rely on the ProMicro A
. 8 | | 16 8 | | 23 8 | | 19
. 9 |___________| 10 9 |___________| 21 9 |___________| 10
**/

// The logical layout is a 12×4 matrix: 3×6 + 3 thumb keys for each hand.
// Ergogen has generated an 8*6 matrix rather than a 12×4 one, in order to save two pins:
// the two halves are stacked onto one anather. So here’s q quick helper to work around that.

use crate::layout;
const MATRIX_COLS: usize = 6;
const MATRIX_ROWS: usize = 8;
Expand All @@ -43,9 +41,9 @@ fn matrix_to_layout(row: usize, col: usize) -> (usize, usize) {
// So here's a `Col2RowMatrix` type to implement this.

// rp2040 implementations of the embedded_hal::digital::InputPin,OutputPin} traits
use rp2040_hal::gpio;
use embedded_hal::digital::{InputPin, OutputPin};
type KbInputPin = gpio::Pin<gpio::DynPinId, gpio::FunctionSioInput, gpio::PullDown>;
use rp2040_hal::gpio;
type KbInputPin = gpio::Pin<gpio::DynPinId, gpio::FunctionSioInput, gpio::PullDown>;
type KbOutputPin = gpio::Pin<gpio::DynPinId, gpio::FunctionSioOutput, gpio::PullDown>;

pub type QuackenZeroMatrix = Col2RowMatrix<KbOutputPin, KbInputPin>;
Expand Down Expand Up @@ -92,7 +90,7 @@ where
Ok(())
}

/// Creates a new Generic FroMicro matrix.
/// Creates a new Generic ProMicro matrix.
pub fn new_promicro(pins: gpio::Pins) -> Result<QuackenZeroMatrix, Infallible> {
QuackenZeroMatrix::new(
[
Expand Down Expand Up @@ -132,22 +130,33 @@ where
pins.gpio4.into_pull_down_input().into_dyn_pin(),
pins.gpio5.into_pull_down_input().into_dyn_pin(),
pins.gpio9.into_pull_down_input().into_dyn_pin(),
pins.gpio28.into_pull_down_input().into_dyn_pin(), // promicro 20
pins.gpio27.into_pull_down_input().into_dyn_pin(), // promicro 19
pins.gpio26.into_pull_down_input().into_dyn_pin(), // promicro 18
pins.gpio21.into_pull_down_input().into_dyn_pin(), // promicro 10
pins.gpio28.into_pull_down_input().into_dyn_pin(), // promicro 20
pins.gpio27.into_pull_down_input().into_dyn_pin(), // promicro 19
pins.gpio26.into_pull_down_input().into_dyn_pin(), // promicro 18
pins.gpio21.into_pull_down_input().into_dyn_pin(), // promicro 10
],
)
}

// To use after creating the QuackenZeroMatrix if the microcontroller was soldered face down.
// Cols and rows order is modified like this:
// [c0, c1, c2, c3, c4, c5] --> [c3, c4, c5, c0, c1, c2]
// [r0, r1, r2, r3, r4, r5, r6, r7] --> [r4, r5, r6, r7, r0, r1, r2, r3]
pub fn upside_down(&mut self) {
self.cols.rotate_left(MATRIX_COLS / 2);
self.rows.rotate_left(MATRIX_ROWS / 2);
}

/// Scans the matrix and checks which keys are pressed.
///
/// Every column pin in order is pulled high, and then each row pin is tested:
/// if it's high, the key is marked as pressed.
///
/// Delay function allows pause to let input pins settle.
pub fn get_with_delay<F: FnMut(), E>(&mut self, mut delay: F)
-> Result<[[bool; layout::COLS]; layout::ROWS], E>
pub fn get_with_delay<F: FnMut(), E>(
&mut self,
mut delay: F,
) -> Result<[[bool; layout::COLS]; layout::ROWS], E>
where
C: OutputPin<Error = E>,
R: InputPin<Error = E>,
Expand Down