Skip to content

Instantly share code, notes, and snippets.

@salami738
Created February 16, 2018 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save salami738/776e5337f0a610b006ed13b52935fd75 to your computer and use it in GitHub Desktop.
Save salami738/776e5337f0a610b006ed13b52935fd75 to your computer and use it in GitHub Desktop.
Gamepad_and_ADC_UART.ino
#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