diff --git a/doc/keybrd_library_developer_guide.md b/doc/keybrd_library_developer_guide.md index 1706c05..15b527a 100644 --- a/doc/keybrd_library_developer_guide.md +++ b/doc/keybrd_library_developer_guide.md @@ -177,7 +177,6 @@ Refer to it like a table of contents while reading the keybrd library. if key is pressed set rowState bit Row::debounce() debounce - Row::detectEdge() detect edge Row::pressRelease() for each key in row if rising edge Key_*::press() scanCode->press() diff --git a/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino b/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino index a119139..127a887 100644 --- a/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino +++ b/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino @@ -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); // -------------- 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); Matrix matrix_L(ptrsRows_L, ROW_L_COUNT, 1); diff --git a/src/Code_LayeredCodeScBase.h b/src/Code_LayeredCodeScBase.h index c2eb62b..9d0171e 100644 --- a/src/Code_LayeredCodeScBase.h +++ b/src/Code_LayeredCodeScBase.h @@ -4,7 +4,7 @@ #include #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 layer1: KEY_UP //up arrow When the key is pressed, the active layer is retrieved from refLayerState, diff --git a/src/Row.cpp b/src/Row.cpp new file mode 100644 index 0000000..e677111 --- /dev/null +++ b/src/Row.cpp @@ -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; +} diff --git a/src/Row.h b/src/Row.h new file mode 100644 index 0000000..5c6172c --- /dev/null +++ b/src/Row.h @@ -0,0 +1,45 @@ +#ifndef ROW_H +#define ROW_H + +#include + +#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 diff --git a/src/RowBase.cpp b/src/RowBase.cpp index 15fe44e..ccdeab0 100644 --- a/src/RowBase.cpp +++ b/src/RowBase.cpp @@ -15,6 +15,11 @@ void RowBase::process(const bool activeHigh) pressRelease(rowEnd, debouncedChanged); } +void RowBase::wait() +{ + delayMicroseconds(DELAY_MICROSECONDS); //delay between Row scans to debounce switches +} + /* Strobes the row and reads the columns. Strobe is on for shortest possible time to preserve IR LED on DodoHand's optic switch. diff --git a/src/RowBase.h b/src/RowBase.h index 219e830..50bbcbe 100644 --- a/src/RowBase.h +++ b/src/RowBase.h @@ -6,43 +6,7 @@ #include #include -/* -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 { @@ -58,7 +22,6 @@ class RowBase void scan(const bool activeHigh); uint8_t getRowState(uint16_t& rowEnd, const bool activeHigh); 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); virtual void keyWasPressed(); protected: diff --git a/src/RowPort_PCA9655E.cpp b/src/RowPort_PCA9655E.cpp index 6f22e7a..f5c3d7c 100644 --- a/src/RowPort_PCA9655E.cpp +++ b/src/RowPort_PCA9655E.cpp @@ -43,4 +43,5 @@ void RowPort_PCA9655E::setActivePinHigh(const uint8_t activePin) Wire.write(outputByteCommand); Wire.write(port.outputVal |= activePin); Wire.endTransmission(); + //todo delayMicroseconds(1500); }