HID keyboard skipping char

nosense
Posts: 2
Joined: Wed Jun 25, 2025 8:18 pm

HID keyboard skipping char

Postby nosense » Wed Jun 25, 2025 8:42 pm

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;
}

igi540
Espressif staff
Espressif staff
Posts: 7
Joined: Thu Jan 23, 2025 1:00 pm

Re: HID keyboard skipping char

Postby igi540 » Thu Jun 26, 2025 11:26 am

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.

nosense
Posts: 2
Joined: Wed Jun 25, 2025 8:18 pm

Re: HID keyboard skipping char

Postby nosense » Thu Jun 26, 2025 8:06 pm

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!
Last edited by nosense on Thu Jun 26, 2025 8:08 pm, edited 1 time in total.

Sprite
Espressif staff
Espressif staff
Posts: 10650
Joined: Thu Nov 26, 2015 4:08 am

Re: HID keyboard skipping char

Postby Sprite » Fri Jun 27, 2025 1:56 am

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.

nosense
Posts: 2
Joined: Wed Jun 25, 2025 8:18 pm

Re: HID keyboard skipping char

Postby nosense » Fri Jun 27, 2025 9:22 am

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.

Who is online

Users browsing this forum: ChatGPT-User, PerplexityBot and 2 guests