From 3cf76111398d7df38f5840ce98cd585376cc6245 Mon Sep 17 00:00:00 2001 From: achol Date: Wed, 17 Oct 2018 11:23:20 +0200 Subject: [PATCH] add support for hid gamepad interface add documentation for HID joystick Add joystick_task to read analog axes values even when no key is pressed or release. update doc Update docs/feature_joystick.md Manage pin setup and read to maintain matrix scan after analog read --- common_features.mk | 9 ++ docs/_summary.md | 1 + docs/feature_joystick.md | 85 ++++++++++++++++ quantum/joystick.c | 11 +++ quantum/joystick.h | 48 +++++++++ quantum/process_keycode/process_joystick.c | 79 +++++++++++++++ quantum/process_keycode/process_joystick.h | 13 +++ quantum/quantum.c | 7 ++ quantum/quantum_keycodes.h | 35 +++++++ tmk_core/common/keyboard.c | 7 ++ tmk_core/protocol/chibios/usb_main.c | 73 ++++++++++++++ tmk_core/protocol/lufa/lufa.c | 83 ++++++++++++++++ tmk_core/protocol/usb_descriptor.c | 103 +++++++++++++++++++- tmk_core/protocol/usb_descriptor.h | 15 +++ tmk_core/protocol/vusb/vusb.c | 108 ++++++++++++++++++++- 15 files changed, 675 insertions(+), 2 deletions(-) create mode 100644 docs/feature_joystick.md create mode 100644 quantum/joystick.c create mode 100644 quantum/joystick.h create mode 100644 quantum/process_keycode/process_joystick.c create mode 100644 quantum/process_keycode/process_joystick.h diff --git a/common_features.mk b/common_features.mk index 269ca2b137..9180b4f7cb 100644 --- a/common_features.mk +++ b/common_features.mk @@ -511,3 +511,12 @@ ifeq ($(strip $(AUTO_SHIFT_ENABLE)), yes) OPT_DEFS += -DAUTO_SHIFT_MODIFIERS endif endif + +ifeq ($(strip $(JOYSTICK_ENABLE)), yes) + OPT_DEFS += -DJOYSTICK_ENABLE + SRC += $(QUANTUM_DIR)/process_keycode/process_joystick.c + SRC += $(QUANTUM_DIR)/joystick.c + ifeq ($(PLATFORM),AVR) + SRC += drivers/avr/analog.c + endif +endif diff --git a/docs/_summary.md b/docs/_summary.md index d6186bbf99..336def3dc7 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -97,6 +97,7 @@ * [DIP Switch](feature_dip_switch.md) * [Encoders](feature_encoders.md) * [Haptic Feedback](feature_haptic_feedback.md) + * [Joystick](feature_joystick.md) * [Proton C Conversion](proton_c_conversion.md) * [PS/2 Mouse](feature_ps2_mouse.md) * [Split Keyboard](feature_split_keyboard.md) diff --git a/docs/feature_joystick.md b/docs/feature_joystick.md new file mode 100644 index 0000000000..43a4f22835 --- /dev/null +++ b/docs/feature_joystick.md @@ -0,0 +1,85 @@ +## Joystick HID Device + +The keyboard can be made to be recognized as a joystick HID device by the Operating System. + +This is enabled by adding the following to `rules.mk` + +``` +JOYSTICK_ENABLE = yes +``` + +### Configuring the joystick + +The default joystick has 2 axes and and 8 buttons. This can be changed from the config.h file : + +``` +//max 32 for JOYSTICK_BUTTON_COUNT +#define JOYSTICK_BUTTON_COUNT 16 +//max 6 for JOYSTICK_AXES_COUNT +#define JOYSTICK_AXES_COUNT 3 +``` + +When defining axes for your joystick, you have to provide a definition array for it. You can do this from your keymap.c file. +A joystick will either be read from an input pin that allows the use of an ADC, or can be virtual, so that its value is provided by your code. +You have to define an array of type ''joystick_config_t'' and of proper size, in the following way : + +``` +//joystick config +joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = { + [0] = {A1, D7, 65280, 65472}, + [1] = {JS_VIRTUAL_AXIS, JS_VIRTUAL_AXIS, 65280, 65472} +}; +``` + +In this example, the first axis will be read from the D7 pin while A1 is set high, using an analogRead, whereas the second axis will not be read. +If you connected the your analog component to Vcc instead of an output pin, you can set the output setting to JS_VIRTUAL_AXIS. +In order to give a value to the second axis, you can do so in any customizable entry point of quantum : as an action, in process_record_user or in matrix_scan_user, or even in joystick_task(void) which is called even when no key has been pressed. +You assign a value by writing to joystick_status.axes[axis_index] a signed 8bit value (-127 to 127). Then it is necessary to assign the flag JS_UPDATED to joystick_status.status in order for the change to be taken into account. + +The following example writes two axes based on keypad presses, with KP_5 as a precision modifier : + +``` +#ifdef JOYSTICK_ENABLE +static bool precision_on; +#endif + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch(keycode){ +#ifdef JOYSTICK_ENABLE + // virtual joystick + case KC_P8: + joystick_status.axes[1] -= (record->event.pressed ? 1 : -1) * (precision_on ?70 : 127); + joystick_status.status |= JS_UPDATED; + break; + case KC_P2: + joystick_status.axes[1] += (record->event.pressed ? 1 : -1) * (precision_on ?70 : 127); + joystick_status.status |= JS_UPDATED; + break; + case KC_P4: + joystick_status.axes[0] -= (record->event.pressed ? 1 : -1) * (precision_on ?70 : 127); + joystick_status.status |= JS_UPDATED; + break; + case KC_P6: + joystick_status.axes[0] += (record->event.pressed ? 1 : -1) * (precision_on ?70 : 127); + joystick_status.status |= JS_UPDATED; + break; + case KC_P5: + precision_on = record->event.pressed; + joystick_status.axes[0] *= record->event.pressed ? 70/127.f : 127/70.f; + joystick_status.axes[1] *= record->event.pressed ? 70/127.f : 127/70.f; + joystick_status.status |= JS_UPDATED; + break; +#endif + } + + return true; +} +``` + +### Triggering joystick buttons + +Joystick buttons are normal quantum keycode, defined as JS_BUTTON0 to JS_BUTTON_MAX, which depends on the number of buttons you have configured. +To trigger a joystick button, just add the corresponding keycode to your keymap. + + + diff --git a/quantum/joystick.c b/quantum/joystick.c new file mode 100644 index 0000000000..e6906913db --- /dev/null +++ b/quantum/joystick.c @@ -0,0 +1,11 @@ +#include "joystick.h" + +joystick_t joystick_status = { + .buttons = {0}, + .axes = {0}, + .status = 0 +}; + +//array defining the reading of analog values for each axis +__attribute__ ((weak)) +joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {}; diff --git a/quantum/joystick.h b/quantum/joystick.h new file mode 100644 index 0000000000..6ca9ab8878 --- /dev/null +++ b/quantum/joystick.h @@ -0,0 +1,48 @@ +#ifndef JOYSTICK_H +#define JOYSTICK_H + +#ifndef JOYSTICK_BUTTON_COUNT +#define JOYSTICK_BUTTON_COUNT 8 +#endif + +#ifndef JOYSTICK_AXES_COUNT +#define JOYSTICK_AXES_COUNT 4 +#endif + +#include + +//configure on input_pin of the joystick_axes array entry to JS_VIRTUAL_AXIS +// to prevent it from being read from the ADC. This allows outputing forged axis value. +// +#define JS_VIRTUAL_AXIS 0xFF + +typedef struct { + uint8_t output_pin; + uint8_t input_pin; + + //the AVR ADC offers 10 bit precision, with significant bits on the higher part + uint16_t min_digit; + uint16_t max_digit; +} joystick_config_t; + +extern joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT]; + +enum joystick_status{ + JS_INITIALIZED = 1, + JS_UPDATED = 2 +}; + +typedef struct { + + uint8_t buttons[JOYSTICK_BUTTON_COUNT/8+1]; + + int8_t axes[JOYSTICK_AXES_COUNT]; + uint8_t status:2; +} joystick_t; + +extern joystick_t joystick_status; + +//to be implemented in the hid protocol library +void send_joystick_packet(joystick_t* joystick); + +#endif //JOYSTICK_H \ No newline at end of file diff --git a/quantum/process_keycode/process_joystick.c b/quantum/process_keycode/process_joystick.c new file mode 100644 index 0000000000..f90fd23937 --- /dev/null +++ b/quantum/process_keycode/process_joystick.c @@ -0,0 +1,79 @@ +#include "process_joystick.h" + +#include +#include + +#ifdef __AVR__ +# include +#endif + +#include + +bool process_joystick_buttons(uint16_t keycode, keyrecord_t *record); + +bool process_joystick(uint16_t keycode, keyrecord_t *record){ + + if (process_joystick_buttons(keycode, record) + && (joystick_status.status & JS_UPDATED)>0){ + send_joystick_packet(&joystick_status); + joystick_status.status &= ~JS_UPDATED; + } + + return true; +} + +__attribute__ ((weak)) +void joystick_task(void){ + if (process_joystick_analog() && (joystick_status.status & JS_UPDATED)){ + send_joystick_packet(&joystick_status); + joystick_status.status &= ~JS_UPDATED; + } +} + +bool process_joystick_buttons(uint16_t keycode, keyrecord_t *record){ + + if (keycode < JS_BUTTON0 || keycode > JS_BUTTON_MAX){ + return true; + } else { + if (record->event.pressed){ + joystick_status.buttons[(keycode-JS_BUTTON0)/8] |= 1<<(keycode%8); + } else { + joystick_status.buttons[(keycode-JS_BUTTON0)/8] &= ~(1<<(keycode%8)); + } + + joystick_status.status |= JS_UPDATED; + } + + return true; +} + +__attribute__ ((weak)) +bool process_joystick_analog(){ +#if JOYSTICK_AXES_COUNT > 0 + for (int axis_index=0 ; axis_index +#include "quantum.h" + +bool process_joystick(uint16_t keycode, keyrecord_t *record); + +void joystick_task(void); + +bool process_joystick_analog(void); + +#endif //PROCESS_JOYSTICK_H \ No newline at end of file diff --git a/quantum/quantum.c b/quantum/quantum.c index 749a08eea9..c8e062f8ff 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -63,6 +63,10 @@ float bell_song[][2] = SONG(TERMINAL_SOUND); # endif #endif +#ifdef JOYSTICK_ENABLE +#include +#endif //JOYSTICK_ENABLE + static void do_code16(uint16_t code, void (*f)(uint8_t)) { switch (code) { case QK_MODS ... QK_MODS_MAX: @@ -265,6 +269,9 @@ bool process_record_quantum(keyrecord_t *record) { #endif #if defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE) process_rgb(keycode, record) && +#endif +#ifdef JOYSTICK_ENABLE + process_joystick(keycode, record) && #endif true)) { return false; diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index 54428fe1f2..e8b010d76c 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -512,6 +512,41 @@ enum quantum_keycodes { DYN_MACRO_PLAY1, DYN_MACRO_PLAY2, + JS_BUTTON0, + JS_BUTTON_MIN = JS_BUTTON0, + JS_BUTTON1, + JS_BUTTON2, + JS_BUTTON3, + JS_BUTTON4, + JS_BUTTON5, + JS_BUTTON6, + JS_BUTTON7, + JS_BUTTON8, + JS_BUTTON9, + JS_BUTTON10, + JS_BUTTON11, + JS_BUTTON12, + JS_BUTTON13, + JS_BUTTON14, + JS_BUTTON15, + JS_BUTTON16, + JS_BUTTON17, + JS_BUTTON18, + JS_BUTTON19, + JS_BUTTON20, + JS_BUTTON21, + JS_BUTTON22, + JS_BUTTON23, + JS_BUTTON24, + JS_BUTTON25, + JS_BUTTON26, + JS_BUTTON27, + JS_BUTTON28, + JS_BUTTON29, + JS_BUTTON30, + JS_BUTTON31, + JS_BUTTON_MAX = JS_BUTTON31, + // always leave at the end SAFE_RANGE }; diff --git a/tmk_core/common/keyboard.c b/tmk_core/common/keyboard.c index a767d9c877..ae50a6e5e9 100644 --- a/tmk_core/common/keyboard.c +++ b/tmk_core/common/keyboard.c @@ -71,6 +71,9 @@ along with this program. If not, see . #ifdef MIDI_ENABLE # include "process_midi.h" #endif +#ifdef JOYSTICK_ENABLE +# include "process_joystick.h" +#endif #ifdef HD44780_ENABLE # include "hd44780.h" #endif @@ -396,6 +399,10 @@ MATRIX_LOOP_END: } #endif +#ifdef JOYSTICK_ENABLE + joystick_task(); +#endif + // update LED if (led_status != host_keyboard_leds()) { led_status = host_keyboard_leds(); diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index ecc83d9ecc..3c7c7ea8bd 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -47,6 +47,10 @@ extern keymap_config_t keymap_config; #endif +#ifdef JOYSTICK_ENABLE +# include +#endif + /* --------------------------------------------------------- * Global interface variables and declarations * --------------------------------------------------------- @@ -246,6 +250,9 @@ typedef struct { #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]; @@ -283,6 +290,14 @@ static usb_driver_configs_t drivers = { # 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)) @@ -870,3 +885,61 @@ void virtser_task(void) { } #endif + +#ifdef JOYSTICK_ENABLE + +typedef struct { + #if JOYSTICK_AXES_COUNT>0 + int8_t axes[JOYSTICK_AXES_COUNT]; + #endif + + #if JOYSTICK_BUTTON_COUNT>0 + uint8_t buttons[(JOYSTICK_BUTTON_COUNT-1)/8+1]; + #endif +} __attribute__ ((packed)) joystick_report_t; + +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)); +} + +#endif diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index d673841fd5..a4c617622b 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -85,6 +85,10 @@ extern keymap_config_t keymap_config; # include "raw_hid.h" #endif +#ifdef JOYSTICK_ENABLE + #include "joystick.h" +#endif + uint8_t keyboard_idle = 0; /* 0: Boot Protocol, 1: Report Protocol(default) */ uint8_t keyboard_protocol = 1; @@ -263,6 +267,85 @@ static void Console_Task(void) { } #endif +/******************************************************************************* + * Joystick + ******************************************************************************/ +#ifdef JOYSTICK_ENABLE + +typedef struct { + #if JOYSTICK_AXES_COUNT>0 + int8_t axes[JOYSTICK_AXES_COUNT]; + #endif + + #if JOYSTICK_BUTTON_COUNT>0 + uint8_t buttons[(JOYSTICK_BUTTON_COUNT-1)/8+1]; + #endif +} __attribute__ ((packed)) joystick_report_t; + +void send_joystick_packet(joystick_t* joystick){ + + uint8_t timeout = 255; + uint8_t where = where_to_send(); + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } + + joystick_report_t r = { + #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 + }; + + /* Select the Joystick Report Endpoint */ + Endpoint_SelectEndpoint(JOYSTICK_IN_EPNUM); + + /* Check if write ready for a polling interval around 10ms */ + while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40); + if (!Endpoint_IsReadWriteAllowed()) return; + + /* Write Joystick Report Data */ + Endpoint_Write_Stream_LE(&r, sizeof(joystick_report_t), NULL); + + /* Finalize the stream transfer to send the last packet */ + Endpoint_ClearIN(); +} +#endif + /******************************************************************************* * USB Events ******************************************************************************/ diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index f2b91b099e..718035158c 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -278,6 +278,53 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = { }; #endif +#ifdef JOYSTICK_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] = +{ + HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */ + HID_RI_USAGE(8, 0x04), /* Joystick */ + HID_RI_COLLECTION(8, 0x01), /* Application */ + HID_RI_USAGE(8, 0x01), /* Pointer */ + HID_RI_COLLECTION(8, 0x00), /* Physical */ + + #if JOYSTICK_AXES_COUNT >= 1 + HID_RI_USAGE(8, 0x30), // USAGE (X) + #endif + #if JOYSTICK_AXES_COUNT >= 2 + HID_RI_USAGE(8, 0x31), // USAGE (Y) + #endif + #if JOYSTICK_AXES_COUNT >= 3 + HID_RI_USAGE(8, 0x32), // USAGE (Z) + #endif + #if JOYSTICK_AXES_COUNT >= 4 + HID_RI_USAGE(8, 0x33), // USAGE (RX) + #endif + #if JOYSTICK_AXES_COUNT >= 5 + HID_RI_USAGE(8, 0x34), // USAGE (RY) + #endif + #if JOYSTICK_AXES_COUNT >= 6 + HID_RI_USAGE(8, 0x35), // USAGE (RZ) + #endif + HID_RI_LOGICAL_MINIMUM(8, -127), + HID_RI_LOGICAL_MAXIMUM(8, 127), + HID_RI_REPORT_COUNT(8, JOYSTICK_AXES_COUNT), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + + HID_RI_USAGE_PAGE(8, 0x09), /* Button */ + HID_RI_USAGE_MINIMUM(8, 0x01), /* Button 1 */ + HID_RI_USAGE_MAXIMUM(8, JOYSTICK_BUTTON_COUNT), /* Button 5 */ + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, JOYSTICK_BUTTON_COUNT), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_RI_END_COLLECTION(0), + HID_RI_END_COLLECTION(0), +}; +#endif + /* * Device descriptor */ @@ -287,7 +334,6 @@ const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = { .Type = DTYPE_Device }, .USBSpecification = VERSION_BCD(1, 1, 0), - #if VIRTSER_ENABLE .Class = USB_CSCP_IADDeviceClass, .SubClass = USB_CSCP_IADDeviceSubclass, @@ -812,6 +858,49 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { .PollingIntervalMS = 0x05 }, #endif + + + /* + * Joystick + */ +#ifdef JOYSTICK_ENABLE + .Joystick_Interface = + { + .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, + + .InterfaceNumber = JOYSTICK_INTERFACE, + .AlternateSetting = 0x00, + + .TotalEndpoints = 1, + + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + + .InterfaceStrIndex = NO_DESCRIPTOR + }, + + .Joystick_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(JoystickReport) + }, + + .Joystick_INEndpoint = + { + .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, + + .EndpointAddress = (ENDPOINT_DIR_IN | JOYSTICK_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = JOYSTICK_EPSIZE, + .PollingIntervalMS = 0x0A + }, +#endif }; /* @@ -943,6 +1032,12 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const Size = sizeof(USB_HID_Descriptor_HID_t); break; +#endif +#ifdef JOYSTICK_ENABLE + case JOYSTICK_INTERFACE: + Address = &ConfigurationDescriptor.Joystick_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + break; #endif } @@ -987,6 +1082,12 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const Size = sizeof(ConsoleReport); break; +#endif +#ifdef JOYSTICK_ENABLE + case JOYSTICK_INTERFACE: + Address = &JoystickReport; + Size = sizeof(JoystickReport); + break; #endif } diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index b2423fa7e6..bcc9cab4bb 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -123,6 +123,13 @@ typedef struct { USB_Descriptor_Endpoint_t CDC_DataOutEndpoint; USB_Descriptor_Endpoint_t CDC_DataInEndpoint; #endif + +#ifdef JOYSTICK_ENABLE + // Joystick HID Interface + USB_Descriptor_Interface_t Joystick_Interface; + USB_HID_Descriptor_HID_t Joystick_HID; + USB_Descriptor_Endpoint_t Joystick_INEndpoint; +#endif } USB_Descriptor_Configuration_t; /* @@ -164,6 +171,9 @@ enum usb_interfaces { CDI_INTERFACE, #endif +#if defined(JOYSTICK_ENABLE) + JOYSTICK_INTERFACE, +#endif TOTAL_INTERFACES }; @@ -224,6 +234,10 @@ enum usb_endpoints { # define CDC_IN_EPADDR (ENDPOINT_DIR_IN | CDC_IN_EPNUM) # define CDC_OUT_EPADDR (ENDPOINT_DIR_OUT | CDC_OUT_EPNUM) #endif +#ifdef JOYSTICK_ENABLE + JOYSTICK_IN_EPNUM = NEXT_EPNUM, + JOYSTICK_OUT_EPNUM = NEXT_EPNUM, +#endif }; #ifdef PROTOCOL_LUFA @@ -248,6 +262,7 @@ enum usb_endpoints { #define MIDI_STREAM_EPSIZE 64 #define CDC_NOTIFICATION_EPSIZE 8 #define CDC_EPSIZE 16 +#define JOYSTICK_EPSIZE 8 uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const void** const DescriptorAddress); #endif diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index abf723952e..61d1ba53e7 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -26,6 +26,8 @@ along with this program. If not, see . #include "debug.h" #include "host_driver.h" #include "vusb.h" +#include "joystick.h" +#include "joystick.h" #include static uint8_t vusb_keyboard_leds = 0; @@ -79,6 +81,7 @@ 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); +static void send_joystick(void); static host_driver_t driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer}; @@ -139,6 +142,68 @@ static void send_consumer(uint16_t data) { #endif } +typedef struct { + uint8_t report_id; + + #if JOYSTICK_AXES_COUNT>0 + int8_t axes[JOYSTICK_AXES_COUNT]; + #endif + + #if JOYSTICK_BUTTON_COUNT>0 + uint8_t buttons[(JOYSTICK_BUTTON_COUNT-1)/8+1]; + #endif +} __attribute__ ((packed)) vusb_joystick_report_t; + +void send_joystick_packet(joystick_t* status) +{ + vusb_joystick_report_t r = { + .report_id = 0x4, + + #if JOYSTICK_AXES_COUNT>0 + .axes = { + status->axes[0] + + #if JOYSTICK_AXES_COUNT >= 2 + ,status->axes[1] + #endif + #if JOYSTICK_AXES_COUNT >= 3 + ,status->axes[2] + #endif + #if JOYSTICK_AXES_COUNT >= 4 + ,status->axes[3] + #endif + #if JOYSTICK_AXES_COUNT >= 5 + ,status->axes[4] + #endif + #if JOYSTICK_AXES_COUNT >= 6 + ,status->axes[5] + #endif + }, + #endif //JOYSTICK_AXES_COUNT>0 + + #if JOYSTICK_BUTTON_COUNT>0 + .buttons = { + status->buttons[0] + + #if JOYSTICK_BUTTON_COUNT>8 + ,status->buttons[1] + #endif + #if JOYSTICK_BUTTON_COUNT>16 + ,status->buttons[2] + #endif + #if JOYSTICK_BUTTON_COUNT>24 + ,status->buttons[3] + #endif + } + #endif //JOYSTICK_BUTTON_COUNT>0 + }; + if (usbInterruptIsReady3()) { + usbSetInterrupt3((void *)&r, sizeof(vusb_joystick_report_t)); + } +} + + + /*------------------------------------------------------------------* * Request from host * *------------------------------------------------------------------*/ @@ -251,7 +316,7 @@ const PROGMEM uchar keyboard_hid_report[] = { 0xC0 // End Collection }; -#if defined(MOUSE_ENABLE) || defined(EXTRAKEY_ENABLE) +#if defined(MOUSE_ENABLE) || defined(EXTRAKEY_ENABLE) || defined(JOYSTICK_ENABLE) const PROGMEM uchar mouse_extra_hid_report[] = { # ifdef MOUSE_ENABLE // Mouse report descriptor @@ -332,6 +397,47 @@ const PROGMEM uchar mouse_extra_hid_report[] = { 0x81, 0x00, // Input (Data, Array, Absolute) 0xC0 // End Collection # endif +#if JOYSTICK_ENABLE + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x04, // USAGE (Joystick) + 0xa1, 0x01, // COLLECTION (Application) + 0x85, 0x4, // REPORT_ID (4) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + #if JOYSTICK_AXES_COUNT >= 1 + 0x09, 0x30, // USAGE (X) + #endif + #if JOYSTICK_AXES_COUNT >= 2 + 0x09, 0x31, // USAGE (Y) + #endif + #if JOYSTICK_AXES_COUNT >= 3 + 0x09, 0x32, // USAGE (Z) + #endif + #if JOYSTICK_AXES_COUNT >= 4 + 0x09, 0x33, // USAGE (RX) + #endif + #if JOYSTICK_AXES_COUNT >= 5 + 0x09, 0x34, // USAGE (RY) + #endif + #if JOYSTICK_AXES_COUNT >= 6 + 0x09, 0x35, // USAGE (RZ) + #endif + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, JOYSTICK_AXES_COUNT, // REPORT_COUNT (JOYSTICK_AXES_COUNT) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, JOYSTICK_BUTTON_COUNT, // USAGE_MAXIMUM + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, JOYSTICK_BUTTON_COUNT, // REPORT_COUNT + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0 // END_COLLECTION +#endif //GAMEPAD_ENABLE }; #endif