Skip to content

Commit c6ab48e

Browse files
committed
#154 interrupt support for keyboard manager with improved example.
1 parent eca871f commit c6ab48e

File tree

4 files changed

+177
-55
lines changed

4 files changed

+177
-55
lines changed

examples/matrixKeyboard/matrixKeyboard.ino

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
* any matrix keyboard quite easily.
55
* It just sends the characters that are typed on the keyboard to Serial. The keyboard in This
66
* example is connected directly to Arduino pins, but could just as easily be connected over
7-
* a PCF8574, MCP23017 or other IoAbstraction.
7+
* a PCF8574, MCP23017 or other IoAbstraction. For interrupt mode, you cannot use a PCF8574
8+
* because the interrupt on the device would be triggered by the output changes when scanning.
9+
* Only MCP23017 and device pins can be used in interrupt mode.
810
*/
911

1012
#include <Wire.h>
1113
#include <IoAbstraction.h>
14+
#include <IoAbstractionWire.h>
1215
#include<TaskManagerIO.h>
1316
#include <KeyboardManager.h>
1417

1518
//
1619
// We need to make a keyboard layout that the manager can use. choose one of the below.
1720
// The parameter in brackets is the variable name.
1821
//
19-
MAKE_KEYBOARD_LAYOUT_3X4(keyLayout)
20-
//MAKE_KEYBOARD_LAYOUT_4X4(keyLayout)
22+
//MAKE_KEYBOARD_LAYOUT_3X4(keyLayout)
23+
MAKE_KEYBOARD_LAYOUT_4X4(keyLayout)
2124

2225
//
2326
// We need a keyboard manager class too
@@ -47,10 +50,31 @@ public:
4750
}
4851
} myListener;
4952

50-
void setup() {
51-
while(!Serial);
52-
Serial.begin(115200);
53+
/**
54+
* In this method we initialise the keyboard for I2C operation, we assume a 4x4 keyboard was enabled at the top of
55+
* the sketch, and as we are using an MCP23017, we can run in interrupt mode to avoid polling the I2C bus. Note that
56+
* you cannot enable interrupt mode on PCF8574.
57+
*/
58+
void initialiseKeyboard4X4ForInterrupt23017() {
59+
Wire.begin();
5360

61+
keyLayout.setRowPin(0, 11);
62+
keyLayout.setRowPin(1, 10);
63+
keyLayout.setRowPin(2, 9);
64+
keyLayout.setRowPin(3, 8);
65+
keyLayout.setColPin(0, 15);
66+
keyLayout.setColPin(1, 14);
67+
keyLayout.setColPin(2, 13);
68+
keyLayout.setColPin(3, 12);
69+
70+
keyboard.initialise(ioFrom23017(0x20, ACTIVE_LOW_OPEN, 10), &keyLayout, &myListener, true);
71+
}
72+
73+
/**
74+
* In this method we initialise the keyboard to use the arduino pins directly. We assume a 4x3 keyboard was set at the
75+
* top. We use the keyboard in polling mode in this case. Polling mode can be used on any device.
76+
*/
77+
void initialiseKeyboard3X4ForPollingDevicePins() {
5478
keyLayout.setRowPin(0, 22);
5579
keyLayout.setRowPin(1, 23);
5680
keyLayout.setRowPin(2, 24);
@@ -62,8 +86,22 @@ void setup() {
6286
// create the keyboard mapped to arduino pins and with the layout chosen above.
6387
// it will callback our listener
6488
keyboard.initialise(arduinoIo, &keyLayout, &myListener);
89+
}
90+
91+
void setup() {
92+
while(!Serial);
93+
Serial.begin(115200);
94+
95+
tm_internal::setLoggingDelegate([](tm_internal::TmErrorCode errorCode, int task) {
96+
serdebugF3("TMLog ", errorCode, task);
97+
});
98+
99+
// here you can choose between two stock configurations or you could alter one of the
100+
// methods to meet your hardware requirements.
101+
initialiseKeyboard4X4ForInterrupt23017();
102+
//initialiseKeyboard3X4ForPollingDevicePins();
65103

66-
// start repeating at 850 millis then repeat every 350ms
104+
// now set up the repeat key start and interval
67105
keyboard.setRepeatKeyMillis(850, 350);
68106

69107
Serial.println("Keyboard is initialised!");

src/IoAbstraction.h

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,25 @@ class ShiftRegisterIoAbstraction : public BasicIoAbstraction {
5454
*/
5555
ShiftRegisterIoAbstraction(pinid_t readClockPin, pinid_t readDataPin, pinid_t readLatchPin,
5656
pinid_t writeClockPin, pinid_t writeDataPin, pinid_t writeLatchPin, uint8_t numRead, uint8_t numWrite);
57-
virtual ~ShiftRegisterIoAbstraction() { }
58-
virtual void pinDirection(pinid_t pin, uint8_t mode);
59-
virtual void writeValue(pinid_t pin, uint8_t value);
60-
virtual uint8_t readValue(uint8_t pin);
57+
~ShiftRegisterIoAbstraction() override { }
58+
void pinDirection(pinid_t pin, uint8_t mode) override;
59+
void writeValue(pinid_t pin, uint8_t value) override;
60+
uint8_t readValue(uint8_t pin) override;
6161
/**
6262
* Interrupts are not supported on shift registers
6363
*/
64-
virtual void attachInterrupt(pinid_t, RawIntHandler, uint8_t) {;}
65-
virtual bool runLoop();
64+
void attachInterrupt(pinid_t, RawIntHandler, uint8_t) override {;}
65+
bool runLoop() override;
6666

6767
/**
6868
* writes to the output shift register - currently always port 0
6969
*/
70-
virtual void writePort(pinid_t port, uint8_t portVal);
70+
void writePort(pinid_t port, uint8_t portVal) override;
7171

7272
/**
7373
* reads from the input shift register - currently always port 3
7474
*/
75-
virtual uint8_t readPort(pinid_t port);
75+
uint8_t readPort(pinid_t port) override;
7676
};
7777

7878
class ShiftRegisterIoAbstraction165In : public BasicIoAbstraction {
@@ -91,21 +91,21 @@ class ShiftRegisterIoAbstraction165In : public BasicIoAbstraction {
9191
* @see outputOnlyFromShiftRegister
9292
*/
9393
ShiftRegisterIoAbstraction165In(pinid_t readClockPin, pinid_t readDataPin, pinid_t readLatchPin, pinid_t numRead);
94-
~ShiftRegisterIoAbstraction165In() override { }
94+
~ShiftRegisterIoAbstraction165In() override = default;
9595

9696
/** Input only abstraction, does nothing because only input is supported */
97-
virtual void pinDirection(pinid_t pin, uint8_t mode) { }
97+
void pinDirection(pinid_t pin, uint8_t mode) override { }
9898

99-
virtual uint8_t readValue(pinid_t pin);
100-
virtual bool runLoop();
101-
virtual uint8_t readPort(pinid_t port);
99+
uint8_t readValue(pinid_t pin) override;
100+
bool runLoop() override;
101+
uint8_t readPort(pinid_t port) override;
102102

103103
//
104104
// Features not implemented on this abstaction
105105
//
106-
virtual void writePort(pinid_t port, uint8_t portVal) { }
107-
virtual void writeValue(pinid_t pin, uint8_t value) { }
108-
virtual void attachInterrupt(pinid_t, RawIntHandler, uint8_t) { }
106+
void writePort(pinid_t port, uint8_t portVal) override { }
107+
void writeValue(pinid_t pin, uint8_t value) override { }
108+
void attachInterrupt(pinid_t, RawIntHandler, uint8_t) override { }
109109

110110
uint8_t shiftInFor165() const;
111111
};
@@ -208,8 +208,8 @@ class MultiIoAbstraction : public BasicIoAbstraction {
208208
pinid_t limits[MAX_ALLOWABLE_DELEGATES];
209209
uint8_t numDelegates;
210210
public:
211-
MultiIoAbstraction(pinid_t arduinoPinsNeeded = 100);
212-
virtual ~MultiIoAbstraction();
211+
explicit MultiIoAbstraction(pinid_t arduinoPinsNeeded = 100);
212+
~MultiIoAbstraction() override;
213213
void addIoExpander(IoAbstractionRef expander, pinid_t numOfPinsNeeded);
214214

215215
/**

src/KeyboardManager.cpp

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,51 @@
99
#include "KeyboardManager.h"
1010
#include "IoLogging.h"
1111

12+
MatrixKeyboardManager* MatrixKeyboardManager::INSTANCE = nullptr;
13+
14+
ISR_ATTR void rawKeyboardInterrupt() {
15+
auto kbMgr = MatrixKeyboardManager::INSTANCE;
16+
if(kbMgr->keyMode == KEYMODE_NOT_PRESSED) {
17+
// we only need to be notified when not pressed. As in other states we are polling
18+
kbMgr->markTriggeredAndNotify();
19+
}
20+
}
21+
1222
MatrixKeyboardManager::MatrixKeyboardManager() {
13-
this->ioRef = NULL;
14-
this->layout = NULL;
15-
this->listener = NULL;
23+
this->ioRef = nullptr;
24+
this->layout = nullptr;
25+
this->listener = nullptr;
26+
currentKey = 0;
27+
keyMode = KEYMODE_NOT_PRESSED;
28+
interruptMode = false;
29+
counter = 0;
30+
INSTANCE = this;
1631
}
1732

18-
void MatrixKeyboardManager::initialise(IoAbstractionRef ref, KeyboardLayout* layout, KeyboardListener* listener) {
33+
void MatrixKeyboardManager::initialise(IoAbstractionRef ref, KeyboardLayout* layout_, KeyboardListener* listener_, bool interruptMode_) {
1934
this->ioRef = ref;
20-
this->layout = layout;
21-
this->listener = listener;
35+
this->layout = layout_;
36+
this->listener = listener_;
37+
this->interruptMode = interruptMode_;
2238

2339
for(int i=0; i<layout->numColumns(); i++) {
2440
ioDevicePinMode(ioRef, layout->getColPin(i), OUTPUT);
25-
ioDeviceDigitalWrite(ioRef, layout->getColPin(i), HIGH);
41+
ioDeviceDigitalWrite(ioRef, layout->getColPin(i), LOW);
42+
}
43+
for(int i=0; i<layout->numRows(); i++) {
44+
ioDevicePinMode(ioRef, layout->getRowPin(i), INPUT_PULLUP);
45+
if(interruptMode && INSTANCE) {
46+
ioRef->attachInterrupt(layout->getRowPin(i), rawKeyboardInterrupt, CHANGE);
47+
}
2648
}
27-
for(int i=0; i<layout->numRows(); i++) ioDevicePinMode(ioRef, layout->getRowPin(i), INPUT_PULLUP);
2849

2950
ioDeviceSync(ioRef);
3051

3152
currentKey = 0;
32-
taskManager.scheduleFixedRate(KEYBOARD_TASK_MILLIS, this);
53+
taskManager.registerEvent(this);
3354
}
3455

35-
void MatrixKeyboardManager::setToOuput(int col) {
56+
void MatrixKeyboardManager::setToOutput(int col) {
3657
for(int i=0; i<layout->numColumns(); i++) {
3758
ioDeviceDigitalWrite(ioRef, layout->getColPin(i), col != i);
3859
}
@@ -43,18 +64,26 @@ void MatrixKeyboardManager::setRepeatKeyMillis(int startAfterMillis, int repeatM
4364
repeatTicks = repeatMillis / KEYBOARD_TASK_MILLIS;
4465
}
4566

67+
inline bool isDebouncing(KeyMode keyMode) {
68+
return keyMode == KEYMODE_DEBOUNCE || keyMode == KEYMODE_DEBOUNCE1 || keyMode == KEYMODE_DEBOUNCE2;
69+
}
70+
4671
void MatrixKeyboardManager::exec() {
47-
if(ioRef == NULL) return;
72+
if(ioRef == nullptr) {
73+
serdebugF("ioRef null");
74+
return;
75+
}
4876

4977
char pressThisTime = 0;
5078

79+
5180
// then we read back the right state
5281
for(int c=0;c<layout->numColumns();c++) {
53-
setToOuput(c);
82+
setToOutput(c);
5483
ioDeviceSync(ioRef); // first we set the right column low.
5584
taskManager.yieldForMicros(500); // let things settle while other tasks run.
5685
ioDeviceSync(ioRef); // then we read the latest row states back
57-
86+
5887
for(int r=0; r<layout->numRows(); r++) {
5988
if(!ioDeviceDigitalRead(ioRef, layout->getRowPin(r))) {
6089
pressThisTime = layout->keyFor(r, c);
@@ -63,11 +92,10 @@ void MatrixKeyboardManager::exec() {
6392
}
6493
}
6594

66-
6795
// if the key is the same as last time and not zero
6896
if(pressThisTime == currentKey && pressThisTime) {
6997
// then we either have finished debouncing or are repeating
70-
if(keyMode == KEYMODE_DEBOUNCE) {
98+
if(isDebouncing(keyMode)) {
7199
keyMode = KEYMODE_PRESSED;
72100
counter = repeatStartTicks;
73101
listener->keyPressed(currentKey, false);
@@ -79,16 +107,57 @@ void MatrixKeyboardManager::exec() {
79107
}
80108
}
81109
else keyMode = KEYMODE_DEBOUNCE;
82-
}
83-
else {
84-
// otherwise the keys are not the same, we are either in released
85-
// state or debouncing.
110+
} else {
111+
// first clear any existing state
86112
if(keyMode == KEYMODE_PRESSED) {
87113
keyMode = KEYMODE_NOT_PRESSED;
88114
counter = 0;
89115
listener->keyReleased(currentKey);
116+
currentKey = 0;
90117
}
91-
if(pressThisTime != 0) keyMode = KEYMODE_DEBOUNCE;
92-
currentKey = pressThisTime;
118+
doDebounce(pressThisTime);
119+
}
120+
121+
enableAllOutputsForInterrupt();
122+
}
123+
124+
uint32_t MatrixKeyboardManager::timeOfNextCheck() {
125+
126+
if(interruptMode && (keyMode == KEYMODE_NOT_PRESSED)) {
127+
return secondsToMicros(1);
128+
} else {
129+
setTriggered(true);
130+
return millisToMicros(KEYBOARD_TASK_MILLIS);
131+
}
132+
}
133+
134+
void MatrixKeyboardManager::enableAllOutputsForInterrupt() {
135+
if(interruptMode && keyMode == KEYMODE_NOT_PRESSED) {
136+
// in interrupt mode we set all output pins low when nothing is pressed so that any change will be detected.
137+
// this effectively means that each column pin is low and will pull down the input line. We don't need to
138+
// know what is pressed, just that something was pressed.
139+
for(int i=0; i < layout->numColumns(); i++) {
140+
ioDeviceDigitalWrite(ioRef, layout->getColPin(i), 0);
141+
}
142+
ioDeviceSync(ioRef);
143+
}
144+
}
145+
146+
void MatrixKeyboardManager::doDebounce(char pressedNow) {
147+
if(isDebouncing(keyMode) && currentKey == pressedNow) {
148+
currentKey = pressedNow;
149+
keyMode = KEYMODE_PRESSED;
150+
return;
151+
}
152+
153+
if(pressedNow && keyMode == KEYMODE_NOT_PRESSED) {
154+
currentKey = pressedNow;
155+
keyMode = KEYMODE_DEBOUNCE;
156+
} else if(keyMode == KEYMODE_DEBOUNCE) {
157+
keyMode = KEYMODE_DEBOUNCE1;
158+
} else if(keyMode == KEYMODE_DEBOUNCE1) {
159+
keyMode = KEYMODE_DEBOUNCE2;
160+
} else if (keyMode == KEYMODE_DEBOUNCE2) {
161+
keyMode = KEYMODE_NOT_PRESSED;
93162
}
94163
}

src/KeyboardManager.h

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,35 +96,50 @@ class KeyboardLayout {
9696
enum KeyMode: uint8_t {
9797
KEYMODE_NOT_PRESSED,
9898
KEYMODE_DEBOUNCE,
99+
KEYMODE_DEBOUNCE1,
100+
KEYMODE_DEBOUNCE2,
99101
KEYMODE_PRESSED,
100102
KEYMODE_REPEATING
101103
};
102104

103105
#define KEYBOARD_TASK_MILLIS 50
104106

105107
/**
106-
* A keyboard manager that can determine if a key is pressed or released for a given
107-
* layout of keyboard. It is configured during initialisation with an IoAbstraction
108-
* that is used to access hardware, a specific keyboard layout and a listener that
109-
* will be called back when keys are pressed and released.
108+
* A keyboard manager that can determine if a key is pressed or released for a given layout of keyboard. It is configured
109+
* during initialisation with an IoAbstraction that is used to access hardware, a specific keyboard layout and a listener
110+
* that will be called back when keys are pressed and released. The keyboard layout also sets what character is associated
111+
* with each key.
112+
*
113+
* You can decide between polling operation and interrupt operation, if interrupt operation is chosen then all the row
114+
* pins must be on interrupt capable pins, which on many boards is best achieved by using an MCP23017 for all the pins.
115+
* Do not enable interrupt mode on a PCF8574 as the changing of the output pins will trigger the interrupt.
110116
*/
111-
class MatrixKeyboardManager : public Executable {
117+
class MatrixKeyboardManager : public BaseEvent {
112118
private:
119+
static MatrixKeyboardManager* INSTANCE;
113120
KeyboardListener* listener;
114121
KeyboardLayout* layout;
115122
IoAbstractionRef ioRef;
116123
uint8_t repeatTicks = 10;
117124
uint8_t repeatStartTicks = 30;
118125
char currentKey;
119-
KeyMode keyMode;
126+
volatile KeyMode keyMode;
120127
uint8_t counter;
128+
bool interruptMode;
121129
public:
122130
MatrixKeyboardManager();
123-
void initialise(IoAbstractionRef ref, KeyboardLayout* layout, KeyboardListener* listener);
131+
void initialise(IoAbstractionRef ref, KeyboardLayout* layout, KeyboardListener* listener, bool interruptMode = false);
124132
void setRepeatKeyMillis(int startAfterMillis, int repeatMillis);
125-
void exec();
133+
134+
uint32_t timeOfNextCheck() override;
135+
void exec() override;
136+
137+
friend void rawKeyboardInterrupt();
126138
private:
127-
void setToOuput(int i);
139+
void setToOutput(int i);
140+
void enableAllOutputsForInterrupt();
141+
142+
void doDebounce(char time);
128143
};
129144

130145
#define MAKE_KEYBOARD_LAYOUT_3X4(varName) const char KEYBOARD_STD_3X4_KEYS[] PROGMEM = "123456789*0#"; KeyboardLayout varName(4, 3, KEYBOARD_STD_3X4_KEYS);

0 commit comments

Comments
 (0)