Skip to content

Commit 82e674c

Browse files
committed
Initial working N64 controller support, with hardcoded mappings
1 parent eda2f31 commit 82e674c

File tree

3 files changed

+150
-27
lines changed

3 files changed

+150
-27
lines changed

firmware/libsi/include/si/device/n64_controller.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,49 @@
99
#define SI_CMD_N64_POLL_LEN 1
1010
#define SI_CMD_N64_POLL_RESP 4
1111

12+
/**
13+
* N64 controller input state.
14+
*
15+
* On the wire, the button state bits are sent in the following order:
16+
* A, B, Z, Start, Up, Down, Left, Right, Reset, 0, L, R, C-Up, C-Down, C-Left, C-Right
17+
*/
18+
struct si_device_n64_input_state {
19+
union {
20+
struct {
21+
// Byte 0
22+
uint8_t right : 1; // Bit 0
23+
uint8_t left : 1; // Bit 1
24+
uint8_t down : 1; // Bit 2
25+
uint8_t up : 1; // Bit 3
26+
uint8_t start : 1; // Bit 4
27+
uint8_t z : 1; // Bit 5
28+
uint8_t b : 1; // Bit 6
29+
uint8_t a : 1; // Bit 7
30+
31+
// Byte 1
32+
uint8_t c_right : 1; // Bit 0
33+
uint8_t c_left : 1; // Bit 1
34+
uint8_t c_down : 1; // Bit 2
35+
uint8_t c_up : 1; // Bit 3
36+
uint8_t r : 1; // Bit 4
37+
uint8_t l : 1; // Bit 5
38+
uint8_t : 1; // Bit 6
39+
uint8_t rst : 1; // Bit 7
40+
41+
} __attribute__((packed));
42+
uint8_t bytes[2];
43+
} buttons;
44+
45+
uint8_t stick_x;
46+
uint8_t stick_y;
47+
} __attribute__((packed));
48+
49+
/**
50+
* N64 controller device state.
51+
*/
1252
struct si_device_n64_controller {
53+
uint8_t info[3];
54+
struct si_device_n64_input_state input;
1355
};
1456

1557
/**

firmware/libsi/src/device/n64_controller.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <string.h>
2+
13
#include "si/si.h"
24

35
#include "si/commands.h"
@@ -13,7 +15,9 @@ static int handle_info(const uint8_t *command, si_callback_fn callback, void *co
1315
{
1416
struct si_device_n64_controller *device = (struct si_device_n64_controller *)context;
1517

16-
// TODO
18+
// Respond with the device info
19+
si_write_bytes(device->info, SI_CMD_INFO_RESP, callback);
20+
1721
return SI_CMD_INFO_RESP;
1822
}
1923

@@ -27,7 +31,9 @@ static int handle_reset(const uint8_t *command, si_callback_fn callback, void *c
2731
{
2832
struct si_device_n64_controller *device = (struct si_device_n64_controller *)context;
2933

30-
// TODO
34+
// Respond with the device info
35+
si_write_bytes(device->info, SI_CMD_INFO_RESP, callback);
36+
3137
return SI_CMD_RESET_RESP;
3238
}
3339

@@ -41,11 +47,21 @@ static int handle_poll(const uint8_t *command, si_callback_fn callback, void *co
4147
{
4248
struct si_device_n64_controller *device = (struct si_device_n64_controller *)context;
4349

50+
si_write_bytes((uint8_t *)&device->input, SI_CMD_N64_POLL_RESP, callback);
51+
4452
return SI_CMD_N64_POLL_RESP;
4553
}
4654

4755
void si_device_n64_init(struct si_device_n64_controller *device)
4856
{
57+
// Present as a wired N64 controller, with no accessory
58+
device->info[0] = 0x05;
59+
device->info[1] = 0x00;
60+
device->info[2] = 0x02;
61+
62+
// Resting state on the N64 controller is all zeros
63+
memset(&device->input, 0, sizeof(device->input));
64+
4965
si_command_register(SI_CMD_INFO, SI_CMD_INFO_LEN, handle_info, device);
5066
si_command_register(SI_CMD_RESET, SI_CMD_RESET_LEN, handle_reset, device);
5167
si_command_register(SI_CMD_N64_POLL, SI_CMD_N64_POLL_LEN, handle_poll, device);

firmware/receiver/src/main.c

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "si/commands.h"
99
#include "si/device/gc_controller.h"
10+
#include "si/device/n64_controller.h"
1011
#include "wavebird/message.h"
1112
#include "wavebird/packet.h"
1213
#include "wavebird/radio.h"
@@ -32,6 +33,9 @@ typedef enum {
3233

3334
// Present as a wired GameCube controller without rumble
3435
WP_CONT_TYPE_GC_WIRED_NOMOTOR,
36+
37+
// Present as an OEM N64 controller
38+
WP_CONT_TYPE_N64,
3539
} wp_controller_type_t;
3640

3741
// Settings structure
@@ -77,7 +81,14 @@ struct {
7781
} packet_stats = {0};
7882

7983
// SI state
80-
static struct si_device_gc_controller si_device;
84+
static union {
85+
struct si_device_gc_controller gc;
86+
struct si_device_n64_controller n64;
87+
} si_device;
88+
89+
static uint8_t n64_stick_x_origin = 0x80;
90+
static uint8_t n64_stick_y_origin = 0x80;
91+
8192
static bool enable_si_command_handling = true;
8293

8394
// Buttons, switches, and LEDs
@@ -106,14 +117,18 @@ static void initialize_controller(uint8_t controller_type)
106117
{
107118
if (controller_type == WP_CONT_TYPE_GC_WAVEBIRD) {
108119
// Present as an OEM WaveBird receiver
109-
si_device_gc_init(&si_device, SI_TYPE_GC | SI_GC_WIRELESS | SI_GC_NOMOTOR);
120+
si_device_gc_init(&si_device.gc, SI_TYPE_GC | SI_GC_WIRELESS | SI_GC_NOMOTOR);
110121
enable_si_command_handling = true;
111122
} else if (controller_type == WP_CONT_TYPE_GC_WIRED_NOMOTOR) {
112123
// Present as a wired GameCube controller without rumble
113-
si_device_gc_init(&si_device, SI_TYPE_GC | SI_GC_STANDARD | SI_GC_NOMOTOR);
124+
si_device_gc_init(&si_device.gc, SI_TYPE_GC | SI_GC_STANDARD | SI_GC_NOMOTOR);
114125
} else if (controller_type == WP_CONT_TYPE_GC_WIRED) {
115126
// Present as an OEM wired GameCube controller
116-
si_device_gc_init(&si_device, SI_TYPE_GC | SI_GC_STANDARD);
127+
si_device_gc_init(&si_device.gc, SI_TYPE_GC | SI_GC_STANDARD);
128+
} else if (controller_type == WP_CONT_TYPE_N64) {
129+
// Present as an OEM N64 controller
130+
si_device_n64_init(&si_device.n64);
131+
enable_si_command_handling = true;
117132
}
118133
}
119134

@@ -143,25 +158,25 @@ static void handle_channel_wheel_change(struct channel_wheel *channel_wheel, uin
143158
#endif
144159

145160
// Update the input state of a GC controller from a WaveBird packet
146-
static void update_gc_input_state(struct si_device_gc_controller *si_device, const uint8_t *message)
161+
static void update_gc_input_state(struct si_device_gc_controller *device, const uint8_t *message)
147162
{
148163
// Clear the buttons in the SI input state
149-
si_device->input.buttons.bytes[0] &= ~0x1F;
150-
si_device->input.buttons.bytes[1] &= ~0x7F;
164+
device->input.buttons.bytes[0] &= ~0x1F;
165+
device->input.buttons.bytes[1] &= ~0x7F;
151166

152167
// Copy the buttons from the WaveBird message
153-
si_device->input.buttons.bytes[0] |= (message[3] & 0x80) >> 7 | (message[2] & 0x0F) << 1;
154-
si_device->input.buttons.bytes[1] |= (message[3] & 0x7F);
168+
device->input.buttons.bytes[0] |= (message[3] & 0x80) >> 7 | (message[2] & 0x0F) << 1;
169+
device->input.buttons.bytes[1] |= (message[3] & 0x7F);
155170

156171
// Copy the stick, substick, and trigger values
157-
memcpy(&si_device->input.stick_x, &message[4], 6);
172+
memcpy(&device->input.stick_x, &message[4], 6);
158173

159174
// Set the input state as valid
160-
si_device_gc_set_input_valid(si_device, true);
175+
si_device_gc_set_input_valid(device, true);
161176
}
162177

163178
// Update the origin state of a GC controller from a WaveBird packet
164-
static void update_gc_origin_state(struct si_device_gc_controller *si_device, const uint8_t *message)
179+
static void update_gc_origin_state(struct si_device_gc_controller *device, const uint8_t *message)
165180
{
166181
// Copy the origin values from the packet
167182
uint8_t new_origin[] = {
@@ -171,15 +186,49 @@ static void update_gc_origin_state(struct si_device_gc_controller *si_device, co
171186
};
172187

173188
// Check if the origin packet is different from the last known origin
174-
if (memcmp(&si_device->origin.stick_x, new_origin, 6) != 0) {
189+
if (memcmp(&device->origin.stick_x, new_origin, 6) != 0) {
175190
// Update the origin state
176-
memcpy(&si_device->origin.stick_x, new_origin, 6);
191+
memcpy(&device->origin.stick_x, new_origin, 6);
177192

178193
// Set the "need origin" flag to true so the host knows to fetch the new origin
179-
si_device->input.buttons.need_origin = true;
194+
device->input.buttons.need_origin = true;
180195
}
181196
}
182197

198+
// Update the input state of an N64 controller from a WaveBird packet
199+
static void update_n64_input_state(struct si_device_n64_controller *device, const uint8_t *message)
200+
{
201+
// Map the buttons
202+
uint16_t buttons = wavebird_input_state_get_buttons(message);
203+
device->input.buttons.a = (buttons & WB_BUTTONS_A) ? 1 : 0;
204+
device->input.buttons.b = (buttons & WB_BUTTONS_B) ? 1 : 0;
205+
device->input.buttons.z = (buttons & WB_BUTTONS_Z) ? 1 : 0;
206+
device->input.buttons.start = (buttons & WB_BUTTONS_START) ? 1 : 0;
207+
device->input.buttons.up = (buttons & WB_BUTTONS_UP) ? 1 : 0;
208+
device->input.buttons.down = (buttons & WB_BUTTONS_DOWN) ? 1 : 0;
209+
device->input.buttons.left = (buttons & WB_BUTTONS_LEFT) ? 1 : 0;
210+
device->input.buttons.right = (buttons & WB_BUTTONS_RIGHT) ? 1 : 0;
211+
device->input.buttons.l = (buttons & WB_BUTTONS_L) ? 1 : 0;
212+
device->input.buttons.r = (buttons & WB_BUTTONS_R) ? 1 : 0;
213+
214+
// Map the substick to the C buttons
215+
device->input.buttons.c_left = wavebird_input_state_get_substick_x(message) < 64 ? 1 : 0;
216+
device->input.buttons.c_right = wavebird_input_state_get_substick_x(message) > 192 ? 1 : 0;
217+
device->input.buttons.c_up = wavebird_input_state_get_substick_y(message) > 192 ? 1 : 0;
218+
device->input.buttons.c_down = wavebird_input_state_get_substick_y(message) < 64 ? 1 : 0;
219+
220+
// Copy the main stick values
221+
device->input.stick_x = (uint8_t)(int8_t)((wavebird_input_state_get_stick_x(message) - n64_stick_x_origin) * 0.8);
222+
device->input.stick_y = (uint8_t)(int8_t)((wavebird_input_state_get_stick_y(message) - n64_stick_y_origin) * 0.8);
223+
}
224+
225+
// Update the origin state of an N64 controller from a WaveBird packet
226+
static void update_n64_origin_state(struct si_device_n64_controller *device, const uint8_t *message)
227+
{
228+
n64_stick_x_origin = wavebird_origin_get_stick_x(message);
229+
n64_stick_y_origin = wavebird_origin_get_stick_y(message);
230+
}
231+
183232
// Handle packets from the WaveBird radio
184233
static void handle_wavebird_packet(const uint8_t *packet)
185234
{
@@ -203,13 +252,13 @@ static void handle_wavebird_packet(const uint8_t *packet)
203252
// Check the controller id is as expected
204253
if (settings.cont_type == WP_CONT_TYPE_GC_WAVEBIRD) {
205254
// Implement wireless ID pinning exactly as OEM WaveBird receivers do
206-
if (si_device_gc_wireless_id_fixed(&si_device)) {
255+
if (si_device_gc_wireless_id_fixed(&si_device.gc)) {
207256
// Drop packets from other controllers if the ID has been fixed
208-
if (si_device_gc_get_wireless_id(&si_device) != wireless_id)
257+
if (si_device_gc_get_wireless_id(&si_device.gc) != wireless_id)
209258
return;
210259
} else {
211260
// Set the controller ID if it is not fixed
212-
si_device_gc_set_wireless_id(&si_device, wireless_id);
261+
si_device_gc_set_wireless_id(&si_device.gc, wireless_id);
213262
}
214263
} else {
215264
// Emulate wireless ID pinning for wired controllers
@@ -231,7 +280,11 @@ static void handle_wavebird_packet(const uint8_t *packet)
231280
// Handle the packet
232281
if (wavebird_message_get_type(message) == WB_MESSAGE_TYPE_INPUT_STATE) {
233282
// Update the SI input state
234-
update_gc_input_state(&si_device, message);
283+
if (settings.cont_type == WP_CONT_TYPE_N64) {
284+
update_n64_input_state(&si_device.n64, message);
285+
} else {
286+
update_gc_input_state(&si_device.gc, message);
287+
}
235288

236289
// We have a good input state, enable SI command handling if it was disabled
237290
enable_si_command_handling = true;
@@ -240,7 +293,11 @@ static void handle_wavebird_packet(const uint8_t *packet)
240293
stale_input_timeout = millis + INPUT_VALID_MS;
241294
} else {
242295
// Update the SI origin state
243-
update_gc_origin_state(&si_device, message);
296+
if (settings.cont_type == WP_CONT_TYPE_N64) {
297+
update_n64_origin_state(&si_device.n64, message);
298+
} else {
299+
update_gc_origin_state(&si_device.gc, message);
300+
}
244301
}
245302
}
246303

@@ -401,6 +458,7 @@ int main(void)
401458

402459
// Initialize persistent settings
403460
settings_init(&settings, sizeof(wp_settings_t), SETTINGS_SIGNATURE, &DEFAULT_SETTINGS);
461+
settings.cont_type = WP_CONT_TYPE_N64; // TODO: Remove
404462

405463
// Initialize and configure the WaveBird radio
406464
wavebird_radio_configure_qualification(qualify_packet, 5);
@@ -427,9 +485,11 @@ int main(void)
427485
DEBUG_PRINT("WavePhoenix receiver ready!\n");
428486
DEBUG_PRINT("- Firmware version: %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
429487
DEBUG_PRINT("- Radio channel: %u\n", settings.chan + 1);
430-
DEBUG_PRINT("- Controller type: %s\n", (settings.cont_type == WP_CONT_TYPE_GC_WAVEBIRD) ? "WaveBird"
431-
: (settings.cont_type == WP_CONT_TYPE_GC_WIRED) ? "Wired"
432-
: "Wired (no motor)");
488+
DEBUG_PRINT("- Controller type: %s\n", (settings.cont_type == WP_CONT_TYPE_GC_WAVEBIRD) ? "WaveBird"
489+
: (settings.cont_type == WP_CONT_TYPE_GC_WIRED) ? "Wired"
490+
: (settings.cont_type == WP_CONT_TYPE_GC_WIRED_NOMOTOR) ? "Wired (no motor)"
491+
: (settings.cont_type == WP_CONT_TYPE_N64) ? "N64"
492+
: "Unknown");
433493
DEBUG_PRINT("\n");
434494

435495
// Wait for the SI bus to be idle before starting the main loop
@@ -449,7 +509,12 @@ int main(void)
449509
led_effect_update(status_led, millis);
450510

451511
// Invalidate stale inputs
452-
if (si_device.input_valid && (int32_t)(millis - stale_input_timeout) >= 0)
453-
si_device_gc_set_input_valid(&si_device, false);
512+
if (settings.cont_type == WP_CONT_TYPE_N64) {
513+
// TODO
514+
} else {
515+
if (si_device.gc.input_valid && (int32_t)(millis - stale_input_timeout) >= 0) {
516+
si_device_gc_set_input_valid(&si_device.gc, false);
517+
}
518+
}
454519
}
455520
}

0 commit comments

Comments
 (0)