XIAO SAMD21 as joystick and keyboard

I'm trying to write code for the XIAO SAMD21 that will control both the joystick and the keyboard simultaneously, but I'm having trouble connecting them; they won't work at all. Both programs below work separately. Does anyone know how to connect them so they work together? My attempts end up with USB communication hanging up.

Joystick:

#include "Adafruit_TinyUSB.h"

// Obiekt HID
Adafruit_USBD_HID usb_hid;

// === Deskryptor HID: 64 przyciski + oś Z ===
uint8_t const hid_report_descriptor[] = {
  0x05, 0x01,  // Usage Page (Generic Desktop)
  0x09, 0x04,  // Usage (Joystick)
  0xA1, 0x01,  // Collection (Application)

  // 64 przyciski (8 bajtów)
  0x05, 0x09,  //   Usage Page (Button)
  0x19, 0x01,  //   Usage Minimum (Button 1)
  0x29, 0x40,  //   Usage Maximum (Button 64)
  0x15, 0x00,  //   Logical Minimum (0)
  0x25, 0x01,  //   Logical Maximum (1)
  0x95, 0x40,  //   Report Count (64)
  0x75, 0x01,  //   Report Size (1 bit)
  0x81, 0x02,  //   Input (Data, Variable, Absolute)

  // Oś Z (hamulec)
  0x05, 0x01,        //   Usage Page (Generic Desktop)
  0x09, 0x32,        //   Usage (Z)
  0x15, 0x00,        //   Logical Minimum (0)
  0x26, 0xFF, 0x00,  //   Logical Maximum (255)
  0x75, 0x08,        //   Report Size (8 bitów)
  0x95, 0x01,        //   Report Count (1)
  0x81, 0x02,        //   Input (Data, Variable, Absolute)

  0xC0               // End Collection
};

// Struktura raportu HID (8 bajtów przycisków + 1 bajt osi)
struct __attribute__((packed)) {
  uint8_t buttons[8];
  uint8_t z;
} hid_report;

void setup() {
  usb_hid.setReportDescriptor(hid_report_descriptor, sizeof(hid_report_descriptor));
  usb_hid.setPollInterval(2);
  usb_hid.begin();

  // Czekamy, aż host (PC) zainicjuje USB
  while (!USBDevice.mounted()) {
    delay(10);
  }
}

void loop() {
  static uint32_t t = 0;

  // Oś "pływa" sinusoidalnie w zakresie 0–255
  float value = (sin(t * 0.05) * 0.5 + 0.5) * 255.0;
  hid_report.z = (uint8_t)value;

  // Wszystkie przyciski niewciśnięte
  memset(hid_report.buttons, 0x00, sizeof(hid_report.buttons));

  // Wysłanie raportu (Report ID = domyślnie 0)
  usb_hid.sendReport(0, &hid_report, sizeof(hid_report));

  t++;
  delay(10);
}

Keyboard:

#include <Adafruit_TinyUSB.h>

// --- konfiguracja pinów przycisków ---
#define BTN_A 9
#define BTN_Arrow 10

#define keya HID_KEY_A

// --- obiekt HID klawiatury TinyUSB ---
Adafruit_USBD_HID usb_hid_keyboard;

// --- pełny raport HID (modifier,reserved,6 keycodes) ---
uint8_t keyReport[8] = { 0 };

// --- blokady stanów przycisków ---
bool btnArrowLeftLock = false;
bool btnALock = false;

// --- standardowy deskryptor HID klawiatury (8-bajtowy raport) ---
const uint8_t keyboard_report_descriptor[] = {
  0x05, 0x01,  // USAGE_PAGE (Generic Desktop)
  0x09, 0x06,  // USAGE (Keyboard)
  0xA1, 0x01,  // COLLECTION (Application)
  0x05, 0x07,  //   USAGE_PAGE (Keyboard)
  0x19, 0xE0,  //   USAGE_MINIMUM (Keyboard LeftControl)
  0x29, 0xE7,  //   USAGE_MAXIMUM (Keyboard Right GUI)
  0x15, 0x00,  //   LOGICAL_MINIMUM (0)
  0x25, 0x01,  //   LOGICAL_MAXIMUM (1)
  0x75, 0x01,  //   REPORT_SIZE (1)
  0x95, 0x08,  //   REPORT_COUNT (8)
  0x81, 0x02,  //   INPUT (Data,Var,Abs)
  0x95, 0x01,  //   REPORT_COUNT (1)
  0x75, 0x08,  //   REPORT_SIZE (8)
  0x81, 0x03,  //   INPUT (Cnst,Var,Abs)
  0x95, 0x05,  //   REPORT_COUNT (5)
  0x75, 0x01,  //   REPORT_SIZE (1)
  0x05, 0x08,  //   USAGE_PAGE (LEDs)
  0x19, 0x01,  //   USAGE_MINIMUM (Num Lock)
  0x29, 0x05,  //   USAGE_MAXIMUM (Kana)
  0x91, 0x02,  //   OUTPUT (Data,Var,Abs)
  0x95, 0x01,  //   REPORT_COUNT (1)
  0x75, 0x03,  //   REPORT_SIZE (3)
  0x91, 0x03,  //   OUTPUT (Cnst,Var,Abs)
  0x95, 0x06,  //   REPORT_COUNT (6)
  0x75, 0x08,  //   REPORT_SIZE (8)
  0x15, 0x00,  //   LOGICAL_MINIMUM (0)
  0x25, 0x65,  //   LOGICAL_MAXIMUM (101)
  0x05, 0x07,  //   USAGE_PAGE (Keyboard)
  0x19, 0x00,  //   USAGE_MINIMUM (Reserved)
  0x29, 0x65,  //   USAGE_MAXIMUM (Keyboard Application)
  0x81, 0x00,  //   INPUT (Data,Arr,Abs)
  0xC0
};

void setup() {
  // piny wejściowe
  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_Arrow, INPUT_PULLUP);

  // ustaw deskryptor i uruchom HID
  usb_hid_keyboard.setPollInterval(2);
  usb_hid_keyboard.setReportDescriptor(keyboard_report_descriptor, sizeof(keyboard_report_descriptor));
  usb_hid_keyboard.begin();

  // daj hostowi chwilę na enumerację
  delay(1000);
}

void loop() {
  // Przycisk A -> litera 'A'
  if (!digitalRead(BTN_A)) {
    keyboardPressShifted(keya);
    btnALock = 1;
  } else {
    if (btnALock == 1) {
      keyboardReleaseShifted(keya);
      btnALock = 0;
    }
  }

  if (!digitalRead(BTN_Arrow)) {
    keyboardPress(HID_KEY_ARROW_LEFT);
    btnArrowLeftLock = 1;
  } else {
    if (btnArrowLeftLock == 1) {
      keyboardRelease(HID_KEY_ARROW_LEFT);
      btnArrowLeftLock = 0;
    }
  }

  delay(5);
}

// odpowiednik Keyboard.press(key)
void keyboardPress(uint8_t keycode) {
  // ustaw modyfikatory w keyReport[0] jeśli potrzebne
  // wstaw kod klawisza w pierwszy wolny slot (2..7) - tutaj używamy tylko slotu 2
  keyReport[2] = keycode;
  usb_hid_keyboard.sendReport(0, keyReport, sizeof(keyReport));
}

// odpowiednik Keyboard.release(key)
void keyboardRelease(uint8_t keycode) {
  // jeśli puszczamy konkretny keycode, usuń go z raportu - tu upraszczamy: czyścimy slot 2
  keyReport[2] = 0;
  usb_hid_keyboard.sendReport(0, keyReport, sizeof(keyReport));
}

// puść wszystkie klawisze
void keyboardReleaseAll() {
  memset(keyReport, 0, sizeof(keyReport));
  usb_hid_keyboard.sendReport(0, keyReport, sizeof(keyReport));
}

void keyboardPressShifted(uint8_t keycode) {
  keyReport[0] = KEYBOARD_MODIFIER_LEFTSHIFT;  // włącz SHIFT
  keyReport[2] = keycode;                      // wpisz literę
  usb_hid_keyboard.sendReport(0, keyReport, sizeof(keyReport));
}

void keyboardReleaseShifted(uint8_t keycode) {
  keyReport[0] = 0;  // wyłącz SHIFT
  keyReport[2] = 0;  // zwolnij klawisz
  usb_hid_keyboard.sendReport(0, keyReport, sizeof(keyReport));
}

Why is there two sketches? I thought you wanted the XIAO to control a joystick and keyboard at the same time.

Have you tried the following sample sketch?

Can you verify this is the board you selected?

Because both codes work individually and I can't make them both work at the same time. Sure, I already chose seeeduino XIAO and I have already tried the example sketch shown, but I don't know how to configure the descriptor correctly in my case

Do you mean this?

Yes, I suspect I'm trying to merge both of my descriptors incorrectly and that's why USB communication stops working.

I always start with example code. Good luck.

So how would you do it?

No idea, everything I read is confusing. Since I have never needed and won;t likely need joystick (don;t own one) I am confused by what I read. Try searching the forum for keyboard OR joystick.
Once you know it can be done, read the tutorials on state machine. Dealing with devices like joysticks and kbds that are as slow as molasses is a classic state machine.

Duh, if your two sketches above work independently, create a new sketch to riun the state machine and either change your two skectches to cpp files or leave as ino just rename them so when they compile in alphabetical order any variables are handled correctly.

I tried following the examples, but it still doesn't work and I don't know what I'm doing wrong. I think the code is much clearer now. Does anyone know how to solve this problem?

#include "Adafruit_TinyUSB.h"

// === Report ID ===
enum {
  RID_KEYBOARD = 1,
  RID_GAMEPAD,
};

// === Deskryptor HID (klawiatura + gamepad) ===
uint8_t const desc_hid_report[] = {
  TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RID_KEYBOARD)),
  TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(RID_GAMEPAD)),
};

// Obiekt HID
Adafruit_USBD_HID usb_hid;

// Struktura raportu gamepada
hid_gamepad_report_t gp = {0};

// Piny testowe
const int pinJoystick = 9;
const int pinKeyboard = 10;

void setup() {
  pinMode(pinJoystick, INPUT_PULLUP);
  pinMode(pinKeyboard, INPUT_PULLUP);

  if (!TinyUSBDevice.isInitialized()) {
    TinyUSBDevice.begin(0);
  }

  usb_hid.setPollInterval(2);
  usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
  usb_hid.setStringDescriptor("SAMD21 Dual HID");
  usb_hid.begin();

  // Wymuszenie ponownej enumeracji, żeby komputer wykrył HID
  if (TinyUSBDevice.mounted()) {
    TinyUSBDevice.detach();
    delay(10);
    TinyUSBDevice.attach();
  }
}

void loop() {
  // Wysyłanie raportu joysticka
  if (digitalRead(pinJoystick) == LOW) {
    gp.x = 127;   // max w prawo
    gp.y = 0;
    gp.buttons = 1; // przycisk A
  } else {
    gp.x = 0;
    gp.y = 0;
    gp.buttons = 0;
  }
  usb_hid.sendReport(RID_GAMEPAD, &gp, sizeof(gp));

  // Wysyłanie raportu klawiatury
  if (digitalRead(pinKeyboard) == LOW) {
    uint8_t report[6] = {HID_KEY_A, 0, 0, 0, 0, 0};
    usb_hid.sendReport(RID_KEYBOARD, report, sizeof(report));
  } else {
    uint8_t report[6] = {0};
    usb_hid.sendReport(RID_KEYBOARD, report, sizeof(report));
  }

  delay(10);
}