Code:
// debouncing a button matrix
//
// based on code:
// https://forum.pjrc.com/threads/46188-Help-neede-Debouncing-a-button-matrix?&p=154816&viewfull=1#post154816
//
// NOTE: we are pulling up columns and read the rows as this matches the present hardware. In the
// mentioned post it is vice versa: there, the rows are pulled up and the columns are read)
// define serial speed for serial debugging
const uint32_t serialSpeed = 115200;
// teensy pin connected to ST_CP of 74HC595
const uint8_t slaveSelectPin = 10;
// teensy pin connected to DS of 74HC595
const uint8_t dataPin = 11;
// teensy pin connected to SH_CP of 74HC595
const uint8_t clockPin = 13;
// bit mask for shift register ("activates" columns as the bit masks are cycled and shifted out to the shift register)
uint8_t columnBitMask[] = {
B00000001,
B00000010,
B00000100,
B00001000,
B00010000,
B00100000,
B01000000,
B10000000
};
const uint8_t pinTable[] = { 6, 7, 8, 20, 21, 22, 23, 24 }; // choosing arbitrary pins here
const uint8_t numCols = sizeof(columnBitMask);
const uint8_t numRows = sizeof(pinTable);
// debounce stuff
const uint8_t keyPressScans = 8;
const uint8_t keyReleaseScans = 8;
// holds the current reading of whole row (for each column)
uint8_t keyRowState[numCols];
// one counter per key
uint8_t keyCounter[numCols][numRows];
// set up timer for checking button states
elapsedMicros buttonStateTimer = 0;
void setup()
{
// wait for serial and begin serial communication
while (!Serial && (millis() < 1000));
Serial.begin(serialSpeed);
Serial.println("## Counter Debounce Test ##");
// set input mode for the row pins
for (int i = 0; i < numRows; i++) {
pinMode(pinTable[i], INPUT);
}
//set shift register slave select pin as output in order to control the columns
pinMode(slaveSelectPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
uint8_t colCounter = 0;
uint8_t rowCounter = 0;
// set the row state to 0 (B00000000) for each column
for (colCounter = 0; colCounter < numCols; colCounter++) {
keyRowState[colCounter] = 0;
}
// set the counter to 0 for all keys
for (colCounter = 0; colCounter < numCols; colCounter++) {
for (rowCounter = 0; rowCounter < numRows; rowCounter++) {
keyCounter[colCounter][rowCounter] = 0;
}
}
}
void loop()
{
uint8_t colCounter = 0;
uint8_t rowCounter = 0;
uint8_t state = 0;
// query the port every 250 microseconds i.e. scan rate = 4kHz
if (buttonStateTimer > 250) {
// reset the polling timer
buttonStateTimer = 0;
// iterate columns
for (colCounter = 0; colCounter < numCols; colCounter++) {
state = 0;
// shift out the bit mask for the current columns
//
// if we shift out the a pattern like "B00000001" to the serial input of the shift register, the parallel ouputs will be represent the serial input
// serial in LSB is mapped to ouptut Q(A)
// serial in MSB is mapped to ouptut Q(H)
//
// Parallel Outs Q(n)
// Q(A) Q(B) Q(C) Q(D) Q(E) Q(F) Q(G) Q(H):
// Serial In
// B00000001 1 0 0 0 0 0 0 0
// B00000010 0 1 0 0 0 0 0 0
// B00000100 0 0 1 0 0 0 0 0
// B00001000 0 0 0 1 0 0 0 0
// B00010000 0 0 0 0 1 0 0 0
// B00100000 0 0 0 0 0 1 0 0
// B01000000 0 0 0 0 0 0 1 0
// B10000000 0 0 0 0 0 0 0 1
//
// -> with each column counter loop we pull up one column
//
shiftOutBitMask(columnBitMask[colCounter]);
// after pulling up one column in the current counter loop, we will read all row inputs to detect if there was a key press
// example: the first and third row pin is HIGH, the rest is LOW
// -> state is 00000101
//
state = readInputPins();
// loop through the rows to generate events for the keys on this column
for (rowCounter = 0; rowCounter < numRows; rowCounter++) {
// create a mask for the current row
// the mask looks like this:
// loop 0 (rowCounter = 0): 00000001
// loop 1 (rowCounter = 1): 00000010
// loop 2 (rowCounter = 2): 00000100
// ...
const uint8_t mask = 1u << rowCounter;
// key state "ignore":
// keyCounter is set -> so we are ignoring the current reading and just decrease the keyCounter so that we know, when we have to check the kex again
if (keyCounter[colCounter][rowCounter] > 0) {
// recent key event -> ignore state changes
keyCounter[colCounter][rowCounter]--;
printKeyEventIgnored(colCounter, rowCounter, keyCounter[colCounter][rowCounter]);
}
// key state: "press":
// loop 0 (rowCounter = 0):
// state: 00000101
// mask: 00000001
// state & mask: 00000001
// (state & mask): 00000001 -> true
//
// loop 0 (colCounter = 0):
// keyRowState[colCounter]: 00000000
// mask: 00000001
// (keyRowState[colCounter] & mask): 00000000 -> false
// !(keyRowState[colCounter] & mask): 00000000 -> true
//
// both expression are true -> jump into block
else if ((state & mask) && !(keyRowState[colCounter] & mask)) {
// we have a key press event ->
// set the keyCounter in order to ignore the next "keyPressScans" values
keyCounter[colCounter][rowCounter] = keyPressScans;
// loop 0 (colCounter = 0):
// keyRowState[colCounter]: 00000000
// mask: 00000001
// ORed with mask: 00000001
//
// -> keyRowState[colCounter] represents the actual key presses / releases of the row keys of the current column
keyRowState[colCounter] |= mask;
printKeyEvent(colCounter, rowCounter, true);
}
// key state: "release":
// loop 0 (rowCounter = 0):
// state: 00000101
// mask: 00000001
// state & mask: 00000001
// !(state & mask): 00000000 -> false
//
// loop 0 (colCounter = 0):
// keyRowState[colCounter]: 00000000
// mask: 00000001
// (keyRowState[colCounter] & mask): 00000000 -> false
//
// both expression are false -> do not jump into block, we do not have a key release yet
//
//
// Another example (example 2) (a little later):
// assumptions:
// state = 00000100 (the third row pin is HIGH, the rest is LOW; so, in comparison to the example above, the first row pin was released)
// keyRowState[0] = 00000001 (keyRowState[0] expresses the state in the loop before)
//
// loop 0 (rowCounter = 0):
// state: 00000100
// mask: 00000001
// state & mask: 00000000
// !(state & mask): 00000001 -> true
//
// loop 0 (colCounter = 0):
// keyRowState[colCounter]: 00000001
// mask: 00000001
// (keyRowState[colCounter] & mask): 00000001 -> true
//
// both expression are true -> do jump into block, we have a key release now
else if (!(state & mask) && (keyRowState[colCounter] & mask)) {
// we have a key release event ->
// set the keyCounter in order to ignore the next "keyReleaseScans" values
keyCounter[colCounter][rowCounter] = keyReleaseScans;
// example 2:
// loop 0 (colCounter = 0):
// keyRowState[colCounter]: 00000001
// mask: 00000001
// ~mask (NOT mask): 11111110
// ANDed with ~mask: 00000000
//
// -> keyRowState[colCounter] represents the actual key presses / releases of the row keys of the current column
keyRowState[colCounter] &= ~mask;
printKeyEvent(colCounter, rowCounter, false);
}
}
}
// finally, process the events
}
}
void shiftOutBitMask(uint8_t bitMask) {
// pull slaveSelectPin LOW to keep current state
digitalWriteFast(slaveSelectPin, LOW);
// shift out the bits for the current column; replace by SPI transmit
shiftOut(dataPin, clockPin, MSBFIRST, bitMask);
// pull latch pin high so that the bitmask is represented on the 74HC595 output pins
// 74HC595 outputs the reveived update on this rising edge
digitalWriteFast(slaveSelectPin, HIGH);
}
// reads the specified input pins into a single 8 bit variable (single reads, with bit shifting) and returns it
uint8_t readInputPins() {
uint8_t ret = 0;
if (digitalReadFast(pinTable[0])) ret |= (1 << 0);
if (digitalReadFast(pinTable[1])) ret |= (1 << 1);
if (digitalReadFast(pinTable[2])) ret |= (1 << 2);
if (digitalReadFast(pinTable[3])) ret |= (1 << 3);
if (digitalReadFast(pinTable[4])) ret |= (1 << 4);
if (digitalReadFast(pinTable[5])) ret |= (1 << 5);
if (digitalReadFast(pinTable[6])) ret |= (1 << 6);
if (digitalReadFast(pinTable[7])) ret |= (1 << 7);
return ret;
}
void printKeyEvent(uint8_t col, uint8_t row, boolean isKeyPress) {
Serial.print(col);
Serial.print(" ");
Serial.print(row);
Serial.print(" was ");
if (isKeyPress) {
Serial.print("pressed");
}
else {
Serial.print("released");
}
Serial.println("");
}
void printKeyEventIgnored(uint8_t col, uint8_t row, uint8_t counter) {
Serial.print(col);
Serial.print(" ");
Serial.print(row);
Serial.print(" ignored. Will be ignored further ");
Serial.print(counter);
Serial.println(" times.");
}