From 0cfbe7164311ab4dcd40a210fdbb85262bf8e325 Mon Sep 17 00:00:00 2001 From: jiaxin96 <572673807@qq.com> Date: Sat, 19 Jun 2021 16:05:53 +0800 Subject: [PATCH] add nrf52 uart module support lib --- .../biu_nrf52_ble_lib/biu_ble_common.cpp | 42 + .../biu_nrf52_ble_lib/biu_ble_common.h | 40 + .../biu_nrf52_ble_lib/biu_nrf52.cpp | 613 +++++++++ .../yandrstudio/biu_nrf52_ble_lib/main.c | 297 +++++ .../biu_nrf52_ble_lib/outputselect.c | 81 ++ .../biu_nrf52_ble_lib/outputselect.h | 43 + .../biu_nrf52_ble_lib/ring_buffer.hpp | 67 + .../yandrstudio/biu_nrf52_ble_lib/uart.h | 84 ++ .../yandrstudio/biu_nrf52_ble_lib/usb_main.c | 1146 +++++++++++++++++ .../yandrstudio/biu_nrf52_ble_lib/usb_main.h | 112 ++ 10 files changed, 2525 insertions(+) create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.cpp create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.h create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/biu_nrf52.cpp create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/main.c create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.c create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.h create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/ring_buffer.hpp create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/uart.h create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.c create mode 100644 keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.h diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.cpp b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.cpp new file mode 100644 index 000000000000..d398cd11d257 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.cpp @@ -0,0 +1,42 @@ + +#include "biu_ble_common.h" + +__attribute__((weak)) bool bluetooth_init(void) { return true; } + +__attribute__((weak)) void bluetooth_task(void) {} + +__attribute__((weak)) bool bluetooth_is_connected(void) { return true; } + +__attribute__((weak)) void bluetooth_unpair_all(void) {} + +__attribute__((weak)) void bluetooth_unpair_one(uint8_t device_id) {} + +__attribute__((weak)) void bluetooth_pair(void) {} + +__attribute__((weak)) void bluetooth_switch_one(uint8_t device_id) {} + +__attribute__((weak)) void bluetooth_send_keyboard(report_keyboard_t *report) {} + +#ifdef EXTRAKEY_ENABLE +__attribute__((weak)) void bluetooth_send_extra(uint8_t report_id, uint16_t keycode) {} +#endif + + +#ifdef MOUSE_ENABLE +__attribute__((weak)) void bluetooth_send_mouse(report_mouse_t *report) {} +#endif + +#ifdef NKRO_ENABLE +__attribute__((weak)) void bluetooth_send_keyboard_nkro(report_keyboard_t *report) {} +#endif + +#ifdef JOYSTICK_ENABLE +__attribute__((weak)) void bluetooth_send_joystick(joystick_report_t *report) {} +#endif + +__attribute__((weak)) void bluetooth_send_battery_level(void) { } + + + + + diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.h b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.h new file mode 100644 index 000000000000..df2ad810c9b2 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_ble_common.h @@ -0,0 +1,40 @@ +#pragma once + +#include "report.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool bluetooth_init(void); +void bluetooth_task(void); +bool bluetooth_is_connected(void); +void bluetooth_unpair_all(void); +void bluetooth_unpair_one(uint8_t device_id); +void bluetooth_pair(void); +void bluetooth_switch_one(uint8_t device_id); + +void bluetooth_send_keyboard(report_keyboard_t *report); + +#ifdef EXTRAKEY_ENABLE +void bluetooth_send_extra(uint8_t report_id, uint16_t keycode); +#endif + +#ifdef MOUSE_ENABLE +void bluetooth_send_mouse(report_mouse_t *report); +#endif + +#ifdef NKRO_ENABLE +void bluetooth_send_keyboard_nkro(report_keyboard_t *report); +#endif + +#ifdef JOYSTICK_ENABLE +void bluetooth_send_joystick(joystick_report_t *report); +#endif + +void bluetooth_send_battery_level(void); + +#ifdef __cplusplus +} +#endif diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_nrf52.cpp b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_nrf52.cpp new file mode 100644 index 000000000000..257c6c719de2 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/biu_nrf52.cpp @@ -0,0 +1,613 @@ +#include "biu_ble_common.h" +#include "report.h" +#include "timer.h" +#include "wait.h" +#include "uart.h" +#include "gpio.h" +#include "print.h" +#include "analog.h" +#include "keycode.h" +#include +#include +#include +#include +#include "ring_buffer.hpp" + + +// These are the pin assignments for the stm32f401ccu6 boards. +// You may define them to something else in your config.h +// if yours is wired up differently. +#ifndef BIUNRF52ResetPin +# define BIUNRF52ResetPin A1 +#endif + +// UART SETTING +#ifndef BIUNRF52RxPin +# define BIUNRF52RxPin A10 +#endif + +#ifndef BIUNRF52TxPin +# define BIUNRF52TxPin A9 +#endif + +#ifndef BIUNRF52UartBaud +# define BIUNRF52UartBaud 115200 +#endif + +// ADC SETTING +#define SAMPLE_BATTERY +#ifndef BATTERY_LEVEL_PIN +# define BATTERY_LEVEL_PIN B0 // Adc pin +#endif + +#ifndef BATTERY_LEVEL_SW_PIN +# define BATTERY_LEVEL_SW_PIN A1 // Adc pin +#endif + +#ifndef BATTERY_V_HEI +# define BATTERY_V_HEI 3800 // 3/8V +#endif + +#ifndef BATTERY_V_LOW +# define BATTERY_V_LOW 3500 // 3.5V +#endif + +#ifndef BATTERY_V_MAX +# define BATTERY_V_MAX 5000 // 5V +#endif + +#ifndef BATTERY_ADC_MAX +# define BATTERY_ADC_MAX 3080 +#endif + +// TIMEOUT INTERVAL SETTING +#define BiuNrf52MsgTimeout 150 /* milliseconds */ +#define BiuNrf52MsgShortTimeout 10 /* milliseconds */ +#define BiuNrf52MsgBackOff 25 /* microseconds */ +#define BatteryUpdateInterval 60000 /* milliseconds */ + + +enum biunrf52_type { + BiuNrf52MsgCommand = 0x10, + BiuNrf52MsgResponse = 0x20, + BiuNrf52MsgAlert = 0x40, + BiuNrf52MsgError = 0x80, + BiuNrf52MsgSlaveNotReady = 0xFE, // Try again later + BiuNrf52MsgSlaveOverflow = 0xFF, // You read more data than is available +}; + +enum ble_cmd { + BleUartHead = 0xFF, + BleUartTail = 0xFE, +}; + +enum ble_system_event_bits { + BleSystemConnected = 0, + BleSystemDisconnected = 1, + BleSystemUartRx = 8, + BleSystemMidiRx = 10, +}; + + + +#define BiuNrf52MsgMaxPayload 64 +struct biunrf52_msg { + uint8_t type; + uint8_t len; + uint8_t payload[BiuNrf52MsgMaxPayload]; +} __attribute__((packed)); + +enum queue_type { + QTKeyReport, // 1-byte modifier + 6-byte key report +#ifdef EXTRAKEY_ENABLE + QTConsumer, // 16-bit key code + QTSystem, +#endif + +#ifdef MOUSE_ENABLE + QTMouseMove, // 5-byte report +#endif + +#ifdef NKRO_ENABLE + QTNkro, // KEYBOARD_REPORT_BITS+1 -byte report +#endif + +#ifdef JOYSTICK_ENABLE + QTJoyStick, // xx-byte 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; +#ifdef EXTRAKEY_ENABLE + struct __attribute__((packed)) { + uint8_t consumer_h, consumer_l; + } extkey; +#endif +#ifdef MOUSE_ENABLE + struct __attribute__((packed)) { + uint8_t buttons; + int8_t x, y, v, h; + } mousemove; +#endif +#ifdef NKRO_ENABLE + struct __attribute__((packed)) { + uint8_t mods; + uint8_t bits[KEYBOARD_REPORT_BITS]; // for chibios, it is 32-2 + } nkey; +#endif + }; +}; + +/* HID report INDEXs */ +enum hid_report_index { + REPORT_INDEX_KEYBOARD = 0, + REPORT_INDEX_MOUSE, // 1 + REPORT_INDEX_SYSTEM, // 2 + REPORT_INDEX_CONSUMER,// 3 + REPORT_INDEX_NKRO, //4 + REPORT_INDEX_JOYSTICK // 5 +}; + +enum ble_command_biu { + START_ADV_WITH_WL = 6, + START_ADV_WITHOUT_WL, // 7 + STOP_ADV, // 8 + DEL_ALL_BOUND, // 9 + DEL_BOUND, // 10 0a + DEL_CURR_BOUND, // 11 0b + SWITCH_BOUND, // 12 0c + BLE_DEL_AND_ADV, // = 13 // 0d ble reset + SET_TX_POWER, // 14 0e + ENTER_INTO_SLEEP_MODEL, // 15 0f + USER_DEBUG_INFO, // 16 (10) + GET_BAT_INFO, // 17 (11) + ASK_CURRENT_NRF_STATE, // 18 (12) +}; + + + +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; + +// Items that we wish to send , 64+1, must has an empty pos +static RingBuffer 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 resp_buf; + + + + + +static bool send_a_pkt(const char *cmd, uint8_t cmd_len, uint16_t timeout) { + uint16_t timerStart = timer_read(); + bool ready = false; + dprint("BLE SEND: "); + for (uint8_t i = 0; i= BiuNrf52MsgMaxPayload) { + dprintf("ble send data to long: %d, maxlen is %d\n", cmd_len, BiuNrf52MsgMaxPayload); + return false; + } + + do { + // some to confirm the uart is start and can put new data + ready = !sdPutWouldBlock(&SERIAL_DRIVER); + + if (ready) { + break; + } + wait_ms(BiuNrf52MsgShortTimeout); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + // uart is ready; send the rest of the packet + for (uint8_t i = 0; i < cmd_len; ++i) { + uart_putchar((uint8_t)cmd[i]); + } + return true; + } + + + return false; +} +static void clear_uart_rx_buffer(void) { + while (!sdGetWouldBlock(&SERIAL_DRIVER)) { + uint8_t a = uart_getchar(); + } +} + +// Read a single SDEP packet +static bool receive_a_pkt(struct biunrf52_msg *msg, uint16_t timeout) { + uint16_t timerStart = timer_read(); + bool ready = false; + + do { + ready = !sdGetWouldBlock(&SERIAL_DRIVER); + if (ready) { + break; + } + wait_ms(BiuNrf52MsgShortTimeout); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + if (uart_getchar() == BleUartHead) { + if (!sdGetWouldBlock(&SERIAL_DRIVER)) { + msg->type = uart_getchar(); + } + if (!sdGetWouldBlock(&SERIAL_DRIVER)) { + msg->len = uart_getchar(); + } + if (msg->len > BiuNrf52MsgMaxPayload) { + clear_uart_rx_buffer(); + return false; + } + for (uint8_t i = 0; i < msg->len; ++i) { + if (!sdGetWouldBlock(&SERIAL_DRIVER)) { + msg->payload[i] = uart_getchar(); + } + } + if (!sdGetWouldBlock(&SERIAL_DRIVER)) { + if(uart_getchar() != BleUartTail) { + clear_uart_rx_buffer(); + } + } else { + clear_uart_rx_buffer(); + } + } else { + clear_uart_rx_buffer(); + return false; + } + return true; + } + return false; +} + + +static bool process_queue_item(struct queue_item *item, uint16_t timeout) { + char cmdbuf[128] = {0}; + cmdbuf[0] = BleUartHead; + + // 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: + cmdbuf[1] = REPORT_INDEX_KEYBOARD; + cmdbuf[2] = item->key.modifier; + cmdbuf[3] = 0x00; // reserved + memcpy(cmdbuf+4, item->key.keys, 6); + cmdbuf[10] = BleUartTail; + return send_a_pkt(cmdbuf, 11, timeout); +# ifdef EXTRAKEY_ENABLE + case QTConsumer: // 16-bit key code + cmdbuf[1] = REPORT_INDEX_CONSUMER; + cmdbuf[2] = item->extkey.consumer_l; + cmdbuf[3] = item->extkey.consumer_h; + cmdbuf[4] = BleUartTail; + return send_a_pkt(cmdbuf, 5, timeout); + case QTSystem: // 16-bit key code + cmdbuf[1] = REPORT_INDEX_SYSTEM; + cmdbuf[2] = item->extkey.consumer_l; + cmdbuf[3] = item->extkey.consumer_h; + cmdbuf[4] = BleUartTail; + return send_a_pkt(cmdbuf, 5, timeout); +# endif + +# ifdef MOUSE_ENABLE + case QTMouseMove: // 5-byte report + cmdbuf[1] = REPORT_INDEX_MOUSE; + cmdbuf[2] = item->mousemove.buttons; + cmdbuf[3] = item->mousemove.x; // int8_t -> uint8_t + cmdbuf[4] = item->mousemove.y; // int8_t -> uint8_t + cmdbuf[5] = item->mousemove.v; // int8_t -> uint8_t + cmdbuf[6] = item->mousemove.h; // int8_t -> uint8_t + cmdbuf[7] = BleUartTail; + return send_a_pkt(cmdbuf, 8, timeout); +# endif + +# ifdef NKRO_ENABLE + case QTNkro: // KEYBOARD_REPORT_BITS+1 -byte report + cmdbuf[1] = REPORT_INDEX_NKRO; + cmdbuf[2] = item->nkey.mods; + memcpy(cmdbuf+3, item->nkey.bits, KEYBOARD_REPORT_BITS); + cmdbuf[KEYBOARD_REPORT_BITS+2+1] = BleUartTail; + return send_a_pkt(cmdbuf, KEYBOARD_REPORT_BITS+2+1+1, timeout); +# endif + +# ifdef JOYSTICK_ENABLE + case QTJoyStick: // xx-byte report + return send_a_pkt(cmdbuf, NULL, 0, true, timeout); +# endif + + default: + return true; + } +} + + +static void resp_buf_read_one() { + uint16_t last_send; + struct biunrf52_msg msg; + + // if empty, then do nothing + if (!resp_buf.peek(last_send)) { + return; + } + + // determine whether data transfer is possible + if (uart_available()) { + if (receive_a_pkt(&msg, BiuNrf52MsgTimeout)) { + resp_buf.get(last_send); + dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); + } + } else { + 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 = BiuNrf52MsgTimeout) { + struct queue_item item; + + // Don't send anything more until we get an ACK + // if (!resp_buf.empty()) { + // return; + // } + // if empty, then do nothing + 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"); + wait_ms(BiuNrf52MsgTimeout); + resp_buf_read_one(); + } +} + +void bluetooth_send_keyboard(report_keyboard_t *report) { + + struct queue_item item; + + item.queue_type = QTKeyReport; + item.key.modifier = report->mods; + item.added = timer_read(); + memcpy(item.key.keys, report->keys, 6); + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } +} + + + +#ifdef EXTRAKEY_ENABLE +void bluetooth_send_extra(uint8_t report_id, uint16_t data) { + + struct queue_item item; + if (report_id == REPORT_ID_SYSTEM) { + item.queue_type = QTSystem; + } else { + item.queue_type = QTConsumer; + } + item.extkey.consumer_h = (data >> 8) & 0xff; + item.extkey.consumer_l = data & 0xff; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } + +} +#endif + +#ifdef MOUSE_ENABLE +void bluetooth_send_mouse(report_mouse_t *report) { + struct queue_item item; + item.queue_type = QTMouseMove; + item.mousemove.buttons = report->buttons; + item.mousemove.x = report->x; + item.mousemove.y = report->y; + item.mousemove.v = report->v; + item.mousemove.h = report->h; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } +} +#endif + +#ifdef NKRO_ENABLE +void bluetooth_send_keyboard_nkro(report_keyboard_t *report) { + struct queue_item item; + + item.queue_type = QTNkro; + item.nkey.mods = report->nkro.mods; + item.added = timer_read(); + memcpy(item.nkey.bits, report->nkro.bits, KEYBOARD_REPORT_BITS); + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } +} +#endif + +#ifdef JOYSTICK_ENABLE +void bluetooth_send_joystick(joystick_report_t *report) { + +} +#endif + +void bluetooth_send_battery_level() { + +#ifdef BATTERY_LEVEL_SW_PIN + setPinOutput(BATTERY_LEVEL_SW_PIN); + writePinLow(BATTERY_LEVEL_SW_PIN); +#endif + + uint16_t adc_val = analogReadPinAdc(BATTERY_LEVEL_PIN,0); + +#ifdef BATTERY_LEVEL_SW_PIN + writePinHigh(BATTERY_LEVEL_SW_PIN); +#endif + + uint16_t bat_lev = (BATTERY_V_MAX*adc_val/BATTERY_ADC_MAX - BATTERY_V_LOW)*100/(BATTERY_V_HEI-BATTERY_V_LOW); + + uart_putchar((bat_lev >> 8) & 0xFF); // H + uart_putchar(bat_lev & 0xFF); // L + +} + + +bool bluetooth_init(void) { + state.initialized = false; + state.configured = false; + state.is_connected = false; + + // start the uart data trans + uart_init(BIUNRF52UartBaud); + + // Perform a hardware reset + setPinOutput(BIUNRF52ResetPin); + writePinHigh(BIUNRF52ResetPin); + wait_ms(100); + writePinLow(BIUNRF52ResetPin); + wait_ms(100); + writePinHigh(BIUNRF52ResetPin); + + + // set the adc read sw off +# ifdef BATTERY_LEVEL_SW_PIN + setPinOutput(BATTERY_LEVEL_SW_PIN); + writePinHigh(BATTERY_LEVEL_SW_PIN); +# endif + + wait_ms(1500); // Give it 1.5 second to initialize or some ack frame + // todo ack + + state.initialized = true; + return state.initialized; +} + +void connected(void) { + uprintf("biu ble connected\n"); + // start adv with wl + uart_putchar(0xff); + state.is_connected = true; +} + +void disconnected(void) { + uprintf("biu ble disconnected\n"); + // stop adv + uart_putchar(0xff); + state.is_connected = false; +} + +bool bluetooth_is_connected(void) { return state.is_connected; } + + +void bluetooth_unpair_all(void) { + uprintf("biu ble del all\n"); + // stop adv and del all + uart_putchar(0xff); + state.is_connected = false; +} +void bluetooth_unpair_one(uint8_t device_id) { + uprintf("biu ble del %d\n", device_id); + // stop adv and del one + uart_putchar(0xff); + state.is_connected = false; +} +void bluetooth_pair(void) { + uprintf("biu ble pair cuurt\n"); + // start adv with wl (auto) + uart_putchar(0xff); + state.is_connected = true; +} +void bluetooth_switch_one(uint8_t device_id) { + uprintf("biu ble pair %d\n", device_id); + // switch adv with wl (auto) + uart_putchar(0xff); + state.is_connected = true; +} + + +bool biu_ble_enable_keyboard(void) { + char resbuf[128]; + + if (!state.initialized && !bluetooth_init()) { + return false; + } + + state.configured = false; + + // ask the nrf state + // uart_putchar(0xff); + + // wait or some thing + // todo + + // check the nrf returned msg + // uint8_t nrf_msg = uart_getchar(); + + + state.configured = true; + + // Check connection status in a little while; allow the ATZ time + // to kick in. + state.last_connection_update = timer_read(); + + return state.configured; +} + +void bluetooth_task(void) { + char resbuf[128]; + if (!state.configured && !biu_ble_enable_keyboard()) { + return; + } + resp_buf_read_one(); + send_buf_send_one(BiuNrf52MsgTimeout); + + +#ifdef SAMPLE_BATTERY + if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval) { + state.last_battery_update = timer_read(); + bluetooth_send_battery_level(); + } +#endif +} diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/main.c b/keyboards/yandrstudio/biu_nrf52_ble_lib/main.c new file mode 100644 index 000000000000..f3373d9b95e9 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/main.c @@ -0,0 +1,297 @@ +/* + * (c) 2015 flabberast + * + * Based on the following work: + * - Guillaume Duc's raw hid example (MIT License) + * https://github.com/guiduc/usb-hid-chibios-example + * - PJRC Teensy examples (MIT License) + * https://www.pjrc.com/teensy/usb_keyboard.html + * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) + * https://github.com/tmk/tmk_keyboard/ + * - ChibiOS demo code (Apache 2.0 License) + * http://www.chibios.org + * + * Since some GPL'd code is used, this work is licensed under + * GPL v2 or later. + */ + +#include +#include + +#include "usb_main.h" + +/* TMK includes */ +#include "report.h" +#include "host.h" +#include "host_driver.h" +#include "keyboard.h" +#include "action.h" +#include "action_util.h" +#include "mousekey.h" +#include "led.h" +#include "sendchar.h" +#include "debug.h" +#include "print.h" + + +#ifdef BIU_BLE5_ENABLE +#include "biu_ble_common.h" +#include "outputselect.h" +#endif + + +#ifndef EARLY_INIT_PERFORM_BOOTLOADER_JUMP +// Change this to be TRUE once we've migrated keyboards to the new init system +// Remember to change docs/platformdev_chibios_earlyinit.md as well. +# define EARLY_INIT_PERFORM_BOOTLOADER_JUMP FALSE +#endif + +#ifdef SLEEP_LED_ENABLE +# include "sleep_led.h" +#endif +#ifdef SERIAL_LINK_ENABLE +# include "serial_link/system/serial_link.h" +#endif +#ifdef VISUALIZER_ENABLE +# include "visualizer/visualizer.h" +#endif +#ifdef MIDI_ENABLE +# include "qmk_midi.h" +#endif +#ifdef STM32_EEPROM_ENABLE +# include "eeprom_stm32.h" +#endif +#ifdef EEPROM_DRIVER +# include "eeprom_driver.h" +#endif +#include "suspend.h" +#include "wait.h" + +/* ------------------------- + * TMK host driver defs + * ------------------------- + */ + +// /* declarations */ +// uint8_t keyboard_leds(void); +// void send_keyboard(report_keyboard_t *report); +// void send_mouse(report_mouse_t *report); +// void send_system(uint16_t data); +// void send_consumer(uint16_t data); + +/* host struct */ +host_driver_t chibios_driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer}; + +#ifdef VIRTSER_ENABLE +void virtser_task(void); +#endif + +#ifdef RAW_ENABLE +void raw_hid_task(void); +#endif + +#ifdef CONSOLE_ENABLE +void console_task(void); +#endif +#ifdef MIDI_ENABLE +void midi_ep_task(void); +#endif + +#ifdef BIU_BLE5_ENABLE +void bluetooth_task(void); +#endif + +/* TESTING + * Amber LED blinker thread, times are in milliseconds. + */ +/* set this variable to non-zero anywhere to blink once */ +// static THD_WORKING_AREA(waThread1, 128); +// static THD_FUNCTION(Thread1, arg) { + +// (void)arg; +// chRegSetThreadName("blinker"); +// while (true) { +// systime_t time; + +// time = USB_DRIVER.state == USB_ACTIVE ? 250 : 500; +// palClearLine(LINE_CAPS_LOCK); +// chSysPolledDelayX(MS2RTC(STM32_HCLK, time)); +// palSetLine(LINE_CAPS_LOCK); +// chSysPolledDelayX(MS2RTC(STM32_HCLK, time)); +// } +// } + +/* Early initialisation + */ +__attribute__((weak)) void early_hardware_init_pre(void) { +#if EARLY_INIT_PERFORM_BOOTLOADER_JUMP + void enter_bootloader_mode_if_requested(void); + enter_bootloader_mode_if_requested(); +#endif // EARLY_INIT_PERFORM_BOOTLOADER_JUMP +} + +__attribute__((weak)) void early_hardware_init_post(void) {} + +__attribute__((weak)) void board_init(void) {} + +// This overrides what's normally in ChibiOS board definitions +void __early_init(void) { + early_hardware_init_pre(); + + // This is the renamed equivalent of __early_init in the board.c file + void __chibios_override___early_init(void); + __chibios_override___early_init(); + + early_hardware_init_post(); +} + +// This overrides what's normally in ChibiOS board definitions +void boardInit(void) { + // This is the renamed equivalent of boardInit in the board.c file + void __chibios_override_boardInit(void); + __chibios_override_boardInit(); + + board_init(); +} + +/* Main thread + */ + +int cut_usb_st = 0; + +int main(void) __attribute__((weak)); +int main(void) { + /* ChibiOS/RT init */ + halInit(); + chSysInit(); + +#ifdef STM32_EEPROM_ENABLE + EEPROM_Init(); +#endif +#ifdef EEPROM_DRIVER + eeprom_driver_init(); +#endif + + // TESTING + // chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL); + + keyboard_setup(); + + + /* Init USB */ + usb_event_queue_init(); + init_usb_driver(&USB_DRIVER); + +#ifdef MIDI_ENABLE + setup_midi(); +#endif + +#ifdef SERIAL_LINK_ENABLE + init_serial_link(); +#endif + +#ifdef VISUALIZER_ENABLE + visualizer_init(); +#endif + + host_driver_t *driver = NULL; + + /* Wait until the USB or serial link is active */ + while (true) { +#if defined(WAIT_FOR_USB) || defined(SERIAL_LINK_ENABLE) + if (USB_DRIVER.state == USB_ACTIVE) { + driver = &chibios_driver; + break; + } +#else + driver = &chibios_driver; + break; +#endif +#ifdef SERIAL_LINK_ENABLE + if (is_serial_link_connected()) { + driver = get_serial_link_driver(); + break; + } + serial_link_update(); +#endif + wait_ms(50); + } + + /* Do need to wait here! + * Otherwise the next print might start a transfer on console EP + * before the USB is completely ready, which sometimes causes + * HardFaults. + */ + wait_ms(50); + + /* init TMK modules */ + keyboard_init(); + host_set_driver(driver); + + +#ifdef SLEEP_LED_ENABLE + sleep_led_init(); +#endif + + print("Keyboard start.\n"); + + /* Main loop */ + while (true) { + usb_event_queue_task(); + +#if !defined(NO_USB_STARTUP_CHECK) + if (USB_DRIVER.state == USB_SUSPENDED) { + print("[s]"); +# ifdef VISUALIZER_ENABLE + visualizer_suspend(); +# endif + while (USB_DRIVER.state == USB_SUSPENDED) { + /* Do this in the suspended state */ +# ifdef SERIAL_LINK_ENABLE + serial_link_update(); +# endif + suspend_power_down(); // on AVR this deep sleeps for 15ms + /* Remote wakeup */ + if (suspend_wakeup_condition()) { + usbWakeupHost(&USB_DRIVER); + restart_usb_driver(&USB_DRIVER); + } + } + /* Woken up */ + // variables has been already cleared by the wakeup hook + send_keyboard_report(); +# ifdef MOUSEKEY_ENABLE + mousekey_send(); +# endif /* MOUSEKEY_ENABLE */ + +# ifdef VISUALIZER_ENABLE + visualizer_resume(); +# endif + } +#endif + + keyboard_task(); +#ifdef CONSOLE_ENABLE + console_task(); +#endif +#ifdef MIDI_ENABLE + midi_ep_task(); +#endif +#ifdef VIRTSER_ENABLE + virtser_task(); +#endif +#ifdef RAW_ENABLE + raw_hid_task(); +#endif +#ifdef BIU_BLE5_ENABLE + if (where_to_send() == OUTPUT_BLUETOOTH) { + bluetooth_task(); + } +#endif + + + // Run housekeeping + housekeeping_task_kb(); + housekeeping_task_user(); + } +} diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.c b/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.c new file mode 100644 index 000000000000..4f3efd28b57d --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.c @@ -0,0 +1,81 @@ +/* +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 . +*/ + +#include "outputselect.h" +#include "biu_ble_common.h" +#if defined(PROTOCOL_CHIBIOS) +# include "usb_main.h" +#endif + +#include "uart.h" +#include "print.h" + + +bool biu_ble5_is_connected() { + return bluetooth_is_connected(); +} + + + +uint8_t desired_output = OUTPUT_DEFAULT; + +/** \brief Set Output + * + * FIXME: Needs doc + */ +void set_output(uint8_t output) { + set_output_user(output); + desired_output = output; +} + +/** \brief Set Output User + * + * FIXME: Needs doc + */ +__attribute__((weak)) void set_output_user(uint8_t output) {} + +static bool is_usb_configured(void) { +#if defined(PROTOCOL_CHIBIOS) + return USB_DRIVER.state == USB_ACTIVE; +#endif +} + +/** \brief Auto Detect Output + * + * FIXME: Needs doc + */ +uint8_t auto_detect_output(void) { + if (is_usb_configured()) { + return OUTPUT_USB; + } + +#ifdef BIU_BLE5_ENABLE + if (biu_ble5_is_connected()) { + return OUTPUT_BLUETOOTH; + } +#endif + + return OUTPUT_NONE; +} + +/** \brief Where To Send + * + * FIXME: Needs doc + */ +uint8_t where_to_send(void) { + if (desired_output == OUTPUT_AUTO) { + return auto_detect_output(); + } + return desired_output; +} diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.h b/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.h new file mode 100644 index 000000000000..85c65aac1d53 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/outputselect.h @@ -0,0 +1,43 @@ +/* +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 . +*/ + +#pragma once + +#include +#include +enum outputs { + OUTPUT_AUTO, + OUTPUT_NONE, + OUTPUT_USB, + OUTPUT_BLUETOOTH +}; + +#ifndef OUTPUT_DEFAULT +# define OUTPUT_DEFAULT OUTPUT_BLUETOOTH +#endif + + +#ifdef __cplusplus +extern "C" { +#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); +bool biu_ble5_is_connected(void); + +#ifdef __cplusplus +} +#endif diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/ring_buffer.hpp b/keyboards/yandrstudio/biu_nrf52_ble_lib/ring_buffer.hpp new file mode 100644 index 000000000000..8d9148dab9ec --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/ring_buffer.hpp @@ -0,0 +1,67 @@ +#pragma once +#include +// A simple ringbuffer holding Size elements of type T +template +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); + } +}; diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/uart.h b/keyboards/yandrstudio/biu_nrf52_ble_lib/uart.h new file mode 100644 index 000000000000..fc01f56f6493 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/uart.h @@ -0,0 +1,84 @@ +/* Copyright 2021 + * + * 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 3 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 . + */ + +#pragma once + +#include + +#include + +#ifndef SERIAL_DRIVER +# define SERIAL_DRIVER SD1 +#endif + +#ifndef SD1_TX_PIN +# define SD1_TX_PIN A9 +#endif + +#ifndef SD1_TX_PAL_MODE +# define SD1_TX_PAL_MODE 7 +#endif + +#ifndef SD1_RX_PIN +# define SD1_RX_PIN A10 +#endif + +#ifndef SD1_RX_PAL_MODE +# define SD1_RX_PAL_MODE 7 +#endif + +#ifndef SD1_CTS_PIN +# define SD1_CTS_PIN A11 +#endif + +#ifndef SD1_CTS_PAL_MODE +# define SD1_CTS_PAL_MODE 7 +#endif + +#ifndef SD1_RTS_PIN +# define SD1_RTS_PIN A12 +#endif + +#ifndef SD1_RTS_PAL_MODE +# define SD1_RTS_PAL_MODE 7 +#endif + +#ifndef SD1_CR1 +# define SD1_CR1 0 +#endif + +#ifndef SD1_CR2 +# define SD1_CR2 0 +#endif + +#ifndef SD1_CR3 +# define SD1_CR3 0 +#endif +#ifdef __cplusplus +extern "C" { +#endif + +void uart_init(uint32_t baud); + +void uart_putchar(uint8_t c); + +uint8_t uart_getchar(void); + +bool uart_available(void); + +#ifdef __cplusplus +} +#endif diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.c b/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.c new file mode 100644 index 000000000000..d6897bc0be02 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.c @@ -0,0 +1,1146 @@ +/* + * (c) 2015 flabberast + * + * Based on the following work: + * - Guillaume Duc's raw hid example (MIT License) + * https://github.com/guiduc/usb-hid-chibios-example + * - PJRC Teensy examples (MIT License) + * https://www.pjrc.com/teensy/usb_keyboard.html + * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) + * https://github.com/tmk/tmk_keyboard/ + * - ChibiOS demo code (Apache 2.0 License) + * http://www.chibios.org + * + * Since some GPL'd code is used, this work is licensed under + * GPL v2 or later. + */ + +/* + * Implementation notes: + * + * USBEndpointConfig - Configured using explicit order instead of struct member name. + * This is due to ChibiOS hal LLD differences, which is dependent on hardware, + * "USBv1" devices have `ep_buffers` and "OTGv1" have `in_multiplier`. + * Given `USBv1/hal_usb_lld.h` marks the field as "not currently used" this code file + * makes the assumption this is safe to avoid littering with preprocessor directives. + */ + +#include +#include +#include + +#include "usb_main.h" + +#include "host.h" +#include "debug.h" +#include "suspend.h" +#ifdef SLEEP_LED_ENABLE +# include "sleep_led.h" +# include "led.h" +#endif +#include "wait.h" +#include "usb_descriptor.h" +#include "usb_driver.h" + + +#ifdef BIU_BLE5_ENABLE +#include "outputselect.h" +#include "biu_ble_common.h" +#endif + + + + +#ifdef NKRO_ENABLE +# include "keycode_config.h" + +extern keymap_config_t keymap_config; +#endif + +#ifdef JOYSTICK_ENABLE +# include "joystick.h" +#endif + +/* --------------------------------------------------------- + * Global interface variables and declarations + * --------------------------------------------------------- + */ + +#ifndef usb_lld_connect_bus +# define usb_lld_connect_bus(usbp) +#endif + +#ifndef usb_lld_disconnect_bus +# define usb_lld_disconnect_bus(usbp) +#endif + +uint8_t keyboard_idle __attribute__((aligned(2))) = 0; +uint8_t keyboard_protocol __attribute__((aligned(2))) = 1; +uint8_t keyboard_led_state = 0; +volatile uint16_t keyboard_idle_count = 0; +static virtual_timer_t keyboard_idle_timer; +static void keyboard_idle_timer_cb(void *arg); + +report_keyboard_t keyboard_report_sent = {{0}}; +#ifdef MOUSE_ENABLE +report_mouse_t mouse_report_blank = {0}; +#endif /* MOUSE_ENABLE */ +#ifdef EXTRAKEY_ENABLE +uint8_t extra_report_blank[3] = {0}; +#endif /* EXTRAKEY_ENABLE */ + +/* --------------------------------------------------------- + * Descriptors and USB driver objects + * --------------------------------------------------------- + */ + +/* HID specific constants */ +#define HID_GET_REPORT 0x01 +#define HID_GET_IDLE 0x02 +#define HID_GET_PROTOCOL 0x03 +#define HID_SET_REPORT 0x09 +#define HID_SET_IDLE 0x0A +#define HID_SET_PROTOCOL 0x0B + +/* + * Handles the GET_DESCRIPTOR callback + * + * Returns the proper descriptor + */ +static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) { + (void)usbp; + static USBDescriptor desc; + uint16_t wValue = ((uint16_t)dtype << 8) | dindex; + desc.ud_string = NULL; + desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const) & desc.ud_string); + if (desc.ud_string == NULL) + return NULL; + else + return &desc; +} + +#ifndef KEYBOARD_SHARED_EP +/* keyboard endpoint state structure */ +static USBInEndpointState kbd_ep_state; +/* keyboard endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ +static const USBEndpointConfig kbd_ep_config = { + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ + NULL, /* SETUP packet notification callback */ + kbd_in_cb, /* IN notification callback */ + NULL, /* OUT notification callback */ + KEYBOARD_EPSIZE, /* IN maximum packet size */ + 0, /* OUT maximum packet size */ + &kbd_ep_state, /* IN Endpoint state */ + NULL, /* OUT endpoint state */ + 2, /* IN multiplier */ + NULL /* SETUP buffer (not a SETUP endpoint) */ +}; +#endif + +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) +/* mouse endpoint state structure */ +static USBInEndpointState mouse_ep_state; + +/* mouse endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ +static const USBEndpointConfig mouse_ep_config = { + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ + NULL, /* SETUP packet notification callback */ + mouse_in_cb, /* IN notification callback */ + NULL, /* OUT notification callback */ + MOUSE_EPSIZE, /* IN maximum packet size */ + 0, /* OUT maximum packet size */ + &mouse_ep_state, /* IN Endpoint state */ + NULL, /* OUT endpoint state */ + 2, /* IN multiplier */ + NULL /* SETUP buffer (not a SETUP endpoint) */ +}; +#endif + +#ifdef SHARED_EP_ENABLE +/* shared endpoint state structure */ +static USBInEndpointState shared_ep_state; + +/* shared endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ +static const USBEndpointConfig shared_ep_config = { + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ + NULL, /* SETUP packet notification callback */ + shared_in_cb, /* IN notification callback */ + NULL, /* OUT notification callback */ + SHARED_EPSIZE, /* IN maximum packet size */ + 0, /* OUT maximum packet size */ + &shared_ep_state, /* IN Endpoint state */ + NULL, /* OUT endpoint state */ + 2, /* IN multiplier */ + NULL /* SETUP buffer (not a SETUP endpoint) */ +}; +#endif + +#if STM32_USB_USE_OTG1 +typedef struct { + size_t queue_capacity_in; + size_t queue_capacity_out; + USBInEndpointState in_ep_state; + USBOutEndpointState out_ep_state; + USBInEndpointState int_ep_state; + USBEndpointConfig inout_ep_config; + USBEndpointConfig int_ep_config; + const QMKUSBConfig config; + QMKUSBDriver driver; +} usb_driver_config_t; +#else +typedef struct { + size_t queue_capacity_in; + size_t queue_capacity_out; + USBInEndpointState in_ep_state; + USBOutEndpointState out_ep_state; + USBInEndpointState int_ep_state; + USBEndpointConfig in_ep_config; + USBEndpointConfig out_ep_config; + USBEndpointConfig int_ep_config; + const QMKUSBConfig config; + QMKUSBDriver driver; +} usb_driver_config_t; +#endif + +#if STM32_USB_USE_OTG1 +/* Reusable initialization structure - see USBEndpointConfig comment at top of file */ +# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \ + { \ + .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \ + .inout_ep_config = \ + { \ + stream##_IN_MODE, /* Interrupt EP */ \ + NULL, /* SETUP packet notification callback */ \ + qmkusbDataTransmitted, /* IN notification callback */ \ + qmkusbDataReceived, /* OUT notification callback */ \ + stream##_EPSIZE, /* IN maximum packet size */ \ + stream##_EPSIZE, /* OUT maximum packet size */ \ + NULL, /* IN Endpoint state */ \ + NULL, /* OUT endpoint state */ \ + 2, /* IN multiplier */ \ + NULL /* SETUP buffer (not a SETUP endpoint) */ \ + }, \ + .int_ep_config = \ + { \ + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \ + NULL, /* SETUP packet notification callback */ \ + qmkusbInterruptTransmitted, /* IN notification callback */ \ + NULL, /* OUT notification callback */ \ + CDC_NOTIFICATION_EPSIZE, /* IN maximum packet size */ \ + 0, /* OUT maximum packet size */ \ + NULL, /* IN Endpoint state */ \ + NULL, /* OUT endpoint state */ \ + 2, /* IN multiplier */ \ + NULL, /* SETUP buffer (not a SETUP endpoint) */ \ + }, \ + .config = { \ + .usbp = &USB_DRIVER, \ + .bulk_in = stream##_IN_EPNUM, \ + .bulk_out = stream##_OUT_EPNUM, \ + .int_in = notification, \ + .in_buffers = stream##_IN_CAPACITY, \ + .out_buffers = stream##_OUT_CAPACITY, \ + .in_size = stream##_EPSIZE, \ + .out_size = stream##_EPSIZE, \ + .fixed_size = fixedsize, \ + .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \ + .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \ + } \ + } +#else +/* Reusable initialization structure - see USBEndpointConfig comment at top of file */ +# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \ + { \ + .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \ + .in_ep_config = \ + { \ + stream##_IN_MODE, /* Interrupt EP */ \ + NULL, /* SETUP packet notification callback */ \ + qmkusbDataTransmitted, /* IN notification callback */ \ + NULL, /* OUT notification callback */ \ + stream##_EPSIZE, /* IN maximum packet size */ \ + 0, /* OUT maximum packet size */ \ + NULL, /* IN Endpoint state */ \ + NULL, /* OUT endpoint state */ \ + 2, /* IN multiplier */ \ + NULL /* SETUP buffer (not a SETUP endpoint) */ \ + }, \ + .out_ep_config = \ + { \ + stream##_OUT_MODE, /* Interrupt EP */ \ + NULL, /* SETUP packet notification callback */ \ + NULL, /* IN notification callback */ \ + qmkusbDataReceived, /* OUT notification callback */ \ + 0, /* IN maximum packet size */ \ + stream##_EPSIZE, /* OUT maximum packet size */ \ + NULL, /* IN Endpoint state */ \ + NULL, /* OUT endpoint state */ \ + 2, /* IN multiplier */ \ + NULL, /* SETUP buffer (not a SETUP endpoint) */ \ + }, \ + .int_ep_config = \ + { \ + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \ + NULL, /* SETUP packet notification callback */ \ + qmkusbInterruptTransmitted, /* IN notification callback */ \ + NULL, /* OUT notification callback */ \ + CDC_NOTIFICATION_EPSIZE, /* IN maximum packet size */ \ + 0, /* OUT maximum packet size */ \ + NULL, /* IN Endpoint state */ \ + NULL, /* OUT endpoint state */ \ + 2, /* IN multiplier */ \ + NULL, /* SETUP buffer (not a SETUP endpoint) */ \ + }, \ + .config = { \ + .usbp = &USB_DRIVER, \ + .bulk_in = stream##_IN_EPNUM, \ + .bulk_out = stream##_OUT_EPNUM, \ + .int_in = notification, \ + .in_buffers = stream##_IN_CAPACITY, \ + .out_buffers = stream##_OUT_CAPACITY, \ + .in_size = stream##_EPSIZE, \ + .out_size = stream##_EPSIZE, \ + .fixed_size = fixedsize, \ + .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \ + .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \ + } \ + } +#endif + +typedef struct { + union { + struct { +#ifdef CONSOLE_ENABLE + usb_driver_config_t console_driver; +#endif +#ifdef RAW_ENABLE + usb_driver_config_t raw_driver; +#endif +#ifdef MIDI_ENABLE + usb_driver_config_t midi_driver; +#endif +#ifdef VIRTSER_ENABLE + usb_driver_config_t serial_driver; +#endif +#ifdef JOYSTICK_ENABLE + usb_driver_config_t joystick_driver; +#endif + }; + usb_driver_config_t array[0]; + }; +} usb_driver_configs_t; + +static usb_driver_configs_t drivers = { +#ifdef CONSOLE_ENABLE +# define CONSOLE_IN_CAPACITY 4 +# define CONSOLE_OUT_CAPACITY 4 +# define CONSOLE_IN_MODE USB_EP_MODE_TYPE_INTR +# define CONSOLE_OUT_MODE USB_EP_MODE_TYPE_INTR + .console_driver = QMK_USB_DRIVER_CONFIG(CONSOLE, 0, true), +#endif +#ifdef RAW_ENABLE +# define RAW_IN_CAPACITY 4 +# define RAW_OUT_CAPACITY 4 +# define RAW_IN_MODE USB_EP_MODE_TYPE_INTR +# define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR + .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false), +#endif + +#ifdef MIDI_ENABLE +# define MIDI_STREAM_IN_CAPACITY 4 +# define MIDI_STREAM_OUT_CAPACITY 4 +# define MIDI_STREAM_IN_MODE USB_EP_MODE_TYPE_BULK +# define MIDI_STREAM_OUT_MODE USB_EP_MODE_TYPE_BULK + .midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false), +#endif + +#ifdef VIRTSER_ENABLE +# define CDC_IN_CAPACITY 4 +# define CDC_OUT_CAPACITY 4 +# define CDC_IN_MODE USB_EP_MODE_TYPE_BULK +# define CDC_OUT_MODE USB_EP_MODE_TYPE_BULK + .serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false), +#endif + +#ifdef JOYSTICK_ENABLE +# define JOYSTICK_IN_CAPACITY 4 +# define JOYSTICK_OUT_CAPACITY 4 +# define JOYSTICK_IN_MODE USB_EP_MODE_TYPE_BULK +# define JOYSTICK_OUT_MODE USB_EP_MODE_TYPE_BULK + .joystick_driver = QMK_USB_DRIVER_CONFIG(JOYSTICK, 0, false), +#endif +}; + +#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t)) + +/* --------------------------------------------------------- + * USB driver functions + * --------------------------------------------------------- + */ + +#define USB_EVENT_QUEUE_SIZE 16 +usbevent_t event_queue[USB_EVENT_QUEUE_SIZE]; +uint8_t event_queue_head; +uint8_t event_queue_tail; + +void usb_event_queue_init(void) { + // Initialise the event queue + memset(&event_queue, 0, sizeof(event_queue)); + event_queue_head = 0; + event_queue_tail = 0; +} + +static inline bool usb_event_queue_enqueue(usbevent_t event) { + uint8_t next = (event_queue_head + 1) % USB_EVENT_QUEUE_SIZE; + if (next == event_queue_tail) { + return false; + } + event_queue[event_queue_head] = event; + event_queue_head = next; + return true; +} + +static inline bool usb_event_queue_dequeue(usbevent_t *event) { + if (event_queue_head == event_queue_tail) { + return false; + } + *event = event_queue[event_queue_tail]; + event_queue_tail = (event_queue_tail + 1) % USB_EVENT_QUEUE_SIZE; + return true; +} + +static inline void usb_event_suspend_handler(void) { +#ifdef SLEEP_LED_ENABLE + sleep_led_enable(); +#endif /* SLEEP_LED_ENABLE */ +} + +static inline void usb_event_wakeup_handler(void) { + suspend_wakeup_init(); +#ifdef SLEEP_LED_ENABLE + sleep_led_disable(); + // NOTE: converters may not accept this + led_set(host_keyboard_leds()); +#endif /* SLEEP_LED_ENABLE */ +} + +void usb_event_queue_task(void) { + usbevent_t event; + while (usb_event_queue_dequeue(&event)) { + switch (event) { + case USB_EVENT_SUSPEND: + usb_event_suspend_handler(); + break; + case USB_EVENT_WAKEUP: + usb_event_wakeup_handler(); + break; + default: + // Nothing to do, we don't handle it. + break; + } + } +} + +/* Handles the USB driver global events + * TODO: maybe disable some things when connection is lost? */ +static void usb_event_cb(USBDriver *usbp, usbevent_t event) { + switch (event) { + case USB_EVENT_ADDRESS: + return; + + case USB_EVENT_CONFIGURED: + osalSysLockFromISR(); + /* Enable the endpoints specified into the configuration. */ +#ifndef KEYBOARD_SHARED_EP + usbInitEndpointI(usbp, KEYBOARD_IN_EPNUM, &kbd_ep_config); +#endif +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) + usbInitEndpointI(usbp, MOUSE_IN_EPNUM, &mouse_ep_config); +#endif +#ifdef SHARED_EP_ENABLE + usbInitEndpointI(usbp, SHARED_IN_EPNUM, &shared_ep_config); +#endif + for (int i = 0; i < NUM_USB_DRIVERS; i++) { +#if STM32_USB_USE_OTG1 + usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].inout_ep_config); +#else + usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].in_ep_config); + usbInitEndpointI(usbp, drivers.array[i].config.bulk_out, &drivers.array[i].out_ep_config); +#endif + if (drivers.array[i].config.int_in) { + usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config); + } + qmkusbConfigureHookI(&drivers.array[i].driver); + } + osalSysUnlockFromISR(); + return; + case USB_EVENT_SUSPEND: + usb_event_queue_enqueue(USB_EVENT_SUSPEND); + /* Falls into.*/ + case USB_EVENT_UNCONFIGURED: + /* Falls into.*/ + case USB_EVENT_RESET: + for (int i = 0; i < NUM_USB_DRIVERS; i++) { + chSysLockFromISR(); + /* Disconnection event on suspend.*/ + qmkusbSuspendHookI(&drivers.array[i].driver); + chSysUnlockFromISR(); + } + return; + + case USB_EVENT_WAKEUP: + // TODO: from ISR! print("[W]"); + for (int i = 0; i < NUM_USB_DRIVERS; i++) { + chSysLockFromISR(); + /* Disconnection event on suspend.*/ + qmkusbWakeupHookI(&drivers.array[i].driver); + chSysUnlockFromISR(); + } + usb_event_queue_enqueue(USB_EVENT_WAKEUP); + return; + + case USB_EVENT_STALLED: + return; + } +} + +/* Function used locally in os/hal/src/usb.c for getting descriptors + * need it here for HID descriptor */ +static uint16_t get_hword(uint8_t *p) { + uint16_t hw; + + hw = (uint16_t)*p++; + hw |= (uint16_t)*p << 8U; + return hw; +} + +/* + * 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 + */ + +static uint8_t set_report_buf[2] __attribute__((aligned(2))); +static void set_led_transfer_cb(USBDriver *usbp) { + if (usbp->setup[6] == 2) { /* LSB(wLength) */ + uint8_t report_id = set_report_buf[0]; + if ((report_id == REPORT_ID_KEYBOARD) || (report_id == REPORT_ID_NKRO)) { + keyboard_led_state = set_report_buf[1]; + } + } else { + keyboard_led_state = set_report_buf[0]; + } +} + +/* Callback for SETUP request on the endpoint 0 (control) */ +static bool usb_request_hook_cb(USBDriver *usbp) { + const USBDescriptor *dp; + + /* usbp->setup fields: + * 0: bmRequestType (bitmask) + * 1: bRequest + * 2,3: (LSB,MSB) wValue + * 4,5: (LSB,MSB) wIndex + * 6,7: (LSB,MSB) wLength (number of bytes to transfer if there is a data phase) */ + + /* Handle HID class specific requests */ + if (((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) && ((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)) { + switch (usbp->setup[0] & USB_RTYPE_DIR_MASK) { + case USB_RTYPE_DIR_DEV2HOST: + switch (usbp->setup[1]) { /* bRequest */ + case HID_GET_REPORT: + switch (usbp->setup[4]) { /* LSB(wIndex) (check MSB==0?) */ + case KEYBOARD_INTERFACE: + usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, sizeof(keyboard_report_sent), NULL); + return TRUE; + break; + +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) + case MOUSE_INTERFACE: + usbSetupTransfer(usbp, (uint8_t *)&mouse_report_blank, sizeof(mouse_report_blank), NULL); + return TRUE; + break; +#endif + + default: + usbSetupTransfer(usbp, NULL, 0, NULL); + return TRUE; + break; + } + break; + + case HID_GET_PROTOCOL: + if ((usbp->setup[4] == KEYBOARD_INTERFACE) && (usbp->setup[5] == 0)) { /* wIndex */ + usbSetupTransfer(usbp, &keyboard_protocol, 1, NULL); + return TRUE; + } + break; + + case HID_GET_IDLE: + usbSetupTransfer(usbp, &keyboard_idle, 1, NULL); + return TRUE; + break; + } + break; + + case USB_RTYPE_DIR_HOST2DEV: + switch (usbp->setup[1]) { /* bRequest */ + case HID_SET_REPORT: + switch (usbp->setup[4]) { /* LSB(wIndex) (check MSB==0?) */ + case KEYBOARD_INTERFACE: +#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP) + case SHARED_INTERFACE: +#endif + usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb); + return TRUE; + break; + } + break; + + case HID_SET_PROTOCOL: + if ((usbp->setup[4] == KEYBOARD_INTERFACE) && (usbp->setup[5] == 0)) { /* wIndex */ + keyboard_protocol = ((usbp->setup[2]) != 0x00); /* LSB(wValue) */ +#ifdef NKRO_ENABLE + keymap_config.nkro = !!keyboard_protocol; + if (!keymap_config.nkro && keyboard_idle) { +#else /* NKRO_ENABLE */ + if (keyboard_idle) { +#endif /* NKRO_ENABLE */ + /* arm the idle timer if boot protocol & idle */ + osalSysLockFromISR(); + chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); + osalSysUnlockFromISR(); + } + } + usbSetupTransfer(usbp, NULL, 0, NULL); + return TRUE; + break; + + case HID_SET_IDLE: + keyboard_idle = usbp->setup[3]; /* MSB(wValue) */ + /* arm the timer */ +#ifdef NKRO_ENABLE + if (!keymap_config.nkro && keyboard_idle) { +#else /* NKRO_ENABLE */ + if (keyboard_idle) { +#endif /* NKRO_ENABLE */ + osalSysLockFromISR(); + chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); + osalSysUnlockFromISR(); + } + usbSetupTransfer(usbp, NULL, 0, NULL); + return TRUE; + break; + } + break; + } + } + + /* Handle the Get_Descriptor Request for HID class (not handled by the default hook) */ + if ((usbp->setup[0] == 0x81) && (usbp->setup[1] == USB_REQ_GET_DESCRIPTOR)) { + dp = usbp->config->get_descriptor_cb(usbp, usbp->setup[3], usbp->setup[2], get_hword(&usbp->setup[4])); + if (dp == NULL) return FALSE; + usbSetupTransfer(usbp, (uint8_t *)dp->ud_string, dp->ud_size, NULL); + return TRUE; + } + + for (int i = 0; i < NUM_USB_DRIVERS; i++) { + if (drivers.array[i].config.int_in) { + // NOTE: Assumes that we only have one serial driver + return qmkusbRequestsHook(usbp); + } + } + + return FALSE; +} + +/* Start-of-frame callback */ +static void usb_sof_cb(USBDriver *usbp) { + kbd_sof_cb(usbp); + osalSysLockFromISR(); + for (int i = 0; i < NUM_USB_DRIVERS; i++) { + qmkusbSOFHookI(&drivers.array[i].driver); + } + osalSysUnlockFromISR(); +} + +/* USB driver configuration */ +static const USBConfig usbcfg = { + usb_event_cb, /* USB events callback */ + usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */ + usb_request_hook_cb, /* Requests hook callback */ + usb_sof_cb /* Start Of Frame callback */ +}; + +/* + * Initialize the USB driver + */ +void init_usb_driver(USBDriver *usbp) { + for (int i = 0; i < NUM_USB_DRIVERS; i++) { +#if STM32_USB_USE_OTG1 + QMKUSBDriver *driver = &drivers.array[i].driver; + drivers.array[i].inout_ep_config.in_state = &drivers.array[i].in_ep_state; + drivers.array[i].inout_ep_config.out_state = &drivers.array[i].out_ep_state; + drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state; + qmkusbObjectInit(driver, &drivers.array[i].config); + qmkusbStart(driver, &drivers.array[i].config); +#else + QMKUSBDriver *driver = &drivers.array[i].driver; + drivers.array[i].in_ep_config.in_state = &drivers.array[i].in_ep_state; + drivers.array[i].out_ep_config.out_state = &drivers.array[i].out_ep_state; + drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state; + qmkusbObjectInit(driver, &drivers.array[i].config); + qmkusbStart(driver, &drivers.array[i].config); +#endif + } + + /* + * Activates the USB driver and then the USB bus pull-up on D+. + * Note, a delay is inserted in order to not have to disconnect the cable + * after a reset. + */ + usbDisconnectBus(usbp); + wait_ms(1500); + usbStart(usbp, &usbcfg); + usbConnectBus(usbp); + + chVTObjectInit(&keyboard_idle_timer); +} + +void restart_usb_driver(USBDriver *usbp) { + usbStop(usbp); + usbDisconnectBus(usbp); + +#if USB_SUSPEND_WAKEUP_DELAY > 0 + // Some hubs, kvm switches, and monitors do + // weird things, with USB device state bouncing + // around wildly on wakeup, yielding race + // conditions that can corrupt the keyboard state. + // + // Pause for a while to let things settle... + wait_ms(USB_SUSPEND_WAKEUP_DELAY); +#endif + + usbStart(usbp, &usbcfg); + usbConnectBus(usbp); +} + +/* --------------------------------------------------------- + * Keyboard functions + * --------------------------------------------------------- + */ +/* keyboard IN callback hander (a kbd report has made it IN) */ +#ifndef KEYBOARD_SHARED_EP +void kbd_in_cb(USBDriver *usbp, usbep_t ep) { + /* STUB */ + (void)usbp; + (void)ep; +} +#endif + +/* start-of-frame handler + * TODO: i guess it would be better to re-implement using timers, + * so that this is not going to have to be checked every 1ms */ +void kbd_sof_cb(USBDriver *usbp) { (void)usbp; } + +/* Idle requests timer code + * callback (called from ISR, unlocked state) */ +static void keyboard_idle_timer_cb(void *arg) { + USBDriver *usbp = (USBDriver *)arg; + + osalSysLockFromISR(); + + /* check that the states of things are as they're supposed to */ + if (usbGetDriverStateI(usbp) != USB_ACTIVE) { + /* do not rearm the timer, should be enabled on IDLE request */ + osalSysUnlockFromISR(); + return; + } + +#ifdef NKRO_ENABLE + if (!keymap_config.nkro && keyboard_idle && keyboard_protocol) { +#else /* NKRO_ENABLE */ + if (keyboard_idle && keyboard_protocol) { +#endif /* NKRO_ENABLE */ + /* TODO: are we sure we want the KBD_ENDPOINT? */ + if (!usbGetTransmitStatusI(usbp, KEYBOARD_IN_EPNUM)) { + usbStartTransmitI(usbp, KEYBOARD_IN_EPNUM, (uint8_t *)&keyboard_report_sent, KEYBOARD_EPSIZE); + } + /* rearm the timer */ + chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); + } + + /* do not rearm the timer if the condition above fails + * it should be enabled again on either IDLE or SET_PROTOCOL requests */ + osalSysUnlockFromISR(); +} + +/* LED status */ +uint8_t keyboard_leds(void) { return keyboard_led_state; } + +/* prepare and start sending a report IN + * not callable from ISR or locked state */ +void send_keyboard(report_keyboard_t *report) { + +#ifdef BIU_BLE5_ENABLE + if (where_to_send() == OUTPUT_BLUETOOTH) { +#ifdef NKRO_ENABLE + if (keymap_config.nkro && keyboard_protocol) { + bluetooth_send_keyboard_nkro(report); + } else +#endif + { + bluetooth_send_keyboard(report); + } + + return; + } +#endif + + osalSysLock(); + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + goto unlock; + } + +#ifdef NKRO_ENABLE + if (keymap_config.nkro && keyboard_protocol) { /* NKRO protocol */ + /* need to wait until the previous packet has made it through */ + /* can rewrite this using the synchronous API, then would wait + * until *after* the packet has been transmitted. I think + * this is more efficient */ + /* busy wait, should be short and not very common */ + if (usbGetTransmitStatusI(&USB_DRIVER, SHARED_IN_EPNUM)) { + /* Need to either suspend, or loop and call unlock/lock during + * every iteration - otherwise the system will remain locked, + * no interrupts served, so USB not going through as well. + * Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */ + osalThreadSuspendS(&(&USB_DRIVER)->epc[SHARED_IN_EPNUM]->in_state->thread); + + /* after osalThreadSuspendS returns USB status might have changed */ + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + goto unlock; + } + } + usbStartTransmitI(&USB_DRIVER, SHARED_IN_EPNUM, (uint8_t *)report, sizeof(struct nkro_report)); + } else +#endif /* NKRO_ENABLE */ + { /* regular protocol */ + /* need to wait until the previous packet has made it through */ + /* busy wait, should be short and not very common */ + if (usbGetTransmitStatusI(&USB_DRIVER, KEYBOARD_IN_EPNUM)) { + /* Need to either suspend, or loop and call unlock/lock during + * every iteration - otherwise the system will remain locked, + * no interrupts served, so USB not going through as well. + * Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */ + osalThreadSuspendS(&(&USB_DRIVER)->epc[KEYBOARD_IN_EPNUM]->in_state->thread); + + /* after osalThreadSuspendS returns USB status might have changed */ + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + goto unlock; + } + } + uint8_t *data, size; + if (keyboard_protocol) { + data = (uint8_t *)report; + size = KEYBOARD_REPORT_SIZE; + } else { /* boot protocol */ + data = &report->mods; + size = 8; + } + usbStartTransmitI(&USB_DRIVER, KEYBOARD_IN_EPNUM, data, size); + } + keyboard_report_sent = *report; + +unlock: + osalSysUnlock(); +} + +/* --------------------------------------------------------- + * Mouse functions + * --------------------------------------------------------- + */ + +#ifdef MOUSE_ENABLE + +# ifndef MOUSE_SHARED_EP +/* mouse IN callback hander (a mouse report has made it IN) */ +void mouse_in_cb(USBDriver *usbp, usbep_t ep) { + (void)usbp; + (void)ep; +} +# endif + +void send_mouse(report_mouse_t *report) { + +#ifdef BIU_BLE5_ENABLE + if (where_to_send() == OUTPUT_BLUETOOTH) { + bluetooth_send_mouse(report); + return; + } +#endif + + osalSysLock(); + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + osalSysUnlock(); + return; + } + + if (usbGetTransmitStatusI(&USB_DRIVER, MOUSE_IN_EPNUM)) { + /* Need to either suspend, or loop and call unlock/lock during + * every iteration - otherwise the system will remain locked, + * no interrupts served, so USB not going through as well. + * Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */ + if (osalThreadSuspendTimeoutS(&(&USB_DRIVER)->epc[MOUSE_IN_EPNUM]->in_state->thread, TIME_MS2I(10)) == MSG_TIMEOUT) { + osalSysUnlock(); + return; + } + } + usbStartTransmitI(&USB_DRIVER, MOUSE_IN_EPNUM, (uint8_t *)report, sizeof(report_mouse_t)); + osalSysUnlock(); +} + +#else /* MOUSE_ENABLE */ +void send_mouse(report_mouse_t *report) { (void)report; } +#endif /* MOUSE_ENABLE */ + +/* --------------------------------------------------------- + * Shared EP functions + * --------------------------------------------------------- + */ +#ifdef SHARED_EP_ENABLE +/* shared IN callback hander */ +void shared_in_cb(USBDriver *usbp, usbep_t ep) { + /* STUB */ + (void)usbp; + (void)ep; +} +#endif + +/* --------------------------------------------------------- + * Extrakey functions + * --------------------------------------------------------- + */ + +#ifdef EXTRAKEY_ENABLE +static void send_extra(uint8_t report_id, uint16_t data) { +#ifdef BIU_BLE5_ENABLE + if (where_to_send() == OUTPUT_BLUETOOTH) { + bluetooth_send_extra(report_id, data); + return; + } +#endif + osalSysLock(); + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + osalSysUnlock(); + return; + } + + report_extra_t report = {.report_id = report_id, .usage = data}; + + usbStartTransmitI(&USB_DRIVER, SHARED_IN_EPNUM, (uint8_t *)&report, sizeof(report_extra_t)); + osalSysUnlock(); +} +#endif + +void send_system(uint16_t data) { +#ifdef EXTRAKEY_ENABLE + send_extra(REPORT_ID_SYSTEM, data); +#endif +} + +void send_consumer(uint16_t data) { +#ifdef EXTRAKEY_ENABLE + send_extra(REPORT_ID_CONSUMER, data); +#endif +} + +/* --------------------------------------------------------- + * Console functions + * --------------------------------------------------------- + */ + +#ifdef CONSOLE_ENABLE + +int8_t sendchar(uint8_t c) { + static bool timed_out = false; + /* The `timed_out` state is an approximation of the ideal `is_listener_disconnected?` state. + * + * When a 5ms timeout write has timed out, hid_listen is most likely not running, or not + * listening to this keyboard, so we go into the timed_out state. In this state we assume + * that hid_listen is most likely not gonna be connected to us any time soon, so it would + * be wasteful to write follow-up characters with a 5ms timeout, it would all add up and + * unncecessarily slow down the firmware. However instead of just dropping the characters, + * we write them with a TIME_IMMEDIATE timeout, which is a zero timeout, + * and this will succeed only if hid_listen gets connected again. When a write with + * TIME_IMMEDIATE timeout succeeds, we know that hid_listen is listening to us again, and + * we can go back to the timed_out = false state, and following writes will be executed + * with a 5ms timeout. The reason we don't just send all characters with the TIME_IMMEDIATE + * timeout is that this could cause bytes to be lost even if hid_listen is running, if there + * is a lot of data being sent over the console. + * + * This logic will work correctly as long as hid_listen is able to receive at least 200 + * bytes per second. On a heavily overloaded machine that's so overloaded that it's + * unusable, and constantly swapping, hid_listen might have trouble receiving 200 bytes per + * second, so some bytes might be lost on the console. + */ + + const sysinterval_t timeout = timed_out ? TIME_IMMEDIATE : TIME_MS2I(5); + const size_t result = chnWriteTimeout(&drivers.console_driver.driver, &c, 1, timeout); + timed_out = (result == 0); + return result; +} + +// Just a dummy function for now, this could be exposed as a weak function +// Or connected to the actual QMK console +static void console_receive(uint8_t *data, uint8_t length) { + (void)data; + (void)length; +} + +void console_task(void) { + uint8_t buffer[CONSOLE_EPSIZE]; + size_t size = 0; + do { + size_t size = chnReadTimeout(&drivers.console_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); + if (size > 0) { + console_receive(buffer, size); + } + } while (size > 0); +} + +#endif /* CONSOLE_ENABLE */ + +#ifdef RAW_ENABLE +void raw_hid_send(uint8_t *data, uint8_t length) { + // TODO: implement variable size packet + if (length != RAW_EPSIZE) { + return; + } + chnWrite(&drivers.raw_driver.driver, data, length); +} + +__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. +} + +void raw_hid_task(void) { + uint8_t buffer[RAW_EPSIZE]; + size_t size = 0; + do { + size_t size = chnReadTimeout(&drivers.raw_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); + if (size > 0) { + raw_hid_receive(buffer, size); + } + } while (size > 0); +} + +#endif + +#ifdef MIDI_ENABLE + +void send_midi_packet(MIDI_EventPacket_t *event) { chnWrite(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t)); } + +bool recv_midi_packet(MIDI_EventPacket_t *const event) { + size_t size = chnReadTimeout(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t), TIME_IMMEDIATE); + return size == sizeof(MIDI_EventPacket_t); +} +void midi_ep_task(void) { + uint8_t buffer[MIDI_STREAM_EPSIZE]; + size_t size = 0; + do { + size_t size = chnReadTimeout(&drivers.midi_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); + if (size > 0) { + MIDI_EventPacket_t event; + recv_midi_packet(&event); + } + } while (size > 0); +} +#endif + +#ifdef VIRTSER_ENABLE + +void virtser_send(const uint8_t byte) { chnWrite(&drivers.serial_driver.driver, &byte, 1); } + +__attribute__((weak)) void virtser_recv(uint8_t c) { + // Ignore by default +} + +void virtser_task(void) { + uint8_t numBytesReceived = 0; + uint8_t buffer[16]; + do { + numBytesReceived = chnReadTimeout(&drivers.serial_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); + for (int i = 0; i < numBytesReceived; i++) { + virtser_recv(buffer[i]); + } + } while (numBytesReceived > 0); +} + +#endif + +#ifdef JOYSTICK_ENABLE + +void send_joystick_packet(joystick_t *joystick) { + joystick_report_t rep = { +# if JOYSTICK_AXES_COUNT > 0 + .axes = + { + joystick->axes[0], + +# if JOYSTICK_AXES_COUNT >= 2 + joystick->axes[1], +# endif +# if JOYSTICK_AXES_COUNT >= 3 + joystick->axes[2], +# endif +# if JOYSTICK_AXES_COUNT >= 4 + joystick->axes[3], +# endif +# if JOYSTICK_AXES_COUNT >= 5 + joystick->axes[4], +# endif +# if JOYSTICK_AXES_COUNT >= 6 + joystick->axes[5], +# endif + }, +# endif // JOYSTICK_AXES_COUNT>0 + +# if JOYSTICK_BUTTON_COUNT > 0 + .buttons = + { + joystick->buttons[0], + +# if JOYSTICK_BUTTON_COUNT > 8 + joystick->buttons[1], +# endif +# if JOYSTICK_BUTTON_COUNT > 16 + joystick->buttons[2], +# endif +# if JOYSTICK_BUTTON_COUNT > 24 + joystick->buttons[3], +# endif + } +# endif // JOYSTICK_BUTTON_COUNT>0 + }; + + // chnWrite(&drivers.joystick_driver.driver, (uint8_t *)&rep, sizeof(rep)); + osalSysLock(); + if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { + osalSysUnlock(); + return; + } + + usbStartTransmitI(&USB_DRIVER, JOYSTICK_IN_EPNUM, (uint8_t *)&rep, sizeof(joystick_report_t)); + osalSysUnlock(); +} + +#endif diff --git a/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.h b/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.h new file mode 100644 index 000000000000..9189ef780af3 --- /dev/null +++ b/keyboards/yandrstudio/biu_nrf52_ble_lib/usb_main.h @@ -0,0 +1,112 @@ +/* + * (c) 2015 flabberast + * + * Based on the following work: + * - Guillaume Duc's raw hid example (MIT License) + * https://github.com/guiduc/usb-hid-chibios-example + * - PJRC Teensy examples (MIT License) + * https://www.pjrc.com/teensy/usb_keyboard.html + * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) + * https://github.com/tmk/tmk_keyboard/ + * - ChibiOS demo code (Apache 2.0 License) + * http://www.chibios.org + * + * Since some GPL'd code is used, this work is licensed under + * GPL v2 or later. + */ + +#pragma once + +// TESTING +// extern uint8_t blinkLed; + +#include +#include + +#include "host_driver.h" + + +/* declarations */ +uint8_t keyboard_leds(void); +void send_keyboard(report_keyboard_t *report); +void send_mouse(report_mouse_t *report); +void send_system(uint16_t data); +void send_consumer(uint16_t data); + + +/* ------------------------- + * General USB driver header + * ------------------------- + */ + +/* The USB driver to use */ +#define USB_DRIVER USBD1 + +/* Initialize the USB driver and bus */ +void init_usb_driver(USBDriver *usbp); + +/* Restart the USB driver and bus */ +void restart_usb_driver(USBDriver *usbp); + +/* --------------- + * USB Event queue + * --------------- + */ + +/* Initialisation of the FIFO */ +void usb_event_queue_init(void); + +/* Task to dequeue and execute any handlers for the USB events on the main thread */ +void usb_event_queue_task(void); + +/* --------------- + * Keyboard header + * --------------- + */ + +/* extern report_keyboard_t keyboard_report_sent; */ + +/* keyboard IN request callback handler */ +void kbd_in_cb(USBDriver *usbp, usbep_t ep); + +/* start-of-frame handler */ +void kbd_sof_cb(USBDriver *usbp); + +#ifdef NKRO_ENABLE +/* nkro IN callback hander */ +void nkro_in_cb(USBDriver *usbp, usbep_t ep); +#endif /* NKRO_ENABLE */ + +/* ------------ + * Mouse header + * ------------ + */ + +#ifdef MOUSE_ENABLE + +/* mouse IN request callback handler */ +void mouse_in_cb(USBDriver *usbp, usbep_t ep); +#endif /* MOUSE_ENABLE */ + +/* --------------- + * Shared EP header + * --------------- + */ + +/* shared IN request callback handler */ +void shared_in_cb(USBDriver *usbp, usbep_t ep); + +/* -------------- + * Console header + * -------------- + */ + +#ifdef CONSOLE_ENABLE + +/* Putchar over the USB console */ +int8_t sendchar(uint8_t c); + +/* Flush output (send everything immediately) */ +void console_flush_output(void); + +#endif /* CONSOLE_ENABLE */