Browse Source

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

achol 7 years ago
parent
commit
3cf7611139

+ 9 - 0
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

+ 1 - 0
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)

+ 85 - 0
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.
+
+
+

+ 11 - 0
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] = {};

+ 48 - 0
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 <stdint.h>
+
+//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

+ 79 - 0
quantum/process_keycode/process_joystick.c

@@ -0,0 +1,79 @@
+#include "process_joystick.h"
+
+#include <quantum/joystick.h>
+#include <quantum/quantum_keycodes.h>
+
+#ifdef __AVR__
+# include <drivers/avr/analog.h>
+#endif
+
+#include <string.h>
+
+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<JOYSTICK_AXES_COUNT ; ++axis_index){
+    if (joystick_axes[axis_index].output_pin==JS_VIRTUAL_AXIS || joystick_axes[axis_index].input_pin==JS_VIRTUAL_AXIS){
+      continue;
+    }
+    
+    setPinOutput(joystick_axes[axis_index].output_pin);
+    writePinHigh(joystick_axes[axis_index].output_pin);
+    
+    //disable pull-up resistance
+    setPinInput(joystick_axes[axis_index].input_pin);
+    writePinLow(joystick_axes[axis_index].input_pin);
+    
+#ifdef __AVR__
+    int16_t axis_val = analogReadPin(joystick_axes[axis_index].input_pin);
+#else
+    int16_t axis_val = 0;
+#endif
+    if (axis_val!=joystick_status.axes[axis_index]){
+      joystick_status.axes[axis_index] = axis_val;
+      joystick_status.status |= JS_UPDATED;
+    }
+    
+    writePinLow(joystick_axes[axis_index].output_pin);
+  }
+#endif
+  return true;
+}

+ 13 - 0
quantum/process_keycode/process_joystick.h

@@ -0,0 +1,13 @@
+#ifndef PROCESS_JOYSTICK_H
+#define PROCESS_JOYSTICK_H
+
+#include <stdint.h>
+#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

+ 7 - 0
quantum/quantum.c

@@ -63,6 +63,10 @@ float bell_song[][2] = SONG(TERMINAL_SOUND);
 #    endif
 #endif
 
+#ifdef JOYSTICK_ENABLE
+#include <process_keycode/process_joystick.h>
+#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;

+ 35 - 0
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
 };

+ 7 - 0
tmk_core/common/keyboard.c

@@ -71,6 +71,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #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();

+ 73 - 0
tmk_core/protocol/chibios/usb_main.c

@@ -47,6 +47,10 @@
 extern keymap_config_t keymap_config;
 #endif
 
+#ifdef JOYSTICK_ENABLE
+# include <quantum/joystick.h>
+#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

+ 83 - 0
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
  ******************************************************************************/

+ 102 - 1
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
             }
 

+ 15 - 0
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

+ 107 - 1
tmk_core/protocol/vusb/vusb.c

@@ -26,6 +26,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "debug.h"
 #include "host_driver.h"
 #include "vusb.h"
+#include "joystick.h"
+#include "joystick.h"
 #include <util/delay.h>
 
 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