Joystick feature improvements (#19052)

This commit is contained in:
Ryan 2022-11-27 03:14:45 +11:00 committed by GitHub
parent a5a20cc792
commit 1e95f7be8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 475 additions and 215 deletions

View File

@ -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:
### Configuring the Joystick
```make
JOYSTICK_DRIVER = digital
```
By default, two axes and eight buttons are defined. This can be changed in your `config.h`:
## Configuration :id=configuration
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.
?> 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.
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.
### Axes :id=axes
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`.
When defining axes for your joystick, you must provide a definition array typically in your `keymap.c`.
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.
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;
@ -105,7 +92,7 @@ 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;
}
@ -113,13 +100,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
}
```
### 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|
|-----------------------|-------|-----------|
@ -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.

View File

@ -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)
};

View File

@ -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

View File

@ -17,5 +17,5 @@
#pragma once
#define JOYSTICK_AXES_COUNT 4
#define JOYSTICK_AXIS_COUNT 4
#define JOYSTICK_BUTTON_COUNT 4

View File

@ -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),

View File

@ -17,5 +17,5 @@
#pragma once
#define JOYSTICK_AXES_COUNT 4
#define JOYSTICK_AXIS_COUNT 4
#define JOYSTICK_BUTTON_COUNT 0

View File

@ -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),

View File

@ -1,4 +1,4 @@
#pragma once
#define JOYSTICK_AXES_COUNT 2
#define JOYSTICK_AXIS_COUNT 2
#define JOYSTICK_BUTTON_COUNT 1

View File

@ -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
};

View File

@ -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>

View File

@ -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

View File

@ -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)
};

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -19,45 +19,48 @@
#include "analog.h"
#include "wait.h"
// clang-format off
joystick_t joystick_status = {
joystick_t joystick_state = {
.buttons = {0},
.axes = {
#if JOYSTICK_AXES_COUNT > 0
0
.axes =
{
#if JOYSTICK_AXIS_COUNT > 0
0
#endif
},
.status = 0
},
.dirty = false,
};
// clang-format on
// array defining the reading of analog values for each axis
__attribute__((weak)) joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {};
__attribute__((weak)) joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {};
__attribute__((weak)) void joystick_task(void) {
joystick_read_axes();
}
void joystick_flush(void) {
if ((joystick_status.status & JS_UPDATED) > 0) {
host_joystick_send(&joystick_status);
joystick_status.status &= ~JS_UPDATED;
if (joystick_state.dirty) {
host_joystick_send(&joystick_state);
joystick_state.dirty = false;
}
}
void register_joystick_button(uint8_t button) {
joystick_status.buttons[button / 8] |= 1 << (button % 8);
joystick_status.status |= JS_UPDATED;
if (button >= JOYSTICK_BUTTON_COUNT) return;
joystick_state.buttons[button / 8] |= 1 << (button % 8);
joystick_state.dirty = true;
joystick_flush();
}
void unregister_joystick_button(uint8_t button) {
joystick_status.buttons[button / 8] &= ~(1 << (button % 8));
joystick_status.status |= JS_UPDATED;
if (button >= JOYSTICK_BUTTON_COUNT) return;
joystick_state.buttons[button / 8] &= ~(1 << (button % 8));
joystick_state.dirty = true;
joystick_flush();
}
int16_t joystick_read_axis(uint8_t axis) {
if (axis >= JOYSTICK_AXIS_COUNT) return 0;
// disable pull-up resistor
writePinLow(joystick_axes[axis].input_pin);
@ -93,24 +96,24 @@ int16_t joystick_read_axis(uint8_t axis) {
// test the converted value against the lower range
int32_t ref = joystick_axes[axis].mid_digit;
int32_t range = joystick_axes[axis].min_digit;
int32_t ranged_val = ((axis_val - ref) * -JOYSTICK_RESOLUTION) / (range - ref);
int32_t ranged_val = ((axis_val - ref) * -JOYSTICK_MAX_VALUE) / (range - ref);
if (ranged_val > 0) {
// the value is in the higher range
range = joystick_axes[axis].max_digit;
ranged_val = ((axis_val - ref) * JOYSTICK_RESOLUTION) / (range - ref);
ranged_val = ((axis_val - ref) * JOYSTICK_MAX_VALUE) / (range - ref);
}
// clamp the result in the valid range
ranged_val = ranged_val < -JOYSTICK_RESOLUTION ? -JOYSTICK_RESOLUTION : ranged_val;
ranged_val = ranged_val > JOYSTICK_RESOLUTION ? JOYSTICK_RESOLUTION : ranged_val;
ranged_val = ranged_val < -JOYSTICK_MAX_VALUE ? -JOYSTICK_MAX_VALUE : ranged_val;
ranged_val = ranged_val > JOYSTICK_MAX_VALUE ? JOYSTICK_MAX_VALUE : ranged_val;
return ranged_val;
}
void joystick_read_axes() {
#if JOYSTICK_AXES_COUNT > 0
for (int i = 0; i < JOYSTICK_AXES_COUNT; ++i) {
#if JOYSTICK_AXIS_COUNT > 0
for (int i = 0; i < JOYSTICK_AXIS_COUNT; ++i) {
if (joystick_axes[i].input_pin == JS_VIRTUAL_AXIS) {
continue;
}
@ -123,8 +126,10 @@ void joystick_read_axes() {
}
void joystick_set_axis(uint8_t axis, int16_t value) {
if (value != joystick_status.axes[axis]) {
joystick_status.axes[axis] = value;
joystick_status.status |= JS_UPDATED;
if (axis >= JOYSTICK_AXIS_COUNT) return;
if (value != joystick_state.axes[axis]) {
joystick_state.axes[axis] = value;
joystick_state.dirty = true;
}
}

View File

@ -16,32 +16,41 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "gpio.h"
/**
* \defgroup joystick
*
* HID Joystick
* \{
*/
#ifndef JOYSTICK_BUTTON_COUNT
# define JOYSTICK_BUTTON_COUNT 8
#elif JOYSTICK_BUTTON_COUNT > 32
# error Joystick feature only supports up to 32 buttons
#endif
#ifndef JOYSTICK_AXES_COUNT
# define JOYSTICK_AXES_COUNT 4
#elif JOYSTICK_AXES_COUNT > 6
#ifndef JOYSTICK_AXIS_COUNT
# define JOYSTICK_AXIS_COUNT 2
#elif JOYSTICK_AXIS_COUNT > 6
# error Joystick feature only supports up to 6 axes
#endif
#if JOYSTICK_AXES_COUNT == 0 && JOYSTICK_BUTTON_COUNT == 0
#if JOYSTICK_AXIS_COUNT == 0 && JOYSTICK_BUTTON_COUNT == 0
# error Joystick feature requires at least one axis or button
#endif
#ifndef JOYSTICK_AXES_RESOLUTION
# define JOYSTICK_AXES_RESOLUTION 8
#elif JOYSTICK_AXES_RESOLUTION < 8 || JOYSTICK_AXES_RESOLUTION > 16
# error JOYSTICK_AXES_RESOLUTION must be between 8 and 16
#ifndef JOYSTICK_AXIS_RESOLUTION
# define JOYSTICK_AXIS_RESOLUTION 8
#elif JOYSTICK_AXIS_RESOLUTION < 8 || JOYSTICK_AXIS_RESOLUTION > 16
# error JOYSTICK_AXIS_RESOLUTION must be between 8 and 16
#endif
#define JOYSTICK_RESOLUTION ((1L << (JOYSTICK_AXES_RESOLUTION - 1)) - 1)
#define JOYSTICK_MAX_VALUE ((1L << (JOYSTICK_AXIS_RESOLUTION - 1)) - 1)
// 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.
@ -68,30 +77,56 @@ typedef struct {
uint16_t max_digit;
} joystick_config_t;
extern joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT];
enum joystick_status {
JS_INITIALIZED = 1,
JS_UPDATED,
};
extern joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT];
typedef struct {
uint8_t buttons[(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1];
int16_t axes[JOYSTICK_AXES_COUNT];
uint8_t status : 2;
int16_t axes[JOYSTICK_AXIS_COUNT];
bool dirty;
} joystick_t;
extern joystick_t joystick_status;
extern joystick_t joystick_state;
void joystick_task(void);
/**
* \brief Send the joystick report to the host, if it has been marked as dirty.
*/
void joystick_flush(void);
/**
* \brief Set the state of a button, and flush the report.
*
* \param button The index of the button to press, from 0 to 31.
*/
void register_joystick_button(uint8_t button);
/**
* \brief Reset the state of a button, and flush the report.
*
* \param button The index of the button to release, from 0 to 31.
*/
void unregister_joystick_button(uint8_t button);
/**
* \brief Sample and process the analog value of the given axis.
*
* \param axis The axis to read.
*
* \return A signed 16-bit integer, where 0 is the resting or mid point.
*/
int16_t joystick_read_axis(uint8_t axis);
void joystick_read_axes(void);
void joystick_set_axis(uint8_t axis, int16_t value);
void joystick_read_axes(void);
/**
* \brief Set the value of the given axis.
*
* \param axis The axis to set the value of.
* \param value The value to set.
*/
void joystick_set_axis(uint8_t axis, int16_t value);
void host_joystick_send(joystick_t *joystick);
/** \} */

View File

@ -68,6 +68,19 @@ ifeq ($(strip $(NO_USB_STARTUP_CHECK)), yes)
TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK
endif
ifeq ($(strip $(JOYSTICK_SHARED_EP)), yes)
TMK_COMMON_DEFS += -DJOYSTICK_SHARED_EP
SHARED_EP_ENABLE = yes
endif
ifeq ($(strip $(JOYSTICK_ENABLE)), yes)
TMK_COMMON_DEFS += -DJOYSTICK_ENABLE
ifeq ($(strip $(SHARED_EP_ENABLE)), yes)
TMK_COMMON_DEFS += -DJOYSTICK_SHARED_EP
SHARED_EP_ENABLE = yes
endif
endif
ifeq ($(strip $(DIGITIZER_SHARED_EP)), yes)
TMK_COMMON_DEFS += -DDIGITIZER_SHARED_EP
SHARED_EP_ENABLE = yes

View File

@ -637,20 +637,20 @@ static uint8_t udi_hid_raw_report_recv[UDI_HID_RAW_REPORT_SIZE];
COMPILER_WORD_ALIGNED
UDC_DESC_STORAGE udi_hid_raw_report_desc_t udi_hid_raw_report_desc = {{
0x06, RAW_USAGE_PAGE_LO, RAW_USAGE_PAGE_HI, // Usage Page (Vendor Defined)
0x09, RAW_USAGE_ID, // Usage (Vendor Defined)
0xA1, 0x01, // Collection (Application)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
0x06, HID_VALUE_16(RAW_USAGE_PAGE), // Usage Page (Vendor Defined)
0x09, RAW_USAGE_ID, // Usage (Vendor Defined)
0xA1, 0x01, // Collection (Application)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
// Data to host
0x09, 0x62, // Usage (Vendor Defined)
0x95, RAW_EPSIZE, // Report Count
0x81, 0x02, // Input (Data, Variable, Absolute)
0x09, 0x62, // Usage (Vendor Defined)
0x95, RAW_EPSIZE, // Report Count
0x81, 0x02, // Input (Data, Variable, Absolute)
// Data from host
0x09, 0x63, // Usage (Vendor Defined)
0x95, RAW_EPSIZE, // Report Count
0x91, 0x02, // Output (Data, Variable, Absolute)
0x09, 0x63, // Usage (Vendor Defined)
0x95, RAW_EPSIZE, // Report Count
0x91, 0x02, // Output (Data, Variable, Absolute)
0xC0 // End Collection
}};

View File

@ -183,7 +183,7 @@ static const USBEndpointConfig shared_ep_config = {
};
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
/* joystick endpoint state structure */
static USBInEndpointState joystick_ep_state;
@ -507,7 +507,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
#ifdef SHARED_EP_ENABLE
usbInitEndpointI(usbp, SHARED_IN_EPNUM, &shared_ep_config);
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
usbInitEndpointI(usbp, JOYSTICK_IN_EPNUM, &joystick_ep_config);
#endif
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)

View File

@ -164,24 +164,27 @@ void host_joystick_send(joystick_t *joystick) {
if (!driver) return;
report_joystick_t report = {
# if JOYSTICK_AXES_COUNT > 0
# ifdef JOYSTICK_SHARED_EP
.report_id = REPORT_ID_JOYSTICK,
# endif
# if JOYSTICK_AXIS_COUNT > 0
.axes =
{
joystick->axes[0],
# if JOYSTICK_AXES_COUNT >= 2
# if JOYSTICK_AXIS_COUNT >= 2
joystick->axes[1],
# endif
# if JOYSTICK_AXES_COUNT >= 3
# if JOYSTICK_AXIS_COUNT >= 3
joystick->axes[2],
# endif
# if JOYSTICK_AXES_COUNT >= 4
# if JOYSTICK_AXIS_COUNT >= 4
joystick->axes[3],
# endif
# if JOYSTICK_AXES_COUNT >= 5
# if JOYSTICK_AXIS_COUNT >= 5
joystick->axes[4],
# endif
# if JOYSTICK_AXES_COUNT >= 6
# if JOYSTICK_AXIS_COUNT >= 6
joystick->axes[5],
# endif
},

View File

@ -404,7 +404,7 @@ void EVENT_USB_Device_ConfigurationChanged(void) {
ConfigSuccess &= Endpoint_ConfigureEndpoint((CDC_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_BULK, CDC_EPSIZE, 1);
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
/* Setup joystick endpoint */
ConfigSuccess &= Endpoint_ConfigureEndpoint((JOYSTICK_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, JOYSTICK_EPSIZE, 1);
#endif

View File

@ -235,11 +235,14 @@ typedef struct {
} __attribute__((packed)) report_digitizer_t;
typedef struct {
#if JOYSTICK_AXES_COUNT > 0
# if JOYSTICK_AXES_RESOLUTION > 8
int16_t axes[JOYSTICK_AXES_COUNT];
#ifdef JOYSTICK_SHARED_EP
uint8_t report_id;
#endif
#if JOYSTICK_AXIS_COUNT > 0
# if JOYSTICK_AXIS_RESOLUTION > 8
int16_t axes[JOYSTICK_AXIS_COUNT];
# else
int8_t axes[JOYSTICK_AXES_COUNT];
int8_t axes[JOYSTICK_AXIS_COUNT];
# endif
#endif

View File

@ -174,6 +174,75 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = {
# endif
#endif
#ifdef JOYSTICK_ENABLE
# ifndef JOYSTICK_SHARED_EP
const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] = {
# elif !defined(SHARED_REPORT_STARTED)
const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = {
# define SHARED_REPORT_STARTED
# endif
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
HID_RI_USAGE(8, 0x04), // Joystick
HID_RI_COLLECTION(8, 0x01), // Application
# ifdef JOYSTICK_SHARED_EP
HID_RI_REPORT_ID(8, REPORT_ID_JOYSTICK),
# endif
HID_RI_COLLECTION(8, 0x00), // Physical
# if JOYSTICK_AXIS_COUNT > 0
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
HID_RI_USAGE(8, 0x30), // X
# if JOYSTICK_AXIS_COUNT > 1
HID_RI_USAGE(8, 0x31), // Y
# endif
# if JOYSTICK_AXIS_COUNT > 2
HID_RI_USAGE(8, 0x32), // Z
# endif
# if JOYSTICK_AXIS_COUNT > 3
HID_RI_USAGE(8, 0x33), // Rx
# endif
# if JOYSTICK_AXIS_COUNT > 4
HID_RI_USAGE(8, 0x34), // Ry
# endif
# if JOYSTICK_AXIS_COUNT > 5
HID_RI_USAGE(8, 0x35), // Rz
# endif
# if JOYSTICK_AXIS_RESOLUTION == 8
HID_RI_LOGICAL_MINIMUM(8, -JOYSTICK_MAX_VALUE),
HID_RI_LOGICAL_MAXIMUM(8, JOYSTICK_MAX_VALUE),
HID_RI_REPORT_COUNT(8, JOYSTICK_AXIS_COUNT),
HID_RI_REPORT_SIZE(8, 0x08),
# else
HID_RI_LOGICAL_MINIMUM(16, -JOYSTICK_MAX_VALUE),
HID_RI_LOGICAL_MAXIMUM(16, JOYSTICK_MAX_VALUE),
HID_RI_REPORT_COUNT(8, JOYSTICK_AXIS_COUNT),
HID_RI_REPORT_SIZE(8, 0x10),
# endif
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
# endif
# if JOYSTICK_BUTTON_COUNT > 0
HID_RI_USAGE_PAGE(8, 0x09), // Button
HID_RI_USAGE_MINIMUM(8, 0x01),
HID_RI_USAGE_MAXIMUM(8, JOYSTICK_BUTTON_COUNT),
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),
# if (JOYSTICK_BUTTON_COUNT % 8) != 0
HID_RI_REPORT_COUNT(8, 8 - (JOYSTICK_BUTTON_COUNT % 8)),
HID_RI_REPORT_SIZE(8, 0x01),
HID_RI_INPUT(8, HID_IOF_CONSTANT),
# endif
# endif
HID_RI_END_COLLECTION(0),
HID_RI_END_COLLECTION(0),
# ifndef JOYSTICK_SHARED_EP
};
# endif
#endif
#ifdef DIGITIZER_ENABLE
# ifndef DIGITIZER_SHARED_EP
const USB_Descriptor_HIDReport_Datatype_t PROGMEM DigitizerReport[] = {
@ -360,65 +429,6 @@ 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_COLLECTION(8, 0x00), // Physical
# if JOYSTICK_AXES_COUNT > 0
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
HID_RI_USAGE(8, 0x30), // X
# if JOYSTICK_AXES_COUNT > 1
HID_RI_USAGE(8, 0x31), // Y
# endif
# if JOYSTICK_AXES_COUNT > 2
HID_RI_USAGE(8, 0x32), // Z
# endif
# if JOYSTICK_AXES_COUNT > 3
HID_RI_USAGE(8, 0x33), // Rx
# endif
# if JOYSTICK_AXES_COUNT > 4
HID_RI_USAGE(8, 0x34), // Ry
# endif
# if JOYSTICK_AXES_COUNT > 5
HID_RI_USAGE(8, 0x35), // Rz
# endif
# if JOYSTICK_AXES_RESOLUTION == 8
HID_RI_LOGICAL_MINIMUM(8, -JOYSTICK_RESOLUTION),
HID_RI_LOGICAL_MAXIMUM(8, JOYSTICK_RESOLUTION),
HID_RI_REPORT_COUNT(8, JOYSTICK_AXES_COUNT),
HID_RI_REPORT_SIZE(8, 0x08),
# else
HID_RI_LOGICAL_MINIMUM(16, -JOYSTICK_RESOLUTION),
HID_RI_LOGICAL_MAXIMUM(16, JOYSTICK_RESOLUTION),
HID_RI_REPORT_COUNT(8, JOYSTICK_AXES_COUNT),
HID_RI_REPORT_SIZE(8, 0x10),
# endif
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
# endif
# if JOYSTICK_BUTTON_COUNT > 0
HID_RI_USAGE_PAGE(8, 0x09), // Button
HID_RI_USAGE_MINIMUM(8, 0x01),
HID_RI_USAGE_MAXIMUM(8, JOYSTICK_BUTTON_COUNT),
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),
# if (JOYSTICK_BUTTON_COUNT % 8) != 0
HID_RI_REPORT_COUNT(8, 8 - (JOYSTICK_BUTTON_COUNT % 8)),
HID_RI_REPORT_SIZE(8, 0x01),
HID_RI_INPUT(8, HID_IOF_CONSTANT),
# endif
# endif
HID_RI_END_COLLECTION(0),
HID_RI_END_COLLECTION(0)
};
#endif
/*
* Device descriptor
*/
@ -958,10 +968,10 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
},
#endif
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
/*
* Joystick
*/
#ifdef JOYSTICK_ENABLE
.Joystick_Interface = {
.Header = {
.Size = sizeof(USB_Descriptor_Interface_t),
@ -1169,7 +1179,7 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
break;
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
case JOYSTICK_INTERFACE:
Address = &ConfigurationDescriptor.Joystick_HID;
Size = sizeof(USB_HID_Descriptor_HID_t);
@ -1226,7 +1236,7 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
break;
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
case JOYSTICK_INTERFACE:
Address = &JoystickReport;
Size = sizeof(JoystickReport);

View File

@ -132,7 +132,7 @@ typedef struct {
USB_Descriptor_Endpoint_t CDC_DataInEndpoint;
#endif
#ifdef JOYSTICK_ENABLE
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
// Joystick HID Interface
USB_Descriptor_Interface_t Joystick_Interface;
USB_HID_Descriptor_HID_t Joystick_HID;
@ -187,7 +187,7 @@ enum usb_interfaces {
CDI_INTERFACE,
#endif
#if defined(JOYSTICK_ENABLE)
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
JOYSTICK_INTERFACE,
#endif
@ -267,7 +267,11 @@ enum usb_endpoints {
#endif
#ifdef JOYSTICK_ENABLE
# if !defined(JOYSTICK_SHARED_EP)
JOYSTICK_IN_EPNUM = NEXT_EPNUM,
# else
# define JOYSTICK_IN_EPNUM SHARED_IN_EPNUM
# endif
#endif
#ifdef DIGITIZER_ENABLE

View File

@ -20,6 +20,8 @@
#define USBCONCAT(a, b) a##b
#define USBSTR(s) USBCONCAT(L, s)
#define HID_VALUE_16(v) ((uint8_t)(v & 0xFF)), ((uint8_t)(v >> 8))
/////////////////////
// RAW Usage page and ID configuration
@ -30,6 +32,3 @@
#ifndef RAW_USAGE_ID
# define RAW_USAGE_ID 0x61
#endif
#define RAW_USAGE_PAGE_HI ((uint8_t)(RAW_USAGE_PAGE >> 8))
#define RAW_USAGE_PAGE_LO ((uint8_t)(RAW_USAGE_PAGE & 0xFF))

View File

@ -35,6 +35,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "raw_hid.h"
#endif
#ifdef JOYSTICK_ENABLE
# include "joystick.h"
#endif
#if defined(CONSOLE_ENABLE)
# define RBUF_SIZE 128
# include "ring_buffer.h"
@ -275,6 +279,14 @@ static void send_extra(report_extra_t *report) {
#endif
}
void send_joystick(report_joystick_t *report) {
#ifdef JOYSTICK_ENABLE
if (usbInterruptIsReadyShared()) {
usbSetInterruptShared((void *)report, sizeof(report_joystick_t));
}
#endif
}
void send_digitizer(report_digitizer_t *report) {
#ifdef DIGITIZER_ENABLE
if (usbInterruptIsReadyShared()) {
@ -526,6 +538,65 @@ const PROGMEM uchar shared_hid_report[] = {
0xC0, // End Collection
#endif
#ifdef JOYSTICK_ENABLE
// Joystick report descriptor
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x85, REPORT_ID_JOYSTICK, // Report ID
0xA1, 0x00, // Collection (Physical)
# if JOYSTICK_AXIS_COUNT > 0
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
# if JOYSTICK_AXIS_COUNT > 1
0x09, 0x31, // Usage (Y)
# endif
# if JOYSTICK_AXIS_COUNT > 2
0x09, 0x32, // Usage (Z)
# endif
# if JOYSTICK_AXIS_COUNT > 3
0x09, 0x33, // Usage (Rx)
# endif
# if JOYSTICK_AXIS_COUNT > 4
0x09, 0x34, // Usage (Ry)
# endif
# if JOYSTICK_AXIS_COUNT > 5
0x09, 0x35, // Usage (Rz)
# endif
# if JOYSTICK_AXIS_RESOLUTION == 8
0x15, -JOYSTICK_MAX_VALUE, // Logical Minimum
0x25, JOYSTICK_MAX_VALUE, // Logical Maximum
0x95, JOYSTICK_AXIS_COUNT, // Report Count
0x75, 0x08, // Report Size (8)
# else
0x16, HID_VALUE_16(-JOYSTICK_MAX_VALUE), // Logical Minimum
0x26, HID_VALUE_16(JOYSTICK_MAX_VALUE), // Logical Maximum
0x95, JOYSTICK_AXIS_COUNT, // Report Count
0x75, 0x10, // Report Size (16)
# endif
0x81, 0x02, // Input (Data, Variable, Absolute)
# endif
# if JOYSTICK_BUTTON_COUNT > 0
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)
0x95, JOYSTICK_BUTTON_COUNT, // Report Count
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
# if (JOYSTICK_BUTTON_COUNT % 8) != 0
0x95, 8 - (JOYSTICK_BUTTON_COUNT % 8), // Report Count
0x75, 0x01, // Report Size (1)
0x81, 0x03, // Input (Constant)
# endif
# endif
0xC0, // End Collection
0xC0, // End Collection
#endif
#ifdef DIGITIZER_ENABLE
// Digitizer report descriptor
0x05, 0x0D, // Usage Page (Digitizers)
@ -587,9 +658,9 @@ const PROGMEM uchar shared_hid_report[] = {
#ifdef RAW_ENABLE
const PROGMEM uchar raw_hid_report[] = {
0x06, RAW_USAGE_PAGE_LO, RAW_USAGE_PAGE_HI, // Usage Page (Vendor Defined)
0x09, RAW_USAGE_ID, // Usage (Vendor Defined)
0xA1, 0x01, // Collection (Application)
0x06, HID_VALUE_16(RAW_USAGE_PAGE), // Usage Page (Vendor Defined)
0x09, RAW_USAGE_ID, // Usage (Vendor Defined)
0xA1, 0x01, // Collection (Application)
// Data to host
0x09, 0x62, // Usage (Vendor Defined)
0x15, 0x00, // Logical Minimum (0)