USB HID Controller Design


Worked for 6 mins 16 sec
Referenced guidance.
Thought for 13 sec — Clarifying task execution
Thought for 2 sec — Considering skills placement routing
Referenced guidance.
Thought for 3 sec — Checking board inspections
Board check passed
Checking routing progress.
Completed 5 design checks — found 128 issues.
Getting a layout overview.
Reading schematic details.
Thought for 13 sec — Considering board placement issues
Moved footprints.
Moving footprints.
Moving footprints.
Moving footprints.
Thought for 3 sec — Following tool instructions
Moving footprints.
Moving footprints.
Thought for 3 sec
Moving footprints.
Thought for 2 sec
Moving footprints.
Thought for 4 sec
Moving footprints.
Thought for 19 sec — Evaluating crystal placement
Moving footprints.
Thought for 23 sec — Considering rotation settings
Moving footprints.
Moving footprints.
Thought for 38 sec
Moving footprints.
Moving footprints.
Thought for 3 sec — Assessing placement options
Moving footprints.
Thought for 25 sec
Moving footprints.
Moving footprints.
Thought for 13 sec
Moving footprints.
Moving footprints.
Thought for 5 sec
Moving footprints.
Moving footprints.
Thought for 13 sec — Evaluating constraints and positioning
Moving footprints.
Moving footprints.
Thought for 8 sec
Moving footprints.
Moving footprints.
Thought for 21 sec
Moving footprints.
Moving footprints.
Thought for 14 sec
Moving footprints.
Moving footprints.
Thought for 5 sec
Moving footprints.
Moving footprints.
Thought for 19 sec
Moving footprints.
Thought for 3 sec
Moving footprints.
Moving footprints.
Thought for 2 sec
Moving footprints.
Moving footprints.
Thought for 2 sec
Moving footprints.
Moving footprints.
Thought for 3 sec — Addressing placement issues
Moving footprints.
Thought for 59 sec
Moving footprints.
Thought for a moment — Adjusting component spacing
Moving footprints.
Moving footprints.
Moving footprints.

Firmware Getting Started
Toolchain
PlatformIO (atmelavr) + Arduino framework. USB HID mode is Joystick because it naturally supports seven simultaneous buttons and signed turntable motion without keyboard rollover/OS repeat semantics.
platformio.ini:

Ini


[env:leonardo]
platform = atmelavr
board = leonardo
framework = arduino
lib_deps = mheironimus/Joystick@^2.1.1
build_flags = -D USB_VID=0x1209 -D USB_PID=0x32D4
Schematic pin map

Table


FunctionATmega32U4 pinArduino Leonardo pin
BTN1PD44
BTN2PD612
BTN3PD76
BTN4PB48
BTN5PB59
BTN6PB610
BTN7PC65
Encoder APD0 / INT03
Encoder BPD1 / INT12
Encoder IndexPD2 / INT20
U3 is an inverter on all encoder channels; A and B are both inverted, so quadrature direction remains consistent. If mechanics report reversed direction, negate the delta in firmware.
Starter firmware (src/main.cpp)

Cpp


#include <Arduino.h>
#include <Joystick.h>

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK,
  7, 0, true, false, false, false, false, false, false, false, false, false, false);

constexpr uint8_t BTN[7] = {4, 12, 6, 8, 9, 10, 5};
constexpr uint8_t ENC_A = 3, ENC_B = 2, ENC_I = 0;
constexpr uint16_t DEBOUNCE_US = 1000;
volatile int16_t encoderDelta = 0;
volatile uint8_t prevAB = 0;

void encoderISR() {
  uint8_t ab = (digitalRead(ENC_A) << 1) | digitalRead(ENC_B);
  static const int8_t qdec[16] = {0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0};
  encoderDelta += qdec[(prevAB << 2) | ab];
  prevAB = ab;
}

void setup() {
  for (uint8_t p : BTN) pinMode(p, INPUT_PULLUP);
  pinMode(ENC_A, INPUT); pinMode(ENC_B, INPUT); pinMode(ENC_I, INPUT);
  prevAB = (digitalRead(ENC_A) << 1) | digitalRead(ENC_B);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENC_B), encoderISR, CHANGE);
  Joystick.setXAxisRange(-32767, 32767);
  Joystick.begin(false);
}

void loop() {
  static bool stable[7] = {false};
  static bool candidate[7] = {false};
  static uint32_t changedAt[7] = {};
  uint32_t now = micros();
  bool dirty = false;
  for (uint8_t i=0; i<7; ++i) {
    bool pressed = !digitalRead(BTN[i]);
    if (pressed != candidate[i]) { candidate[i] = pressed; changedAt[i] = now; }
    if (candidate[i] != stable[i] && uint32_t(now-changedAt[i]) >= DEBOUNCE_US) {
      stable[i] = candidate[i]; Joystick.setButton(i, stable[i]); dirty = true;
    }
  }
  noInterrupts(); int16_t d = encoderDelta; encoderDelta = 0; interrupts();
  if (d) {
    static int16_t turntable = 0;
    turntable = int16_t(uint16_t(turntable) + uint16_t(d)); // wrapping position axis
    Joystick.setXAxis(turntable);
    dirty = true;
  }
  if (dirty) Joystick.sendState();
}
Debounce strategy
  • Hardware: each button has 100 Ω series + 1 nF shunt (100 ns pole), small enough not to add perceptible rhythm-game latency; C12–C18 may be DNP for firmware-only filtering.
  • Firmware: 1.0 ms stable-time debounce, independently tracked for all seven buttons; no blocking delays.
  • Encoder: no millisecond debounce. Both edges use interrupt-driven 4-state transition table; illegal transitions contribute zero.
Clock and USB notes
  • 16 MHz external crystal; bootloader/fuses must select external crystal and disable clock divide-by-8.
  • Blank ATmega32U4-AU ships without a USB bootloader. Program Caterina-compatible bootloader or application through J2 ICSP first.
  • The starter exposes the turntable as a wrapping signed X position axis, which is broadly compatible and fully functional. Software that requires a relative axis can replace only the HID descriptor/report layer while retaining the same ISR and encoderDelta logic.
Build

Bash


pio run
pio run -t upload
First programming of a blank MCU is through AVR ISP, not USB upload.
  • Toolchain

  • Schematic pin map

  • Starter firmware (`src/main.cpp`)

  • Debounce strategy

  • Clock and USB notes

  • Build