Created
February 16, 2018 21:07
-
-
Save salami738/776e5337f0a610b006ed13b52935fd75 to your computer and use it in GitHub Desktop.
Gamepad_and_ADC_UART.ino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Joystick.h> | |
#include <EEPROM.h> | |
// Uses Serial = USB UART | |
//#define DEBUG | |
// Uses Serial1 = HW Serial 5V logic | |
#define USE_UART_CONTROL | |
#define SERIAL_SPEED 57600 | |
#define SERIAL_TIMEOUT 5000 | |
// 50 = 1000/50 = 20 inputs/s | |
// 25 = 1000/25 = 40 inputs/s | |
// 17 = 1000/17 = 60 inputs/s | |
#define INPUT_DELAY 25 | |
// Arduino left side | |
#define PinShift 7 | |
#define PinR1 8 | |
#define PinL1 9 | |
#define PinUnused1 2 | |
#define PinUnused2 3 | |
#define PinA 4 | |
#define PinB 5 | |
#define PinY 6 | |
// Arduino right side | |
#define PinBatteryVoltage A3 | |
#define PinX A2 | |
#define PinStart A1 | |
#define PinSelect A0 | |
#define PinDPadRight 15 | |
#define PinDPadDown 14 | |
#define PinDPadUp 16 | |
#define PinDPadLeft 10 | |
// currently PIN 2,3 are free (I2C) | |
// and PIN 0,1 (native UART not used, we use USB-UART) https://forum.arduino.cc/index.php?topic=26871.0 | |
// As pointed out you can use them but you !!!loose the ability to communicate and up load sketches!!! | |
#define EEPROM_ADDRESS_ADC_CALIBRATION 0 | |
#define EEPROM_ADDRESS_ADC_CALIBRATION_DEFAULT_VALUE 0 | |
#define EEPROM_ADDRESS_ADC_CALIBRATION_SHIFT 127 | |
#define ADC_MAX_VOLTAGE 4.623 | |
// 10 Bit = 1024 steps | |
#define ADC_STEPS 1024 | |
#define ADC_MULTIPLIER ADC_MAX_VOLTAGE / ADC_STEPS | |
// 8 Buttons: A, B, X, Y, START, SELECT, L1, R1 | |
// 2 Axis : X->LEFT/RIGHT, Y->TOP/BOTTOM | |
// https://github.com/MHeironimus/ArduinoJoystickLibrary | |
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, | |
8, 0, // Button Count, Hat Switch Count | |
true, true, false, // X and Y, but no Z Axis | |
false, false, false, // No Rx, Ry, or Rz | |
false, false, // No rudder or throttle | |
false, false, false); // No accelerator, brake, or steering | |
// we need a signed value, so we can set positve and negative calibration | |
// this value is persisted in EEPROM, so it is power cycle save | |
// 0,004515V/div at 4,623V max and 1024 steps | |
// therefore the max range for calibration is: -0,573V and +0,573V | |
char adcCalibration; | |
void setup() { | |
// wait for usb | |
delay(250); | |
#ifdef DEBUG | |
Serial.begin(SERIAL_SPEED); | |
#endif | |
#ifdef USE_UART_CONTROL | |
// https://www.arduino.cc/reference/en/language/functions/communication/serial/ifserial/ | |
while (!Serial1) { | |
; // wait for serial port to connect. Needed for native USB | |
} | |
Serial1.begin(SERIAL_SPEED); | |
// Make sure, that serial communication doesn't block the arduino | |
// https://www.arduino.cc/en/Serial/SetTimeout | |
// 5s timeout, so manual testing is possible with "cu -l /dev/ttyACM0 -s 57600" | |
Serial1.setTimeout(SERIAL_TIMEOUT); | |
#endif | |
// Declare buttons as inputs | |
pinMode(PinDPadLeft, INPUT_PULLUP); | |
pinMode(PinDPadRight, INPUT_PULLUP); | |
pinMode(PinDPadUp, INPUT_PULLUP); | |
pinMode(PinDPadDown, INPUT_PULLUP); | |
pinMode(PinSelect, INPUT_PULLUP); | |
pinMode(PinStart, INPUT_PULLUP); | |
pinMode(PinX, INPUT_PULLUP); | |
pinMode(PinY, INPUT_PULLUP); | |
pinMode(PinB, INPUT_PULLUP); | |
pinMode(PinA, INPUT_PULLUP); | |
pinMode(PinBatteryVoltage, INPUT); | |
pinMode(PinShift, INPUT_PULLUP); | |
pinMode(PinL1, INPUT_PULLUP); | |
pinMode(PinR1, INPUT_PULLUP); | |
// safety: set unused pins as input, so these can be shortened to GND or VCC without destroying the arduino | |
pinMode(PinUnused1, INPUT_PULLUP); | |
pinMode(PinUnused2, INPUT_PULLUP); | |
// EEPROM.read returns 255, if the EEPROM cell wasn't written to before | |
// Because we can't distinguish between a written value of 255 and 255 (default) | |
// we shouldn't use 255 for calibration | |
byte eepromValue = EEPROM.read(EEPROM_ADDRESS_ADC_CALIBRATION); | |
if (eepromValue == 255) { | |
// EEPROM is empty or maxValue(255) was written to | |
adcCalibration = EEPROM_ADDRESS_ADC_CALIBRATION_DEFAULT_VALUE; | |
EEPROM.write(EEPROM_ADDRESS_ADC_CALIBRATION, EEPROM_ADDRESS_ADC_CALIBRATION_DEFAULT_VALUE); | |
} else { | |
// change range of 0->255 to -127->128, so we can calibrate adc readings in two directions | |
adcCalibration = eepromValue - EEPROM_ADDRESS_ADC_CALIBRATION_SHIFT; | |
} | |
// Initialize Joystick Library - initAutoSendState = false | |
Joystick.begin(true); | |
Joystick.setXAxisRange(-1, 1); | |
Joystick.setYAxisRange(-1, 1); | |
// TODO remove? | |
delay(200); | |
} | |
void loop() { | |
boolean shiftPressed = !digitalRead(PinShift); | |
// For a common ground PCB, it checks for a LOW state | |
boolean pinAPressed = !digitalRead(PinA); | |
boolean pinBPressed = !digitalRead(PinB); | |
boolean pinXPressed = !digitalRead(PinX); | |
boolean pinYPressed = !digitalRead(PinY); | |
boolean pinStartPressed = !digitalRead(PinStart); | |
boolean pinSelectPressed = !digitalRead(PinSelect); | |
boolean pinL1Pressed = !digitalRead(PinL1); | |
boolean pinR1Pressed = !digitalRead(PinR1); | |
Joystick.setButton(0, pinAPressed); | |
Joystick.setButton(1, pinBPressed); | |
Joystick.setButton(2, pinXPressed); | |
Joystick.setButton(3, pinYPressed); | |
Joystick.setButton(4, pinStartPressed); | |
Joystick.setButton(5, pinSelectPressed); | |
Joystick.setButton(6, pinL1Pressed); | |
Joystick.setButton(7, pinR1Pressed); | |
boolean dPadLeftPressed = !digitalRead(PinDPadLeft); | |
boolean dPadRightPressed = !digitalRead(PinDPadRight); | |
boolean dPadUpPressed = !digitalRead(PinDPadUp); | |
boolean dPadDownPressed = !digitalRead(PinDPadDown); | |
if (dPadDownPressed) { | |
Joystick.setYAxis(1); | |
} else if (dPadUpPressed) { | |
Joystick.setYAxis(-1); | |
} else { | |
Joystick.setYAxis(0); | |
} | |
if (dPadLeftPressed) { | |
Joystick.setXAxis(-1); | |
} else if (dPadRightPressed) { | |
Joystick.setXAxis(1); | |
} else { | |
Joystick.setXAxis(0); | |
} | |
#ifdef DEBUG | |
Serial.println( | |
"Left:" + String(dPadLeftPressed) + ", Right:" + String(dPadRightPressed) | |
+ ", Up:" + String(dPadUpPressed) + ", Down:" + String(dPadDownPressed) | |
+ ", A:" + String(pinAPressed) + ", B:" + String(pinBPressed) | |
+ ", X:" + String(pinXPressed) + ", Y:" + String(pinYPressed) | |
+ ", Start:" + String(pinStartPressed) + ", Select:" + String(pinSelectPressed) | |
+ ", Shift:" + String(shiftPressed) + ", L1:" + String(pinL1Pressed) + ", L2:" + String(pinR1Pressed) | |
); | |
#endif | |
boolean serialWasUsed = false; | |
#ifdef USE_UART_CONTROL | |
// We don't send anything over UART (expensive), unless we are asked by the raspberry | |
if (Serial1.available() > 0) { | |
serialWasUsed = true; | |
// https://www.arduino.cc/en/Serial/Read | |
// Empty the buffer - http://forum.arduino.cc/index.php?topic=234151.0 | |
byte firstSerialByte = Serial1.read(); | |
if (firstSerialByte == 'V') { | |
// get battery voltage | |
float batteryVoltageFloat = (analogRead(PinBatteryVoltage) + adcCalibration) * ADC_MULTIPLIER; | |
// precision: 4 --> example: 4.1234 (V) | |
Serial1.println(batteryVoltageFloat, 4); | |
} else if (firstSerialByte == 'C') { | |
// set adc calibration - Syntax: 'C<SPACE><VALUE>' - Value: -127 to 126. 127 is RESERVED, DON'T use | |
// eat the space after the 'C' | |
Serial1.read(); | |
// parseInt(): If no valid digits were read when the time-out (see Serial.setTimeout()) occurs, 0 is returned; | |
adcCalibration = Serial1.parseInt() & 0xFF; | |
// TODO check range, and abort if exceeded or value: 127 is used | |
EEPROM.write(EEPROM_ADDRESS_ADC_CALIBRATION, adcCalibration + EEPROM_ADDRESS_ADC_CALIBRATION_SHIFT); | |
Serial1.print("OK, new value: "); | |
Serial1.println(adcCalibration, DEC); | |
// TODO G = get calibration | |
} else if (firstSerialByte == 'B') { | |
// get button states | |
char uartResponse[18]; | |
uartResponse[0] = boolToChar(shiftPressed); | |
uartResponse[1] = ' '; | |
uartResponse[2] = boolToChar(dPadLeftPressed); | |
uartResponse[3] = ' '; | |
uartResponse[4] = boolToChar(dPadRightPressed); | |
uartResponse[5] = ' '; | |
uartResponse[6] = boolToChar(dPadUpPressed); | |
uartResponse[7] = ' '; | |
uartResponse[8] = boolToChar(dPadDownPressed); | |
uartResponse[9] = ' '; | |
uartResponse[10] = boolToChar(pinAPressed); | |
uartResponse[11] = ' '; | |
uartResponse[12] = boolToChar(pinBPressed); | |
uartResponse[13] = ' '; | |
uartResponse[14] = boolToChar(pinXPressed); | |
uartResponse[15] = ' '; | |
uartResponse[16] = boolToChar(pinYPressed); | |
uartResponse[17] = '\0'; | |
//TODO L1, R1 | |
Serial1.println(uartResponse); | |
} else { | |
Serial1.println("UNKNOWN COMMAND"); | |
} | |
// TODO additional functions: enable/disable Auto-Fire. Remap buttons to keys (Amiga Emulation). | |
// TODO needed? | |
Serial1.flush(); | |
while (Serial1.available() > 0) { | |
// eat up unecessary bytes to free the serial buffer | |
Serial1.read(); | |
} | |
} | |
#endif | |
// easy debouncing (limits the number of inputs/s) | |
if (!serialWasUsed) { | |
delay(INPUT_DELAY); | |
} else { | |
delay(15); | |
} | |
#ifdef DEBUG | |
delay(250); | |
#endif | |
//TODO timer to check how many FPS this code can do! | |
} | |
char boolToChar(boolean input) { | |
if (input) { | |
return '1'; | |
} else { | |
return '0'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment