New only partially working (and only on esp32) N64 input impl

This commit is contained in:
Travis Burtrum 2020-12-06 22:37:56 -05:00
parent 1e78ac7fdc
commit e48656ec91
7 changed files with 450 additions and 8 deletions

View File

@ -128,11 +128,48 @@ extends = micro, in-snes, out-usbradio
src_filter = ${in-snes.src_filter} ${out-usbradio.src_filter}
build_flags = ${in-snes.build_flags} ${out-usbradio.build_flags}
# n64 input
[in-n64]
src_filter = -<*> +<N64.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=3 -O0
#lib_deps = pothos/N64Controller@^0.1.1
[env:esp32-n64-bt]
extends = esp32, in-n64, out-bt
src_filter = ${in-n64.src_filter} ${out-bt.src_filter}
build_flags = ${in-n64.build_flags} ${out-bt.build_flags}
[env:esp32-n64-debug]
extends = esp32, in-n64, out-debug
src_filter = ${in-n64.src_filter} ${out-debug.src_filter}
build_flags = ${in-n64.build_flags} ${out-debug.build_flags}
[env:micro-n64-usb]
extends = micro, in-n64, out-usb
src_filter = ${in-n64.src_filter} ${out-usb.src_filter}
build_flags = ${in-n64.build_flags} ${out-usb.build_flags}
[env:micro-n64-debug]
extends = micro, in-n64, out-debug
src_filter = ${in-n64.src_filter} ${out-debug.src_filter}
build_flags = ${in-n64.build_flags} ${out-debug.build_flags}
[env:micro-n64-radio]
extends = micro, in-n64, out-radio
src_filter = ${in-n64.src_filter} ${out-radio.src_filter}
build_flags = ${in-n64.build_flags} ${out-radio.build_flags}
[env:micro-n64-usbradio]
extends = micro, in-n64, out-usbradio
src_filter = ${in-n64.src_filter} ${out-usbradio.src_filter}
build_flags = ${in-n64.build_flags} ${out-usbradio.build_flags}
# debug input
[in-debug]
src_filter = -<*> +<Debug.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=2
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=0
[env:esp32-debug-bt]
extends = esp32, in-debug, out-bt

View File

@ -19,11 +19,11 @@ void loop() {
Serial.println("Press buttons 1 and 32. Move all axes to center. Set DPAD to down right.");
gamepad.press(c, BUTTON_1);
gamepad.press(c, BUTTON_32);
gamepad.setAxes(c, 0, 0, 0, 0, 0, 0, DPAD_DOWN_RIGHT);
gamepad.setAxis(c, 0, 0, 0, 0, 0, 0, DPAD_DOWN_RIGHT);
delay(500);
Serial.println("Release all buttons. Move all axes to center. Set DPAD to center.");
gamepad.buttons(c, 0);
gamepad.setAxes(c, 0, 0, 0, 0, 0, 0, DPAD_CENTER);
gamepad.setAxis(c, 0, 0, 0, 0, 0, 0, DPAD_CENTER);
delay(500);
}
}

336
src/N64.cpp Normal file
View File

@ -0,0 +1,336 @@
#include "Arduino.h"
//#define PRINT_Y_AXIS_VALUES 1
//#define PRINT_X_AXIS_VALUES 1
//#define PLOT_CONSOLE_POLLING 1
#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 1
#endif
#include "gamepad/Gamepad.h"
#include "util.cpp"
#define DATA_PIN 13
#define LINE_WRITE_HIGH pinMode(DATA_PIN, INPUT_PULLUP)
#define LINE_WRITE_LOW pinMode(DATA_PIN, OUTPUT)
#define DATA_SIZE 450 // number of sample points to poll
#define DATA_OFFSET 0 // number of samples to ignore after staring to poll
#define MAX_INCLINE_AXIS_X 60
#define MAX_INCLINE_AXIS_Y 60
#define BIT_THRESHOLD 6
GAMEPAD_CLASS gamepad;
struct ControllerData {
bool buttonA;
bool buttonB;
bool buttonZ;
bool buttonL;
bool buttonR;
bool buttonStart;
bool DPadUp;
bool DPadDown;
bool DPadLeft;
bool DPadRight;
bool CUp;
bool CDown;
bool CLeft;
bool CRight;
short xAxis;
short yAxis;
bool xAxisRaw[8];
bool yAxisRaw[8];
};
// buffer to hold data being read from controller
bool buffer[DATA_SIZE + DATA_OFFSET];
// bit resolution and offsets
int bitOffsets[32];
int bitResolution;
/** Function to send a Command to the attached N64-Controller.
* Must be run from RAM to defy timing differences introduced from
* reading Code from ESP32's SPI Flash Chip.
*/
//#ifdef ARDUINO_ARCH_ESP32
#if defined(CONFIG_BT_ENABLED)
void IRAM_ATTR sendCommand(byte command)
#else
// todo: what should be done here??
void sendCommand(byte command)
#endif
{
// the current bit to write
bool bit;
// clear output buffer
memset(buffer, 0, DATA_SIZE + DATA_OFFSET);
noInterrupts();
// for each bit
for (int i = 0; i < 8; i++) {
// get value
bit = (1 << (7 - i)) & command;
// write data
LINE_WRITE_LOW;
delayMicroseconds((3 - 2 * bit));
LINE_WRITE_HIGH;
delayMicroseconds((1 + 2 * bit));
}
// console stop bit
LINE_WRITE_LOW;
delayMicroseconds(1);
LINE_WRITE_HIGH;
delayMicroseconds(2);
// read returned data as fast as possible
for (int i = 0; i < DATA_SIZE + DATA_OFFSET; i++) {
buffer[i] = digitalRead(DATA_PIN);
}
interrupts();
// plot polling process from controller if unstructed to
#ifdef PLOT_CONSOLE_POLLING
for (int i = 0; i < DATA_SIZE + DATA_OFFSET; i++) {
Serial.println(buffer[i] * 2500);
}
#endif
}
/* Function to extract a controller bit from the buffer of returned data */
void getBit(bool *bit, int offset, bool *data) {
// sanity check offset
if (offset < 0)
offset = 0;
// count
short count = 0;
// get count from offset to offset + length
for (int i = offset + DATA_OFFSET; i < offset + bitResolution; i++) {
count += *(data + i);
}
// if offset surpasses threshold set bit
*bit = false;
if (count > BIT_THRESHOLD)
*bit = true;
}
/** Function to populate the controller struct if command 0x01 was sent.
* Buttons are set according to data in buffer, raw axis data is written,
* Axis Data is correctly decoded from raw axis data by taking two's complement
* and checking if value if below 'MAX_INCLINE_AXIS_X' or 'MAX_INCLINE_AXIS_Y'.
* If values surpass the maximum incline they are set to match those values.
*/
void populateControllerStruct(ControllerData *data) {
// first byte
getBit(&(data->buttonA), bitOffsets[0], &buffer[0]);
getBit(&(data->buttonB), bitOffsets[1], &buffer[0]);
getBit(&(data->buttonZ), bitOffsets[2], &buffer[0]);
getBit(&(data->buttonStart), bitOffsets[3], &buffer[0]);
getBit(&(data->DPadUp), bitOffsets[4], &buffer[0]);
getBit(&(data->DPadDown), bitOffsets[5], &buffer[0]);
getBit(&(data->DPadLeft), bitOffsets[6], &buffer[0]);
getBit(&(data->DPadRight), bitOffsets[7], &buffer[0]);
// second byte, first two bits are unused
getBit(&(data->buttonL), bitOffsets[10], &buffer[0]);
getBit(&(data->buttonR), bitOffsets[11], &buffer[0]);
getBit(&(data->CUp), bitOffsets[12], &buffer[0]);
getBit(&(data->CDown), bitOffsets[13], &buffer[0]);
getBit(&(data->CLeft), bitOffsets[14], &buffer[0]);
getBit(&(data->CRight), bitOffsets[15], &buffer[0]);
// third byte
getBit(&(data->xAxisRaw[0]), bitOffsets[16], &buffer[0]);
getBit(&(data->xAxisRaw[1]), bitOffsets[17], &buffer[0]);
getBit(&(data->xAxisRaw[2]), bitOffsets[18], &buffer[0]);
getBit(&(data->xAxisRaw[3]), bitOffsets[19], &buffer[0]);
getBit(&(data->xAxisRaw[4]), bitOffsets[20], &buffer[0]);
getBit(&(data->xAxisRaw[5]), bitOffsets[21], &buffer[0]);
getBit(&(data->xAxisRaw[6]), bitOffsets[22], &buffer[0]);
getBit(&(data->xAxisRaw[7]), bitOffsets[23], &buffer[0]);
// fourth byte
getBit(&(data->yAxisRaw[0]), bitOffsets[24], &buffer[0]);
getBit(&(data->yAxisRaw[1]), bitOffsets[25], &buffer[0]);
getBit(&(data->yAxisRaw[2]), bitOffsets[26], &buffer[0]);
getBit(&(data->yAxisRaw[3]), bitOffsets[27], &buffer[0]);
getBit(&(data->yAxisRaw[4]), bitOffsets[28], &buffer[0]);
getBit(&(data->yAxisRaw[5]), bitOffsets[29], &buffer[0]);
getBit(&(data->yAxisRaw[6]), bitOffsets[30], &buffer[0]);
getBit(&(data->yAxisRaw[7]), bitOffsets[31], &buffer[0]);
// sum up bits to get axis bytes
data->xAxis = 0;
data->yAxis = 0;
for (int i = 0; i < 8; i++) {
data->xAxis += (data->xAxisRaw[i] * (0x80 >> (i)));
data->yAxis += (data->yAxisRaw[i] * (0x80 >> (i)));
// print y axis values
#ifdef PRINT_Y_AXIS_VALUES
Serial.printf(
"yRaw: %i %i %i %i %i %i %i %i\n", data->yAxisRaw[0], data->yAxisRaw[1], data->yAxisRaw[2], data->yAxisRaw[3], data->yAxisRaw[4], data->yAxisRaw[5], data->yAxisRaw[6], data->yAxisRaw[7]);
Serial.printf("yAxis: %i \n", data->yAxis);
#endif
// print x axis values
#ifdef PRINT_X_AXIS_VALUES
Serial.printf(
"xRaw: %i %i %i %i %i %i %i %i\n", data->xAxisRaw[0], data->xAxisRaw[1], data->xAxisRaw[2], data->xAxisRaw[3], data->xAxisRaw[4], data->xAxisRaw[5], data->xAxisRaw[6], data->xAxisRaw[7]);
Serial.printf("xAxis: %i \n", data->xAxis);
#endif
}
// decode xAxis two's complement
if (data->xAxis & 0x80) {
data->xAxis = -1 * (0xff - data->xAxis);
}
// decode yAxis two's complement
if (data->yAxis & 0x80) {
data->yAxis = -1 * (0xff - data->yAxis);
}
// keep x axis below maxIncline
if (data->xAxis > MAX_INCLINE_AXIS_X)
data->xAxis = MAX_INCLINE_AXIS_X;
if (data->xAxis < -MAX_INCLINE_AXIS_X)
data->xAxis = -MAX_INCLINE_AXIS_X;
// keep y axis below maxIncline
if (data->yAxis > MAX_INCLINE_AXIS_Y)
data->yAxis = MAX_INCLINE_AXIS_Y;
if (data->yAxis < -MAX_INCLINE_AXIS_Y)
data->yAxis = -MAX_INCLINE_AXIS_Y;
//Serial.printf("xaxis: %-3i yaxis: %-3i \n",data->xAxis,data->yAxis);
}
void updateOffsetsAndResolution() {
// the current bit counter
int bitCounter = 0;
// to hold the offset of A Button's falling edge
int bitAfallingOffset = 0;
// iterate over buffer
for (int i = 0; i < DATA_SIZE + DATA_OFFSET - 1; i++) {
// if a falling edge is detected
if (buffer[i] == true && buffer[1 + i] == false) {
// store bit's end offset
bitOffsets[bitCounter] = i + 1;
// if it's the A button store offset of the falling edge
if (bitCounter == 0)
bitAfallingOffset = i + 1;
// if it's the B button calculate the bit Resolution
if (bitCounter == 1)
bitResolution = (i + 1) - bitAfallingOffset;
// increment bit counter
bitCounter++;
}
}
//Serial.printf("Bit resolution is %i \n", bitResolution);
// calculate bit's beginning offsets by subtracting resolution
for (int i = 0; i < 32; i++) {
bitOffsets[i] -= bitResolution;
//Serial.printf("beginning of bit %i detected @ begin+%i \n", i + 1, bitOffsets[i]);
}
}
ControllerData controller;
void setup() {
Serial.begin(115200);
gamepad.begin();
// setup io pins
//setupIO();
// the controller data line
LINE_WRITE_HIGH;
#ifdef PLOT_CONSOLE_POLLING
delay(5000);
sendCommand(0x01);
while (true)
;
#endif
delay(5000);
sendCommand(0x01);
updateOffsetsAndResolution();
}
void loop() {
Serial.println("sending command to n64");
// send command 0x01 to n64 controller
sendCommand(0x01);
// store received data in controller struct
populateControllerStruct(&controller);
// output received data to ique
//outputToiQue(&controller);
uint8_t c = 0; // for now just do 1 pad
gamepad.buttons(c, 0);
if (controller.buttonA) {
gamepad.press(c, BUTTON_A);
}
if (controller.buttonB) {
gamepad.press(c, BUTTON_B);
}
if (controller.buttonZ) {
gamepad.press(c, BUTTON_TR);
}
if (controller.buttonL) {
gamepad.press(c, BUTTON_L);
}
if (controller.buttonR) {
gamepad.press(c, BUTTON_R);
}
if (controller.buttonStart) {
gamepad.press(c, BUTTON_START);
}
auto hat = calculateDpadDirection(controller.DPadUp, controller.DPadDown, controller.DPadLeft, controller.DPadRight);
auto cHat = dpadToAxis(calculateDpadDirection(controller.CUp, controller.CDown, controller.CLeft, controller.CRight));
// todo: need to scale max/min to our max/min
gamepad.setAxis(c, controller.xAxis, controller.yAxis, cHat.x, cHat.y, 0, 0, hat);
// polling must not occur faster than every 20 ms
delay(14);
//checkUpdateCombo(&controller);
//Serial.printf("DPAD: %i %i %i %i \n", controller.DPadUp, controller.DPadDown, controller.DPadLeft, controller.DPadRight);
//Serial.printf("C: %i %i %i %i \n", controller.CUp, controller.CDown, controller.CLeft, controller.CRight);
//Serial.printf("Y: %i X: %i\n", controller.yAxis, controller.xAxis);
//Serial.print("C: ");
//Serial.println(controller.CUp);
//delay(500);
}

View File

@ -1,7 +1,9 @@
#include "Arduino.h"
#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif
#define BUTTON_COUNT 12 // SNES has 12, NES only has 8
//individual data pin for each controller

View File

@ -19,9 +19,9 @@ class DebugGamepad : public AbstractGamepad {
Serial.println("DebugGamepad.begin");
}
virtual void setAxes(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
Serial.println("DebugGamepad.setAxes");
AbstractGamepad::setAxes(cIdx, x, y, z, rZ, rX, rY, hat);
virtual void setAxis(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
Serial.println("DebugGamepad.setAxis");
AbstractGamepad::setAxis(cIdx, x, y, z, rZ, rX, rY, hat);
}
virtual void sendHidReport(const uint8_t cIdx, const void* d, int len) {

View File

@ -67,6 +67,10 @@
#define DPAD_LEFT 7
#define DPAD_UP_LEFT 8
#define AXIS_CENTER 0
#define AXIS_MAX 32768
#define AXIS_MIN -32767
#ifndef GAMEPAD_REPORT_ARRAY_ADD
// this is used by radio gamepad to send additional info
#define GAMEPAD_REPORT_ARRAY_ADD 0
@ -185,7 +189,7 @@ class AbstractGamepad {
return true;
}
virtual void setAxes(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
virtual void setAxis(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
if (x == -32768) {
x = -32767;
}
@ -225,7 +229,7 @@ class AbstractGamepad {
}
virtual void setHatSync(const uint8_t cIdx, signed char hat) {
setAxes(cIdx, 0, 0, 0, 0, 0, 0, hat);
setAxis(cIdx, 0, 0, 0, 0, 0, 0, hat);
}
virtual void buttons(const uint8_t cIdx, uint32_t b) {

63
src/util.cpp Normal file
View File

@ -0,0 +1,63 @@
uint8_t calculateDpadDirection(const bool up, const bool down, const bool left, const bool right) {
if (down) {
if (right) {
return DPAD_DOWN_RIGHT;
} else if (left) {
return DPAD_DOWN_LEFT;
} else {
return DPAD_DOWN;
}
} else if (up) {
if (right) {
return DPAD_UP_RIGHT;
} else if (left) {
return DPAD_UP_LEFT;
} else {
return DPAD_UP;
}
} else if (right) {
return DPAD_RIGHT;
} else if (left) {
return DPAD_LEFT;
} else {
return DPAD_CENTERED;
}
}
struct Axis {
int16_t x;
int16_t y;
};
struct Axis axis(int16_t x, int16_t y) {
Axis axis;
axis.x = x;
axis.y = y;
return axis;
}
struct Axis dpadToAxis(uint8_t dpad) {
switch(dpad) {
case DPAD_CENTER:
return axis(AXIS_CENTER, AXIS_CENTER);
case DPAD_UP:
return axis(AXIS_CENTER, AXIS_MIN);
case DPAD_UP_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_RIGHT:
return axis(AXIS_MAX, AXIS_CENTER);
case DPAD_DOWN_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_DOWN:
return axis(AXIS_CENTER, AXIS_MAX);
case DPAD_DOWN_LEFT:
return axis(AXIS_MIN, AXIS_MAX);
case DPAD_LEFT:
return axis(AXIS_MIN, AXIS_CENTER);
case DPAD_UP_LEFT:
return axis(AXIS_MIN, AXIS_MIN);
}
// todo: panic here?
return axis(AXIS_CENTER, AXIS_CENTER);
}