joystick.c
4306 bytes
1/* Copyright 2022
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "joystick.h"
18#include "wait.h"
19
20#if defined(JOYSTICK_ANALOG)
21# include "analog.h"
22#endif
23
24joystick_t joystick_state = {
25 .buttons = {0},
26 .axes =
27 {
28#if JOYSTICK_AXIS_COUNT > 0
29 0
30#endif
31 },
32#ifdef JOYSTICK_HAS_HAT
33 .hat = -1,
34#endif
35 .dirty = false,
36};
37
38// array defining the reading of analog values for each axis
39__attribute__((weak)) joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
40#if JOYSTICK_AXIS_COUNT > 0
41 [0 ...(JOYSTICK_AXIS_COUNT - 1)] = JOYSTICK_AXIS_VIRTUAL
42#endif
43};
44
45__attribute__((weak)) void joystick_axis_init(uint8_t axis) {
46 if (axis >= JOYSTICK_AXIS_COUNT) return;
47
48#if defined(JOYSTICK_ANALOG)
49 gpio_set_pin_input(joystick_axes[axis].input_pin);
50#endif
51}
52
53__attribute__((weak)) uint16_t joystick_axis_sample(uint8_t axis) {
54 if (axis >= JOYSTICK_AXIS_COUNT) return 0;
55
56#if defined(JOYSTICK_ANALOG)
57 return analogReadPin(joystick_axes[axis].input_pin);
58#else
59 // default to resting position
60 return joystick_axes[axis].mid_digit;
61#endif
62}
63
64static inline bool is_virtual_axis(uint8_t axis) {
65 return joystick_axes[axis].input_pin == NO_PIN;
66}
67
68void joystick_flush(void) {
69 if (!joystick_state.dirty) return;
70
71 // TODO: host.h?
72 void host_joystick_send(joystick_t * joystick);
73 host_joystick_send(&joystick_state);
74 joystick_state.dirty = false;
75}
76
77void register_joystick_button(uint8_t button) {
78 if (button >= JOYSTICK_BUTTON_COUNT) return;
79
80 joystick_state.buttons[button / 8] |= 1 << (button % 8);
81 joystick_state.dirty = true;
82 joystick_flush();
83}
84
85void unregister_joystick_button(uint8_t button) {
86 if (button >= JOYSTICK_BUTTON_COUNT) return;
87
88 joystick_state.buttons[button / 8] &= ~(1 << (button % 8));
89 joystick_state.dirty = true;
90 joystick_flush();
91}
92
93int16_t joystick_read_axis(uint8_t axis) {
94 if (axis >= JOYSTICK_AXIS_COUNT) return 0;
95
96 int16_t axis_val = joystick_axis_sample(axis);
97
98 // test the converted value against the lower range
99 int32_t ref = joystick_axes[axis].mid_digit;
100 int32_t range = joystick_axes[axis].min_digit;
101 int32_t ranged_val = ((axis_val - ref) * -JOYSTICK_MAX_VALUE) / (range - ref);
102
103 if (ranged_val > 0) {
104 // the value is in the higher range
105 range = joystick_axes[axis].max_digit;
106 ranged_val = ((axis_val - ref) * JOYSTICK_MAX_VALUE) / (range - ref);
107 }
108
109 // clamp the result in the valid range
110 ranged_val = ranged_val < -JOYSTICK_MAX_VALUE ? -JOYSTICK_MAX_VALUE : ranged_val;
111 ranged_val = ranged_val > JOYSTICK_MAX_VALUE ? JOYSTICK_MAX_VALUE : ranged_val;
112
113 return ranged_val;
114}
115
116void joystick_init_axes(void) {
117#if JOYSTICK_AXIS_COUNT > 0
118 for (int i = 0; i < JOYSTICK_AXIS_COUNT; ++i) {
119 if (is_virtual_axis(i)) {
120 continue;
121 }
122
123 joystick_axis_init(i);
124 }
125#endif
126}
127
128void joystick_read_axes(void) {
129#if JOYSTICK_AXIS_COUNT > 0
130 for (int i = 0; i < JOYSTICK_AXIS_COUNT; ++i) {
131 if (is_virtual_axis(i)) {
132 continue;
133 }
134
135 joystick_set_axis(i, joystick_read_axis(i));
136 }
137
138 joystick_flush();
139#endif
140}
141
142void joystick_set_axis(uint8_t axis, int16_t value) {
143 if (axis >= JOYSTICK_AXIS_COUNT) return;
144
145 if (value != joystick_state.axes[axis]) {
146 joystick_state.axes[axis] = value;
147 joystick_state.dirty = true;
148 }
149}
150
151#ifdef JOYSTICK_HAS_HAT
152void joystick_set_hat(int8_t value) {
153 joystick_state.hat = value;
154 joystick_state.dirty = true;
155}
156#endif
157
158void joystick_init(void) {
159 joystick_init_axes();
160}
161
162void joystick_task(void) {
163 joystick_read_axes();
164}