Browse Source

created src folder, included precompiled binaries

master
m.menzel 2 years ago
parent
commit
75729b083b
10 changed files with 503 additions and 0 deletions
  1. +6
    -0
      README.md
  2. BIN
      bin/iQue.ino.heltec_wifi_lora_32_V2.bin
  3. +206
    -0
      src/ControllerBuffer.cpp
  4. +26
    -0
      src/ControllerBuffer.h
  5. +31
    -0
      src/ControllerData.h
  6. +8
    -0
      src/Debug.h
  7. +135
    -0
      src/Output.cpp
  8. +19
    -0
      src/Output.h
  9. +33
    -0
      src/PinMappings.h
  10. +39
    -0
      src/iQue.ino

+ 6
- 0
README.md View File

@ -20,3 +20,9 @@ As Timing is very critical (a bit read from the controller takes only 4uS !) I d
Yes you will have to edit the Pins used in the file `PinMappings.h` to match the Pins you wired everything up with. The Axis pins have to be connected to the correct Wires coming from the Joystick. For that is adviced to cut the Joystick cable in half and connect the X-Axis pins to the pins labeled 1 & 4 of the Joystick PCB and the Y-Axis pins to the pins labeled 5 & 6.
### Changelog:
13.04.2020:
- moved source code to `src` folder
- added precompiled binaries for Heltec WiFi LoRa 32 V2 to folder `bin`

BIN
bin/iQue.ino.heltec_wifi_lora_32_V2.bin View File


+ 206
- 0
src/ControllerBuffer.cpp View File

@ -0,0 +1,206 @@
#include "ControllerBuffer.h"
#include "PinMappings.h"
#include "Debug.h"
// 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.
*/
void IRAM_ATTR sendCommand(byte command)
{
// the current bit to write
bool bit;
// clear output buffer
memset(buffer,0,DATA_SIZE + DATA_OFFSET);
// 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);
}
// 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("%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("%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]);
}
}

+ 26
- 0
src/ControllerBuffer.h View File

@ -0,0 +1,26 @@
#ifndef CONTROLLER_BUFFER_H
#define CONTROLLER_BUFFER_H
#include "ControllerData.h"
#include <Arduino.h>
#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
extern void IRAM_ATTR sendCommand(byte command);
extern void populateControllerStruct(ControllerData *data);
/** Function to read the offsets of the individual bits so that they can be updated for reading.
* Resolution is detected by taking the distance of two falling edges of Button A and B.
* Buttons must not be pressed by the time this command is invoked !
* sendCommand(0x01) must be invoked before !
*/
extern void updateOffsetsAndResolution();
#endif

+ 31
- 0
src/ControllerData.h View File

@ -0,0 +1,31 @@
#ifndef CONTROLLER_DATA_H
#define CONTROLLER_DATA_H
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];
};
#endif

+ 8
- 0
src/Debug.h View File

@ -0,0 +1,8 @@
#ifndef DEBUG_H
#define DEBUG_H
//#define PLOT_CONSOLE_POLLING
//#define PRINT_X_AXIS_VALUES
//#define PRINT_Y_AXIS_VALUES
#endif

+ 135
- 0
src/Output.cpp View File

@ -0,0 +1,135 @@
#include "Output.h"
#include "PinMappings.h"
#include <Arduino.h>
int encoderXpos = 0;
int encoderYpos = 0;
void setupIO()
{
// the controller data line
LINE_WRITE_HIGH;
pinMode(PIN_BUTTON_A,OUTPUT);
pinMode(PIN_BUTTON_B,OUTPUT);
pinMode(PIN_BUTTON_Z,OUTPUT);
pinMode(PIN_BUTTON_S,OUTPUT);
pinMode(PIN_BUTTON_L,OUTPUT);
pinMode(PIN_BUTTON_R,OUTPUT);
pinMode(PIN_DPAD_UP, OUTPUT);
pinMode(PIN_DPAD_DOWN, OUTPUT);
pinMode(PIN_DPAD_LEFT, OUTPUT);
pinMode(PIN_DPAD_RIGHT,OUTPUT);
pinMode(PIN_C_UP, OUTPUT);
pinMode(PIN_C_DOWN, OUTPUT);
pinMode(PIN_C_LEFT, OUTPUT);
pinMode(PIN_C_RIGHT,OUTPUT);
pinMode(PIN_A_AXIS_X, OUTPUT);
pinMode(PIN_B_AXIS_X, OUTPUT);
pinMode(PIN_A_AXIS_Y, OUTPUT);
pinMode(PIN_B_AXIS_Y, OUTPUT);
}
/** Function to simulate the 90° phaseshifted quadrature encoding used by
* the N64-Controller's joystick's linear encoders. As only change in position
* (not absolute position) is reported the change in the value between writes
* is needed for calculation. Must be run from RAM to mitigate timing differences in
* ESP32's SPI-Flash Chip Access Times.
* @param relativeMovement should be processed by calling 'mapJoystickToEncoderPos'
* with the read joystick position.
*/
void IRAM_ATTR moveAxis(int axisPinA,int axisPinB,int requestedEncoderPos,int *realEncoderPos)
{
// get difference from requested ender position to real encoder position
int difference = requestedEncoderPos - *realEncoderPos;
// store new real encoder position
*realEncoderPos = requestedEncoderPos;
// set pins used for outputting
int pinA = axisPinA;
int pinB = axisPinB;
// invert pins if moving in the other direction
if(difference > 0)
{
pinA = axisPinB;
pinB = axisPinA;
}
// get absolute movement value
difference = abs(difference);
// for each change in movement
for(int i = 0;i < difference;i++)
{
// replicate 90° phaseshifted quadrate output
digitalWrite(pinA,LOW);
delayMicroseconds(2);
digitalWrite(pinB,LOW);
delayMicroseconds(6);
digitalWrite(pinA,HIGH);
delayMicroseconds(2);
digitalWrite(pinB,HIGH);
// debounce as N64 will not detect change correctly otherwise
delayMicroseconds(40);
}
}
/** Function to map Joystick position to relative Encoder Position.
* According to Nintendo the maximum value for any axis to be assumed should
* be +- 60. The encoder starts acting strange after ~ 30 turns in a given
* direction. Also passing on ControllerData struct so that user specified
* modifiers can be used to specify alternate mapping, eg. for
* easier ESS-Position in Ocarina of Time (see my example)
*/
int mapJoystickToEncoderPos(int joyval,ControllerData *data)
{
// ESS-Modifier for Ocarina of Time, hold A+B to stay in ESS-Position
if(data->buttonA && data->buttonB) return -2;
// deadzone handling
if(abs(joyval) < 3) return 0;
// normally return half ???
return (int)(joyval * .5f);
}
void outputToiQue(ControllerData *data)
{
// ===== Write Button Data =====
digitalWrite(PIN_BUTTON_A,data->buttonA);
digitalWrite(PIN_BUTTON_B,data->buttonB);
digitalWrite(PIN_BUTTON_L,data->buttonL);
digitalWrite(PIN_BUTTON_R,data->buttonR);
digitalWrite(PIN_BUTTON_Z,data->buttonZ);
digitalWrite(PIN_BUTTON_S,data->buttonStart);
// ===== Write D-Pad Data =====
digitalWrite(PIN_DPAD_UP, data->DPadUp);
digitalWrite(PIN_DPAD_DOWN, data->DPadDown);
digitalWrite(PIN_DPAD_LEFT, data->DPadLeft);
digitalWrite(PIN_DPAD_RIGHT,data->DPadRight);
// ===== Write C-Button Data =====
digitalWrite(PIN_C_UP, data->CUp);
digitalWrite(PIN_C_DOWN, data->CDown);
digitalWrite(PIN_C_LEFT, data->CLeft);
digitalWrite(PIN_C_RIGHT, data->CRight);
// ===== Write Joystick Data =====
// calculate change in axis between writes
//int xAxisChange = data->xAxis - lastxAxis;
//int yAxisChange = data->yAxis - lastyAxis;
// move axis accordingly
moveAxis(PIN_A_AXIS_X,PIN_B_AXIS_X,mapJoystickToEncoderPos(data->xAxis,data),&encoderXpos);
moveAxis(PIN_A_AXIS_Y,PIN_B_AXIS_Y,mapJoystickToEncoderPos(data->yAxis,data),&encoderYpos);
}

+ 19
- 0
src/Output.h View File

@ -0,0 +1,19 @@
#ifndef OUTPUT_H
#define OUTPUT_H
#include "ControllerData.h"
/** Function to output Data stored in ControllerData struct to the iQue console.
* Buttons states are directly set usig digitalWrite(), Axis Data is written by
* Simulating the 90° shifted quadrate encoding used by the N64 joysticks.
* As these operate on a relative basis the difference between the current
* and the last joystick positions are calculated and simulated.
*/
extern void outputToiQue(ControllerData *data);
/** Function to setup the IO used for interfacing with the N64-Controller
* and the iQue-Console
*/
extern void setupIO();
#endif

+ 33
- 0
src/PinMappings.h View File

@ -0,0 +1,33 @@
#ifndef PIN_MAPPINGS_H
#define PIN_MAPPINGS_H
#define DATA_PIN 13
#define PIN_BUTTON_A 15
#define PIN_BUTTON_B 5
#define PIN_BUTTON_Z 23
#define PIN_BUTTON_S 1
#define PIN_BUTTON_L 1
#define PIN_BUTTON_R 1
#define PIN_DPAD_UP 1
#define PIN_DPAD_DOWN 1
#define PIN_DPAD_LEFT 1
#define PIN_DPAD_RIGHT 1
#define PIN_C_UP 1
#define PIN_C_DOWN 1
#define PIN_C_LEFT 1
#define PIN_C_RIGHT 1
#define PIN_A_AXIS_X 14
#define PIN_B_AXIS_X 27
#define PIN_A_AXIS_Y 2
#define PIN_B_AXIS_Y 4
#define LINE_WRITE_HIGH pinMode(DATA_PIN,INPUT_PULLUP)
#define LINE_WRITE_LOW pinMode(DATA_PIN,OUTPUT)
#endif

+ 39
- 0
src/iQue.ino View File

@ -0,0 +1,39 @@
#include "Output.h"
#include "ControllerData.h"
#include "ControllerBuffer.h"
#include "Debug.h"
ControllerData controller;
void setup()
{
Serial.begin(115200);
// setup io pins
setupIO();
#ifdef PLOT_CONSOLE_POLLING
delay(5000);
sendCommand(0x01);
while(true);
#endif
sendCommand(0x01);
updateOffsetsAndResolution();
}
void loop()
{
// send command 0x01 to n64 controller
sendCommand(0x01);
// store received data in controller struct
populateControllerStruct(&controller);
// output received data to ique
outputToiQue(&controller);
// polling must not occur faster than every 20 ms
delay(14);
}

Loading…
Cancel
Save