diff options
Diffstat (limited to 'tmk_core/protocol/lufa')
-rw-r--r-- | tmk_core/protocol/lufa/adafruit_ble.cpp | 823 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/adafruit_ble.h | 60 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/bluetooth.c | 36 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/bluetooth.h | 79 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/descriptor.c | 987 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/descriptor.h | 271 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/lufa.c | 1293 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/lufa.h | 96 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/outputselect.c | 56 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/outputselect.h | 40 | ||||
-rw-r--r-- | tmk_core/protocol/lufa/ringbuffer.hpp | 66 |
11 files changed, 3807 insertions, 0 deletions
diff --git a/tmk_core/protocol/lufa/adafruit_ble.cpp b/tmk_core/protocol/lufa/adafruit_ble.cpp new file mode 100644 index 0000000000..bee6bb2c19 --- /dev/null +++ b/tmk_core/protocol/lufa/adafruit_ble.cpp @@ -0,0 +1,823 @@ +#include "adafruit_ble.h" +#include <stdio.h> +#include <stdlib.h> +#include <alloca.h> +#include <util/delay.h> +#include <util/atomic.h> +#include "debug.h" +#include "pincontrol.h" +#include "timer.h" +#include "action_util.h" +#include "ringbuffer.hpp" +#include <string.h> + +// These are the pin assignments for the 32u4 boards. +// You may define them to something else in your config.h +// if yours is wired up differently. +#ifndef AdafruitBleResetPin +#define AdafruitBleResetPin D4 +#endif + +#ifndef AdafruitBleCSPin +#define AdafruitBleCSPin B4 +#endif + +#ifndef AdafruitBleIRQPin +#define AdafruitBleIRQPin E6 +#endif + + +#define SAMPLE_BATTERY +#define ConnectionUpdateInterval 1000 /* milliseconds */ + +static struct { + bool is_connected; + bool initialized; + bool configured; + +#define ProbedEvents 1 +#define UsingEvents 2 + bool event_flags; + +#ifdef SAMPLE_BATTERY + uint16_t last_battery_update; + uint32_t vbat; +#endif + uint16_t last_connection_update; +} state; + +// Commands are encoded using SDEP and sent via SPI +// https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md + +#define SdepMaxPayload 16 +struct sdep_msg { + uint8_t type; + uint8_t cmd_low; + uint8_t cmd_high; + struct __attribute__((packed)) { + uint8_t len:7; + uint8_t more:1; + }; + uint8_t payload[SdepMaxPayload]; +} __attribute__((packed)); + +// The recv latency is relatively high, so when we're hammering keys quickly, +// we want to avoid waiting for the responses in the matrix loop. We maintain +// a short queue for that. Since there is quite a lot of space overhead for +// the AT command representation wrapped up in SDEP, we queue the minimal +// information here. + +enum queue_type { + QTKeyReport, // 1-byte modifier + 6-byte key report + QTConsumer, // 16-bit key code +#ifdef MOUSE_ENABLE + QTMouseMove, // 4-byte mouse report +#endif +}; + +struct queue_item { + enum queue_type queue_type; + uint16_t added; + union __attribute__((packed)) { + struct __attribute__((packed)) { + uint8_t modifier; + uint8_t keys[6]; + } key; + + uint16_t consumer; + struct __attribute__((packed)) { + int8_t x, y, scroll, pan; + uint8_t buttons; + } mousemove; + }; +}; + +// Items that we wish to send +static RingBuffer<queue_item, 40> send_buf; +// Pending response; while pending, we can't send any more requests. +// This records the time at which we sent the command for which we +// are expecting a response. +static RingBuffer<uint16_t, 2> resp_buf; + +static bool process_queue_item(struct queue_item *item, uint16_t timeout); + +enum sdep_type { + SdepCommand = 0x10, + SdepResponse = 0x20, + SdepAlert = 0x40, + SdepError = 0x80, + SdepSlaveNotReady = 0xfe, // Try again later + SdepSlaveOverflow = 0xff, // You read more data than is available +}; + +enum ble_cmd { + BleInitialize = 0xbeef, + BleAtWrapper = 0x0a00, + BleUartTx = 0x0a01, + BleUartRx = 0x0a02, +}; + +enum ble_system_event_bits { + BleSystemConnected = 0, + BleSystemDisconnected = 1, + BleSystemUartRx = 8, + BleSystemMidiRx = 10, +}; + +// The SDEP.md file says 2MHz but the web page and the sample driver +// both use 4MHz +#define SpiBusSpeed 4000000 + +#define SdepTimeout 150 /* milliseconds */ +#define SdepShortTimeout 10 /* milliseconds */ +#define SdepBackOff 25 /* microseconds */ +#define BatteryUpdateInterval 10000 /* milliseconds */ + +static bool at_command(const char *cmd, char *resp, uint16_t resplen, + bool verbose, uint16_t timeout = SdepTimeout); +static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, + bool verbose = false); + +struct SPI_Settings { + uint8_t spcr, spsr; +}; + +static struct SPI_Settings spi; + +// Initialize 4Mhz MSBFIRST MODE0 +void SPI_init(struct SPI_Settings *spi) { + spi->spcr = _BV(SPE) | _BV(MSTR); + spi->spsr = _BV(SPI2X); + + static_assert(SpiBusSpeed == F_CPU / 2, "hard coded at 4Mhz"); + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Ensure that SS is OUTPUT High + digitalWrite(B0, PinLevelHigh); + pinMode(B0, PinDirectionOutput); + + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); + pinMode(B1 /* SCK */, PinDirectionOutput); + pinMode(B2 /* MOSI */, PinDirectionOutput); + } +} + +static inline void SPI_begin(struct SPI_Settings*spi) { + SPCR = spi->spcr; + SPSR = spi->spsr; +} + +static inline uint8_t SPI_TransferByte(uint8_t data) { + SPDR = data; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) { + ; // wait + } + return SPDR; +} + +static inline void spi_send_bytes(const uint8_t *buf, uint8_t len) { + if (len == 0) return; + const uint8_t *end = buf + len; + while (buf < end) { + SPDR = *buf; + while (!(SPSR & _BV(SPIF))) { + ; // wait + } + ++buf; + } +} + +static inline uint16_t spi_read_byte(void) { + return SPI_TransferByte(0x00 /* dummy */); +} + +static inline void spi_recv_bytes(uint8_t *buf, uint8_t len) { + const uint8_t *end = buf + len; + if (len == 0) return; + while (buf < end) { + SPDR = 0; // write a dummy to initiate read + while (!(SPSR & _BV(SPIF))) { + ; // wait + } + *buf = SPDR; + ++buf; + } +} + +#if 0 +static void dump_pkt(const struct sdep_msg *msg) { + print("pkt: type="); + print_hex8(msg->type); + print(" cmd="); + print_hex8(msg->cmd_high); + print_hex8(msg->cmd_low); + print(" len="); + print_hex8(msg->len); + print(" more="); + print_hex8(msg->more); + print("\n"); +} +#endif + +// Send a single SDEP packet +static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { + SPI_begin(&spi); + + digitalWrite(AdafruitBleCSPin, PinLevelLow); + uint16_t timerStart = timer_read(); + bool success = false; + bool ready = false; + + do { + ready = SPI_TransferByte(msg->type) != SdepSlaveNotReady; + if (ready) { + break; + } + + // Release it and let it initialize + digitalWrite(AdafruitBleCSPin, PinLevelHigh); + _delay_us(SdepBackOff); + digitalWrite(AdafruitBleCSPin, PinLevelLow); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + // Slave is ready; send the rest of the packet + spi_send_bytes(&msg->cmd_low, + sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len); + success = true; + } + + digitalWrite(AdafruitBleCSPin, PinLevelHigh); + + return success; +} + +static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command, + const uint8_t *payload, uint8_t len, + bool moredata) { + msg->type = SdepCommand; + msg->cmd_low = command & 0xff; + msg->cmd_high = command >> 8; + msg->len = len; + msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0; + + static_assert(sizeof(*msg) == 20, "msg is correctly packed"); + + memcpy(msg->payload, payload, len); +} + +// Read a single SDEP packet +static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) { + bool success = false; + uint16_t timerStart = timer_read(); + bool ready = false; + + do { + ready = digitalRead(AdafruitBleIRQPin); + if (ready) { + break; + } + _delay_us(1); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + SPI_begin(&spi); + + digitalWrite(AdafruitBleCSPin, PinLevelLow); + + do { + // Read the command type, waiting for the data to be ready + msg->type = spi_read_byte(); + if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) { + // Release it and let it initialize + digitalWrite(AdafruitBleCSPin, PinLevelHigh); + _delay_us(SdepBackOff); + digitalWrite(AdafruitBleCSPin, PinLevelLow); + continue; + } + + // Read the rest of the header + spi_recv_bytes(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload))); + + // and get the payload if there is any + if (msg->len <= SdepMaxPayload) { + spi_recv_bytes(msg->payload, msg->len); + } + success = true; + break; + } while (timer_elapsed(timerStart) < timeout); + + digitalWrite(AdafruitBleCSPin, PinLevelHigh); + } + return success; +} + +static void resp_buf_read_one(bool greedy) { + uint16_t last_send; + if (!resp_buf.peek(last_send)) { + return; + } + + if (digitalRead(AdafruitBleIRQPin)) { + struct sdep_msg msg; + +again: + if (sdep_recv_pkt(&msg, SdepTimeout)) { + if (!msg.more) { + // We got it; consume this entry + resp_buf.get(last_send); + dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); + } + + if (greedy && resp_buf.peek(last_send) && digitalRead(AdafruitBleIRQPin)) { + goto again; + } + } + + } else if (timer_elapsed(last_send) > SdepTimeout * 2) { + dprintf("waiting_for_result: timeout, resp_buf size %d\n", + (int)resp_buf.size()); + + // Timed out: consume this entry + resp_buf.get(last_send); + } +} + +static void send_buf_send_one(uint16_t timeout = SdepTimeout) { + struct queue_item item; + + // Don't send anything more until we get an ACK + if (!resp_buf.empty()) { + return; + } + + if (!send_buf.peek(item)) { + return; + } + if (process_queue_item(&item, timeout)) { + // commit that peek + send_buf.get(item); + dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size()); + } else { + dprint("failed to send, will retry\n"); + _delay_ms(SdepTimeout); + resp_buf_read_one(true); + } +} + +static void resp_buf_wait(const char *cmd) { + bool didPrint = false; + while (!resp_buf.empty()) { + if (!didPrint) { + dprintf("wait on buf for %s\n", cmd); + didPrint = true; + } + resp_buf_read_one(true); + } +} + +static bool ble_init(void) { + state.initialized = false; + state.configured = false; + state.is_connected = false; + + pinMode(AdafruitBleIRQPin, PinDirectionInput); + pinMode(AdafruitBleCSPin, PinDirectionOutput); + digitalWrite(AdafruitBleCSPin, PinLevelHigh); + + SPI_init(&spi); + + // Perform a hardware reset + pinMode(AdafruitBleResetPin, PinDirectionOutput); + digitalWrite(AdafruitBleResetPin, PinLevelHigh); + digitalWrite(AdafruitBleResetPin, PinLevelLow); + _delay_ms(10); + digitalWrite(AdafruitBleResetPin, PinLevelHigh); + + _delay_ms(1000); // Give it a second to initialize + + state.initialized = true; + return state.initialized; +} + +static inline uint8_t min(uint8_t a, uint8_t b) { + return a < b ? a : b; +} + +static bool read_response(char *resp, uint16_t resplen, bool verbose) { + char *dest = resp; + char *end = dest + resplen; + + while (true) { + struct sdep_msg msg; + + if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) { + dprint("sdep_recv_pkt failed\n"); + return false; + } + + if (msg.type != SdepResponse) { + *resp = 0; + return false; + } + + uint8_t len = min(msg.len, end - dest); + if (len > 0) { + memcpy(dest, msg.payload, len); + dest += len; + } + + if (!msg.more) { + // No more data is expected! + break; + } + } + + // Ensure the response is NUL terminated + *dest = 0; + + // "Parse" the result text; we want to snip off the trailing OK or ERROR line + // Rewind past the possible trailing CRLF so that we can strip it + --dest; + while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) { + *dest = 0; + --dest; + } + + // Look back for start of preceeding line + char *last_line = strrchr(resp, '\n'); + if (last_line) { + ++last_line; + } else { + last_line = resp; + } + + bool success = false; + static const char kOK[] PROGMEM = "OK"; + + success = !strcmp_P(last_line, kOK ); + + if (verbose || !success) { + dprintf("result: %s\n", resp); + } + return success; +} + +static bool at_command(const char *cmd, char *resp, uint16_t resplen, + bool verbose, uint16_t timeout) { + const char *end = cmd + strlen(cmd); + struct sdep_msg msg; + + if (verbose) { + dprintf("ble send: %s\n", cmd); + } + + if (resp) { + // They want to decode the response, so we need to flush and wait + // for all pending I/O to finish before we start this one, so + // that we don't confuse the results + resp_buf_wait(cmd); + *resp = 0; + } + + // Fragment the command into a series of SDEP packets + while (end - cmd > SdepMaxPayload) { + sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true); + if (!sdep_send_pkt(&msg, timeout)) { + return false; + } + cmd += SdepMaxPayload; + } + + sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false); + if (!sdep_send_pkt(&msg, timeout)) { + return false; + } + + if (resp == NULL) { + auto now = timer_read(); + while (!resp_buf.enqueue(now)) { + resp_buf_read_one(false); + } + auto later = timer_read(); + if (TIMER_DIFF_16(later, now) > 0) { + dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now)); + } + return true; + } + + return read_response(resp, resplen, verbose); +} + +bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) { + auto cmdbuf = (char *)alloca(strlen_P(cmd) + 1); + strcpy_P(cmdbuf, cmd); + return at_command(cmdbuf, resp, resplen, verbose); +} + +bool adafruit_ble_is_connected(void) { + return state.is_connected; +} + +bool adafruit_ble_enable_keyboard(void) { + char resbuf[128]; + + if (!state.initialized && !ble_init()) { + return false; + } + + state.configured = false; + + // Disable command echo + static const char kEcho[] PROGMEM = "ATE=0"; + // Make the advertised name match the keyboard + static const char kGapDevName[] PROGMEM = + "AT+GAPDEVNAME=" STR(PRODUCT) " " STR(DESCRIPTION); + // Turn on keyboard support + static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1"; + + // Adjust intervals to improve latency. This causes the "central" + // system (computer/tablet) to poll us every 10-30 ms. We can't + // set a smaller value than 10ms, and 30ms seems to be the natural + // processing time on my macbook. Keeping it constrained to that + // feels reasonable to type to. + static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,"; + + // Reset the device so that it picks up the above changes + static const char kATZ[] PROGMEM = "ATZ"; + + // Turn down the power level a bit + static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12"; + static PGM_P const configure_commands[] PROGMEM = { + kEcho, + kGapIntervals, + kGapDevName, + kHidEnOn, + kPower, + kATZ, + }; + + uint8_t i; + for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]); + ++i) { + PGM_P cmd; + memcpy_P(&cmd, configure_commands + i, sizeof(cmd)); + + if (!at_command_P(cmd, resbuf, sizeof(resbuf))) { + dprintf("failed BLE command: %S: %s\n", cmd, resbuf); + goto fail; + } + } + + state.configured = true; + + // Check connection status in a little while; allow the ATZ time + // to kick in. + state.last_connection_update = timer_read(); +fail: + return state.configured; +} + +static void set_connected(bool connected) { + if (connected != state.is_connected) { + if (connected) { + print("****** BLE CONNECT!!!!\n"); + } else { + print("****** BLE DISCONNECT!!!!\n"); + } + state.is_connected = connected; + + // TODO: if modifiers are down on the USB interface and + // we cut over to BLE or vice versa, they will remain stuck. + // This feels like a good point to do something like clearing + // the keyboard and/or generating a fake all keys up message. + // However, I've noticed that it takes a couple of seconds + // for macOS to to start recognizing key presses after BLE + // is in the connected state, so I worry that doing that + // here may not be good enough. + } +} + +void adafruit_ble_task(void) { + char resbuf[48]; + + if (!state.configured && !adafruit_ble_enable_keyboard()) { + return; + } + resp_buf_read_one(true); + send_buf_send_one(SdepShortTimeout); + + if (resp_buf.empty() && (state.event_flags & UsingEvents) && + digitalRead(AdafruitBleIRQPin)) { + // Must be an event update + if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { + uint32_t mask = strtoul(resbuf, NULL, 16); + + if (mask & BleSystemConnected) { + set_connected(true); + } else if (mask & BleSystemDisconnected) { + set_connected(false); + } + } + } + + if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) { + bool shouldPoll = true; + if (!(state.event_flags & ProbedEvents)) { + // Request notifications about connection status changes. + // This only works in SPIFRIEND firmware > 0.6.7, which is why + // we check for this conditionally here. + // Note that at the time of writing, HID reports only work correctly + // with Apple products on firmware version 0.6.7! + // https://forums.adafruit.com/viewtopic.php?f=8&t=104052 + if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) { + at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf)); + state.event_flags |= UsingEvents; + } + state.event_flags |= ProbedEvents; + + // leave shouldPoll == true so that we check at least once + // before relying solely on events + } else { + shouldPoll = false; + } + + static const char kGetConn[] PROGMEM = "AT+GAPGETCONN"; + state.last_connection_update = timer_read(); + + if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) { + set_connected(atoi(resbuf)); + } + } + +#ifdef SAMPLE_BATTERY + // I don't know if this really does anything useful yet; the reported + // voltage level always seems to be around 3200mV. We may want to just rip + // this code out. + if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval && + resp_buf.empty()) { + state.last_battery_update = timer_read(); + + if (at_command_P(PSTR("AT+HWVBAT"), resbuf, sizeof(resbuf))) { + state.vbat = atoi(resbuf); + } + } +#endif +} + +static bool process_queue_item(struct queue_item *item, uint16_t timeout) { + char cmdbuf[48]; + char fmtbuf[64]; + + // Arrange to re-check connection after keys have settled + state.last_connection_update = timer_read(); + +#if 1 + if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) { + dprintf("send latency %dms\n", + TIMER_DIFF_16(state.last_connection_update, item->added)); + } +#endif + + switch (item->queue_type) { + case QTKeyReport: + strcpy_P(fmtbuf, + PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier, + item->key.keys[0], item->key.keys[1], item->key.keys[2], + item->key.keys[3], item->key.keys[4], item->key.keys[5]); + return at_command(cmdbuf, NULL, 0, true, timeout); + + case QTConsumer: + strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer); + return at_command(cmdbuf, NULL, 0, true, timeout); + +#ifdef MOUSE_ENABLE + case QTMouseMove: + strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x, + item->mousemove.y, item->mousemove.scroll, item->mousemove.pan); + if (!at_command(cmdbuf, NULL, 0, true, timeout)) { + return false; + } + strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); + if (item->mousemove.buttons & MOUSE_BTN1) { + strcat(cmdbuf, "L"); + } + if (item->mousemove.buttons & MOUSE_BTN2) { + strcat(cmdbuf, "R"); + } + if (item->mousemove.buttons & MOUSE_BTN3) { + strcat(cmdbuf, "M"); + } + if (item->mousemove.buttons == 0) { + strcat(cmdbuf, "0"); + } + return at_command(cmdbuf, NULL, 0, true, timeout); +#endif + default: + return true; + } +} + +bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, + uint8_t nkeys) { + struct queue_item item; + bool didWait = false; + + item.queue_type = QTKeyReport; + item.key.modifier = hid_modifier_mask; + item.added = timer_read(); + + while (nkeys >= 0) { + item.key.keys[0] = keys[0]; + item.key.keys[1] = nkeys >= 1 ? keys[1] : 0; + item.key.keys[2] = nkeys >= 2 ? keys[2] : 0; + item.key.keys[3] = nkeys >= 3 ? keys[3] : 0; + item.key.keys[4] = nkeys >= 4 ? keys[4] : 0; + item.key.keys[5] = nkeys >= 5 ? keys[5] : 0; + + if (!send_buf.enqueue(item)) { + if (!didWait) { + dprint("wait for buf space\n"); + didWait = true; + } + send_buf_send_one(); + continue; + } + + if (nkeys <= 6) { + return true; + } + + nkeys -= 6; + keys += 6; + } + + return true; +} + +bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration) { + struct queue_item item; + + item.queue_type = QTConsumer; + item.consumer = keycode; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } + return true; +} + +#ifdef MOUSE_ENABLE +bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, + int8_t pan, uint8_t buttons) { + struct queue_item item; + + item.queue_type = QTMouseMove; + item.mousemove.x = x; + item.mousemove.y = y; + item.mousemove.scroll = scroll; + item.mousemove.pan = pan; + item.mousemove.buttons = buttons; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } + return true; +} +#endif + +uint32_t adafruit_ble_read_battery_voltage(void) { + return state.vbat; +} + +bool adafruit_ble_set_mode_leds(bool on) { + if (!state.configured) { + return false; + } + + // The "mode" led is the red blinky one + at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0); + + // Pin 19 is the blue "connected" LED; turn that off too. + // When turning LEDs back on, don't turn that LED on if we're + // not connected, as that would be confusing. + at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1") + : PSTR("AT+HWGPIO=19,0"), + NULL, 0); + return true; +} + +// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel +bool adafruit_ble_set_power_level(int8_t level) { + char cmd[46]; + if (!state.configured) { + return false; + } + snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level); + return at_command(cmd, NULL, 0, false); +} diff --git a/tmk_core/protocol/lufa/adafruit_ble.h b/tmk_core/protocol/lufa/adafruit_ble.h new file mode 100644 index 0000000000..036b7d14ea --- /dev/null +++ b/tmk_core/protocol/lufa/adafruit_ble.h @@ -0,0 +1,60 @@ +/* Bluetooth Low Energy Protocol for QMK. + * Author: Wez Furlong, 2016 + * Supports the Adafruit BLE board built around the nRF51822 chip. + */ +#pragma once +#ifdef MODULE_ADAFRUIT_BLE +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Instruct the module to enable HID keyboard support and reset */ +extern bool adafruit_ble_enable_keyboard(void); + +/* Query to see if the BLE module is connected */ +extern bool adafruit_ble_query_is_connected(void); + +/* Returns true if we believe that the BLE module is connected. + * This uses our cached understanding that is maintained by + * calling ble_task() periodically. */ +extern bool adafruit_ble_is_connected(void); + +/* Call this periodically to process BLE-originated things */ +extern void adafruit_ble_task(void); + +/* Generates keypress events for a set of keys. + * The hid modifier mask specifies the state of the modifier keys for + * this set of keys. + * Also sends a key release indicator, so that the keys do not remain + * held down. */ +extern bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, + uint8_t nkeys); + +/* Send a consumer keycode, holding it down for the specified duration + * (milliseconds) */ +extern bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration); + +#ifdef MOUSE_ENABLE +/* Send a mouse/wheel movement report. + * The parameters are signed and indicate positive of negative direction + * change. */ +extern bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, + int8_t pan, uint8_t buttons); +#endif + +/* Compute battery voltage by reading an analog pin. + * Returns the integer number of millivolts */ +extern uint32_t adafruit_ble_read_battery_voltage(void); + +extern bool adafruit_ble_set_mode_leds(bool on); +extern bool adafruit_ble_set_power_level(int8_t level); + +#ifdef __cplusplus +} +#endif + +#endif // MODULE_ADAFRUIT_BLE diff --git a/tmk_core/protocol/lufa/bluetooth.c b/tmk_core/protocol/lufa/bluetooth.c new file mode 100644 index 0000000000..549606162d --- /dev/null +++ b/tmk_core/protocol/lufa/bluetooth.c @@ -0,0 +1,36 @@ +/* +Bluefruit Protocol for TMK firmware +Author: Benjamin Gould, 2013 + Jack Humbert, 2015 +Based on code Copyright 2011 Jun Wako <wakojun@gmail.com> +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> +#include "report.h" +#include "print.h" +#include "debug.h" +#include "bluetooth.h" + +void bluefruit_keyboard_print_report(report_keyboard_t *report) +{ + if (!debug_keyboard) return; + dprintf("keys: "); for (int i = 0; i < KEYBOARD_REPORT_KEYS; i++) { debug_hex8(report->keys[i]); dprintf(" "); } + dprintf(" mods: "); debug_hex8(report->mods); + dprintf(" reserved: "); debug_hex8(report->reserved); + dprintf("\n"); +} + +void bluefruit_serial_send(uint8_t data) +{ + serial_send(data); +}
\ No newline at end of file diff --git a/tmk_core/protocol/lufa/bluetooth.h b/tmk_core/protocol/lufa/bluetooth.h new file mode 100644 index 0000000000..f4b2f6f8b1 --- /dev/null +++ b/tmk_core/protocol/lufa/bluetooth.h @@ -0,0 +1,79 @@ +/* +Bluefruit Protocol for TMK firmware +Author: Benjamin Gould, 2013 + Jack Humbert, 2015 +Based on code Copyright 2011 Jun Wako <wakojun@gmail.com> +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef BLUETOOTH_H +#define BLUETOOTH_H + +#include "../serial.h" + +void bluefruit_serial_send(uint8_t data); + +/* ++-----------------+-------------------+-------+ +| Consumer Key | Bit Map | Hex | ++-----------------+-------------------+-------+ +| Home | 00000001 00000000 | 01 00 | +| KeyboardLayout | 00000010 00000000 | 02 00 | +| Search | 00000100 00000000 | 04 00 | +| Snapshot | 00001000 00000000 | 08 00 | +| VolumeUp | 00010000 00000000 | 10 00 | +| VolumeDown | 00100000 00000000 | 20 00 | +| Play/Pause | 01000000 00000000 | 40 00 | +| Fast Forward | 10000000 00000000 | 80 00 | +| Rewind | 00000000 00000001 | 00 01 | +| Scan Next Track | 00000000 00000010 | 00 02 | +| Scan Prev Track | 00000000 00000100 | 00 04 | +| Random Play | 00000000 00001000 | 00 08 | +| Stop | 00000000 00010000 | 00 10 | ++-------------------------------------+-------+ +*/ +#define CONSUMER2BLUEFRUIT(usage) \ + (usage == AUDIO_MUTE ? 0x0000 : \ + (usage == AUDIO_VOL_UP ? 0x1000 : \ + (usage == AUDIO_VOL_DOWN ? 0x2000 : \ + (usage == TRANSPORT_NEXT_TRACK ? 0x0002 : \ + (usage == TRANSPORT_PREV_TRACK ? 0x0004 : \ + (usage == TRANSPORT_STOP ? 0x0010 : \ + (usage == TRANSPORT_STOP_EJECT ? 0x0000 : \ + (usage == TRANSPORT_PLAY_PAUSE ? 0x4000 : \ + (usage == AL_CC_CONFIG ? 0x0000 : \ + (usage == AL_EMAIL ? 0x0000 : \ + (usage == AL_CALCULATOR ? 0x0000 : \ + (usage == AL_LOCAL_BROWSER ? 0x0000 : \ + (usage == AC_SEARCH ? 0x0400 : \ + (usage == AC_HOME ? 0x0100 : \ + (usage == AC_BACK ? 0x0000 : \ + (usage == AC_FORWARD ? 0x0000 : \ + (usage == AC_STOP ? 0x0000 : \ + (usage == AC_REFRESH ? 0x0000 : \ + (usage == AC_BOOKMARKS ? 0x0000 : 0))))))))))))))))))) + +#define CONSUMER2RN42(usage) \ + (usage == AUDIO_MUTE ? 0x0040 : \ + (usage == AUDIO_VOL_UP ? 0x0010 : \ + (usage == AUDIO_VOL_DOWN ? 0x0020 : \ + (usage == TRANSPORT_NEXT_TRACK ? 0x0100 : \ + (usage == TRANSPORT_PREV_TRACK ? 0x0200 : \ + (usage == TRANSPORT_STOP ? 0x0400 : \ + (usage == TRANSPORT_STOP_EJECT ? 0x0800 : \ + (usage == TRANSPORT_PLAY_PAUSE ? 0x0080 : \ + (usage == AL_EMAIL ? 0x0200 : \ + (usage == AL_LOCAL_BROWSER ? 0x8000 : \ + (usage == AC_SEARCH ? 0x0400 : \ + (usage == AC_HOME ? 0x0100 : 0)))))))))))) + + #endif diff --git a/tmk_core/protocol/lufa/descriptor.c b/tmk_core/protocol/lufa/descriptor.c new file mode 100644 index 0000000000..357954368b --- /dev/null +++ b/tmk_core/protocol/lufa/descriptor.c @@ -0,0 +1,987 @@ +/* + * Copyright 2012 Jun Wako <wakojun@gmail.com> + * This file is based on: + * LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse + * LUFA-120219/Demos/Device/Lowlevel/GenericHID + */ + +/* + LUFA Library + Copyright (C) Dean Camera, 2012. + + dean [at] fourwalledcubicle [dot] com + www.lufa-lib.org +*/ + +/* + Copyright 2012 Dean Camera (dean [at] fourwalledcubicle [dot] com) + Copyright 2010 Denver Gingerich (denver [at] ossguy [dot] com) + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that the copyright notice and this + permission notice and warranty disclaimer appear in supporting + documentation, and that the name of the author not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. + + The author disclaim all warranties with regard to this + software, including all implied warranties of merchantability + and fitness. In no event shall the author be liable for any + special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether + in an action of contract, negligence or other tortious action, + arising out of or in connection with the use or performance of + this software. +*/ + +#include "util.h" +#include "report.h" +#include "descriptor.h" + +#ifndef USB_MAX_POWER_CONSUMPTION +#define USB_MAX_POWER_CONSUMPTION 500 +#endif + +/******************************************************************************* + * HID Report Descriptors + ******************************************************************************/ +const USB_Descriptor_HIDReport_Datatype_t PROGMEM KeyboardReport[] = +{ + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x06), /* Keyboard */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE_PAGE(8, 0x07), /* Key Codes */ + HID_RI_USAGE_MINIMUM(8, 0xE0), /* Keyboard Left Control */ + HID_RI_USAGE_MAXIMUM(8, 0xE7), /* Keyboard Right GUI */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, 0x08), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_CONSTANT), /* reserved */ + + HID_RI_USAGE_PAGE(8, 0x08), /* LEDs */ + HID_RI_USAGE_MINIMUM(8, 0x01), /* Num Lock */ + HID_RI_USAGE_MAXIMUM(8, 0x05), /* Kana */ + HID_RI_REPORT_COUNT(8, 0x05), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x03), + HID_RI_OUTPUT(8, HID_IOF_CONSTANT), + + HID_RI_USAGE_PAGE(8, 0x07), /* Keyboard */ + HID_RI_USAGE_MINIMUM(8, 0x00), /* Reserved (no event indicated) */ + HID_RI_USAGE_MAXIMUM(8, 0xFF), /* Keyboard Application */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, 0x06), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_RI_END_COLLECTION(0), +}; + +#ifdef MOUSE_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM MouseReport[] = +{ + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x02), /* Mouse */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE(8, 0x01), /* Pointer */ + HID_RI_COLLECTION(8, 0x00), /* Physical */ + + HID_RI_USAGE_PAGE(8, 0x09), /* Button */ + HID_RI_USAGE_MINIMUM(8, 0x01), /* Button 1 */ + HID_RI_USAGE_MAXIMUM(8, 0x05), /* Button 5 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, 0x05), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x03), + HID_RI_INPUT(8, HID_IOF_CONSTANT), + + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x30), /* Usage X */ + HID_RI_USAGE(8, 0x31), /* Usage Y */ + HID_RI_LOGICAL_MINIMUM(8, -127), + HID_RI_LOGICAL_MAXIMUM(8, 127), + HID_RI_REPORT_COUNT(8, 0x02), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + + HID_RI_USAGE(8, 0x38), /* Wheel */ + HID_RI_LOGICAL_MINIMUM(8, -127), + HID_RI_LOGICAL_MAXIMUM(8, 127), + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + + HID_RI_USAGE_PAGE(8, 0x0C), /* Consumer */ + HID_RI_USAGE(16, 0x0238), /* AC Pan (Horizontal wheel) */ + HID_RI_LOGICAL_MINIMUM(8, -127), + HID_RI_LOGICAL_MAXIMUM(8, 127), + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + + HID_RI_END_COLLECTION(0), + HID_RI_END_COLLECTION(0), +}; +#endif + +#ifdef EXTRAKEY_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM ExtrakeyReport[] = +{ + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x80), /* System Control */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_REPORT_ID(8, REPORT_ID_SYSTEM), + HID_RI_LOGICAL_MINIMUM(16, 0x0001), + HID_RI_LOGICAL_MAXIMUM(16, 0x0003), + HID_RI_USAGE_MINIMUM(16, 0x0081), /* System Power Down */ + HID_RI_USAGE_MAXIMUM(16, 0x0083), /* System Wake Up */ + HID_RI_REPORT_SIZE(8, 16), + HID_RI_REPORT_COUNT(8, 1), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_RI_END_COLLECTION(0), + + HID_RI_USAGE_PAGE(8, 0x0C), /* Consumer */ + HID_RI_USAGE(8, 0x01), /* Consumer Control */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_REPORT_ID(8, REPORT_ID_CONSUMER), + HID_RI_LOGICAL_MINIMUM(16, 0x0001), + HID_RI_LOGICAL_MAXIMUM(16, 0x029C), + HID_RI_USAGE_MINIMUM(16, 0x0001), /* +10 */ + HID_RI_USAGE_MAXIMUM(16, 0x029C), /* AC Distribute Vertically */ + HID_RI_REPORT_SIZE(8, 16), + HID_RI_REPORT_COUNT(8, 1), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_RI_END_COLLECTION(0), +}; +#endif + +#ifdef RAW_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM RawReport[] = +{ + HID_RI_USAGE_PAGE(16, 0xFF60), /* Vendor Page 0xFF60 */ + HID_RI_USAGE(8, 0x61), /* Vendor Usage 0x61 */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE(8, 0x62), /* Vendor Usage 0x62 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, RAW_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_RI_USAGE(8, 0x63), /* Vendor Usage 0x63 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, RAW_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), + HID_RI_END_COLLECTION(0), +}; +#endif + +#ifdef CONSOLE_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = +{ + HID_RI_USAGE_PAGE(16, 0xFF31), /* Vendor Page(PJRC Teensy compatible) */ + HID_RI_USAGE(8, 0x74), /* Vendor Usage(PJRC Teensy compatible) */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE(8, 0x75), /* Vendor Usage 0x75 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, CONSOLE_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_RI_USAGE(8, 0x76), /* Vendor Usage 0x76 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, CONSOLE_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), + HID_RI_END_COLLECTION(0), +}; +#endif + +#ifdef NKRO_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM NKROReport[] = +{ + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x06), /* Keyboard */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE_PAGE(8, 0x07), /* Key Codes */ + HID_RI_USAGE_MINIMUM(8, 0xE0), /* Keyboard Left Control */ + HID_RI_USAGE_MAXIMUM(8, 0xE7), /* Keyboard Right GUI */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, 0x08), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_RI_USAGE_PAGE(8, 0x08), /* LEDs */ + HID_RI_USAGE_MINIMUM(8, 0x01), /* Num Lock */ + HID_RI_USAGE_MAXIMUM(8, 0x05), /* Kana */ + HID_RI_REPORT_COUNT(8, 0x05), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x03), + HID_RI_OUTPUT(8, HID_IOF_CONSTANT), + + HID_RI_USAGE_PAGE(8, 0x07), /* Key Codes */ + HID_RI_USAGE_MINIMUM(8, 0x00), /* Keyboard 0 */ + HID_RI_USAGE_MAXIMUM(8, (NKRO_EPSIZE-1)*8-1), /* Keyboard Right GUI */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, (NKRO_EPSIZE-1)*8), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_RI_END_COLLECTION(0), +}; +#endif + +/******************************************************************************* + * Device Descriptors + ******************************************************************************/ +const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = +{ + .Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device}, + + .USBSpecification = VERSION_BCD(1,1,0), +#if VIRTSER_ENABLE + .Class = USB_CSCP_IADDeviceClass, + .SubClass = USB_CSCP_IADDeviceSubclass, + .Protocol = USB_CSCP_IADDeviceProtocol, +#else + .Class = USB_CSCP_NoDeviceClass, + .SubClass = USB_CSCP_NoDeviceSubclass, + .Protocol = USB_CSCP_NoDeviceProtocol, +#endif + + .Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE, + + /* specified in config.h */ + .VendorID = VENDOR_ID, + .ProductID = PRODUCT_ID, + .ReleaseNumber = DEVICE_VER, + + .ManufacturerStrIndex = 0x01, + .ProductStrIndex = 0x02, + .SerialNumStrIndex = NO_DESCRIPTOR, + + .NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS +}; + +/******************************************************************************* + * Configuration Descriptors + ******************************************************************************/ +const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = +{ + .Config = + { + .Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration}, + + .TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t), + .TotalInterfaces = TOTAL_INTERFACES, + + .ConfigurationNumber = 1, + .ConfigurationStrIndex = NO_DESCRIPTOR, + + .ConfigAttributes = (USB_CONFIG_ATTR_RESERVED | USB_CONFIG_ATTR_REMOTEWAKEUP), + + .MaxPowerConsumption = USB_CONFIG_POWER_MA(USB_MAX_POWER_CONSUMPTION) + }, + + /* + * Keyboard + */ + .Keyboard_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = KEYBOARD_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 1, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_BootSubclass, + .Protocol = HID_CSCP_KeyboardBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Keyboard_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(KeyboardReport) + }, + + .Keyboard_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | KEYBOARD_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = KEYBOARD_EPSIZE, + .PollingIntervalMS = 0x0A + }, + + /* + * Mouse + */ +#ifdef MOUSE_ENABLE + .Mouse_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = MOUSE_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 1, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_BootSubclass, + .Protocol = HID_CSCP_MouseBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Mouse_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(MouseReport) + }, + + .Mouse_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | MOUSE_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = MOUSE_EPSIZE, + .PollingIntervalMS = 0x0A + }, +#endif + + /* + * Extra + */ +#ifdef EXTRAKEY_ENABLE + .Extrakey_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = EXTRAKEY_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 1, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Extrakey_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(ExtrakeyReport) + }, + + .Extrakey_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | EXTRAKEY_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = EXTRAKEY_EPSIZE, + .PollingIntervalMS = 0x0A + }, +#endif + + /* + * Raw + */ + #ifdef RAW_ENABLE + .Raw_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = RAW_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 2, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Raw_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(RawReport) + }, + + .Raw_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | RAW_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = RAW_EPSIZE, + .PollingIntervalMS = 0x01 + }, + + .Raw_OUTEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_OUT | RAW_OUT_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = RAW_EPSIZE, + .PollingIntervalMS = 0x01 + }, + #endif + + /* + * Console + */ +#ifdef CONSOLE_ENABLE + .Console_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = CONSOLE_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 2, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Console_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(ConsoleReport) + }, + + .Console_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | CONSOLE_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = CONSOLE_EPSIZE, + .PollingIntervalMS = 0x01 + }, + + .Console_OUTEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_OUT | CONSOLE_OUT_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = CONSOLE_EPSIZE, + .PollingIntervalMS = 0x01 + }, +#endif + + /* + * NKRO + */ +#ifdef NKRO_ENABLE + .NKRO_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = NKRO_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 1, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .NKRO_HID = + { + .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, + + .HIDSpec = VERSION_BCD(1,1,1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(NKROReport) + }, + + .NKRO_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | NKRO_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = NKRO_EPSIZE, + .PollingIntervalMS = 0x01 + }, +#endif + +#ifdef MIDI_ENABLE + .Audio_ControlInterface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = AC_INTERFACE, + .AlternateSetting = 0, + + .TotalEndpoints = 0, + + .Class = AUDIO_CSCP_AudioClass, + .SubClass = AUDIO_CSCP_ControlSubclass, + .Protocol = AUDIO_CSCP_ControlProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Audio_ControlInterface_SPC = + { + .Header = {.Size = sizeof(USB_Audio_Descriptor_Interface_AC_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_Header, + + .ACSpecification = VERSION_BCD(1,0,0), + .TotalLength = sizeof(USB_Audio_Descriptor_Interface_AC_t), + + .InCollection = 1, + .InterfaceNumber = AS_INTERFACE, + }, + + .Audio_StreamInterface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = AS_INTERFACE, + .AlternateSetting = 0, + + .TotalEndpoints = 2, + + .Class = AUDIO_CSCP_AudioClass, + .SubClass = AUDIO_CSCP_MIDIStreamingSubclass, + .Protocol = AUDIO_CSCP_StreamingProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Audio_StreamInterface_SPC = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_AudioInterface_AS_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_General, + + .AudioSpecification = VERSION_BCD(1,0,0), + + .TotalLength = (sizeof(USB_Descriptor_Configuration_t) - + offsetof(USB_Descriptor_Configuration_t, Audio_StreamInterface_SPC)) + }, + + .MIDI_In_Jack_Emb = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal, + + .JackType = MIDI_JACKTYPE_Embedded, + .JackID = 0x01, + + .JackStrIndex = NO_DESCRIPTOR + }, + + .MIDI_In_Jack_Ext = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_InputJack_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_InputTerminal, + + .JackType = MIDI_JACKTYPE_External, + .JackID = 0x02, + + .JackStrIndex = NO_DESCRIPTOR + }, + + .MIDI_Out_Jack_Emb = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal, + + .JackType = MIDI_JACKTYPE_Embedded, + .JackID = 0x03, + + .NumberOfPins = 1, + .SourceJackID = {0x02}, + .SourcePinID = {0x01}, + + .JackStrIndex = NO_DESCRIPTOR + }, + + .MIDI_Out_Jack_Ext = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_OutputJack_t), .Type = DTYPE_CSInterface}, + .Subtype = AUDIO_DSUBTYPE_CSInterface_OutputTerminal, + + .JackType = MIDI_JACKTYPE_External, + .JackID = 0x04, + + .NumberOfPins = 1, + .SourceJackID = {0x01}, + .SourcePinID = {0x01}, + + .JackStrIndex = NO_DESCRIPTOR + }, + + .MIDI_In_Jack_Endpoint = + { + .Endpoint = + { + .Header = {.Size = sizeof(USB_Audio_Descriptor_StreamEndpoint_Std_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = MIDI_STREAM_OUT_EPADDR, + .Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = MIDI_STREAM_EPSIZE, + .PollingIntervalMS = 0x05 + }, + + .Refresh = 0, + .SyncEndpointNumber = 0 + }, + + .MIDI_In_Jack_Endpoint_SPC = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_Jack_Endpoint_t), .Type = DTYPE_CSEndpoint}, + .Subtype = AUDIO_DSUBTYPE_CSEndpoint_General, + + .TotalEmbeddedJacks = 0x01, + .AssociatedJackID = {0x01} + }, + + .MIDI_Out_Jack_Endpoint = + { + .Endpoint = + { + .Header = {.Size = sizeof(USB_Audio_Descriptor_StreamEndpoint_Std_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = MIDI_STREAM_IN_EPADDR, + .Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = MIDI_STREAM_EPSIZE, + .PollingIntervalMS = 0x05 + }, + + .Refresh = 0, + .SyncEndpointNumber = 0 + }, + + .MIDI_Out_Jack_Endpoint_SPC = + { + .Header = {.Size = sizeof(USB_MIDI_Descriptor_Jack_Endpoint_t), .Type = DTYPE_CSEndpoint}, + .Subtype = AUDIO_DSUBTYPE_CSEndpoint_General, + + .TotalEmbeddedJacks = 0x01, + .AssociatedJackID = {0x03} + }, +#endif + +#ifdef VIRTSER_ENABLE + .CDC_Interface_Association = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_Association_t), .Type = DTYPE_InterfaceAssociation}, + + .FirstInterfaceIndex = CCI_INTERFACE, + .TotalInterfaces = 2, + + .Class = CDC_CSCP_CDCClass, + .SubClass = CDC_CSCP_ACMSubclass, + .Protocol = CDC_CSCP_ATCommandProtocol, + + .IADStrIndex = NO_DESCRIPTOR, + }, + + .CDC_CCI_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = CCI_INTERFACE, + .AlternateSetting = 0, + + .TotalEndpoints = 1, + + .Class = CDC_CSCP_CDCClass, + .SubClass = CDC_CSCP_ACMSubclass, + .Protocol = CDC_CSCP_ATCommandProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .CDC_Functional_Header = + { + .Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalHeader_t), .Type = DTYPE_CSInterface}, + .Subtype = 0x00, + + .CDCSpecification = VERSION_BCD(1,1,0), + }, + + .CDC_Functional_ACM = + { + .Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalACM_t), .Type = DTYPE_CSInterface}, + .Subtype = 0x02, + + .Capabilities = 0x02, + }, + + .CDC_Functional_Union = + { + .Header = {.Size = sizeof(USB_CDC_Descriptor_FunctionalUnion_t), .Type = DTYPE_CSInterface}, + .Subtype = 0x06, + + .MasterInterfaceNumber = CCI_INTERFACE, + .SlaveInterfaceNumber = CDI_INTERFACE, + }, + + .CDC_NotificationEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = CDC_NOTIFICATION_EPADDR, + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = CDC_NOTIFICATION_EPSIZE, + .PollingIntervalMS = 0xFF + }, + + .CDC_DCI_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = CDI_INTERFACE, + .AlternateSetting = 0, + + .TotalEndpoints = 2, + + .Class = CDC_CSCP_CDCDataClass, + .SubClass = CDC_CSCP_NoDataSubclass, + .Protocol = CDC_CSCP_NoDataProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .CDC_DataOutEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = CDC_OUT_EPADDR, + .Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = CDC_EPSIZE, + .PollingIntervalMS = 0x05 + }, + + .CDC_DataInEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = CDC_IN_EPADDR, + .Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = CDC_EPSIZE, + .PollingIntervalMS = 0x05 + }, +#endif +}; + + +/******************************************************************************* + * String Descriptors + ******************************************************************************/ +const USB_Descriptor_String_t PROGMEM LanguageString = +{ + .Header = {.Size = USB_STRING_LEN(1), .Type = DTYPE_String}, + + .UnicodeString = {LANGUAGE_ID_ENG} +}; + +const USB_Descriptor_String_t PROGMEM ManufacturerString = +{ + /* subtract 1 for null terminator */ + .Header = {.Size = USB_STRING_LEN(sizeof(STR(MANUFACTURER))-1), .Type = DTYPE_String}, + + .UnicodeString = LSTR(MANUFACTURER) +}; + +const USB_Descriptor_String_t PROGMEM ProductString = +{ + /* subtract 1 for null terminator */ + .Header = {.Size = USB_STRING_LEN(sizeof(STR(PRODUCT))-1), .Type = DTYPE_String}, + + .UnicodeString = LSTR(PRODUCT) +}; + + +/** This function is called by the library when in device mode, and must be overridden (see library "USB Descriptors" + * documentation) by the application code so that the address and size of a requested descriptor can be given + * to the USB library. When the device receives a Get Descriptor request on the control endpoint, this function + * is called so that the descriptor details can be passed back and the appropriate descriptor sent back to the + * USB host. + */ +uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue, + const uint16_t wIndex, + const void** const DescriptorAddress) +{ + const uint8_t DescriptorType = (wValue >> 8); + const uint8_t DescriptorIndex = (wValue & 0xFF); + + const void* Address = NULL; + uint16_t Size = NO_DESCRIPTOR; + + switch (DescriptorType) + { + case DTYPE_Device: + Address = &DeviceDescriptor; + Size = sizeof(USB_Descriptor_Device_t); + break; + case DTYPE_Configuration: + Address = &ConfigurationDescriptor; + Size = sizeof(USB_Descriptor_Configuration_t); + break; + case DTYPE_String: + switch (DescriptorIndex ) + { + case 0x00: + Address = &LanguageString; + Size = pgm_read_byte(&LanguageString.Header.Size); + break; + case 0x01: + Address = &ManufacturerString; + Size = pgm_read_byte(&ManufacturerString.Header.Size); + break; + case 0x02: + Address = &ProductString; + Size = pgm_read_byte(&ProductString.Header.Size); + break; + } + break; + case HID_DTYPE_HID: + switch (wIndex) { + case KEYBOARD_INTERFACE: + Address = &ConfigurationDescriptor.Keyboard_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#ifdef MOUSE_ENABLE + case MOUSE_INTERFACE: + Address = &ConfigurationDescriptor.Mouse_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#endif +#ifdef EXTRAKEY_ENABLE + case EXTRAKEY_INTERFACE: + Address = &ConfigurationDescriptor.Extrakey_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#endif +#ifdef RAW_ENABLE + case RAW_INTERFACE: + Address = &ConfigurationDescriptor.Raw_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#endif +#ifdef CONSOLE_ENABLE + case CONSOLE_INTERFACE: + Address = &ConfigurationDescriptor.Console_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#endif +#ifdef NKRO_ENABLE + case NKRO_INTERFACE: + Address = &ConfigurationDescriptor.NKRO_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; +#endif + } + break; + case HID_DTYPE_Report: + switch (wIndex) { + case KEYBOARD_INTERFACE: + Address = &KeyboardReport; + Size = sizeof(KeyboardReport); + break; +#ifdef MOUSE_ENABLE + case MOUSE_INTERFACE: + Address = &MouseReport; + Size = sizeof(MouseReport); + break; +#endif +#ifdef EXTRAKEY_ENABLE + case EXTRAKEY_INTERFACE: + Address = &ExtrakeyReport; + Size = sizeof(ExtrakeyReport); + break; +#endif +#ifdef RAW_ENABLE + case RAW_INTERFACE: + Address = &RawReport; + Size = sizeof(RawReport); + break; +#endif +#ifdef CONSOLE_ENABLE + case CONSOLE_INTERFACE: + Address = &ConsoleReport; + Size = sizeof(ConsoleReport); + break; +#endif +#ifdef NKRO_ENABLE + case NKRO_INTERFACE: + Address = &NKROReport; + Size = sizeof(NKROReport); + break; +#endif + } + break; + } + + *DescriptorAddress = Address; + return Size; +} diff --git a/tmk_core/protocol/lufa/descriptor.h b/tmk_core/protocol/lufa/descriptor.h new file mode 100644 index 0000000000..cde44abc9b --- /dev/null +++ b/tmk_core/protocol/lufa/descriptor.h @@ -0,0 +1,271 @@ +/* + * Copyright 2012,2013 Jun Wako <wakojun@gmail.com> + * This file is based on: + * LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse + * LUFA-120219/Demos/Device/Lowlevel/GenericHID + */ + +/* + LUFA Library + Copyright (C) Dean Camera, 2012. + + dean [at] fourwalledcubicle [dot] com + www.lufa-lib.org +*/ + +/* + Copyright 2012 Dean Camera (dean [at] fourwalledcubicle [dot] com) + Copyright 2010 Denver Gingerich (denver [at] ossguy [dot] com) + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that the copyright notice and this + permission notice and warranty disclaimer appear in supporting + documentation, and that the name of the author not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. + + The author disclaim all warranties with regard to this + software, including all implied warranties of merchantability + and fitness. In no event shall the author be liable for any + special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether + in an action of contract, negligence or other tortious action, + arising out of or in connection with the use or performance of + this software. +*/ + +/** \file + * + * Header file for Descriptors.c. + */ + +#ifndef _DESCRIPTORS_H_ +#define _DESCRIPTORS_H_ + +#include <LUFA/Drivers/USB/USB.h> +#include <avr/pgmspace.h> + + +typedef struct +{ + USB_Descriptor_Configuration_Header_t Config; + + // Keyboard HID Interface + USB_Descriptor_Interface_t Keyboard_Interface; + USB_HID_Descriptor_HID_t Keyboard_HID; + USB_Descriptor_Endpoint_t Keyboard_INEndpoint; + +#ifdef MOUSE_ENABLE + // Mouse HID Interface + USB_Descriptor_Interface_t Mouse_Interface; + USB_HID_Descriptor_HID_t Mouse_HID; + USB_Descriptor_Endpoint_t Mouse_INEndpoint; +#endif + +#ifdef EXTRAKEY_ENABLE + // Extrakey HID Interface + USB_Descriptor_Interface_t Extrakey_Interface; + USB_HID_Descriptor_HID_t Extrakey_HID; + USB_Descriptor_Endpoint_t Extrakey_INEndpoint; +#endif + +#ifdef RAW_ENABLE + // Raw HID Interface + USB_Descriptor_Interface_t Raw_Interface; + USB_HID_Descriptor_HID_t Raw_HID; + USB_Descriptor_Endpoint_t Raw_INEndpoint; + USB_Descriptor_Endpoint_t Raw_OUTEndpoint; +#endif + +#ifdef CONSOLE_ENABLE + // Console HID Interface + USB_Descriptor_Interface_t Console_Interface; + USB_HID_Descriptor_HID_t Console_HID; + USB_Descriptor_Endpoint_t Console_INEndpoint; + USB_Descriptor_Endpoint_t Console_OUTEndpoint; +#endif + +#ifdef NKRO_ENABLE + // NKRO HID Interface + USB_Descriptor_Interface_t NKRO_Interface; + USB_HID_Descriptor_HID_t NKRO_HID; + USB_Descriptor_Endpoint_t NKRO_INEndpoint; +#endif + +#ifdef MIDI_ENABLE + // MIDI Audio Control Interface + USB_Descriptor_Interface_t Audio_ControlInterface; + USB_Audio_Descriptor_Interface_AC_t Audio_ControlInterface_SPC; + + // MIDI Audio Streaming Interface + USB_Descriptor_Interface_t Audio_StreamInterface; + USB_MIDI_Descriptor_AudioInterface_AS_t Audio_StreamInterface_SPC; + USB_MIDI_Descriptor_InputJack_t MIDI_In_Jack_Emb; + USB_MIDI_Descriptor_InputJack_t MIDI_In_Jack_Ext; + USB_MIDI_Descriptor_OutputJack_t MIDI_Out_Jack_Emb; + USB_MIDI_Descriptor_OutputJack_t MIDI_Out_Jack_Ext; + USB_Audio_Descriptor_StreamEndpoint_Std_t MIDI_In_Jack_Endpoint; + USB_MIDI_Descriptor_Jack_Endpoint_t MIDI_In_Jack_Endpoint_SPC; + USB_Audio_Descriptor_StreamEndpoint_Std_t MIDI_Out_Jack_Endpoint; + USB_MIDI_Descriptor_Jack_Endpoint_t MIDI_Out_Jack_Endpoint_SPC; +#endif + +#ifdef VIRTSER_ENABLE + USB_Descriptor_Interface_Association_t CDC_Interface_Association; + + // CDC Control Interface + USB_Descriptor_Interface_t CDC_CCI_Interface; + USB_CDC_Descriptor_FunctionalHeader_t CDC_Functional_Header; + USB_CDC_Descriptor_FunctionalACM_t CDC_Functional_ACM; + USB_CDC_Descriptor_FunctionalUnion_t CDC_Functional_Union; + USB_Descriptor_Endpoint_t CDC_NotificationEndpoint; + + // CDC Data Interface + USB_Descriptor_Interface_t CDC_DCI_Interface; + USB_Descriptor_Endpoint_t CDC_DataOutEndpoint; + USB_Descriptor_Endpoint_t CDC_DataInEndpoint; +#endif +} USB_Descriptor_Configuration_t; + + +/* index of interface */ +#define KEYBOARD_INTERFACE 0 + +#ifdef MOUSE_ENABLE +# define MOUSE_INTERFACE (KEYBOARD_INTERFACE + 1) +#else +# define MOUSE_INTERFACE KEYBOARD_INTERFACE +#endif + +#ifdef EXTRAKEY_ENABLE +# define EXTRAKEY_INTERFACE (MOUSE_INTERFACE + 1) +#else +# define EXTRAKEY_INTERFACE MOUSE_INTERFACE +#endif + +#ifdef RAW_ENABLE +# define RAW_INTERFACE (EXTRAKEY_INTERFACE + 1) +#else +# define RAW_INTERFACE EXTRAKEY_INTERFACE +#endif + +#ifdef CONSOLE_ENABLE +# define CONSOLE_INTERFACE (RAW_INTERFACE + 1) +#else +# define CONSOLE_INTERFACE RAW_INTERFACE +#endif + +#ifdef NKRO_ENABLE +# define NKRO_INTERFACE (CONSOLE_INTERFACE + 1) +#else +# define NKRO_INTERFACE CONSOLE_INTERFACE +#endif + +#ifdef MIDI_ENABLE +# define AC_INTERFACE (NKRO_INTERFACE + 1) +# define AS_INTERFACE (NKRO_INTERFACE + 2) +#else +# define AS_INTERFACE NKRO_INTERFACE +#endif + +#ifdef VIRTSER_ENABLE +# define CCI_INTERFACE (AS_INTERFACE + 1) +# define CDI_INTERFACE (AS_INTERFACE + 2) +#else +# define CDI_INTERFACE AS_INTERFACE +#endif + +/* nubmer of interfaces */ +#define TOTAL_INTERFACES (CDI_INTERFACE + 1) + + +// Endopoint number and size +#define KEYBOARD_IN_EPNUM 1 + +#ifdef MOUSE_ENABLE +# define MOUSE_IN_EPNUM (KEYBOARD_IN_EPNUM + 1) +#else +# define MOUSE_IN_EPNUM KEYBOARD_IN_EPNUM +#endif + +#ifdef EXTRAKEY_ENABLE +# define EXTRAKEY_IN_EPNUM (MOUSE_IN_EPNUM + 1) +#else +# define EXTRAKEY_IN_EPNUM MOUSE_IN_EPNUM +#endif + +#ifdef RAW_ENABLE +# define RAW_IN_EPNUM (EXTRAKEY_IN_EPNUM + 1) +# define RAW_OUT_EPNUM (EXTRAKEY_IN_EPNUM + 2) +#else +# define RAW_OUT_EPNUM EXTRAKEY_IN_EPNUM +#endif + +#ifdef CONSOLE_ENABLE +# define CONSOLE_IN_EPNUM (RAW_OUT_EPNUM + 1) +//# define CONSOLE_OUT_EPNUM (RAW_OUT_EPNUM + 2) +# define CONSOLE_OUT_EPNUM (RAW_OUT_EPNUM + 1) +#else +# define CONSOLE_OUT_EPNUM RAW_OUT_EPNUM +#endif + +#ifdef NKRO_ENABLE +# define NKRO_IN_EPNUM (CONSOLE_OUT_EPNUM + 1) +#else +# define NKRO_IN_EPNUM CONSOLE_OUT_EPNUM +#endif + +#ifdef MIDI_ENABLE +# define MIDI_STREAM_IN_EPNUM (NKRO_IN_EPNUM + 1) +// # define MIDI_STREAM_OUT_EPNUM (NKRO_IN_EPNUM + 1) +# define MIDI_STREAM_OUT_EPNUM (NKRO_IN_EPNUM + 2) +# define MIDI_STREAM_IN_EPADDR (ENDPOINT_DIR_IN | MIDI_STREAM_IN_EPNUM) +# define MIDI_STREAM_OUT_EPADDR (ENDPOINT_DIR_OUT | MIDI_STREAM_OUT_EPNUM) +#else +# define MIDI_STREAM_OUT_EPNUM NKRO_IN_EPNUM +#endif + +#ifdef VIRTSER_ENABLE +# define CDC_NOTIFICATION_EPNUM (MIDI_STREAM_OUT_EPNUM + 1) +# define CDC_IN_EPNUM (MIDI_STREAM_OUT_EPNUM + 2) +# define CDC_OUT_EPNUM (MIDI_STREAM_OUT_EPNUM + 3) +# define CDC_NOTIFICATION_EPADDR (ENDPOINT_DIR_IN | CDC_NOTIFICATION_EPNUM) +# define CDC_IN_EPADDR (ENDPOINT_DIR_IN | CDC_IN_EPNUM) +# define CDC_OUT_EPADDR (ENDPOINT_DIR_OUT | CDC_OUT_EPNUM) +#else +# define CDC_OUT_EPNUM MIDI_STREAM_OUT_EPNUM +#endif + +#if defined(__AVR_ATmega32U2__) && CDC_OUT_EPNUM > 4 +# error "Endpoints are not available enough to support all functions. Remove some in Makefile.(MOUSEKEY, EXTRAKEY, CONSOLE, NKRO, MIDI, SERIAL)" +#endif + +#define KEYBOARD_EPSIZE 8 +#define MOUSE_EPSIZE 8 +#define EXTRAKEY_EPSIZE 8 +#define RAW_EPSIZE 32 +#define CONSOLE_EPSIZE 32 +#define NKRO_EPSIZE 32 +#define MIDI_STREAM_EPSIZE 64 +#define CDC_NOTIFICATION_EPSIZE 8 +#define CDC_EPSIZE 16 + + +uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue, + const uint16_t wIndex, + const void** const DescriptorAddress) + ATTR_WARN_UNUSED_RESULT ATTR_NON_NULL_PTR_ARG(3); + + +/* new API */ +#if LUFA_VERSION_INTEGER < 0x140302 + #undef VERSION_BCD + #define VERSION_BCD(Major, Minor, Revision) \ + CPU_TO_LE16( ((Major & 0xFF) << 8) | \ + ((Minor & 0x0F) << 4) | \ + (Revision & 0x0F) ) +#endif + +#endif diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c new file mode 100644 index 0000000000..e3f8724e81 --- /dev/null +++ b/tmk_core/protocol/lufa/lufa.c @@ -0,0 +1,1293 @@ +/* + * Copyright 2012 Jun Wako <wakojun@gmail.com> + * This file is based on: + * LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse + * LUFA-120219/Demos/Device/Lowlevel/GenericHID + */ + +/* + LUFA Library + Copyright (C) Dean Camera, 2012. + + dean [at] fourwalledcubicle [dot] com + www.lufa-lib.org +*/ + +/* + Copyright 2012 Dean Camera (dean [at] fourwalledcubicle [dot] com) + Copyright 2010 Denver Gingerich (denver [at] ossguy [dot] com) + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that the copyright notice and this + permission notice and warranty disclaimer appear in supporting + documentation, and that the name of the author not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. + + The author disclaim all warranties with regard to this + software, including all implied warranties of merchantability + and fitness. In no event shall the author be liable for any + special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether + in an action of contract, negligence or other tortious action, + arising out of or in connection with the use or performance of + this software. +*/ + +#include "report.h" +#include "host.h" +#include "host_driver.h" +#include "keyboard.h" +#include "action.h" +#include "led.h" +#include "sendchar.h" +#include "debug.h" +#ifdef SLEEP_LED_ENABLE +#include "sleep_led.h" +#endif +#include "suspend.h" + +#include "descriptor.h" +#include "lufa.h" +#include "quantum.h" +#include <util/atomic.h> +#include "outputselect.h" + +#ifdef NKRO_ENABLE + #include "keycode_config.h" + + extern keymap_config_t keymap_config; +#endif + + +#ifdef AUDIO_ENABLE + #include <audio.h> +#endif + +#ifdef BLUETOOTH_ENABLE + #ifdef MODULE_ADAFRUIT_BLE + #include "adafruit_ble.h" + #else + #include "bluetooth.h" + #endif +#endif + +#ifdef VIRTSER_ENABLE + #include "virtser.h" +#endif + +#if (defined(RGB_MIDI) | defined(RGBLIGHT_ANIMATIONS)) & defined(RGBLIGHT_ENABLE) + #include "rgblight.h" +#endif + +#ifdef MIDI_ENABLE + #include "sysex_tools.h" +#endif + +#ifdef RAW_ENABLE + #include "raw_hid.h" +#endif + +uint8_t keyboard_idle = 0; +/* 0: Boot Protocol, 1: Report Protocol(default) */ +uint8_t keyboard_protocol = 1; +static uint8_t keyboard_led_stats = 0; + +static report_keyboard_t keyboard_report_sent; + +#ifdef MIDI_ENABLE +static void usb_send_func(MidiDevice * device, uint16_t cnt, uint8_t byte0, uint8_t byte1, uint8_t byte2); +static void usb_get_midi(MidiDevice * device); +static void midi_usb_init(MidiDevice * device); +#endif + +/* Host driver */ +static uint8_t keyboard_leds(void); +static void send_keyboard(report_keyboard_t *report); +static void send_mouse(report_mouse_t *report); +static void send_system(uint16_t data); +static void send_consumer(uint16_t data); +host_driver_t lufa_driver = { + keyboard_leds, + send_keyboard, + send_mouse, + send_system, + send_consumer, +#ifdef MIDI_ENABLE + usb_send_func, + usb_get_midi, + midi_usb_init +#endif +}; + +/******************************************************************************* + * MIDI + ******************************************************************************/ + +#ifdef MIDI_ENABLE +USB_ClassInfo_MIDI_Device_t USB_MIDI_Interface = +{ + .Config = + { + .StreamingInterfaceNumber = AS_INTERFACE, + .DataINEndpoint = + { + .Address = MIDI_STREAM_IN_EPADDR, + .Size = MIDI_STREAM_EPSIZE, + .Banks = 1, + }, + .DataOUTEndpoint = + { + .Address = MIDI_STREAM_OUT_EPADDR, + .Size = MIDI_STREAM_EPSIZE, + .Banks = 1, + }, + }, +}; + +#define SYSEX_START_OR_CONT 0x40 +#define SYSEX_ENDS_IN_1 0x50 +#define SYSEX_ENDS_IN_2 0x60 +#define SYSEX_ENDS_IN_3 0x70 + +#define SYS_COMMON_1 0x50 +#define SYS_COMMON_2 0x20 +#define SYS_COMMON_3 0x30 +#endif + +#ifdef VIRTSER_ENABLE +USB_ClassInfo_CDC_Device_t cdc_device = +{ + .Config = + { + .ControlInterfaceNumber = CCI_INTERFACE, + .DataINEndpoint = + { + .Address = CDC_IN_EPADDR, + .Size = CDC_EPSIZE, + .Banks = 1, + }, + .DataOUTEndpoint = + { + .Address = CDC_OUT_EPADDR, + .Size = CDC_EPSIZE, + .Banks = 1, + }, + .NotificationEndpoint = + { + .Address = CDC_NOTIFICATION_EPADDR, + .Size = CDC_NOTIFICATION_EPSIZE, + .Banks = 1, + }, + }, +}; +#endif + +#ifdef RAW_ENABLE + +void raw_hid_send( uint8_t *data, uint8_t length ) +{ + // TODO: implement variable size packet + if ( length != RAW_EPSIZE ) + { + return; + } + + if (USB_DeviceState != DEVICE_STATE_Configured) + { + return; + } + + // TODO: decide if we allow calls to raw_hid_send() in the middle + // of other endpoint usage. + uint8_t ep = Endpoint_GetCurrentEndpoint(); + + Endpoint_SelectEndpoint(RAW_IN_EPNUM); + + // Check to see if the host is ready to accept another packet + if (Endpoint_IsINReady()) + { + // Write data + Endpoint_Write_Stream_LE(data, RAW_EPSIZE, NULL); + // Finalize the stream transfer to send the last packet + Endpoint_ClearIN(); + } + + Endpoint_SelectEndpoint(ep); +} + +__attribute__ ((weak)) +void raw_hid_receive( uint8_t *data, uint8_t length ) +{ + // Users should #include "raw_hid.h" in their own code + // and implement this function there. Leave this as weak linkage + // so users can opt to not handle data coming in. +} + +static void raw_hid_task(void) +{ + // Create a temporary buffer to hold the read in data from the host + uint8_t data[RAW_EPSIZE]; + bool data_read = false; + + // Device must be connected and configured for the task to run + if (USB_DeviceState != DEVICE_STATE_Configured) + return; + + Endpoint_SelectEndpoint(RAW_OUT_EPNUM); + + // Check to see if a packet has been sent from the host + if (Endpoint_IsOUTReceived()) + { + // Check to see if the packet contains data + if (Endpoint_IsReadWriteAllowed()) + { + /* Read data */ + Endpoint_Read_Stream_LE(data, sizeof(data), NULL); + data_read = true; + } + + // Finalize the stream transfer to receive the last packet + Endpoint_ClearOUT(); + + if ( data_read ) + { + raw_hid_receive( data, sizeof(data) ); + } + } +} +#endif + +/******************************************************************************* + * Console + ******************************************************************************/ +#ifdef CONSOLE_ENABLE +static void Console_Task(void) +{ + /* Device must be connected and configured for the task to run */ + if (USB_DeviceState != DEVICE_STATE_Configured) + return; + + uint8_t ep = Endpoint_GetCurrentEndpoint(); + +#if 0 + // TODO: impl receivechar()/recvchar() + Endpoint_SelectEndpoint(CONSOLE_OUT_EPNUM); + + /* Check to see if a packet has been sent from the host */ + if (Endpoint_IsOUTReceived()) + { + /* Check to see if the packet contains data */ + if (Endpoint_IsReadWriteAllowed()) + { + /* Create a temporary buffer to hold the read in report from the host */ + uint8_t ConsoleData[CONSOLE_EPSIZE]; + + /* Read Console Report Data */ + Endpoint_Read_Stream_LE(&ConsoleData, sizeof(ConsoleData), NULL); + + /* Process Console Report Data */ + //ProcessConsoleHIDReport(ConsoleData); + } + + /* Finalize the stream transfer to send the last packet */ + Endpoint_ClearOUT(); + } +#endif + + /* IN packet */ + Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM); + if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) { + Endpoint_SelectEndpoint(ep); + return; + } + + // fill empty bank + while (Endpoint_IsReadWriteAllowed()) + Endpoint_Write_8(0); + + // flash senchar packet + if (Endpoint_IsINReady()) { + Endpoint_ClearIN(); + } + + Endpoint_SelectEndpoint(ep); +} +#endif + + +/******************************************************************************* + * USB Events + ******************************************************************************/ +/* + * Event Order of Plug in: + * 0) EVENT_USB_Device_Connect + * 1) EVENT_USB_Device_Suspend + * 2) EVENT_USB_Device_Reset + * 3) EVENT_USB_Device_Wake +*/ +void EVENT_USB_Device_Connect(void) +{ + print("[C]"); + /* For battery powered device */ + if (!USB_IsInitialized) { + USB_Disable(); + USB_Init(); + USB_Device_EnableSOFEvents(); + } +} + +void EVENT_USB_Device_Disconnect(void) +{ + print("[D]"); + /* For battery powered device */ + USB_IsInitialized = false; +/* TODO: This doesn't work. After several plug in/outs can not be enumerated. + if (USB_IsInitialized) { + USB_Disable(); // Disable all interrupts + USB_Controller_Enable(); + USB_INT_Enable(USB_INT_VBUSTI); + } +*/ +} + +void EVENT_USB_Device_Reset(void) +{ + print("[R]"); +} + +void EVENT_USB_Device_Suspend() +{ + print("[S]"); +#ifdef SLEEP_LED_ENABLE + sleep_led_enable(); +#endif +} + +void EVENT_USB_Device_WakeUp() +{ + print("[W]"); + suspend_wakeup_init(); + +#ifdef SLEEP_LED_ENABLE + sleep_led_disable(); + // NOTE: converters may not accept this + led_set(host_keyboard_leds()); +#endif +} + + + +#ifdef CONSOLE_ENABLE +static bool console_flush = false; +#define CONSOLE_FLUSH_SET(b) do { \ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {\ + console_flush = b; \ + } \ +} while (0) + +// called every 1ms +void EVENT_USB_Device_StartOfFrame(void) +{ + static uint8_t count; + if (++count % 50) return; + count = 0; + + if (!console_flush) return; + Console_Task(); + console_flush = false; +} + +#endif + +/** Event handler for the USB_ConfigurationChanged event. + * This is fired when the host sets the current configuration of the USB device after enumeration. + * + * ATMega32u2 supports dual bank(ping-pong mode) only on endpoint 3 and 4, + * it is safe to use singl bank for all endpoints. + */ +void EVENT_USB_Device_ConfigurationChanged(void) +{ + bool ConfigSuccess = true; + + /* Setup Keyboard HID Report Endpoints */ + ConfigSuccess &= ENDPOINT_CONFIG(KEYBOARD_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + KEYBOARD_EPSIZE, ENDPOINT_BANK_SINGLE); + +#ifdef MOUSE_ENABLE + /* Setup Mouse HID Report Endpoint */ + ConfigSuccess &= ENDPOINT_CONFIG(MOUSE_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + MOUSE_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif + +#ifdef EXTRAKEY_ENABLE + /* Setup Extra HID Report Endpoint */ + ConfigSuccess &= ENDPOINT_CONFIG(EXTRAKEY_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + EXTRAKEY_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif + +#ifdef RAW_ENABLE + /* Setup Raw HID Report Endpoints */ + ConfigSuccess &= ENDPOINT_CONFIG(RAW_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + RAW_EPSIZE, ENDPOINT_BANK_SINGLE); + ConfigSuccess &= ENDPOINT_CONFIG(RAW_OUT_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_OUT, + RAW_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif + +#ifdef CONSOLE_ENABLE + /* Setup Console HID Report Endpoints */ + ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE); +#if 0 + ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_OUT_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_OUT, + CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif +#endif + +#ifdef NKRO_ENABLE + /* Setup NKRO HID Report Endpoints */ + ConfigSuccess &= ENDPOINT_CONFIG(NKRO_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN, + NKRO_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif + +#ifdef MIDI_ENABLE + ConfigSuccess &= Endpoint_ConfigureEndpoint(MIDI_STREAM_IN_EPADDR, EP_TYPE_BULK, MIDI_STREAM_EPSIZE, ENDPOINT_BANK_SINGLE); + ConfigSuccess &= Endpoint_ConfigureEndpoint(MIDI_STREAM_OUT_EPADDR, EP_TYPE_BULK, MIDI_STREAM_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif + +#ifdef VIRTSER_ENABLE + ConfigSuccess &= Endpoint_ConfigureEndpoint(CDC_NOTIFICATION_EPADDR, EP_TYPE_INTERRUPT, CDC_NOTIFICATION_EPSIZE, ENDPOINT_BANK_SINGLE); + ConfigSuccess &= Endpoint_ConfigureEndpoint(CDC_OUT_EPADDR, EP_TYPE_BULK, CDC_EPSIZE, ENDPOINT_BANK_SINGLE); + ConfigSuccess &= Endpoint_ConfigureEndpoint(CDC_IN_EPADDR, EP_TYPE_BULK, CDC_EPSIZE, ENDPOINT_BANK_SINGLE); +#endif +} + +/* +Appendix G: HID Request Support Requirements + +The following table enumerates the requests that need to be supported by various types of HID class devices. + +Device type GetReport SetReport GetIdle SetIdle GetProtocol SetProtocol +------------------------------------------------------------------------------------------ +Boot Mouse Required Optional Optional Optional Required Required +Non-Boot Mouse Required Optional Optional Optional Optional Optional +Boot Keyboard Required Optional Required Required Required Required +Non-Boot Keybrd Required Optional Required Required Optional Optional +Other Device Required Optional Optional Optional Optional Optional +*/ +/** Event handler for the USB_ControlRequest event. + * This is fired before passing along unhandled control requests to the library for processing internally. + */ +void EVENT_USB_Device_ControlRequest(void) +{ + uint8_t* ReportData = NULL; + uint8_t ReportSize = 0; + + /* Handle HID Class specific requests */ + switch (USB_ControlRequest.bRequest) + { + case HID_REQ_GetReport: + if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE)) + { + Endpoint_ClearSETUP(); + + // Interface + switch (USB_ControlRequest.wIndex) { + case KEYBOARD_INTERFACE: + // TODO: test/check + ReportData = (uint8_t*)&keyboard_report_sent; + ReportSize = sizeof(keyboard_report_sent); + break; + } + + /* Write the report data to the control endpoint */ + Endpoint_Write_Control_Stream_LE(ReportData, ReportSize); + Endpoint_ClearOUT(); + } + + break; + case HID_REQ_SetReport: + if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE)) + { + + // Interface + switch (USB_ControlRequest.wIndex) { + case KEYBOARD_INTERFACE: +#ifdef NKRO_ENABLE + case NKRO_INTERFACE: +#endif + Endpoint_ClearSETUP(); + + while (!(Endpoint_IsOUTReceived())) { + if (USB_DeviceState == DEVICE_STATE_Unattached) + return; + } + keyboard_led_stats = Endpoint_Read_8(); + + Endpoint_ClearOUT(); + Endpoint_ClearStatusStage(); + break; + } + + } + + break; + + case HID_REQ_GetProtocol: + if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE)) + { + if (USB_ControlRequest.wIndex == KEYBOARD_INTERFACE) { + Endpoint_ClearSETUP(); + while (!(Endpoint_IsINReady())); + Endpoint_Write_8(keyboard_protocol); + Endpoint_ClearIN(); + Endpoint_ClearStatusStage(); + } + } + + break; + case HID_REQ_SetProtocol: + if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE)) + { + if (USB_ControlRequest.wIndex == KEYBOARD_INTERFACE) { + Endpoint_ClearSETUP(); + Endpoint_ClearStatusStage(); + + keyboard_protocol = (USB_ControlRequest.wValue & 0xFF); + clear_keyboard(); + } + } + + break; + case HID_REQ_SetIdle: + if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE)) + { + Endpoint_ClearSETUP(); + Endpoint_ClearStatusStage(); + + keyboard_idle = ((USB_ControlRequest.wValue & 0xFF00) >> 8); + } + + break; + case HID_REQ_GetIdle: + if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE)) + { + Endpoint_ClearSETUP(); + while (!(Endpoint_IsINReady())); + Endpoint_Write_8(keyboard_idle); + Endpoint_ClearIN(); + Endpoint_ClearStatusStage(); + } + + break; + } + +#ifdef VIRTSER_ENABLE + CDC_Device_ProcessControlRequest(&cdc_device); +#endif +} + +/******************************************************************************* + * Host driver + ******************************************************************************/ +static uint8_t keyboard_leds(void) +{ + return keyboard_led_stats; +} + +static void send_keyboard(report_keyboard_t *report) +{ + uint8_t timeout = 255; + uint8_t where = where_to_send(); + +#ifdef BLUETOOTH_ENABLE + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + #ifdef MODULE_ADAFRUIT_BLE + adafruit_ble_send_keys(report->mods, report->keys, sizeof(report->keys)); + #elif MODULE_RN42 + bluefruit_serial_send(0xFD); + bluefruit_serial_send(0x09); + bluefruit_serial_send(0x01); + for (uint8_t i = 0; i < KEYBOARD_EPSIZE; i++) { + bluefruit_serial_send(report->raw[i]); + } + #else + bluefruit_serial_send(0xFD); + for (uint8_t i = 0; i < KEYBOARD_EPSIZE; i++) { + bluefruit_serial_send(report->raw[i]); + } + #endif + } +#endif + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } + + /* Select the Keyboard Report Endpoint */ +#ifdef NKRO_ENABLE + if (keyboard_protocol && keymap_config.nkro) { + /* Report protocol - NKRO */ + Endpoint_SelectEndpoint(NKRO_IN_EPNUM); + + /* Check if write ready for a polling interval around 1ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(4); + if (!Endpoint_IsReadWriteAllowed()) return; + + /* Write Keyboard Report Data */ + Endpoint_Write_Stream_LE(report, NKRO_EPSIZE, NULL); + } + else +#endif + { + /* Boot protocol */ + Endpoint_SelectEndpoint(KEYBOARD_IN_EPNUM); + + /* Check if write ready for a polling interval around 10ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + if (!Endpoint_IsReadWriteAllowed()) return; + + /* Write Keyboard Report Data */ + Endpoint_Write_Stream_LE(report, KEYBOARD_EPSIZE, NULL); + } + + /* Finalize the stream transfer to send the last packet */ + Endpoint_ClearIN(); + + keyboard_report_sent = *report; +} + +static void send_mouse(report_mouse_t *report) +{ +#ifdef MOUSE_ENABLE + uint8_t timeout = 255; + uint8_t where = where_to_send(); + +#ifdef BLUETOOTH_ENABLE + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + #ifdef MODULE_ADAFRUIT_BLE + // FIXME: mouse buttons + adafruit_ble_send_mouse_move(report->x, report->y, report->v, report->h, report->buttons); + #else + bluefruit_serial_send(0xFD); + bluefruit_serial_send(0x00); + bluefruit_serial_send(0x03); + bluefruit_serial_send(report->buttons); + bluefruit_serial_send(report->x); + bluefruit_serial_send(report->y); + bluefruit_serial_send(report->v); // should try sending the wheel v here + bluefruit_serial_send(report->h); // should try sending the wheel h here + bluefruit_serial_send(0x00); + #endif + } +#endif + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } + + /* Select the Mouse Report Endpoint */ + Endpoint_SelectEndpoint(MOUSE_IN_EPNUM); + + /* Check if write ready for a polling interval around 10ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + if (!Endpoint_IsReadWriteAllowed()) return; + + /* Write Mouse Report Data */ + Endpoint_Write_Stream_LE(report, sizeof(report_mouse_t), NULL); + + /* Finalize the stream transfer to send the last packet */ + Endpoint_ClearIN(); +#endif +} + +static void send_system(uint16_t data) +{ + uint8_t timeout = 255; + + if (USB_DeviceState != DEVICE_STATE_Configured) + return; + + report_extra_t r = { + .report_id = REPORT_ID_SYSTEM, + .usage = data - SYSTEM_POWER_DOWN + 1 + }; + Endpoint_SelectEndpoint(EXTRAKEY_IN_EPNUM); + + /* Check if write ready for a polling interval around 10ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + if (!Endpoint_IsReadWriteAllowed()) return; + + Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL); + Endpoint_ClearIN(); +} + +static void send_consumer(uint16_t data) +{ + uint8_t timeout = 255; + uint8_t where = where_to_send(); + +#ifdef BLUETOOTH_ENABLE + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + #ifdef MODULE_ADAFRUIT_BLE + adafruit_ble_send_consumer_key(data, 0); + #elif MODULE_RN42 + static uint16_t last_data = 0; + if (data == last_data) return; + last_data = data; + uint16_t bitmap = CONSUMER2RN42(data); + bluefruit_serial_send(0xFD); + bluefruit_serial_send(0x03); + bluefruit_serial_send(0x03); + bluefruit_serial_send(bitmap&0xFF); + bluefruit_serial_send((bitmap>>8)&0xFF); + #else + static uint16_t last_data = 0; + if (data == last_data) return; + last_data = data; + uint16_t bitmap = CONSUMER2BLUEFRUIT(data); + bluefruit_serial_send(0xFD); + bluefruit_serial_send(0x00); + bluefruit_serial_send(0x02); + bluefruit_serial_send((bitmap>>8)&0xFF); + bluefruit_serial_send(bitmap&0xFF); + bluefruit_serial_send(0x00); + bluefruit_serial_send(0x00); + bluefruit_serial_send(0x00); + bluefruit_serial_send(0x00); + #endif + } +#endif + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } + + report_extra_t r = { + .report_id = REPORT_ID_CONSUMER, + .usage = data + }; + Endpoint_SelectEndpoint(EXTRAKEY_IN_EPNUM); + + /* Check if write ready for a polling interval around 10ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + if (!Endpoint_IsReadWriteAllowed()) return; + + Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL); + Endpoint_ClearIN(); +} + + +/******************************************************************************* + * sendchar + ******************************************************************************/ +#ifdef CONSOLE_ENABLE +#define SEND_TIMEOUT 5 +int8_t sendchar(uint8_t c) +{ + // Not wait once timeouted. + // Because sendchar() is called so many times, waiting each call causes big lag. + static bool timeouted = false; + + // prevents Console_Task() from running during sendchar() runs. + // or char will be lost. These two function is mutually exclusive. + CONSOLE_FLUSH_SET(false); + + if (USB_DeviceState != DEVICE_STATE_Configured) + return -1; + + uint8_t ep = Endpoint_GetCurrentEndpoint(); + Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM); + if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) { + goto ERROR_EXIT; + } + + if (timeouted && !Endpoint_IsReadWriteAllowed()) { + goto ERROR_EXIT; + } + + timeouted = false; + + uint8_t timeout = SEND_TIMEOUT; + while (!Endpoint_IsReadWriteAllowed()) { + if (USB_DeviceState != DEVICE_STATE_Configured) { + goto ERROR_EXIT; + } + if (Endpoint_IsStalled()) { + goto ERROR_EXIT; + } + if (!(timeout--)) { + timeouted = true; + goto ERROR_EXIT; + } + _delay_ms(1); + } + + Endpoint_Write_8(c); + + // send when bank is full + if (!Endpoint_IsReadWriteAllowed()) { + while (!(Endpoint_IsINReady())); + Endpoint_ClearIN(); + } else { + CONSOLE_FLUSH_SET(true); + } + + Endpoint_SelectEndpoint(ep); + return 0; +ERROR_EXIT: + Endpoint_SelectEndpoint(ep); + return -1; +} +#else +int8_t sendchar(uint8_t c) +{ + return 0; +} +#endif + +/******************************************************************************* + * MIDI + ******************************************************************************/ + +#ifdef MIDI_ENABLE +static void usb_send_func(MidiDevice * device, uint16_t cnt, uint8_t byte0, uint8_t byte1, uint8_t byte2) { + MIDI_EventPacket_t event; + event.Data1 = byte0; + event.Data2 = byte1; + event.Data3 = byte2; + + uint8_t cable = 0; + +// Endpoint_SelectEndpoint(MIDI_STREAM_IN_EPNUM); + + //if the length is undefined we assume it is a SYSEX message + if (midi_packet_length(byte0) == UNDEFINED) { + switch(cnt) { + case 3: + if (byte2 == SYSEX_END) + event.Event = MIDI_EVENT(cable, SYSEX_ENDS_IN_3); + else + event.Event = MIDI_EVENT(cable, SYSEX_START_OR_CONT); + break; + case 2: + if (byte1 == SYSEX_END) + event.Event = MIDI_EVENT(cable, SYSEX_ENDS_IN_2); + else + event.Event = MIDI_EVENT(cable, SYSEX_START_OR_CONT); + break; + case 1: + if (byte0 == SYSEX_END) + event.Event = MIDI_EVENT(cable, SYSEX_ENDS_IN_1); + else + event.Event = MIDI_EVENT(cable, SYSEX_START_OR_CONT); + break; + default: + return; //invalid cnt + } + } else { + //deal with 'system common' messages + //TODO are there any more? + switch(byte0 & 0xF0){ + case MIDI_SONGPOSITION: + event.Event = MIDI_EVENT(cable, SYS_COMMON_3); + break; + case MIDI_SONGSELECT: + case MIDI_TC_QUARTERFRAME: + event.Event = MIDI_EVENT(cable, SYS_COMMON_2); + break; + default: + event.Event = MIDI_EVENT(cable, byte0); + break; + } + } + +// Endpoint_Write_Stream_LE(&event, sizeof(event), NULL); +// Endpoint_ClearIN(); + + MIDI_Device_SendEventPacket(&USB_MIDI_Interface, &event); + MIDI_Device_Flush(&USB_MIDI_Interface); + MIDI_Device_USBTask(&USB_MIDI_Interface); + USB_USBTask(); +} + +static void usb_get_midi(MidiDevice * device) { + MIDI_EventPacket_t event; + while (MIDI_Device_ReceiveEventPacket(&USB_MIDI_Interface, &event)) { + + midi_packet_length_t length = midi_packet_length(event.Data1); + uint8_t input[3]; + input[0] = event.Data1; + input[1] = event.Data2; + input[2] = event.Data3; + if (length == UNDEFINED) { + //sysex + if (event.Event == MIDI_EVENT(0, SYSEX_START_OR_CONT) || event.Event == MIDI_EVENT(0, SYSEX_ENDS_IN_3)) { + length = 3; + } else if (event.Event == MIDI_EVENT(0, SYSEX_ENDS_IN_2)) { + length = 2; + } else if(event.Event == MIDI_EVENT(0, SYSEX_ENDS_IN_1)) { + length = 1; + } else { + //XXX what to do? + } + } + + //pass the data to the device input function + if (length != UNDEFINED) + midi_device_input(device, length, input); + } + MIDI_Device_USBTask(&USB_MIDI_Interface); + USB_USBTask(); +} + +static void midi_usb_init(MidiDevice * device){ + midi_device_init(device); + midi_device_set_send_func(device, usb_send_func); + midi_device_set_pre_input_process_func(device, usb_get_midi); + + // SetupHardware(); + sei(); +} + +void MIDI_Task(void) +{ + + /* Device must be connected and configured for the task to run */ + dprint("in MIDI_TASK\n"); + if (USB_DeviceState != DEVICE_STATE_Configured) + return; + dprint("continuing in MIDI_TASK\n"); + + Endpoint_SelectEndpoint(MIDI_STREAM_IN_EPADDR); + + if (Endpoint_IsINReady()) + { + + dprint("Endpoint is ready\n"); + + uint8_t MIDICommand = 0; + uint8_t MIDIPitch; + + /* Get board button status - if pressed use channel 10 (percussion), otherwise use channel 1 */ + uint8_t Channel = MIDI_CHANNEL(1); + + MIDICommand = MIDI_COMMAND_NOTE_ON; + MIDIPitch = 0x3E; + + /* Check if a MIDI command is to be sent */ + if (MIDICommand) + { + dprint("Command exists\n"); + MIDI_EventPacket_t MIDIEvent = (MIDI_EventPacket_t) + { + .Event = MIDI_EVENT(0, MIDICommand), + + .Data1 = MIDICommand | Channel, + .Data2 = MIDIPitch, + .Data3 = MIDI_STANDARD_VELOCITY, + }; + + /* Write the MIDI event packet to the endpoint */ + Endpoint_Write_Stream_LE(&MIDIEvent, sizeof(MIDIEvent), NULL); + + /* Send the data in the endpoint to the host */ + Endpoint_ClearIN(); + } + } + + + /* Select the MIDI OUT stream */ + Endpoint_SelectEndpoint(MIDI_STREAM_OUT_EPADDR); + + /* Check if a MIDI command has been received */ + if (Endpoint_IsOUTReceived()) + { + MIDI_EventPacket_t MIDIEvent; + + /* Read the MIDI event packet from the endpoint */ + Endpoint_Read_Stream_LE(&MIDIEvent, sizeof(MIDIEvent), NULL); + + /* If the endpoint is now empty, clear the bank */ + if (!(Endpoint_BytesInEndpoint())) + { + /* Clear the endpoint ready for new packet */ + Endpoint_ClearOUT(); + } + } +} + +#endif + +/******************************************************************************* + * VIRTUAL SERIAL + ******************************************************************************/ + +#ifdef VIRTSER_ENABLE +void virtser_init(void) +{ + cdc_device.State.ControlLineStates.DeviceToHost = CDC_CONTROL_LINE_IN_DSR ; + CDC_Device_SendControlLineStateChange(&cdc_device); +} + +void virtser_recv(uint8_t c) __attribute__ ((weak)); +void virtser_recv(uint8_t c) +{ + // Ignore by default +} + +void virtser_task(void) +{ + uint16_t count = CDC_Device_BytesReceived(&cdc_device); + uint8_t ch; + if (count) + { + ch = CDC_Device_ReceiveByte(&cdc_device); + virtser_recv(ch); + } +} +void virtser_send(const uint8_t byte) +{ + uint8_t timeout = 255; + uint8_t ep = Endpoint_GetCurrentEndpoint(); + + if (cdc_device.State.ControlLineStates.HostToDevice & CDC_CONTROL_LINE_OUT_DTR) + { + /* IN packet */ + Endpoint_SelectEndpoint(cdc_device.Config.DataINEndpoint.Address); + + if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) { + Endpoint_SelectEndpoint(ep); + return; + } + + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + + Endpoint_Write_8(byte); + CDC_Device_Flush(&cdc_device); + + if (Endpoint_IsINReady()) { + Endpoint_ClearIN(); + } + + Endpoint_SelectEndpoint(ep); + } +} +#endif + +/******************************************************************************* + * main + ******************************************************************************/ +static void setup_mcu(void) +{ + /* Disable watchdog if enabled by bootloader/fuses */ + MCUSR &= ~(1 << WDRF); + wdt_disable(); + + /* Disable clock division */ + // clock_prescale_set(clock_div_1); + + CLKPR = (1 << CLKPCE); + CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0); +} + +static void setup_usb(void) +{ + // Leonardo needs. Without this USB device is not recognized. + USB_Disable(); + + USB_Init(); + + // for Console_Task + USB_Device_EnableSOFEvents(); + print_set_sendchar(sendchar); +} + + +#ifdef MIDI_ENABLE +void fallthrough_callback(MidiDevice * device, + uint16_t cnt, uint8_t byte0, uint8_t byte1, uint8_t byte2); +void cc_callback(MidiDevice * device, + uint8_t chan, uint8_t num, uint8_t val); +void sysex_callback(MidiDevice * device, + uint16_t start, uint8_t length, uint8_t * data); + +void setup_midi(void) +{ +#ifdef MIDI_ADVANCED + midi_init(); +#endif + midi_device_init(&midi_device); + midi_device_set_send_func(&midi_device, usb_send_func); + midi_device_set_pre_input_process_func(&midi_device, usb_get_midi); +} +#endif + +int main(void) __attribute__ ((weak)); +int main(void) +{ +#ifdef MIDI_ENABLE + setup_midi(); +#endif + + setup_mcu(); + keyboard_setup(); + setup_usb(); + sei(); + +#ifdef MIDI_ENABLE + midi_register_fallthrough_callback(&midi_device, fallthrough_callback); + midi_register_cc_callback(&midi_device, cc_callback); + midi_register_sysex_callback(&midi_device, sysex_callback); + + // init_notes(); + // midi_send_cc(&midi_device, 0, 1, 2); + // midi_send_cc(&midi_device, 15, 1, 0); + // midi_send_noteon(&midi_device, 0, 64, 127); + // midi_send_noteoff(&midi_device, 0, 64, 127); +#endif + +#if defined(MODULE_ADAFRUIT_EZKEY) || defined(MODULE_RN42) + serial_init(); +#endif + + /* wait for USB startup & debug output */ + +#ifdef WAIT_FOR_USB + while (USB_DeviceState != DEVICE_STATE_Configured) { + #if defined(INTERRUPT_CONTROL_ENDPOINT) + ; + #else + USB_USBTask(); + #endif + } + print("USB configured.\n"); +#else + USB_USBTask(); +#endif + /* init modules */ + keyboard_init(); + host_set_driver(&lufa_driver); +#ifdef SLEEP_LED_ENABLE + sleep_led_init(); +#endif + +#ifdef VIRTSER_ENABLE + virtser_init(); +#endif + + print("Keyboard start.\n"); + while (1) { + #if !defined(NO_USB_STARTUP_CHECK) + while (USB_DeviceState == DEVICE_STATE_Suspended) { + print("[s]"); + suspend_power_down(); + if (USB_Device_RemoteWakeupEnabled && suspend_wakeup_condition()) { + USB_Device_SendRemoteWakeup(); + } + } + #endif + + keyboard_task(); + +#ifdef MIDI_ENABLE + midi_device_process(&midi_device); +#ifdef MIDI_ADVANCED + midi_task(); +#endif +#endif + +#if defined(RGBLIGHT_ANIMATIONS) & defined(RGBLIGHT_ENABLE) + rgblight_task(); +#endif + +#ifdef MODULE_ADAFRUIT_BLE + adafruit_ble_task(); +#endif + +#ifdef VIRTSER_ENABLE + virtser_task(); + CDC_Device_USBTask(&cdc_device); +#endif + +#ifdef RAW_ENABLE + raw_hid_task(); +#endif + +#if !defined(INTERRUPT_CONTROL_ENDPOINT) + USB_USBTask(); +#endif + + } +} + +#ifdef MIDI_ENABLE +void fallthrough_callback(MidiDevice * device, + uint16_t cnt, uint8_t byte0, uint8_t byte1, uint8_t byte2){ + +#ifdef AUDIO_ENABLE + if (cnt == 3) { + switch (byte0 & 0xF0) { + case MIDI_NOTEON: + play_note(((double)261.6)*pow(2.0, -4.0)*pow(2.0,(byte1 & 0x7F)/12.0), (byte2 & 0x7F) / 8); + break; + case MIDI_NOTEOFF: + stop_note(((double)261.6)*pow(2.0, -4.0)*pow(2.0,(byte1 & 0x7F)/12.0)); + break; + } + } + if (byte0 == MIDI_STOP) { + stop_all_notes(); + } +#endif +} + + +void cc_callback(MidiDevice * device, + uint8_t chan, uint8_t num, uint8_t val) { + //sending it back on the next channel + // midi_send_cc(device, (chan + 1) % 16, num, val); +} + +#ifdef API_SYSEX_ENABLE +uint8_t midi_buffer[MIDI_SYSEX_BUFFER] = {0}; +#endif + +void sysex_callback(MidiDevice * device, uint16_t start, uint8_t length, uint8_t * data) { + #ifdef API_SYSEX_ENABLE + // SEND_STRING("\n"); + // send_word(start); + // SEND_STRING(": "); + // Don't store the header + int16_t pos = start - 4; + for (uint8_t place = 0; place < length; place++) { + // send_byte(*data); + if (pos >= 0) { + if (*data == 0xF7) { + // SEND_STRING("\nRD: "); + // for (uint8_t i = 0; i < start + place + 1; i++){ + // send_byte(midi_buffer[i]); + // SEND_STRING(" "); + // } + const unsigned decoded_length = sysex_decoded_length(pos); + uint8_t decoded[API_SYSEX_MAX_SIZE]; + sysex_decode(decoded, midi_buffer, pos); + process_api(decoded_length, decoded); + return; + } + else if (pos >= MIDI_SYSEX_BUFFER) { + return; + } + midi_buffer[pos] = *data; + } + // SEND_STRING(" "); + data++; + pos++; + } + #endif +} + + +#endif diff --git a/tmk_core/protocol/lufa/lufa.h b/tmk_core/protocol/lufa/lufa.h new file mode 100644 index 0000000000..a515737862 --- /dev/null +++ b/tmk_core/protocol/lufa/lufa.h @@ -0,0 +1,96 @@ +/* + * Copyright 2012 Jun Wako <wakojun@gmail.com> + * This file is based on: + * LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse + * LUFA-120219/Demos/Device/Lowlevel/GenericHID + */ + +/* + LUFA Library + Copyright (C) Dean Camera, 2012. + + dean [at] fourwalledcubicle [dot] com + www.lufa-lib.org +*/ + +/* + Copyright 2012 Dean Camera (dean [at] fourwalledcubicle [dot] com) + Copyright 2010 Denver Gingerich (denver [at] ossguy [dot] com) + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that the copyright notice and this + permission notice and warranty disclaimer appear in supporting + documentation, and that the name of the author not be used in + advertising or publicity pertaining to distribution of the + software without specific, written prior permission. + + The author disclaim all warranties with regard to this + software, including all implied warranties of merchantability + and fitness. In no event shall the author be liable for any + special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether + in an action of contract, negligence or other tortious action, + arising out of or in connection with the use or performance of + this software. +*/ + +#ifndef _LUFA_H_ +#define _LUFA_H_ + +#include <avr/io.h> +#include <avr/wdt.h> +#include <avr/power.h> +#include <avr/interrupt.h> +#include <stdbool.h> +#include <string.h> +#include <LUFA/Version.h> +#include <LUFA/Drivers/USB/USB.h> +#include "host.h" +#ifdef MIDI_ENABLE + #include "process_midi.h" +#endif +#ifdef __cplusplus +extern "C" { +#endif + +extern host_driver_t lufa_driver; + +#ifdef __cplusplus +} +#endif + +/* extra report structure */ +typedef struct { + uint8_t report_id; + uint16_t usage; +} __attribute__ ((packed)) report_extra_t; + +#ifdef MIDI_ENABLE + void MIDI_Task(void); + MidiDevice midi_device; +#endif + +#ifdef API_ENABLE + #include "api.h" +#endif + +#ifdef API_SYSEX_ENABLE + #include "api_sysex.h" + // Allocate space for encoding overhead. + //The header and terminator are not stored to save a few bytes of precious ram + #define MIDI_SYSEX_BUFFER (API_SYSEX_MAX_SIZE + API_SYSEX_MAX_SIZE / 7 + (API_SYSEX_MAX_SIZE % 7 ? 1 : 0)) +#endif + +// #if LUFA_VERSION_INTEGER < 0x120730 +// /* old API 120219 */ +// #define ENDPOINT_CONFIG(epnum, eptype, epdir, epsize, epbank) Endpoint_ConfigureEndpoint(epnum, eptype, epdir, epsize, epbank) +// #else + /* new API >= 120730 */ + #define ENDPOINT_BANK_SINGLE 1 + #define ENDPOINT_BANK_DOUBLE 2 + #define ENDPOINT_CONFIG(epnum, eptype, epdir, epsize, epbank) Endpoint_ConfigureEndpoint((epdir) | (epnum) , eptype, epsize, epbank) +// #endif + +#endif diff --git a/tmk_core/protocol/lufa/outputselect.c b/tmk_core/protocol/lufa/outputselect.c new file mode 100644 index 0000000000..0df5d3b75a --- /dev/null +++ b/tmk_core/protocol/lufa/outputselect.c @@ -0,0 +1,56 @@ +/* +Copyright 2017 Priyadi Iman Nurcahyo +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lufa.h" +#include "outputselect.h" +#ifdef MODULE_ADAFRUIT_BLE + #include "adafruit_ble.h" +#endif + +uint8_t desired_output = OUTPUT_DEFAULT; + +void set_output(uint8_t output) { + set_output_user(output); + desired_output = output; +} + +__attribute__((weak)) +void set_output_user(uint8_t output) { +} + +uint8_t auto_detect_output(void) { + if (USB_DeviceState == DEVICE_STATE_Configured) { + return OUTPUT_USB; + } + +#ifdef MODULE_ADAFRUIT_BLE + if (adafruit_ble_is_connected()) { + return OUTPUT_BLUETOOTH; + } +#endif + +#ifdef BLUETOOTH_ENABLE + return OUTPUT_BLUETOOTH; // should check if BT is connected here +#endif + + return OUTPUT_NONE; +} + +uint8_t where_to_send(void) { + if (desired_output == OUTPUT_AUTO) { + return auto_detect_output(); + } + return desired_output; +} + diff --git a/tmk_core/protocol/lufa/outputselect.h b/tmk_core/protocol/lufa/outputselect.h new file mode 100644 index 0000000000..28cc3298e6 --- /dev/null +++ b/tmk_core/protocol/lufa/outputselect.h @@ -0,0 +1,40 @@ +/* +Copyright 2017 Priyadi Iman Nurcahyo +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +enum outputs { + OUTPUT_AUTO, + + OUTPUT_NONE, + OUTPUT_USB, + OUTPUT_BLUETOOTH, + + // backward compatibility + OUTPUT_USB_AND_BT +}; + +/** + * backward compatibility for BLUETOOTH_ENABLE, send to BT and USB by default + */ +#ifndef OUTPUT_DEFAULT + #ifdef BLUETOOTH_ENABLE + #define OUTPUT_DEFAULT OUTPUT_USB_AND_BT + #else + #define OUTPUT_DEFAULT OUTPUT_AUTO + #endif +#endif + +void set_output(uint8_t output); +void set_output_user(uint8_t output); +uint8_t auto_detect_output(void); +uint8_t where_to_send(void);
\ No newline at end of file diff --git a/tmk_core/protocol/lufa/ringbuffer.hpp b/tmk_core/protocol/lufa/ringbuffer.hpp new file mode 100644 index 0000000000..70a3c4881d --- /dev/null +++ b/tmk_core/protocol/lufa/ringbuffer.hpp @@ -0,0 +1,66 @@ +#pragma once +// A simple ringbuffer holding Size elements of type T +template <typename T, uint8_t Size> +class RingBuffer { + protected: + T buf_[Size]; + uint8_t head_{0}, tail_{0}; + public: + inline uint8_t nextPosition(uint8_t position) { + return (position + 1) % Size; + } + + inline uint8_t prevPosition(uint8_t position) { + if (position == 0) { + return Size - 1; + } + return position - 1; + } + + inline bool enqueue(const T &item) { + static_assert(Size > 1, "RingBuffer size must be > 1"); + uint8_t next = nextPosition(head_); + if (next == tail_) { + // Full + return false; + } + + buf_[head_] = item; + head_ = next; + return true; + } + + inline bool get(T &dest, bool commit = true) { + auto tail = tail_; + if (tail == head_) { + // No more data + return false; + } + + dest = buf_[tail]; + tail = nextPosition(tail); + + if (commit) { + tail_ = tail; + } + return true; + } + + inline bool empty() const { return head_ == tail_; } + + inline uint8_t size() const { + int diff = head_ - tail_; + if (diff >= 0) { + return diff; + } + return Size + diff; + } + + inline T& front() { + return buf_[tail_]; + } + + inline bool peek(T &item) { + return get(item, false); + } +}; |