Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Joystick: add support for 8-way hat switch #24515

Merged
merged 2 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion docs/features/joystick.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
10 changes: 10 additions & 0 deletions quantum/joystick.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ joystick_t joystick_state = {
0
#endif
},
#ifdef JOYSTICK_HAS_HAT
.hat = -1,
#endif
.dirty = false,
};

Expand Down Expand Up @@ -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();
}
Expand Down
22 changes: 21 additions & 1 deletion quantum/joystick.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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;
Expand Down Expand Up @@ -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);

/** \} */
4 changes: 4 additions & 0 deletions tmk_core/protocol/host.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
{
Expand Down
5 changes: 5 additions & 0 deletions tmk_core/protocol/report.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tmk_core/protocol/usb_descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
17 changes: 17 additions & 0 deletions tmk_core/protocol/vusb/vusb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down