Page 1 of 1

HID keyboard skipping char

Posted: Wed Jun 25, 2025 8:42 pm
by nosense
Hello,

I'm working on USB HID keyboard, using TinyUSB, and I'm encountering an error when sending strings that included chars with shift modifier.
I'm parsing an ASCII string char by char, converted using a array. I send HID key reports in batches of 6 keys and is send when one of these conditions is true:
- The buffer reaches 6
- The next char have different modifier
- I'ts the last char in data

For the string "hapa.pt", I expect it the full string to be typed and it work when I send keycode individually, but when I send together in the array of 6 I get this: "hap.t"

I'm trying to understand if the problem is in my logic. Can someone help me understand why this happens? Thanks!

Code: Select all

void read_line() {
  uint8_t *data = malloc(512);
  if(!data) return;

  int len = uart_read_bytes(UART_NUM_1, data, (512 - 1), pdMS_TO_TICKS(500));
  
  if(len > 0) {
    int counter = 0;
    uint8_t modifier = 0;
    uint8_t keycode[6] = {0}; 
    for (int i = 0; i < len; i++) {
      uint8_t entry = ascii2keycode[(uint8_t)data[i]];
      modifier = (entry & 0x80) ? KEYBOARD_MODIFIER_LEFTSHIFT : 0x00; // 0x00
      
      uint8_t next = ((ascii2keycode[(uint8_t)data[i+1]]) & 0x80) ? KEYBOARD_MODIFIER_LEFTSHIFT : 0x00;
      if(counter < 6) {
        keycode[counter] = entry & 0x7F; 
        vTaskDelay(pdMS_TO_TICKS(20));
        counter++;
      }

      if(counter == 6 || modifier != next || i + 1 == len) {
        tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, modifier, keycode);
        vTaskDelay(pdMS_TO_TICKS(20));
      
        tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
        vTaskDelay(pdMS_TO_TICKS(20));
        
        counter = 0;
        memset(keycode, '\0', sizeof(keycode));
      }
    }
  }
  free(data);
  data = NULL;

  return;
}

Re: HID keyboard skipping char

Posted: Thu Jun 26, 2025 11:26 am
by igi540
The issue you're encountering is likely due to how HID keyboards handle modifier keys (like Shift) in combination with multiple keycodes. When sending a batch of keys, the modifier (e.g., Shift) applies to all 6 keycodes sent in that report. This becomes a problem when your batch includes both characters that require Shift (like .) and characters that don’t (like a or p).

In your example, "hapa.pt" fails because the . character requires Shift, but the others don’t. When you batch them together and set the modifier to Shift, you're effectively sending Shift + a, Shift + p, etc., which doesn’t produce the intended characters.

So the string "hap.t" likely happens because when you send the batch with modifier = Shift (for the .), the previous keys in the buffer also get affected by the Shift modifier unintentionally.

Your batching logic is good, but you must track modifiers per character and flush the buffer whenever the modifier for the next character differs — which you are already trying to do. However, there is a subtle bug in the modifier comparison logic. You're always setting modifier from the current character, then checking the next character's modifier. But you're already buffering the current keycode, so by the time you detect a modifier mismatch, one keycode has already been added to the buffer with the wrong modifier.

Fix suggestion: Calculate the next modifier before adding the current keycode to the buffer, and flush the buffer if the modifier would change.

Try this corrected logic:

Code: Select all

for (int i = 0; i < len; i++) {
uint8_t entry = ascii2keycode[(uint8_t)data[i]];
uint8_t current_modifier = (entry & 0x80) ? KEYBOARD_MODIFIER_LEFTSHIFT : 0x00;

// Lookahead to see what modifier the next character will require
uint8_t next_modifier = 0;
if (i + 1 < len) {
uint8_t next_entry = ascii2keycode[(uint8_t)data[i + 1]];
next_modifier = (next_entry & 0x80) ? KEYBOARD_MODIFIER_LEFTSHIFT : 0x00;
}

if (counter == 6 || (counter > 0 && current_modifier != modifier)) {
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, modifier, keycode);
vTaskDelay(pdMS_TO_TICKS(20));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
vTaskDelay(pdMS_TO_TICKS(20));
counter = 0;
memset(keycode, '\0', sizeof(keycode));
}

// Set the modifier for the current batch
modifier = current_modifier;
keycode[counter++] = entry & 0x7F;
}
This way, you make sure that each batch of keycodes shares the same modifier, and you don’t carry keys over to a batch that uses a different modifier.

Re: HID keyboard skipping char

Posted: Thu Jun 26, 2025 8:06 pm
by nosense
Hello,

Thanks for help, in my case the problem it was actually that the same keycode was being added more than once in the same HID report, which it's not possible. I ended up refactoring my logic to ensure that the HID report contains different keycodes and the same modifier, this resolved the issue.

Code: Select all

void read_line() {
  uint8_t *data = malloc(512);
  if(!data) return;

  int len = uart_read_bytes(UART_NUM_1, data, (512 - 1), pdMS_TO_TICKS(50));
  
  if(len > 0) {
    int counter = 0;
    uint8_t keycode[6] = {0}; // Permite ler até 6 teclas em simultaneo.
    for (int i = 0; i < len; i++) {
      uint8_t entry = keymap[(uint8_t)data[i]];
      uint8_t next_entry = 0;
      uint8_t modifier = 0;
      uint8_t next_modifier = 0; 

      if(i + 1 < len) {
        next_entry = (keymap[(uint8_t)data[i + 1]]) & 0x7F;
        next_modifier = get_modifier(keymap[(uint8_t)data[i + 1]]);
      }

      if(counter < 6) {
        keycode[counter++] = entry & 0x7F;
        modifier = get_modifier(entry);
      }
      
      for(int j = 0; j < counter; j++) {
        if(keycode[j] == next_entry) {
          counter = 6;
        }
      }

      if(counter == 6 || i + 1 == len || modifier != next_modifier) {

        tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, modifier, keycode);
        vTaskDelay(pdMS_TO_TICKS(20));
        
        tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
        vTaskDelay(pdMS_TO_TICKS(20));

        counter = 0;
        memset(keycode, '\0', sizeof(keycode)); // Reset array
      }
    }
  }
  free(data);
  data = NULL;

  return;
}

Thanks again for your help!

Re: HID keyboard skipping char

Posted: Fri Jun 27, 2025 1:56 am
by Sprite
In your example, "hapa.pt" fails because the . character requires Shift
Just curious: what localization are you on where a period requires shift? For US-international, it's unshifted.

Re: HID keyboard skipping char

Posted: Fri Jun 27, 2025 9:22 am
by nosense
Hi,

I explained that bad in my example, the reason for "hapa.pt" fails isn't because of the modifier "SHIFT". The actual issue is that same keys like 'a' and 'p' are repeated in same HID Report (which allows 6 keys simultaneous press), which isn't possible you can't press the same key twice at the same time. So in that example it's not about Shift, but rather use of the same key in a single report. I fixed this by check if the new char already exists in the buffer, if it does I send the current HID report first before adding the new char.