diff options
Diffstat (limited to 'docs/feature_joystick.md')
-rw-r--r-- | docs/feature_joystick.md | 288 |
1 files changed, 181 insertions, 107 deletions
diff --git a/docs/feature_joystick.md b/docs/feature_joystick.md index 2635298587..e60046a3c3 100644 --- a/docs/feature_joystick.md +++ b/docs/feature_joystick.md @@ -1,154 +1,228 @@ -## 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. -In order to give a value to the second axis, you can do so in any customizable entry point: as an action, in `process_record_user()` or in `matrix_scan_user()`, or even in `joystick_task()` which is called even when no key has been pressed. -You assign a value by writing to `joystick_status.axes[axis_index]` a signed 8-bit value (ranging from -127 to 127). Then it is necessary to assign the flag `JS_UPDATED` to `joystick_status.status` in order for an updated HID report to be sent. +The `low` and `high` values can be swapped to effectively invert the axis. -The following example writes two axes based on keypad presses, with `KC_P5` as a precision modifier: +#### Virtual Axes :id=virtual-axes + +The following example adjusts two virtual axes (X and Y) based on keypad presses, with `KC_P0` as a precision modifier: ```c -#ifdef ANALOG_JOYSTICK_ENABLE -static uint8_t precision_val = 70; -static uint8_t axesFlags = 0; -enum axes { - Precision = 1, - Axis1High = 2, - Axis1Low = 4, - Axis2High = 8, - Axis2Low = 16 +joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = { + JOYSTICK_AXIS_VIRTUAL, // x + JOYSTICK_AXIS_VIRTUAL // y }; -#endif + +static bool precision = false; +static uint16_t precision_mod = 64; +static uint16_t axis_val = 127; bool process_record_user(uint16_t keycode, keyrecord_t *record) { - switch(keycode) { -#ifdef ANALOG_JOYSTICK_ENABLE - // virtual joystick -# if JOYSTICK_AXES_COUNT > 1 + int16_t precision_val = axis_val; + if (precision) { + precision_val -= precision_mod; + } + + switch (keycode) { case KC_P8: - if (record->event.pressed) { - axesFlags |= Axis2Low; - } else { - axesFlags &= ~Axis2Low; - } - joystick_status.status |= JS_UPDATED; - break; + joystick_set_axis(1, record->event.pressed ? -precision_val : 0); + return false; case KC_P2: - if (record->event.pressed) { - axesFlags |= Axis2High; - } else { - axesFlags &= ~Axis2High; - } - joystick_status.status |= JS_UPDATED; - break; -# endif + joystick_set_axis(1, record->event.pressed ? precision_val : 0); + return false; case KC_P4: - if (record->event.pressed) { - axesFlags |= Axis1Low; - } else { - axesFlags &= ~Axis1Low; - } - joystick_status.status |= JS_UPDATED; - break; + joystick_set_axis(0, record->event.pressed ? -precision_val : 0); + return false; case KC_P6: - if (record->event.pressed) { - axesFlags |= Axis1High; - } else { - axesFlags &= ~Axis1High; - } - joystick_status.status |= JS_UPDATED; - break; - case KC_P5: - if (record->event.pressed) { - axesFlags |= Precision; - } else { - axesFlags &= ~Precision; - } - joystick_status.status |= JS_UPDATED; - break; -#endif + joystick_set_axis(0, record->event.pressed ? precision_val : 0); + return false; + case KC_P0: + precision = record->event.pressed; + return false; } return true; } ``` -### Axis Resolution +## Keycodes :id=keycodes + +|Key |Aliases|Description| +|-----------------------|-------|-----------| +|`QK_JOYSTICK_BUTTON_0` |`JS_0` |Button 0 | +|`QK_JOYSTICK_BUTTON_1` |`JS_1` |Button 1 | +|`QK_JOYSTICK_BUTTON_2` |`JS_2` |Button 2 | +|`QK_JOYSTICK_BUTTON_3` |`JS_3` |Button 3 | +|`QK_JOYSTICK_BUTTON_4` |`JS_4` |Button 4 | +|`QK_JOYSTICK_BUTTON_5` |`JS_5` |Button 5 | +|`QK_JOYSTICK_BUTTON_6` |`JS_6` |Button 6 | +|`QK_JOYSTICK_BUTTON_7` |`JS_7` |Button 7 | +|`QK_JOYSTICK_BUTTON_8` |`JS_8` |Button 8 | +|`QK_JOYSTICK_BUTTON_9` |`JS_9` |Button 9 | +|`QK_JOYSTICK_BUTTON_10`|`JS_10`|Button 10 | +|`QK_JOYSTICK_BUTTON_11`|`JS_11`|Button 11 | +|`QK_JOYSTICK_BUTTON_12`|`JS_12`|Button 12 | +|`QK_JOYSTICK_BUTTON_13`|`JS_13`|Button 13 | +|`QK_JOYSTICK_BUTTON_14`|`JS_14`|Button 14 | +|`QK_JOYSTICK_BUTTON_15`|`JS_15`|Button 15 | +|`QK_JOYSTICK_BUTTON_16`|`JS_16`|Button 16 | +|`QK_JOYSTICK_BUTTON_17`|`JS_17`|Button 17 | +|`QK_JOYSTICK_BUTTON_18`|`JS_18`|Button 18 | +|`QK_JOYSTICK_BUTTON_19`|`JS_19`|Button 19 | +|`QK_JOYSTICK_BUTTON_20`|`JS_20`|Button 20 | +|`QK_JOYSTICK_BUTTON_21`|`JS_21`|Button 21 | +|`QK_JOYSTICK_BUTTON_22`|`JS_22`|Button 22 | +|`QK_JOYSTICK_BUTTON_23`|`JS_23`|Button 23 | +|`QK_JOYSTICK_BUTTON_24`|`JS_24`|Button 24 | +|`QK_JOYSTICK_BUTTON_25`|`JS_25`|Button 25 | +|`QK_JOYSTICK_BUTTON_26`|`JS_26`|Button 26 | +|`QK_JOYSTICK_BUTTON_27`|`JS_27`|Button 27 | +|`QK_JOYSTICK_BUTTON_28`|`JS_28`|Button 28 | +|`QK_JOYSTICK_BUTTON_29`|`JS_29`|Button 29 | +|`QK_JOYSTICK_BUTTON_30`|`JS_30`|Button 30 | +|`QK_JOYSTICK_BUTTON_31`|`JS_31`|Button 31 | + +## 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 -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. +A signed 16-bit integer, where 0 is the resting or mid point. -Note that the supported AVR MCUs have a 10-bit ADC, and 12-bit for most STM32 MCUs. +### `void joystick_set_axis(uint8_t axis, int16_t value)` :id=api-joystick-set-axis -### Triggering Joystick Buttons +Set the value of the given axis. -Joystick buttons are normal Quantum keycodes, defined as `JS_BUTTON0` to `JS_BUTTON31`, depending on the number of buttons you have configured. -To trigger a joystick button, just add the corresponding keycode to your keymap. +#### Arguments :id=api-joystick-set-axis-arguments -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). + - `uint8_t axis` + The axis to set the value of. + - `int16_t value` + The value to set. |