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() |
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); |
#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, |
/* 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; | |||||
} |
#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 |
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. |
#include <RowPort.h> | #include <RowPort.h> | ||||
#include <ColPort.h> | #include <ColPort.h> | ||||
/* | |||||
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 | |||||
/* RowBase is an abstract base class. | |||||
*/ | */ | ||||
class RowBase | 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: |
Wire.write(outputByteCommand); | Wire.write(outputByteCommand); | ||||
Wire.write(port.outputVal |= activePin); | Wire.write(port.outputVal |= activePin); | ||||
Wire.endTransmission(); | Wire.endTransmission(); | ||||
//todo delayMicroseconds(1500); | |||||
} | } |