Skip to content

Commit

Permalink
Further refactoring of joystick feature (qmk#18437)
Browse files Browse the repository at this point in the history
  • Loading branch information
fauxpark authored Sep 27, 2022
1 parent fb400f2 commit be8907d
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 309 deletions.
81 changes: 27 additions & 54 deletions docs/feature_joystick.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,71 +70,44 @@ When the ADC reads 900 or higher, the returned axis value will be -127, whereas
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.
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.
#### Virtual Axes
The following example writes two axes based on keypad presses, with `KC_P5` as a precision modifier:
To give a value to virtual axes, call `joystick_set_axis(axis, value)`.
The following example adjusts two virtual axes (X and Y) based on keypad presses, with `KC_P5` 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] = {
[0] = JOYSTICK_AXIS_VIRTUAL, // x
[1] = 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;
joystick_set_axis(0, record->event.pressed ? precision_val : 0);
return false;
case KC_P5:
if (record->event.pressed) {
axesFlags |= Precision;
} else {
axesFlags &= ~Precision;
}
joystick_status.status |= JS_UPDATED;
break;
#endif
precision = record->event.pressed;
return false;
}
return true;
}
Expand Down
6 changes: 1 addition & 5 deletions keyboards/handwired/onekey/keymaps/joystick/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {

void matrix_scan_user() {
int16_t val = (((uint32_t)timer_read() % 5000 - 2500) * 255) / 5000;

if (val != joystick_status.axes[1]) {
joystick_status.axes[1] = val;
joystick_status.status |= JS_UPDATED;
}
joystick_set_axis(1, val);
}

// Joystick config
Expand Down
7 changes: 1 addition & 6 deletions keyboards/synthlabs/solo/keymaps/gamepad/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

#include QMK_KEYBOARD_H

#include "joystick.h"

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT_all(
JS_BUTTON0,JS_BUTTON1,JS_BUTTON2,JS_BUTTON3,JS_BUTTON4,JS_BUTTON5,JS_BUTTON6,
Expand All @@ -24,11 +22,8 @@ joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {

bool encoder_update_kb(uint8_t index, bool clockwise) {
joystick_position += (clockwise ? 2 : -2) * (full_joystick_value / pulses_per_revolution); // +2 and -2 are used, since +1.0 and -1.0 axis output refers to positions at half of a full rotation
joystick_set_axis(0, joystick_position);

if (joystick_position != joystick_status.axes[0]) {
joystick_status.axes[0] = joystick_position;
joystick_status.status |= JS_UPDATED;
}
return true;
}

Expand Down
98 changes: 95 additions & 3 deletions quantum/joystick.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
/* Copyright 2022
*
* 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/>.
*/

#include "joystick.h"

#include "analog.h"
#include "wait.h"

// clang-format off
joystick_t joystick_status = {
.buttons = {0},
Expand All @@ -15,12 +34,13 @@ joystick_t joystick_status = {
// array defining the reading of analog values for each axis
__attribute__((weak)) joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT] = {};

// to be implemented in the hid protocol library
void send_joystick_packet(joystick_t *joystick);
__attribute__((weak)) void joystick_task(void) {
joystick_read_axes();
}

void joystick_flush(void) {
if ((joystick_status.status & JS_UPDATED) > 0) {
send_joystick_packet(&joystick_status);
host_joystick_send(&joystick_status);
joystick_status.status &= ~JS_UPDATED;
}
}
Expand All @@ -36,3 +56,75 @@ void unregister_joystick_button(uint8_t button) {
joystick_status.status |= JS_UPDATED;
joystick_flush();
}

int16_t joystick_read_axis(uint8_t axis) {
// disable pull-up resistor
writePinLow(joystick_axes[axis].input_pin);

// if pin was a pull-up input, we need to uncharge it by turning it low
// before making it a low input
setPinOutput(joystick_axes[axis].input_pin);

wait_us(10);

if (joystick_axes[axis].output_pin != JS_VIRTUAL_AXIS) {
setPinOutput(joystick_axes[axis].output_pin);
writePinHigh(joystick_axes[axis].output_pin);
}

if (joystick_axes[axis].ground_pin != JS_VIRTUAL_AXIS) {
setPinOutput(joystick_axes[axis].ground_pin);
writePinLow(joystick_axes[axis].ground_pin);
}

wait_us(10);

setPinInput(joystick_axes[axis].input_pin);

wait_us(10);

#if defined(ANALOG_JOYSTICK_ENABLE) && (defined(__AVR__) || defined(PROTOCOL_CHIBIOS))
int16_t axis_val = analogReadPin(joystick_axes[axis].input_pin);
#else
// default to resting position
int16_t axis_val = joystick_axes[axis].mid_digit;
#endif

// 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);

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

// 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;

return ranged_val;
}

void joystick_read_axes() {
#if JOYSTICK_AXES_COUNT > 0
for (int i = 0; i < JOYSTICK_AXES_COUNT; ++i) {
if (joystick_axes[i].input_pin == JS_VIRTUAL_AXIS) {
continue;
}

joystick_set_axis(i, joystick_read_axis(i));
}

joystick_flush();
#endif
}

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;
}
}
28 changes: 27 additions & 1 deletion quantum/joystick.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/* Copyright 2022
*
* 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/>.
*/

#pragma once

#include <stdint.h>
Expand Down Expand Up @@ -54,7 +70,10 @@ typedef struct {

extern joystick_config_t joystick_axes[JOYSTICK_AXES_COUNT];

enum joystick_status { JS_INITIALIZED = 1, JS_UPDATED = 2 };
enum joystick_status {
JS_INITIALIZED = 1,
JS_UPDATED,
};

typedef struct {
uint8_t buttons[(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1];
Expand All @@ -65,7 +84,14 @@ typedef struct {

extern joystick_t joystick_status;

void joystick_task(void);
void joystick_flush(void);

void register_joystick_button(uint8_t button);
void unregister_joystick_button(uint8_t button);

int16_t joystick_read_axis(uint8_t axis);
void joystick_read_axes(void);
void joystick_set_axis(uint8_t axis, int16_t value);

void host_joystick_send(joystick_t *joystick);
Loading

0 comments on commit be8907d

Please sign in to comment.