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 feature improvements #19052

Merged
merged 16 commits into from
Nov 26, 2022
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
187 changes: 128 additions & 59 deletions docs/feature_joystick.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,72 @@
## Joystick
# Joystick :id=joystick

The keyboard can be made to be recognized as a joystick HID device by the operating system.
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](adc_driver.md), or can be virtual, so that its value is provided by your code.

!> Joystick support is not currently available on V-USB devices.
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.

The joystick feature provides two services:
* reading analog input devices (eg. potentiometers)
* sending gamepad HID reports
## Usage :id=usage

Both services can be used without the other, depending on whether you just want to read a device but not send gamepad reports (for volume control for instance)
or send gamepad reports based on values computed by the keyboard.

### Analog Input

To use analog input you must first enable it in `rules.mk`:
Add the following to your `rules.mk`:

```make
JOYSTICK_ENABLE = yes
JOYSTICK_DRIVER = analog # or 'digital'
```

An analog device such as a potentiometer found on a gamepad's analog axes is based on a [voltage divider](https://en.wikipedia.org/wiki/Voltage_divider).
It is composed of three connectors linked to the ground, the power input and power output (usually the middle one). The power output holds the voltage that varies based on the position of the cursor,
which value will be read using your MCU's [ADC](https://en.wikipedia.org/wiki/Analog-to-digital_converter).
Depending on which pins are already used by your keyboard's matrix, the rest of the circuit can get a little bit more complicated,
feeding the power input and ground connection through pins and using diodes to avoid bad interactions with the matrix scanning procedures.
By default the joystick driver is `analog`, but you can change this with:

```make
JOYSTICK_DRIVER = digital
```

### Configuring the Joystick
## Configuration :id=configuration

By default, two axes and eight buttons are defined. This can be changed in your `config.h`:
By default, two axes and eight buttons are defined, with a reported resolution of 8 bits (-127 to +127). This can be changed in your `config.h`:

```c
// Max 32
// Min 0, max 32
#define JOYSTICK_BUTTON_COUNT 16
// Max 6: X, Y, Z, Rx, Ry, Rz
#define JOYSTICK_AXES_COUNT 3
// Min 0, max 6: X, Y, Z, Rx, Ry, Rz
#define JOYSTICK_AXIS_COUNT 3
// Min 8, max 16
#define JOYSTICK_AXIS_RESOLUTION 10
```

When defining axes for your joystick, you have to provide a definition array. You can do this from your keymap.c file.
A joystick will either be read from an input pin that allows the use of the 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.

There are three ways for your circuit to work with the ADC, that relies on the use of 1, 2 or 3 pins of the MCU:
* 1 pin: your analog device is directly connected to your device GND and VCC. The only pin used is the ADC pin of your choice.
* 2 pins: your analog device is powered through a pin that allows toggling it on or off. The other pin is used to read the input value through the ADC.
* 3 pins: both the power input and ground are connected to pins that must be set to a proper state before reading and restored afterwards.
?> 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.

The configuration of each axis is performed using one of four macros:
* `JOYSTICK_AXIS_VIRTUAL`: no ADC reading must be performed, that value will be provided by keyboard/keymap-level code
* `JOYSTICK_AXIS_IN(INPUT_PIN, LOW, REST, HIGH)`: a voltage will be read on the provided pin, which must be an ADC-capable pin.
* `JOYSTICK_AXIS_IN_OUT(INPUT_PIN, OUTPUT_PIN, LOW, REST, HIGH)`: the provided `OUTPUT_PIN` will be set high before `INPUT_PIN` is read.
* `JOYSTICK_AXIS_IN_OUT_GROUND(INPUT_PIN, OUTPUT_PIN, GROUND_PIN, LOW, REST, HIGH)`: the `OUTPUT_PIN` will be set high and `GROUND_PIN` will be set low before reading from `INPUT_PIN`.
### Axes :id=axes

In any case where an ADC reading takes place (when `INPUT_PIN` is provided), additional `LOW`, `REST` and `HIGH` parameters are used.
These implement the calibration of the analog device by defining the range of read values that will be mapped to the lowest, resting position and highest possible value for the axis (-127 to 127).
In practice, you have to provide the lowest/highest raw ADC reading, and the raw reading at resting position, when no deflection is applied. You can provide inverted `LOW` and `HIGH` to invert the axis.
When defining axes for your joystick, you must provide a definition array typically in your `keymap.c`.

For instance, an axes configuration can be defined in the following way:
For instance, the below example configures two axes. The X axis is read from the `A4` pin. With the default axis resolution of 8 bits, the range of values between 900 and 575 are scaled to -127 through 0, and values 575 to 285 are scaled to 0 through 127. The Y axis is configured as a virtual axis, and its value is not read from any pin. Instead, the user must update the axis value programmatically.

```c
//joystick config
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
[0] = JOYSTICK_AXIS_IN_OUT_GROUND(A4, B0, A7, 900, 575, 285),
[1] = JOYSTICK_AXIS_VIRTUAL
JOYSTICK_AXIS_IN(A4, 900, 575, 285),
JOYSTICK_AXIS_VIRTUAL
};
```

When the ADC reads 900 or higher, the returned axis value will be -127, whereas it will be 127 when the ADC reads 285 or lower. Zero is returned when 575 is read.
Axes can be configured using one of the following macros:

In this example, the first axis will be read from the `A4` pin while `B0` is set high and `A7` is set low, using `analogReadPin()`, whereas the second axis will not be read.
* `JOYSTICK_AXIS_IN(input_pin, low, rest, high)`
The ADC samples the provided pin. `low`, `high` and `rest` correspond to the minimum, maximum, and resting (or centered) analog values of the axis, respectively.
* `JOYSTICK_AXIS_IN_OUT(input_pin, output_pin, low, rest, high)`
Same as `JOYSTICK_AXIS_IN()`, but the provided `output_pin` will be pulled high before `input_pin` is read.
* `JOYSTICK_AXIS_IN_OUT_GROUND(input_pin, output_pin, ground_pin, low, rest, high)`
Same as `JOYSTICK_AXIS_IN_OUT()`, but the provided `ground_pin` will be pulled low before reading from `input_pin`.
* `JOYSTICK_AXIS_VIRTUAL`
No ADC reading is performed. The value should be provided by user code.

#### Virtual Axes
The `low` and `high` values can be swapped to effectively invert the axis.

To give a value to virtual axes, call `joystick_set_axis(axis, value)`.
#### Virtual Axes :id=virtual-axes

The following example adjusts two virtual axes (X and Y) based on keypad presses, with `KC_P5` as a precision modifier:
The following example adjusts two virtual axes (X and Y) based on keypad presses, with `KC_P0` as a precision modifier:

```c
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
[0] = JOYSTICK_AXIS_VIRTUAL, // x
[1] = JOYSTICK_AXIS_VIRTUAL // y
JOYSTICK_AXIS_VIRTUAL, // x
JOYSTICK_AXIS_VIRTUAL // y
};

static bool precision = false;
Expand All @@ -105,21 +92,15 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
case KC_P6:
joystick_set_axis(0, record->event.pressed ? precision_val : 0);
return false;
case KC_P5:
case KC_P0:
precision = record->event.pressed;
return false;
}
return true;
}
```

### Axis Resolution

By default, the resolution of each axis is 8 bit, giving a range of -127 to +127. If you need higher precision, you can increase it by defining eg. `JOYSTICK_AXES_RESOLUTION 12` in your `config.h`. The resolution must be between 8 and 16.

Note that the supported AVR MCUs have a 10-bit ADC, and 12-bit for most STM32 MCUs.

### Keycodes
## Keycodes :id=keycodes

|Key |Aliases|Description|
|-----------------------|-------|-----------|
Expand Down Expand Up @@ -156,4 +137,92 @@ Note that the supported AVR MCUs have a 10-bit ADC, and 12-bit for most STM32 MC
|`QK_JOYSTICK_BUTTON_30`|`JS_30`|Button 30 |
|`QK_JOYSTICK_BUTTON_31`|`JS_31`|Button 31 |

You can also trigger joystick buttons in code with `register_joystick_button(button)` and `unregister_joystick_button(button)`, where `button` is the 0-based button index (0 = button 1).
## API :id=api

### `struct joystick_t` :id=api-joystick-t

Contains the state of the joystick.

#### Members :id=api-joystick-t-members

- `uint8_t buttons[]`
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.
- `bool dirty`
Whether the current state needs to be sent to the host.

---

### `struct joystick_config_t` :id=api-joystick-config-t

Describes a single axis.

#### Members :id=api-joystick-config-t-members

- `pin_t output_pin`
A pin to set as output high when reading the analog value, or `JS_VIRTUAL_AXIS`.
- `pin_t input_pin`
The pin to read the analog value from, or `JS_VIRTUAL_AXIS`.
- `pin_t ground_pin`
A pin to set as output low when reading the analog value, or `JS_VIRTUAL_AXIS`.
- `uint16_t min_digit`
The minimum analog value.
- `uint16_t mid_digit`
The resting or midpoint analog value.
- `uint16_t max_digit`
The maximum analog value.

---

### `void joystick_flush(void)` :id=api-joystick-flush

Send the joystick report to the host, if it has been marked as dirty.

---

### `void register_joystick_button(uint8_t button)` :id=api-register-joystick-button

Set the state of a button, and flush the report.

#### Arguments :id=api-register-joystick-button-arguments

- `uint8_t button`
The index of the button to press, from 0 to 31.

---

### `void register_joystick_button(uint8_t button)` :id=api-unregister-joystick-button

Reset the state of a button, and flush the report.

#### Arguments :id=api-unregister-joystick-button-arguments

- `uint8_t button`
The index of the button to release, from 0 to 31.

---

### `int16_t joystick_read_axis(uint8_t axis)` :id=api-joystick-read-axis

Sample and process the analog value of the given axis.

#### Arguments :id=api-joystick-read-axis-arguments

- `uint8_t axis`
The axis to read.

#### Return Value :id=api-joystick-read-axis-return

A signed 16-bit integer, where 0 is the resting or mid point.

### `void joystick_set_axis(uint8_t axis, int16_t value)` :id=api-joystick-set-axis

Set the value of the given axis.

#### Arguments :id=api-joystick-set-axis-arguments

- `uint8_t axis`
The axis to set the value of.
- `int16_t value`
The value to set.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "battleship_gamepad.h"

/* joystick config */
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_IN(F5, 1023, 512, 0),
[1] = JOYSTICK_AXIS_IN(F4, 0, 512, 1023)
};
Expand Down
4 changes: 2 additions & 2 deletions keyboards/handwired/battleship_gamepad/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

/* joystick configuration */
#define JOYSTICK_BUTTON_COUNT 25
#define JOYSTICK_AXES_COUNT 2
#define JOYSTICK_AXES_RESOLUTION 10
#define JOYSTICK_AXIS_COUNT 2
#define JOYSTICK_AXIS_RESOLUTION 10

/* COL2ROW or ROW2COL */
#define DIODE_DIRECTION COL2ROW
Expand Down
2 changes: 1 addition & 1 deletion keyboards/handwired/misterdeck/keymaps/default/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@

#pragma once

#define JOYSTICK_AXES_COUNT 4
#define JOYSTICK_AXIS_COUNT 4
#define JOYSTICK_BUTTON_COUNT 4
2 changes: 1 addition & 1 deletion keyboards/handwired/misterdeck/keymaps/default/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
)
};

joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_IN(F4, 0, 512, 1023),
[1] = JOYSTICK_AXIS_IN(F5, 0, 512, 1023),
[2] = JOYSTICK_AXIS_IN(F6, 0, 512, 1023),
Expand Down
2 changes: 1 addition & 1 deletion keyboards/handwired/misterdeck/keymaps/nobuttons/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@

#pragma once

#define JOYSTICK_AXES_COUNT 4
#define JOYSTICK_AXIS_COUNT 4
#define JOYSTICK_BUTTON_COUNT 0
2 changes: 1 addition & 1 deletion keyboards/handwired/misterdeck/keymaps/nobuttons/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
),
};

joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_IN(F4, 0, 512, 1023),
[1] = JOYSTICK_AXIS_IN(F5, 0, 512, 1023),
[2] = JOYSTICK_AXIS_IN(F6, 0, 512, 1023),
Expand Down
2 changes: 1 addition & 1 deletion keyboards/handwired/onekey/keymaps/joystick/config.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma once

#define JOYSTICK_AXES_COUNT 2
#define JOYSTICK_AXIS_COUNT 2
#define JOYSTICK_BUTTON_COUNT 1
2 changes: 1 addition & 1 deletion keyboards/handwired/onekey/keymaps/joystick/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void matrix_scan_user() {
}

// Joystick config
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_IN(ADC_PIN, 0, 512, 1023),
[1] = JOYSTICK_AXIS_VIRTUAL
};
26 changes: 26 additions & 0 deletions keyboards/handwired/onekey/proton_c/halconf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Copyright 2020 QMK
*
* 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 <http://www.gnu.org/licenses/>.
*/

/*
* This file was auto-generated by:
* `qmk chibios-confmigrate -i keyboards/handwired/onekey/blackpill_f401/halconf.h -r platforms/chibios/common/configs/halconf.h`
*/

#pragma once

#define HAL_USE_ADC TRUE

#include_next <halconf.h>
22 changes: 22 additions & 0 deletions keyboards/handwired/onekey/proton_c/mcuconf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Copyright 2020 Nick Brassel (tzarc)
*
* 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 <https://www.gnu.org/licenses/>.
*/

#pragma once

#include_next "mcuconf.h"

#undef STM32_ADC_USE_ADC1
#define STM32_ADC_USE_ADC1 TRUE
2 changes: 1 addition & 1 deletion keyboards/lime/keymaps/default/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
}

/* Joystick axes settings */
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_IN(JOYSTICK_X_PIN, 268, 514, 813),
[1] = JOYSTICK_AXIS_IN(JOYSTICK_Y_PIN, 865, 519, 260)
};
Expand Down
4 changes: 2 additions & 2 deletions keyboards/lime/rev1/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@

/* joystick support */
#ifdef JOYSTICK_ENABLE
# define JOYSTICK_AXES_COUNT 2
# define JOYSTICK_AXIS_COUNT 2
# define JOYSTICK_BUTTON_COUNT 1
# define JOYSTICK_AXES_RESOLUTION 8
# define JOYSTICK_AXIS_RESOLUTION 8
#endif

#define TAP_CODE_DELAY 10
Expand Down
4 changes: 2 additions & 2 deletions keyboards/synthlabs/solo/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@
#define BOOTMAGIC_LITE_COLUMN 1

#define JOYSTICK_BUTTON_COUNT 13
#define JOYSTICK_AXES_COUNT 1
#define JOYSTICK_AXES_RESOLUTION 16
#define JOYSTICK_AXIS_COUNT 1
#define JOYSTICK_AXIS_RESOLUTION 16
2 changes: 1 addition & 1 deletion keyboards/synthlabs/solo/keymaps/gamepad/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
int16_t joystick_position = 0;
int16_t pulses_per_revolution = 24; // Depends on encoder model. Usually 18ppr or 24ppr for Bourns EC11s.
int16_t full_joystick_value = 32767; // Equivalent to max value of int16. +full_joystick_value is +1.0 axis output. -full_joystick_value is -1.0 axis output.
joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
[0] = JOYSTICK_AXIS_VIRTUAL
};

Expand Down
Loading