From a3cfb1dab7679a774d8aa09f7b609f3302dad73d Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 10 Nov 2024 09:10:10 +1100 Subject: [PATCH] Joystick: add support for 8-way hat switch (#24515) --- docs/features/joystick.md | 51 +++++++++++++++++++++++++++++- quantum/joystick.c | 10 ++++++ quantum/joystick.h | 22 ++++++++++++- tmk_core/protocol/host.c | 4 +++ tmk_core/protocol/report.h | 5 +++ tmk_core/protocol/usb_descriptor.c | 17 ++++++++++ tmk_core/protocol/vusb/vusb.c | 17 ++++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) diff --git a/docs/features/joystick.md b/docs/features/joystick.md index 69db4655d59..cbf6c6daf7e 100644 --- a/docs/features/joystick.md +++ b/docs/features/joystick.md @@ -1,6 +1,6 @@ # Joystick {#joystick} -This feature provides game controller input as a joystick device supporting up to 6 axes and 32 buttons. Axes can be read either from an [ADC-capable input pin](../drivers/adc), or can be virtual, so that its value is provided by your code. +This feature provides game controller input as a joystick device supporting up to 6 axes, 32 buttons and a hat switch. Axes can be read either from an [ADC-capable input pin](../drivers/adc), or can be virtual, so that its value is provided by your code. An analog device such as a [potentiometer](https://en.wikipedia.org/wiki/Potentiometer) found on an analog joystick's axes is based on a voltage divider, where adjusting the movable wiper controls the output voltage which can then be read by the microcontroller's ADC. @@ -37,6 +37,42 @@ By default, two axes and eight buttons are defined, with a reported resolution o You must define at least one button or axis. Also note that the maximum ADC resolution of the supported AVR MCUs is 10-bit, and 12-bit for most STM32 MCUs. ::: +### Hat Switch {#hat-switch} + +To enable the 8-way hat switch, add the following to your `config.h`: + +```c +#define JOYSTICK_HAS_HAT +```` + +The position can be set by calling `joystick_set_hat(value)`. The range of values moves clockwise from the top (ie. north), with the default "center" position represented by a value of `-1`: + +``` + 0 + 7 N 1 + NW .--'--. NE + / \ +6 W | -1 | E 2 + \ / + SW '--.--' SE + 5 S 3 + 4 +``` + +Alternatively you can use these predefined names: + +|Define |Value|Angle| +|------------------------|-----|-----| +|`JOYSTICK_HAT_CENTER` |`-1` | | +|`JOYSTICK_HAT_NORTH` |`0` |0° | +|`JOYSTICK_HAT_NORTHEAST`|`1` |45° | +|`JOYSTICK_HAT_EAST` |`2` |90° | +|`JOYSTICK_HAT_SOUTHEAST`|`3` |135° | +|`JOYSTICK_HAT_SOUTH` |`4` |180° | +|`JOYSTICK_HAT_SOUTHWEST`|`5` |225° | +|`JOYSTICK_HAT_WEST` |`6` |270° | +|`JOYSTICK_HAT_NORTHWEST`|`7` |315° | + ### Axes {#axes} When defining axes for your joystick, you must provide a definition array typically in your `keymap.c`. @@ -149,6 +185,8 @@ Contains the state of the joystick. A bit-packed array containing the joystick button states. The size is calculated as `(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1`. - `int16_t axes[]` An array of analog values for each defined axis. + - `int8_t hat` + The hat switch position. - `bool dirty` Whether the current state needs to be sent to the host. @@ -222,3 +260,14 @@ Set the value of the given axis. The axis to set the value of. - `int16_t value` The value to set. + +--- + +### `void joystick_set_hat(int8_t value)` {#api-joystick-set-hat} + +Set the position of the hat switch. + +#### Arguments {#api-joystick-set-hat-arguments} + + - `int8_t value` + The hat switch position to set. diff --git a/quantum/joystick.c b/quantum/joystick.c index 32f19b2cd99..62893fd1997 100644 --- a/quantum/joystick.c +++ b/quantum/joystick.c @@ -29,6 +29,9 @@ joystick_t joystick_state = { 0 #endif }, +#ifdef JOYSTICK_HAS_HAT + .hat = -1, +#endif .dirty = false, }; @@ -145,6 +148,13 @@ void joystick_set_axis(uint8_t axis, int16_t value) { } } +#ifdef JOYSTICK_HAS_HAT +void joystick_set_hat(int8_t value) { + joystick_state.hat = value; + joystick_state.dirty = true; +} +#endif + void joystick_init(void) { joystick_init_axes(); } diff --git a/quantum/joystick.h b/quantum/joystick.h index 5a69ceac64a..24f80c1ad6f 100644 --- a/quantum/joystick.h +++ b/quantum/joystick.h @@ -52,6 +52,16 @@ #define JOYSTICK_MAX_VALUE ((1L << (JOYSTICK_AXIS_RESOLUTION - 1)) - 1) +#define JOYSTICK_HAT_CENTER -1 +#define JOYSTICK_HAT_NORTH 0 +#define JOYSTICK_HAT_NORTHEAST 1 +#define JOYSTICK_HAT_EAST 2 +#define JOYSTICK_HAT_SOUTHEAST 3 +#define JOYSTICK_HAT_SOUTH 4 +#define JOYSTICK_HAT_SOUTHWEST 5 +#define JOYSTICK_HAT_WEST 6 +#define JOYSTICK_HAT_NORTHWEST 7 + // configure on input_pin of the joystick_axes array entry to NO_PIN // to prevent it from being read from the ADC. This allows outputting forged axis value. #define JOYSTICK_AXIS_VIRTUAL \ @@ -73,7 +83,10 @@ extern joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT]; typedef struct { uint8_t buttons[(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1]; int16_t axes[JOYSTICK_AXIS_COUNT]; - bool dirty; +#ifdef JOYSTICK_HAS_HAT + int8_t hat; +#endif + bool dirty; } joystick_t; extern joystick_t joystick_state; @@ -129,4 +142,11 @@ void joystick_read_axes(void); */ void joystick_set_axis(uint8_t axis, int16_t value); +/** + * \brief Set the position of the hat switch. + * + * \param value The hat switch position to set. + */ +void joystick_set_hat(int8_t value); + /** \} */ diff --git a/tmk_core/protocol/host.c b/tmk_core/protocol/host.c index 732fbdc37d4..df805c827c2 100644 --- a/tmk_core/protocol/host.c +++ b/tmk_core/protocol/host.c @@ -193,6 +193,10 @@ void host_joystick_send(joystick_t *joystick) { }, # endif +# ifdef JOYSTICK_HAS_HAT + .hat = joystick->hat, +# endif + # if JOYSTICK_BUTTON_COUNT > 0 .buttons = { diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h index 37c8ea48f1b..d854f51d5c4 100644 --- a/tmk_core/protocol/report.h +++ b/tmk_core/protocol/report.h @@ -246,6 +246,11 @@ typedef struct { joystick_axis_t axes[JOYSTICK_AXIS_COUNT]; #endif +#ifdef JOYSTICK_HAS_HAT + int8_t hat : 4; + uint8_t reserved : 4; +#endif + #if JOYSTICK_BUTTON_COUNT > 0 uint8_t buttons[(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1]; #endif diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index e7d12a07d9f..c7fb660b65d 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -247,6 +247,23 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = { HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), # endif +# ifdef JOYSTICK_HAS_HAT + // Hat Switch (4 bits) + HID_RI_USAGE(8, 0x39), // Hat Switch + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x07), + HID_RI_PHYSICAL_MINIMUM(8, 0), + HID_RI_PHYSICAL_MAXIMUM(16, 315), + HID_RI_UNIT(8, 0x14), // Degree, English Rotation + HID_RI_REPORT_COUNT(8, 1), + HID_RI_REPORT_SIZE(8, 4), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NULLSTATE), + // Padding (4 bits) + HID_RI_REPORT_COUNT(8, 0x04), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_CONSTANT), +# endif + # if JOYSTICK_BUTTON_COUNT > 0 HID_RI_USAGE_PAGE(8, 0x09), // Button HID_RI_USAGE_MINIMUM(8, 0x01), diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index 2a29fe65d90..fdbfcc17dce 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -621,6 +621,23 @@ const PROGMEM uchar shared_hid_report[] = { 0x81, 0x02, // Input (Data, Variable, Absolute) # endif +# ifdef JOYSTICK_HAS_HAT + // Hat Switch (4 bits) + 0x09, 0x39, // Usage (Hat Switch) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (Degree, English Rotation) + 0x95, 0x01, // Report Count (1) + 0x75, 0x04, // Report Size (4) + 0x81, 0x42, // Input (Data, Variable, Absolute, Null State) + // Padding (4 bits) + 0x95, 0x04, // Report Count (4) + 0x75, 0x01, // Report Size (1) + 0x81, 0x01, // Input (Constant) +# endif + # if JOYSTICK_BUTTON_COUNT > 0 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (Button 1)