How to implement 24-key NKRO on Teensy 4.0 when sending 4 separate 6-key reports doesn't work properly

alcnonl42

Member
Hello everyone,


I've developed a comprehensive custom input device controller for Teensy 4.0 in Arduino IDE. The project works as a specialized keyboard/mouse interface for assistive technology. Everything works well except for one critical issue - I can't get true NKRO (N-Key Rollover) functionality.


My current implementation sends 4 separate 6-key HID reports (for CapsLock, NumLock, ScrollLock, and manually triggered keys), which should theoretically allow for up to 24 keys simultaneously. However, when I already have 6 keys pressed (e.g., A, B, C, D, E, F) and try to press additional keys (like 1, 2), Windows either ignores the new keypresses or cancels some previous ones.


I specifically need support for 24 simultaneous keypresses for my project. I've tried QMK where NKRO works, but then I encountered issues with mouse absolute coordinates and EEPROM functionality.


I don't want to abandon my Arduino IDE project as it's nearly perfect otherwise. I've looked at the Teensy core files and see there's no NKRO keyboard option in the USB Type menu.


Has anyone successfully implemented true NKRO on Teensy 4.0? Could you share any code examples or modifications to the core files needed to achieve this?


Thank you for your help!
 
If you want to use a standard keyboard, ignore everything I'm about to say.

If you are willing to build your own hardware, 74HC165 ICs strung in series can read as many simultaneous inputs as you want.

This example requires the circuit to be polled (read the serial data in) and doesn't include anything like de-bounce. But you can prototype with a breadboard and wires really cheap. There might be a way to have inputs trigger an interrupt using diodes or OR gates, but honestly polling is easy.

If you want to save time, buy as many of these as you need to get the job done. They might even work in series.

Making the keyboard is going to be the toughest part. You can buy keyboard switches all over the place, but building the actual keyboard will take some work. At least you can customize it to EXACTLY what you need.
 
Actually, I’m not designing a physical keyboard—it's more like a software-supported virtual keyboard. For example, it records on-screen keyboard and mouse actions and then replays them. I managed to do this with QMK; I bypassed the NKRO limitation, modified the Kint41 layout, and everything else is working.


My only issue is EEPROM—unfortunately, EEPROM doesn't work properly on ARM-based boards in QMK. So now, I'm looking for alternative ways to store data without relying on EEPROM. I tried integrating the EEPROM logic from my Arduino code into QMK, but that attempt was unsuccessful.


Right now, I'm stuck between two projects: I spend half of my free time trying to integrate NKRO into Arduino, and the other half trying to get EEPROM working in QMK. Unfortunately, I haven’t found a solution yet, but I’m still researching.
 
Teensy's USB keyboard code uses the "boot protocol" which only supports 6KRO.

Here's a quick attempt to add NKRO support. To use this, find the hidden folder where Teensy's core library is installed. Then put these 4 files into that folder, replacing the copies already there. You might also try adding a simple syntax error to any file and click Verify in Arduino IDE, just to confirm you really are replacing the files Arduino IDE is actually using.

I didn't write code to support the old 6-key functions, so if you're using those expect your program to no longer compile. To use NKRO, just use Keyboard.press() and Keyboard.release().

So far I only tested with this very simple program. I didn't do any actual tests "pressing" more than 1 key at a time. But this is transmitting 120 bits for all keys, so it ought to do full NKRO. But really I'll let you be the judge of that (and whether this gets merged into a future Teensyduino release will depend on feeback).

Code:
#include <Bounce.h>
Bounce button0 = Bounce(0, 10);
Bounce button1 = Bounce(1, 10);
Bounce button2 = Bounce(2, 10);

void setup() {
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  delay(1);
}

void loop() {
  button0.update();
  button1.update();
  button2.update();
  if (button0.fallingEdge()) {
    Keyboard.println("B0 press");
  }
  if (button1.fallingEdge()) {
    Keyboard.println("B1 press");
  }
  if (button2.fallingEdge()) {
    Keyboard.println("B2 press");
  }
  if (button0.risingEdge()) {
    Keyboard.println("B0 release");
  }
  if (button1.risingEdge()) {
    Keyboard.println("B1 release");
  }
  if (button2.risingEdge()) {
    Keyboard.println("B2 release");
  }
  static elapsedMillis msec = 0;
  if (msec > 200) {
    msec = 0;
    static int count;
    Serial.printf("count = %d\n", count++);
  }
}
 

Attachments

  • usb_desc.c
    139.4 KB · Views: 12
  • usb_desc.h
    39.3 KB · Views: 11
  • usb_keyboard.c
    19.2 KB · Views: 11
  • usb_keyboard.h
    3.8 KB · Views: 9
Oh, I should mention the KEYBOARD_SIZE define in usb_desc.h determines whether boot/6KRO or NKRO is used. Set to 8 bytes for normal, or 16 bytes to use NKRO. I only edited the define for the Tools > USB Type with just keyboard. If you want any of the others with Keyboard, just find them in usb_desc.h and edit their KEYBOARD_SIZE define.
 
Thank you so much, this really worked great and saved me from a lot of trouble. Now after programming this, is it correct that it will work for a lifetime as long as I don't update it?
 
Can you tell me which operating system you used? I tested only with Linux.

About your question, yes, if you don't update those 4 files should stay on your computer and be used for all future Teensy 4.x compiles.

I've also committed this on github, so future versions will have it. But the default remains the 6KRO boot protocol. After updating to a future version, you will need to edit usb_desc.h to configure NKRO by editing the KEYBOARD_SIZE define in usb_desc.h
 
Back
Top