I built a two-key keypad, housed in a case I got 3D printed, and soldered it up to a Pro Micro board. It uses Cherry MX switches soldered to pins 3 and 8 (and GND, of course). The problem is that every so often (~60-180 seconds) the keyboard will freeze for a few seconds and not send any inputs, before resuming again. I'm confident that my soldering is fine.
What might be the issue is the code I wrote to handle the keypresses. Here is the code; it's fairly simple:
#include "Keyboard.h"
#define NUM_BUTTONS 2
#define DEBOUNCE_DELAY_MSEC 30
#define POLLING_INTERVAL_USEC 500
const int buttonPins[2] = {3, 8};
const int buttonOutputs[2] = {122, 120};
unsigned long lastContact[2] = {0, 0};
int previousStates[2] = {HIGH, HIGH};
void setup() {
// Declare the buttons as input_pullup
for (int i = 0; i < NUM_BUTTONS; ++i) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
Keyboard.begin();
}
void loop() {
unsigned long time = millis();
for (int i = 0; i < NUM_BUTTONS; ++i) {
// Checking the state of the button
int buttonState = digitalRead(buttonPins[i]);
if (buttonState == LOW && previousStates[i] == HIGH && time - lastContact[i] >= DEBOUNCE_DELAY_MSEC) {
Keyboard.press(buttonOutputs[i]);
previousStates[i] = LOW;
lastContact[i] = time;
}
if (buttonState == HIGH && previousStates[i] == LOW && time - lastContact[i] >= DEBOUNCE_DELAY_MSEC) {
Keyboard.release(buttonOutputs[i]);
previousStates[i] = HIGH;
lastContact[i] = time;
}
}
delayMicroseconds(POLLING_INTERVAL_USEC);
}
All it does is the usual loop to check the pin values, with some debouncing and a polling delay thrown in.
I can't see any issue with the code myself, but I'm new to Arduino programming so something could have slipped past. It might also be not my fault at all - it could be that the game I'm playing with this keypad has its own issues.
But anyway, if someone could give my code the once over and make some suggestions for improvement, that would be greatly appreciated, thank you.
I can't just now, but you could… make another sketch with the same logic but leave out anything to do with the keyboard calls, substituting print statements for where the keyboard actions are.
I am suspicious that it would work fine, that is to say the keyboard thing is messing with you.
But… I have had too much of one kind of liquid and not enough of another, so it may be something blindingly obvious.
Just a hacker's debugging idea: divide and conquer.
No large deal, but if you look carefully, you will see that several nearly identical lines of code are disappeared and the tests are done a bit differently and in a different order.
Avoiding duplicate lines or near duplication is a plus, as it means that pesky errors can be found, and fixed, in once place.
And each if test has but one condition - always easier to read.
It's just the way I write this kinda thing, which way I assure you I did not invent. Let's say it is more common.
As you might suspect, my version also works perfectly.
Play with it in the simulator here:
//# include "Keyboard.h"
// remove keyboard from equation
// https://forum.arduino.cc/t/my-two-key-keyboard-occasionally-freezes-for-a-couple-of-seconds-at-a-time/1034842
#define NUM_BUTTONS 2
#define DEBOUNCE_DELAY_MSEC 30
#define POLLING_INTERVAL_USEC 500
const int buttonPins[2] = {3, 8};
const int buttonOutputs[2] = {122, 120};
unsigned long lastContact[2] = {0, 0};
int previousStates[2] = {HIGH, HIGH};
void setup() {
Serial.begin(115200);
Serial.println("Jello Whirled!\n");
// Declare the buttons as input_pullup
for (int i = 0; i < NUM_BUTTONS; ++i) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
// Keyboard.begin();
}
void loop() {
unsigned long time = millis();
for (int i = 0; i < NUM_BUTTONS; ++i) {
if (time - lastContact[i] < DEBOUNCE_DELAY_MSEC) // too soon to even look?
continue;
// no, so get the state of the button
int buttonState = digitalRead(buttonPins[i]);
if (buttonState != previousStates[i]) { // button has changed!
if (!buttonState) { // button got pressed
// Keyboard.press(buttonOutputs[i]);
Serial.print("press ");
Serial.println(i);
}
else { // button got released
// Keyboard.release(buttonOutputs[i]);
Serial.print("release ");
Serial.println(i);
}
previousStates[i] = buttonState; // remember this button
lastContact[i] = time; // and when it changed
}
}
delayMicroseconds(POLLING_INTERVAL_USEC);
}
My beach buddy just got here, Imma try to convince her it is way too cold for the beach and let's do something else...
Cheers. This is a certainly an improvement; though I imagine the main loop was probably running at a low enough latency beforehand anyway. No harm in optimizing though!
The funny thing is though, I updated the game I was playing with the keypad (it's a development version) and the problem went away. I used it for about an hour with no hanging. This makes me think it was a bug in the game that got fixed in the update...
Yes, my changes were not at all about optimisation of the running process, more just style changes for readability and easier maintenance.
Your original version might actually be faster, but either is very fast enough.
You did the important thing - read the time once and use that value, read the buttons once.
So just "some suggestions for improvement" what are arguably not that important.
I hate when it turns out to be some problem elsewhere taking up all the hair pulling. The good part is you've done what might have been from the beginning - become very near certain your code works and works how you think.