USB HID Controller Design
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
Table
| Function | ATmega32U4 pin | Arduino Leonardo pin |
|---|---|---|
| BTN1 | PD4 | 4 |
| BTN2 | PD6 | 12 |
| BTN3 | PD7 | 6 |
| BTN4 | PB4 | 8 |
| BTN5 | PB5 | 9 |
| BTN6 | PB6 | 10 |
| BTN7 | PC6 | 5 |
| Encoder A | PD0 / INT0 | 3 |
| Encoder B | PD1 / INT1 | 2 |
| Encoder Index | PD2 / INT2 | 0 |
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(); }
encoderDelta logic.Bash
pio run pio run -t upload
Toolchain
Schematic pin map
Starter firmware (`src/main.cpp`)
Debounce strategy
Clock and USB notes
Build