add RowBase class, move debounce() to Row, add simple Row_DH::debounce()
This commit is contained in:
parent
90c420491e
commit
b6b6e9fee9
@ -177,7 +177,6 @@ Refer to it like a table of contents while reading the keybrd library.
|
|||||||
if key is pressed
|
if key is pressed
|
||||||
set rowState bit
|
set rowState bit
|
||||||
Row::debounce() debounce
|
Row::debounce() debounce
|
||||||
Row::detectEdge() detect edge
|
|
||||||
Row::pressRelease() for each key in row
|
Row::pressRelease() for each key in row
|
||||||
if rising edge
|
if rising edge
|
||||||
Key_*::press() scanCode->press()
|
Key_*::press() scanCode->press()
|
||||||
|
@ -92,7 +92,7 @@ Key* const ptrsKeys_L1[] = { ptrsLayout[1][0], ptrsLayout[1][1] };
|
|||||||
Row row_L1(rowPortF_L, 1<<1, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L1);
|
Row row_L1(rowPortF_L, 1<<1, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L1);
|
||||||
|
|
||||||
// -------------- LEFT MATRIX ------------------
|
// -------------- LEFT MATRIX ------------------
|
||||||
Row* const ptrsRows_L[] = { &row_L0, &row_L1 };
|
RowBase* const ptrsRows_L[] = { &row_L0, &row_L1 };
|
||||||
const uint8_t ROW_L_COUNT = sizeof(ptrsRows_L)/sizeof(*ptrsRows_L);
|
const uint8_t ROW_L_COUNT = sizeof(ptrsRows_L)/sizeof(*ptrsRows_L);
|
||||||
|
|
||||||
Matrix matrix_L(ptrsRows_L, ROW_L_COUNT, 1);
|
Matrix matrix_L(ptrsRows_L, ROW_L_COUNT, 1);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "Code.h"
|
#include "Code.h"
|
||||||
|
|
||||||
/* Class Code_LayeredCodeScBase is a 2-layer code, one object for each layer e.g.
|
/* Class Code_LayeredCodeScBase is a 2-layer code, with one object for each layer e.g.
|
||||||
layer0: ms_up //mouse up
|
layer0: ms_up //mouse up
|
||||||
layer1: KEY_UP //up arrow
|
layer1: KEY_UP //up arrow
|
||||||
When the key is pressed, the active layer is retrieved from refLayerState,
|
When the key is pressed, the active layer is retrieved from refLayerState,
|
||||||
|
94
src/Row.cpp
Normal file
94
src/Row.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* debounce() function
|
||||||
|
Debounce uses multiple samples to debounces switch states,
|
||||||
|
where each sample contains the switch states for a row of switches, one bit per switch.
|
||||||
|
|
||||||
|
Debounce uses Dr. Marty's debounce algorithm from
|
||||||
|
http://drmarty.blogspot.com.br/2009/05/best-switch-debounce-routine-ever.html
|
||||||
|
I2C and TWI protocols do not include any Packet Error Checking (PEC).
|
||||||
|
The goal of Marty's debounce routine is to reject spurious signals,
|
||||||
|
which is useful when connecting split keyboards with a cable using I2C or TWI.
|
||||||
|
Was tested on split keyboard with 3-meter long telephone wire to I/O expander
|
||||||
|
|
||||||
|
Dr. Marty's debounce algorithm:
|
||||||
|
Periodically read samples and update the state when a number consecutive sample bits are equal.
|
||||||
|
|
||||||
|
Output from keybrd/examples/debounce_unit_test.cpp with SAMPLE_COUNT 4:
|
||||||
|
button pressed: 100000001111111110000
|
||||||
|
bouncy signal: 100001001111011110000
|
||||||
|
debounced signal: 000000000001111111110
|
||||||
|
isFallingEdge: 000000000000000000001
|
||||||
|
isRisingEdge: 000000000001000000000
|
||||||
|
There is a latency equal to SAMPLE_COUNT, between button press and debounced signal.
|
||||||
|
|
||||||
|
samples[SAMPLE_COUNT] is a ring buffer. samplesIndex is it's current write index.
|
||||||
|
SAMPLE_COUNT is the number of consecutive equal samples needed to debounce.
|
||||||
|
SAMPLE_COUNT is a macro because it defines samples[SAMPLE_COUNT] array size at compile time.
|
||||||
|
SAMPLE_COUNT should be at lease 1.
|
||||||
|
|
||||||
|
Keyboards with a long I2C wire or in environment with strong electromagnetic interference (EMI)
|
||||||
|
need a larger SAMPLE_COUNT for reliability.
|
||||||
|
Larger SAMPLE_COUNTs are more reliable but consume more memory, where
|
||||||
|
SAMPLE_COUNT*ROW_COUNT = bytes of memory consumed by keyboard
|
||||||
|
SAMPLE_COUNT = 4 is very reliable for a keyboard.
|
||||||
|
|
||||||
|
Avoid sampling the switch input at a rate synchronous to events in the environment
|
||||||
|
that might create periodic EMI. For instance, 50 and 60 Hz.
|
||||||
|
|
||||||
|
A keyboard with a faster scan rate responds faster.
|
||||||
|
Follow these step to tune DELAY_MICROSECONDS for maximum scan rate for a given SAMPLE_COUNT:
|
||||||
|
Initialize DELAY_MICROSECONDS in your sketch:
|
||||||
|
const unsigned int Row::DELAY_MICROSECONDS = 1000;
|
||||||
|
Add this to the sketch's loop() function:
|
||||||
|
debug.print_microseconds_per_scan();
|
||||||
|
Compile and load the sketch into the microcontroller; microseconds_per_scan is printed every second.
|
||||||
|
Adjust the value of DELAY_MICROSECONDS and repeat until:
|
||||||
|
debug.print_microseconds_per_scan() <= DEBOUNCE_TIME / SAMPLE_COUNT
|
||||||
|
|
||||||
|
DEBOUNCE_TIME can be obtained from the switch's datasheet. Some switch bounce times are:
|
||||||
|
Cherry MX specifies 5msec bounce time http://www.cherrycorp.com/english/switches/key/mx.htm
|
||||||
|
hasu measured Cherry MX bounce times .3ms to 1.4ms http://geekhack.org/index.php?topic=42385.0
|
||||||
|
Tactile switch MJTP series bounce 10 ms http://www.apem.com/files/apem/brochures/MJTP_6MM.pdf
|
||||||
|
|
||||||
|
Polling I2C may slow the scan rate enough so that no additional delay is needed:
|
||||||
|
const unsigned int Row::DELAY_MICROSECONDS = 0;
|
||||||
|
|
||||||
|
Slow-scan trick for debug messages that print too fast:
|
||||||
|
change DELAY_MICROSECONDS to a large number like 10000
|
||||||
|
That way debug messages are printed at a managable rate.
|
||||||
|
*/
|
||||||
|
/* debounce() function
|
||||||
|
Parameter rowState is bitwise, 1 means pressed, 0 means released.
|
||||||
|
Returns bitwise debouncedChanged.
|
||||||
|
*/
|
||||||
|
#include "Row.h"
|
||||||
|
|
||||||
|
uint8_t Row::debounce(const uint8_t rowState)
|
||||||
|
{
|
||||||
|
uint8_t debounced; //bitwise, 1 means pressed, 0 means released
|
||||||
|
uint8_t debouncedChanged; //bitwise, 1 means debounced changed
|
||||||
|
uint8_t all_1 = ~0; //bitwise
|
||||||
|
uint8_t all_0 = 0; //bitwise
|
||||||
|
|
||||||
|
samples[samplesIndex] = rowState; //insert rowState into samples[] ring buffer
|
||||||
|
|
||||||
|
if (++samplesIndex >= SAMPLE_COUNT) //if end of ring buffer
|
||||||
|
{
|
||||||
|
samplesIndex = 0; //wrap samplesIndex to beginning of ring buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t j = 0; j < SAMPLE_COUNT; j++) //traverse the sample[] ring buffer
|
||||||
|
{
|
||||||
|
all_1 &= samples[j]; //1 if all samples are 1
|
||||||
|
all_0 |= samples[j]; //0 if all samples are 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// update newDebounce if all the samples agree with one another
|
||||||
|
// if all samples=1 then debounced=1
|
||||||
|
// elseif all samples=0 then debounced=0
|
||||||
|
// else debounced=previousDebounced i.e. no change
|
||||||
|
debounced = all_1 | (all_0 & previousDebounced);
|
||||||
|
|
||||||
|
debouncedChanged = debounced xor previousDebounced;
|
||||||
|
previousDebounced = debounced;
|
||||||
|
return debouncedChanged;
|
||||||
|
}
|
45
src/Row.h
Normal file
45
src/Row.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef ROW_H
|
||||||
|
#define ROW_H
|
||||||
|
|
||||||
|
#include <RowBase.h>
|
||||||
|
|
||||||
|
#define SAMPLE_COUNT 4 //number of consecutive equal bits needed to change a debounced bit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
#define SAMPLE_COUNT in this header file.
|
||||||
|
define and initilize DELAY_MICROSECONDS in sketch.
|
||||||
|
const unsigned int Row::DELAY_MICROSECONDS = 0;
|
||||||
|
|
||||||
|
Instantiation
|
||||||
|
------------
|
||||||
|
Example instantiation of a row:
|
||||||
|
RowPort_AVR rowPortF(DDRF, PORTF);
|
||||||
|
|
||||||
|
ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 | 1<<2 | 1<<3 );
|
||||||
|
ColPort_AVR colPortD(DDRD, PORTD, PIND, 1<<2 | 1<<3 );
|
||||||
|
|
||||||
|
ColPort* const ptrsColPorts[] = { &colPortB, &colPortD };
|
||||||
|
const uint8_t COL_PORTS_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts);
|
||||||
|
|
||||||
|
const PROGMEM Key* const ptrsKeys_0[] = { &k_00, &k_01, &k_02, &k_03, &k_04, &k_05 };
|
||||||
|
Row row_0(ptrsKeys_0, &rowPortF, 1<<0, ptrsColPorts, COL_PORTS_COUNT);
|
||||||
|
|
||||||
|
Number of ColPort::colPins should equal number of keys in Row::ptrsKeys array
|
||||||
|
if a pin is missing, a key will be unresposive
|
||||||
|
if a Key pointer is missing, the keyboard will fail in an unprdictable way
|
||||||
|
*/
|
||||||
|
class Row : public RowBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const unsigned int DELAY_MICROSECONDS; //delay between each Row scan for debouncing
|
||||||
|
uint8_t samples[SAMPLE_COUNT]; //bitwise, one bit per key, most recent readings
|
||||||
|
uint8_t samplesIndex; //samples[] current write index
|
||||||
|
virtual uint8_t debounce(const uint8_t rowState);
|
||||||
|
public:
|
||||||
|
Row( RowPort &refRowPort, const uint8_t rowPin,
|
||||||
|
ColPort *const ptrsColPorts[], const uint8_t colPortCount, Key *const ptrsKeys[])
|
||||||
|
: RowBase(refRowPort, rowPin, ptrsColPorts, colPortCount, ptrsKeys), samplesIndex(0) { }
|
||||||
|
};
|
||||||
|
#endif
|
@ -15,6 +15,11 @@ void RowBase::process(const bool activeHigh)
|
|||||||
pressRelease(rowEnd, debouncedChanged);
|
pressRelease(rowEnd, debouncedChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RowBase::wait()
|
||||||
|
{
|
||||||
|
delayMicroseconds(DELAY_MICROSECONDS); //delay between Row scans to debounce switches
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Strobes the row and reads the columns.
|
Strobes the row and reads the columns.
|
||||||
Strobe is on for shortest possible time to preserve IR LED on DodoHand's optic switch.
|
Strobe is on for shortest possible time to preserve IR LED on DodoHand's optic switch.
|
||||||
|
@ -6,43 +6,7 @@
|
|||||||
#include <RowPort.h>
|
#include <RowPort.h>
|
||||||
#include <ColPort.h>
|
#include <ColPort.h>
|
||||||
|
|
||||||
/*
|
/* RowBase is an abstract base class.
|
||||||
Instantiation
|
|
||||||
------------
|
|
||||||
Example instantiation of a row:
|
|
||||||
RowPort_AVR rowPortF(DDRF, PORTF);
|
|
||||||
|
|
||||||
ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 | 1<<2 | 1<<3 );
|
|
||||||
ColPort_AVR colPortD(DDRD, PORTD, PIND, 1<<2 | 1<<3 );
|
|
||||||
|
|
||||||
ColPort* const ptrsColPorts[] = { &colPortB, &colPortD };
|
|
||||||
const uint8_t COL_PORTS_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts);
|
|
||||||
|
|
||||||
const PROGMEM Key* const ptrsKeys_0[] = { &k_00, &k_01, &k_02, &k_03, &k_04, &k_05 };
|
|
||||||
Row row_0(ptrsKeys_0, &rowPortF, 1<<0, ptrsColPorts, COL_PORTS_COUNT);
|
|
||||||
|
|
||||||
Number of ColPort::colPins should equal number of keys in Row::ptrsKeys array
|
|
||||||
if a pin is missing, a key will be unresposive
|
|
||||||
if a Key pointer is missing, the keyboard will fail in an unprdictable way
|
|
||||||
|
|
||||||
A keyboard with a faster scan rate is more resposive.
|
|
||||||
Follow these step to tune DELAY_MICROSECONDS for maximum scan rate within debounce times:
|
|
||||||
Initialize DELAY_MICROSECONDS in your sketch:
|
|
||||||
const unsigned int Row::DELAY_MICROSECONDS = 1000;
|
|
||||||
Add this to the sketche's loop() function:
|
|
||||||
keybrd.print_microseconds_per_scan();
|
|
||||||
Compile and load the sketch into the microcontroller, which will print the actual microseconds_per_scan
|
|
||||||
Incrementaly adjust the DELAY_MICROSECONDS untill the printed microseconds_per_scan is near the switches bounce time
|
|
||||||
A switche's debounce time can be obtained from the switche's datasheet
|
|
||||||
Cherry MX has 5ms bounce time http://www.cherrycorp.com/english/switches/key/mx.htm
|
|
||||||
hasu measured Cherry MX bounce times .3ms to 1.4ms http://geekhack.org/index.php?topic=42385.0
|
|
||||||
Tactile switch MJTP series bounce 10 ms http://www.apem.com/files/apem/brochures/MJTP_6MM.pdf
|
|
||||||
Optic switches 0 bounce time because optic doesn't bounce
|
|
||||||
|
|
||||||
Slow-scan trick for debug message that print too fast
|
|
||||||
Keyboard.print(F("debug message"));
|
|
||||||
Change DELAY_MICROSECONDS to a large number like 10000
|
|
||||||
That way printing debug messages is slowed to a managable rate
|
|
||||||
*/
|
*/
|
||||||
class RowBase
|
class RowBase
|
||||||
{
|
{
|
||||||
@ -58,7 +22,6 @@ class RowBase
|
|||||||
void scan(const bool activeHigh);
|
void scan(const bool activeHigh);
|
||||||
uint8_t getRowState(uint16_t& rowEnd, const bool activeHigh);
|
uint8_t getRowState(uint16_t& rowEnd, const bool activeHigh);
|
||||||
virtual uint8_t debounce(const uint8_t rowState)=0;
|
virtual uint8_t debounce(const uint8_t rowState)=0;
|
||||||
//void detectEdge(uint8_t debounced, uint8_t& isFallingEdge, uint8_t& isRisingEdge);
|
|
||||||
void pressRelease(const uint16_t rowEnd, const uint8_t debouncedChanged);
|
void pressRelease(const uint16_t rowEnd, const uint8_t debouncedChanged);
|
||||||
virtual void keyWasPressed();
|
virtual void keyWasPressed();
|
||||||
protected:
|
protected:
|
||||||
|
@ -43,4 +43,5 @@ void RowPort_PCA9655E::setActivePinHigh(const uint8_t activePin)
|
|||||||
Wire.write(outputByteCommand);
|
Wire.write(outputByteCommand);
|
||||||
Wire.write(port.outputVal |= activePin);
|
Wire.write(port.outputVal |= activePin);
|
||||||
Wire.endTransmission();
|
Wire.endTransmission();
|
||||||
|
//todo delayMicroseconds(1500);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user