https://www.youtube.com/watch?v=umkD1piCNvc

위 내용을 참고하여 코드를 작성해봤고 보통 Custom HID를 사용하는걸 권장한다고한다. 하지만 처음으로 USB HID를 사용함으로 일반 HID을 사용했다.

image.png

STM32CubeIDE에서 USB Device 기능을 활성화하고 HID(Human Interface Device) 클래스를 선택하면, ST에서 기본적으로 제공하는 마우스(Mouse) 예제 코드가 생성된다. 이 코드를 수정하여 햅틱 모터 스크롤 + 매크로 버튼을 인식 할 수 있게 해줘야한다.

USBD_HID_CfgDesc 수정

    0x01 -> 0x00,   /* bInterfaceSubClass : 1=BOOT, 0=no boot */
    0x02 -> 0x00,   /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */

no boot 으로 설정 및 마우스가 아닌 복합 장치임으로 0x00

HID_MOUSE_ReportDesc 수정

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{

    /* 키보드 파트: 단축키(Ctrl+S 등) 및 매크로용 */
    0x05, 0x01,        /* Usage Page (Generic Desktop) */
    0x09, 0x06,        /* Usage (Keyboard) */
    0xA1, 0x01,        /* Collection (Application) */
    0x85, 0x01,        /* REPORT ID (1) - 키보드 데이터임을 표시 */
    0x05, 0x07,        /* Usage Page (Key Codes) */
    0x19, 0xE0,        /* Usage Minimum (224) - Modifier keys (Ctrl, Alt...) */
    0x29, 0xE7,        /* Usage Maximum (231) */
    0x15, 0x00,        /* Logical Minimum (0) */
    0x25, 0x01,        /* Logical Maximum (1) */
    0x75, 0x01,        /* Report Size (1 bit) */
    0x95, 0x08,        /* Report Count (8 bits) -> Modifier 바이트 */
    0x81, 0x02,        /* Input (Data, Var, Abs) */
    0x95, 0x01,        /* Report Count (1) */
    0x75, 0x08,        /* Report Size (8 bits) */
    0x81, 0x01,        /* Input (Constant) -> 예약 영역(패딩) */
    0x95, 0x06,        /* Report Count (6) -> 동시 입력 가능한 키 개수 */
    0x75, 0x08,        /* Report Size (8 bits) */
    0x15, 0x00,        /* Logical Minimum (0) */
    0x25, 0x65,        /* Logical Maximum (101) */
    0x05, 0x07,        /* Usage Page (Key Codes) */
    0x19, 0x00,        /* Usage Minimum (0) */
    0x29, 0x65,        /* Usage Maximum (101) */
    0x81, 0x00,        /* Input (Data, Array) -> 키 배열 */
    0xC0,               /* End Collection */

    /* 마우스 파트: 햅틱 모터 스크롤용 */
    0x05, 0x01,        // Usage Page (Generic Desktop)
    0x09, 0x02,        // Usage (Mouse)
    0xA1, 0x01,        // Collection (Application)
    0x85, 0x02,        // Report ID (2)

    0x09, 0x01,        // Usage (Pointer)
    0xA1, 0x00,        // Collection (Physical)

    // -------------------- 버튼 --------------------
    0x05, 0x09,        // Usage Page (Button)
    0x19, 0x01,        // Usage Minimum (1)
    0x29, 0x03,        // Usage Maximum (3)
    0x15, 0x00,
    0x25, 0x01,
    0x95, 0x03,        // Report Count (3)
    0x75, 0x01,        // Report Size (1)
    0x81, 0x02,        // Input (Data, Var, Abs)

    // 패딩
    0x95, 0x01,
    0x75, 0x05,
    0x81, 0x01,

    // -------------------- X, Y, Wheel --------------------
    0x05, 0x01,        // Usage Page (Generic Desktop)
    0x09, 0x30,        // Usage (X)
    0x09, 0x31,        // Usage (Y)
    0x09, 0x38,        // Usage (Wheel)
    0x15, 0x81,        // Logical Minimum (-127)
    0x25, 0x7F,        // Logical Maximum (127)
    0x75, 0x08,        // Report Size (8)
    0x95, 0x03,        // Report Count (3)
    0x81, 0x06,        // Input (Data, Var, Rel)

    0xC0,              // End Collection (Physical)
    0xC0               // End Collection (Application)

};

test code

bool usbHidSendReport(uint8_t *report, uint16_t len)
{
  if (is_usb_mode != USB_HID_MODE) return false;

  if (USBD_HID_SendReport(&hUsbDeviceFS, report, len) == USBD_OK)
  {
    return true;
  }
  return false;
}
typedef struct {
    uint8_t modifier;
    uint8_t key_code;
    bool    last_state;
} macro_key_t;

// 6개의 매크로 버튼 설정 (Ctrl+S, Ctrl+C 등)
static macro_key_t macro_tbl[6] = {
    {0x01, 0x16, false}, // KEY1: Ctrl + S
    {0x01, 0x06, false}, // KEY2: Ctrl + C
    {0x01, 0x19, false}, // KEY3: Ctrl + V
    {0x00, 0x28, false}, // KEY4: Enter (예시)
    {0x00, 0x3B, false}, // KEY5: F1 (예시)
    {0x08, 0x15, false}, // KEY6: Win + R (유튜브 실행용 시작)
};

void macroUpdate(void) {
  // 1. 매크로 버튼 처리
  for (int i=0; i<6; i++) {
    bool current_state = buttonGetPressed(i + 2); // KEY1은 인덱스 2부터 시작

    if (current_state == true && macro_tbl[i].last_state == false) {
      // 버튼을 새로 눌렀을 때 USB 전송 (Keyboard Report ID: 1)
      uint8_t report[9] = {1, macro_tbl[i].modifier, 0, macro_tbl[i].key_code, 0, 0, 0, 0, 0};
      usbHidSendReport(report, 9);
      printf("hid event\\r\\n");

      macro_tbl[i].last_state = true;
    }
    else if (current_state == false && macro_tbl[i].last_state == true) {
      // 버튼을 뗐을 때 (Key Release)
      uint8_t report[9] = {1, 0, 0, 0, 0, 0, 0, 0, 0};
      usbHidSendReport(report, 9);

      macro_tbl[i].last_state = false;
    }
  }

https://en.wikipedia.org/wiki/USB_human_interface_device_class

https://www.usb.org/hid

https://adafruit-playground.com/u/Gamblor21/pages/a-beginners-guide-to-writing-usb-hid-report-descriptors-by-a-beginner