Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Optionally decouple layout matrix and pin matrix via transform function #121

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "keyberon"
version = "0.2.0"
version = "0.3.0"
authors = ["Guillaume Pinot <[email protected]>", "Robin Krahl <[email protected]>"]
edition = "2018"
description = "Pure Rust keyboard firmware."
Expand Down
4 changes: 2 additions & 2 deletions src/chording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@
/// // The PressedKeys are normally determined by calling the matrix
/// // and the for loop is just to get past the debouncer
/// for _ in 0..DEBOUNCE_COUNT {
/// assert_eq!(0, debouncer.events([[true, true, false]]).count());
/// assert_eq!(0, debouncer.events([[true, true, false]], None).count());
/// }
/// let mut events = chording
/// .tick(debouncer.events([[true, true, false]]).collect())
/// .tick(debouncer.events([[true, true, false]], None).collect())
/// .into_iter();
/// let event = events.next();
/// assert_eq!(Some(Event::Press(0, 2)), event);
Expand Down
54 changes: 45 additions & 9 deletions src/debounce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl<T: PartialEq> Debouncer<T> {
///
/// ```
/// use keyberon::debounce::Debouncer;
/// use keyberon::debounce::transpose;
/// use keyberon::layout::Event;
/// let mut debouncer = Debouncer::new(
/// [[false, false], [false, false]],
Expand All @@ -83,24 +84,46 @@ impl<T: PartialEq> Debouncer<T> {
/// );
///
/// // no changes
/// assert_eq!(0, debouncer.events([[false, false], [false, false]]).count());
/// assert_eq!(0, debouncer.events([[false, false], [false, false]], None).count());
///
/// // `(0, 1)` pressed, but debouncer is filtering
/// assert_eq!(0, debouncer.events([[false, true], [false, false]]).count());
/// assert_eq!(0, debouncer.events([[false, true], [false, false]]).count());
/// assert_eq!(0, debouncer.events([[false, true], [false, false]], None).count());
/// assert_eq!(0, debouncer.events([[false, true], [false, false]], None).count());
///
/// // `(0, 1)` stable enough, event appear.
/// assert_eq!(
/// vec![Event::Press(0, 1)],
/// debouncer.events([[false, true], [false, false]]).collect::<Vec<_>>(),
/// debouncer.events([[false, true], [false, false]], None).collect::<Vec<_>>(),
/// );
/// /// Again, but with transposed event output
/// let mut debouncer = Debouncer::new(
/// [[false, false], [false, false]],
/// [[false, false], [false, false]],
/// 2,
/// );
/// // `(0, 1)` pressed, but debouncer is filtering
/// assert_eq!(0, debouncer.events([[false, true], [false, false]], None).count());
/// assert_eq!(0, debouncer.events([[false, true], [false, false]], None).count());
///
/// // `(0, 1)` stable enough, event appear.
/// assert_eq!(
/// vec![Event::Press(1, 0)],
/// debouncer.events([[false, true], [false, false]], Some(transpose)).collect::<Vec<_>>(),
/// );
/// ```
pub fn events<'a, U>(&'a mut self, new: T) -> impl Iterator<Item = Event> + 'a
pub fn events<'a, U>(
&'a mut self,
new: T,
transform: Option<fn(u8, u8) -> (u8, u8)>,
) -> impl Iterator<Item = Event> + 'a
where
&'a T: IntoIterator<Item = U>,
U: IntoIterator<Item = &'a bool>,
U::IntoIter: 'a,
{
let noop_xform: fn(u8, u8) -> (u8, u8) = |x, y| (x, y);
let transform = transform.unwrap_or(noop_xform);

if self.update(new) {
Left(
self.new
Expand All @@ -109,10 +132,14 @@ impl<T: PartialEq> Debouncer<T> {
.enumerate()
.flat_map(move |(i, (o, n))| {
o.into_iter().zip(n.into_iter()).enumerate().filter_map(
move |(j, bools)| match bools {
(false, true) => Some(Event::Press(i as u8, j as u8)),
(true, false) => Some(Event::Release(i as u8, j as u8)),
_ => None,
move |(j, bools)| {
let (row, col) = transform(i as u8, j as u8);

match bools {
(false, true) => Some(Event::Press(row, col)),
(true, false) => Some(Event::Release(row, col)),
_ => None,
}
},
)
}),
Expand All @@ -122,3 +149,12 @@ impl<T: PartialEq> Debouncer<T> {
}
}
}

/// If your inputs are as rows, and outputs are the columns, use this in `Debouncer::events`
pub fn standard(row: u8, col: u8) -> (u8, u8) {
(row, col)
}
/// If your inputs are as columns, and outputs are the rows, use this in `Debouncer::events`
pub fn transpose(row: u8, col: u8) -> (u8, u8) {
(col, row)
}
85 changes: 37 additions & 48 deletions src/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,71 @@ use embedded_hal::digital::v2::{InputPin, OutputPin};
/// numbers etc.). Most HAL-s have a method of downgrading pins
/// to a common (erased) struct. (for example see
/// [stm32f0xx_hal::gpio::PA0::downgrade](https://docs.rs/stm32f0xx-hal/0.17.1/stm32f0xx_hal/gpio/gpioa/struct.PA0.html#method.downgrade))
pub struct Matrix<C, R, const CS: usize, const RS: usize>
#[allow(non_upper_case_globals)]
pub struct Matrix<I, O, const InN: usize, const OutN: usize>
where
C: InputPin,
R: OutputPin,
I: InputPin,
O: OutputPin,
{
cols: [C; CS],
rows: [R; RS],
ins: [I; InN],
outs: [O; OutN],
}

impl<C, R, const CS: usize, const RS: usize> Matrix<C, R, CS, RS>
#[allow(non_upper_case_globals)]
impl<I, O, const InN: usize, const OutN: usize, E> Matrix<I, O, InN, OutN>
where
C: InputPin,
R: OutputPin,
I: InputPin<Error = E>,
O: OutputPin<Error = E>,
{
/// Creates a new Matrix.
///
/// Assumes columns are pull-up inputs,
/// and rows are output pins which are set high when not being scanned.
pub fn new<E>(cols: [C; CS], rows: [R; RS]) -> Result<Self, E>
pub fn new(ins: [I; InN], outs: [O; OutN]) -> Result<Self, E>
where
C: InputPin<Error = E>,
R: OutputPin<Error = E>,
I: InputPin<Error = E>,
O: OutputPin<Error = E>,
{
let mut res = Self { cols, rows };
let mut res = Self { ins, outs };
res.clear()?;
Ok(res)
}
fn clear<E>(&mut self) -> Result<(), E>
fn clear(&mut self) -> Result<(), E>
where
C: InputPin<Error = E>,
R: OutputPin<Error = E>,
I: InputPin<Error = E>,
O: OutputPin<Error = E>,
{
for r in self.rows.iter_mut() {
for r in self.outs.iter_mut() {
r.set_high()?;
}
Ok(())
}
/// Scans the matrix and checks which keys are pressed.
///
/// Every row pin in order is pulled low, and then each column
/// pin is tested; if it's low, the key is marked as pressed.
/// Scans the pins and checks which keys are pressed (state is "low").
///
/// Delay function allows pause to let input pins settle
pub fn get_with_delay<F: FnMut(), E>(&mut self, mut delay: F) -> Result<[[bool; CS]; RS], E>
where
C: InputPin<Error = E>,
R: OutputPin<Error = E>,
{
let mut keys = [[false; CS]; RS];

for (ri, row) in self.rows.iter_mut().enumerate() {
row.set_low()?;
/// For each out-pin, sets it to lo-then-high. If an input follows this cycle, then
/// we can deduce that the key connecting these two pins is pressed
pub fn down_keys(&mut self) -> Result<[[bool; InN]; OutN], E> {
self.down_keys_with_delay(|| ())
}

/// Same as `down_keys`, with a delay following the set_low() to allow the switch to settle
pub fn down_keys_with_delay<F: FnMut()>(
&mut self,
mut delay: F,
) -> Result<[[bool; InN]; OutN], E> {
let mut keys = [[false; InN]; OutN];

for (out_idx, out_pin) in self.outs.iter_mut().enumerate() {
out_pin.set_low()?;
delay();
for (ci, col) in self.cols.iter().enumerate() {
if col.is_low()? {
keys[ri][ci] = true;
for (in_idx, in_pin) in self.ins.iter().enumerate() {
if in_pin.is_low()? {
keys[out_idx][in_idx] = true;
}
}
row.set_high()?;
out_pin.set_high()?;
}
Ok(keys)
}

/// Scans the matrix and checks which keys are pressed.
///
/// Every row pin in order is pulled low, and then each column
/// pin is tested; if it's low, the key is marked as pressed.
/// Scans the pins and checks which keys are pressed (state is "low").
pub fn get<E>(&mut self) -> Result<[[bool; CS]; RS], E>
where
C: InputPin<Error = E>,
R: OutputPin<Error = E>,
{
self.get_with_delay(|| ())
}
}

/// Matrix-representation of switches directly attached to the pins ("diodeless").
Expand Down