Keyboard matrix scan breaks when I try to implement Bounce2

I have this fairly large keyboard project running on a Teensy 3.6, and I'm trying to replace my delay(5) function with Bounce2 implementation. The section of code I am changing is working just fine(I'm typing on it right now) in this form:

void setup() {

  Serial.begin(115200);

  for (uint8_t j=0; j<columnsCount; j++) {
    pinMode(columns[j], INPUT_PULLUP);                 
  }

  for (uint8_t i=0; i<rowsCount; i++) {
    pinMode(rows[i], OUTPUT);
    digitalWrite(rows[i], HIGH);
  }

  Keyboard.begin();
  Mouse.begin();
}

void loop() {

  for (uint8_t i=0; i<rowsCount; i++) {
    digitalWrite(rows[i], LOW);

    for (uint8_t j=0; j<columnsCount; j++) {
      Key* key = getKey(i, j);

      boolean current = !digitalRead(columns[j]);
      boolean previous = key->pressed;
      key->pressed = current;

      LayoutKey* layout = getLayoutKey(key->row, key->column);

      if (current && !previous) {
        keyPressed(key, layout);
      } else if (!current && previous) {
        keyReleased(key, layout);
      }
    }

    digitalWrite(rows[i], HIGH);
    delay(5);
  }
}

But when I change it to this, and many many other attempts, it doesn't work.

#include <Bounce2.h>

Bounce buttons[columnsCount];

void setup() {

  Serial.begin(115200);

  for (uint8_t j=0; j<columnsCount; j++) {
    buttons[j].attach( columns[j], INPUT_PULLUP );
    buttons[j].interval(10);              
  }

  for (uint8_t i=0; i<rowsCount; i++) {
    pinMode(rows[i], OUTPUT);
    digitalWrite(rows[i], HIGH);
  }

  Keyboard.begin();
  Mouse.begin();
}

void loop() {

  for (uint8_t i = 0; i < rowsCount; i++) {
    digitalWrite(rows[i], LOW);

    for (uint8_t j = 0; j < columnsCount; j++) {
      Key* key = getKey(i, j);

      buttons[j].update();

      LayoutKey* layout = getLayoutKey(key->row, key->column);

      if (buttons[j].fell()) {
        keyPressed(key, layout);
      }
      if (buttons[j].rose()) {
        keyReleased(key, layout);
      }
    }

    digitalWrite(rows[i], HIGH);
  }
}

I've looked as hard as I can for Bounce2 documentation with arrays, copied some code from 1D arrays, never found any examples for 2D arrays. I presume the problem is that I have no idea what I'm doing. I've even asked ChatGPT for help, but all it does is hallucinate. I think that's because Bounce2 is not in it's training data, but the code it tried to tell me will work with the previous version of Bounce doesn't work either. Maybe the paid version is better, but I'm not impressed.

Anyway I digress, the problem that happens with the updated snippet here is that it doesn't remotely work. The keys don't register, not even physically. I checked as much as I could think to check with the serial monitor, and no related variables are changing when keys are pressed. Other sections of the code, such as my capslock LED works when I toggle capslock from windows on-screen-keyboard. I have an i2c capacitive touch strip that has some keybinds tied to gestures and they all work just fine, so the rest of the code isn't getting blocked or anything. It's just this matrix scan routine that breaks.

I rarely used Bounce2, and never with a button array, but I wonder where is the constructor call...
Standard usage for single variable in the examples I have:

Bounce b = Bounce();

This way the object "b" is initialized. I don't know if this is the cause of your issue, but it seems to me you should call the constructor for each item. I suppose you need to do something like this below, give it a try:

...
void setup() {
  Serial.begin(115200);

  for (uint8_t j=0; j<columnsCount; j++) {
    buttons[j] = Bounce(); // Constructor call needed here?
    buttons[j].attach( columns[j], INPUT_PULLUP );
    buttons[j].interval(10);              
  }
...

IMHO the best is always a hardware debounce, as it's the ultimate solution and it needs just a couple of passive and cheap components.

Otherwise, if the project doesn't include hardware debounce, I usually just bypass the minimum bounce time then wait for stable signal, it's not complicated. And if you need a non-blocking (no delays) solution see HERE.

@eli_beeblebrox the 5 milliseconds delay in your original partially shared code is in an odd place. And is an odd delay.

Is that how you have it in the real code?

Why are you trying to use a bounce library? Are the buttons misbehaving now? Do,you need to get rid of 5 * number of rows milliseconds delay?

You may need do nothing it's clever than has been suggested already: just run the keypad scanning loops every 20 milliseconds. Period end of story.

No delay need, no debounce.

This pattern can be used to do something every 20 milliseconds

  static unsigned long lastLook;

  unsigned long now = millis();

  if (now - lastTime >= 20) {

/* code here will execute every 20 milliseconds */

    lastTime = now;
  }

As long as those lines are execute very frequently. Like when placed somewhere in your beautiful, non-blocking free-running loop.

HTH

a7

Gentlemen, thanks for the responses on this most momentous of American holidays.

Right there. That is the constructor call. It apparently works for this guy using Bounce2.

I would love to implement that, but only if I can get away with putting the filters on the column or row wires instead of each of the switches individually. There are 84 keys on this thing and its handwired - including the SK6812 Mini-e under each key. I've had enough soldering this past week to make me wonder how much lead I inhaled even with ventilation and a fan.

I see, for some reason I was expecting writing my own debounce code to be more complicated. I'll give this a shot tomorrow and see if I'm smart enough to adapt it to a matrix and have it register on fall instead of waiting for the end of the debounce delay. I've got some freedom to celebrate today.

Yes. It's all copy and pasted with some unrelated sections removed for brevity. I don't want to overwhelm you with my layer system and rgbled assignments for instance.

Oh wow, I copied this scan loop from someone else's Teensy 3.X keyboard project and never thought about its location like that. That is a weird spot.

Yes. There's contact chatter if I reduce that delay, but if I remove it entirely, there's a weird effect going on with the first column(Esc. Tilde, Tab, etc) that causes the key below the one I'm holding to behave as if rapidly pressed(think turbo mode from third party console controllers) while the key above it is held down. Real head scratcher, I'm 99% certain there's nothing wrong with the wiring and I have no idea what in the code would cause that behavior, but I'm considering adding a low pass filter or a ferrule to just that column to see if that filters out some wire cross talk or interference just in case.

I've been trying to do this, but the problem I mentioned earlier about removing the delay pops up.

But I just came up with the idea to implement this without removing the delay and instead just setting it to 1. It works, and I was able to reduce the interval of that timer to 14ms.

void loop() {

  unsigned long now = millis();

  if (now - lastTime >= 14) {

  for (uint8_t i=0; i<rowsCount; i++) {
    digitalWrite(rows[i], LOW);

    for (uint8_t j=0; j<columnsCount; j++) {
      Key* key = getKey(i, j);

      boolean current = !digitalRead(columns[j]);
      boolean previous = key->pressed;
      key->pressed = current;

      LayoutKey* layout = getLayoutKey(key->row, key->column);

      if (current && !previous) {
        keyPressed(key, layout);
      } else if (!current && previous) {
        keyReleased(key, layout);
      }
    }

    digitalWrite(rows[i], HIGH);
    delay(1);
  }
  lastTime = now;
  }
}

So... that's an improvement I guess. I'd still rather be able to remove it entirely as I'd like my Trill Bar code to run uninterrupted, and I also want the satisfaction of knowing my kepresses are registered on fall and not shortly after.

When the keyboard reports other keys whilst you hold one down is the first key recognized the one you actually pressed? I'm guessing the first key will be one of the keys in the row (column?), not always the one you pressing.

An admiral goal. And one I like to achieve.

Here you have a possibility of waiting 15 milliseconds for a keypress (or kepress) to come through.

For some, end of the job. For percussionists, an intolerable delay.

You can rework the millis/now/lastTime pattern and use another very common technique which allows near-instant recognition.

I'm at the beach or I would write it out for you. The idea is to read the switches, but not if you've read them and got a keypress recently (since 20 ms, say). The rate of key presses is limited to the denounce interval chosen, but the first one will come instantly and subsequent ones probably will, too. After all how fast can you press a button?

However, the other symptoms you describe speak to problems which should be ferret out rather than masked by debouncing. Before tinkering with your success at this point. The ghost keys, something's not right there yet.

You need to take a picture of this thing and post it here! Curious.

a7

Sorry, I can't get that point. The "bounce" is always between a row and a column, right? So, IMHO it'd be enough putting debounce components on either the row pins or the columns ones, the one with lesser pins (I don't know how many rows and columns you actually have, but for 84 I suppose something like 6x14, so 6 could be the choice). I'd give it a try and see if it works, a test is pretty easily I think/hope...

WELL THEN, it's been quite some time, had a lot of life happen in the interim and made many attempts at this code, none of which worked. Maybe I'm not as smart as my mom thinks.

Yes, as you said. To be clear, I'll press Tilde and then according to keyboard tester, it shows tilde is pressed and puts one grave accent symbol in the entry field, followed by rapid tab presses as long as Tilde is held, and still showing tilde held.

I'll have you know, it's not exactly an unnoticeable delay for us guitarists either. But the real reason I want it is because I'm ill. My psychiatrist tells me I'm a "gamer" and one of my symptoms is that I expend a pathological amount of energy thinking about input latency.

Are you back yet?
Jokes aside, I would appreciate that now that my many attempts have failed.

If the issue is physical, I'm not too worried about masking it as long I can reach my performance goals. I'll eventually be making a version 3 with a PCB and possibly even incorporate a microntroller myself without using an arduino.

Project files are now on github. I figure that'll make it easier to help. Pictures in the media folder. Cameras never quite do LEDs justice, but these images aren't bad.

I looked at the code, good that you lost the Bounce2 idea.

If the keyboard scan is what you want to only do every so often is the first part of your loop, the suggested idiomatic pattern is what you need.

I can't read through the hole thread - seems like you could have tried it.

Oh, @alto777 posted

  static unsigned long lastLook;

  unsigned long now = millis();

  if (now - lastTime >= 20) {

/* code here will execute every 20 milliseconds */

    lastTime = now;
  }

above. A slight improvement would be to update lastTime only if a key got seen pressed or released and acted upon.

Then a next key will fire instantly if you take more than 20 milliseconds getting around to trying to scan again, and will simply will not scan if asked to sooner than that.

Start with a generous certainly too big time constant, like 500 milliseconds. See keys pressed at the rate of 1 per second firing instantly. See the key release come 500 milliseconds after the key press.See keys struck less than 1/2 second since the last key that fired get… ignored. The same ignorance is the bouncing filter.

I think.

As for the beach, this day was not so good. Cold, rainy and dark. We usually make the best of it, today no one went.

a7

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