My two-key keyboard occasionally freezes for a couple of seconds at a time

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.

That all looks plausible, not seeing a problem.

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. :wink:

Just a hacker's debugging idea: divide and conquer.

a7

2 Likes

OK, play with it here in the wokwi simulator:

is your code hacked a bit.

//# 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) {
    // 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]);

Serial.print("press ");
Serial.println(i);

      previousStates[i] = LOW;
      lastContact[i] = time;
    }

    if (buttonState == HIGH && previousStates[i] == LOW && time - lastContact[i] >= DEBOUNCE_DELAY_MSEC) {
//      Keyboard.release(buttonOutputs[i]);

Serial.print("release ");
Serial.println(i);

      previousStates[i] = HIGH;
      lastContact[i] = time;
    }
  }

  delayMicroseconds(POLLING_INTERVAL_USEC);
}

It works fine. The structure is not how I write things like this, but logically identical.

So unless you have an hardware issue, it looks like something going on with the part I have no experience about.

HTH

a7

Thank you very much for your help! I'll keep investigating other possible causes then.

I reworked the logic of your program.

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. :expressionless:

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...

L8R

a7

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...

But anyway, thanks again for your help!

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.

a7

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.