/ AtariFruit_Joystick / AtariFruit_Joystick.ino
AtariFruit_Joystick.ino
1 // SPDX-FileCopyrightText: 2017 John Edgar Park for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 /********************************************************************* 6 AtariFruit 2600 Joystick 7 by John Park for Adafruit 8 9 For nRF52 Feather and Atari CX40 (2600) joystick. 10 Reads joystick direction and fire button, sends iCade commands over Bluetooth. 11 12 based on: 13 Teensy iCade Input 14 15 by Allen C. Huffman (alsplace@pobox.com) 16 http://subethasoftware.com/2013/01/04/teensy-2-0-icade-source-code/ 17 18 *********************************************************************/ 19 #include <bluefruit.h> 20 21 BLEDis bledis; 22 BLEHidAdafruit blehid; 23 24 #define VERSION "0.1" 25 #define LED_ON 26 /* 27 iCade keyboard mappings. 28 See developer doc at: http://www.ionaudio.com/products/details/icade 29 30 WE YT UF IM OG 31 AQ< -->DC 32 XZ HR JN KP LV 33 34 Atari joystick port, looking at the male DB9 on the Atari. 35 See: http://old.pinouts.ru/Inputs/JoystickAtari2600_pinout.shtml 36 37 1 2 3 4 5/ Up Dn Lt Rt PA 38 6 7 8 9/ Bt +5 Gd PB 39 */ 40 41 /* 42 The following I/O pins will be used as digital inputs 43 for each specific iCade function. 44 */ 45 #define UP_PIN 15 // WHT 46 #define DOWN_PIN 7 // BLU 47 #define LEFT_PIN 11 // GRN 48 #define RIGHT_PIN 16 // BRN 49 #define BTN1_PIN A2 50 #define BTN2_PIN 27 51 #define BTN3_PIN A0 52 #define BTN4_PIN A1 53 #define BTN5_PIN 30 // ORG 54 #define BTN6_PIN A3 55 #define BTN7_PIN A4 56 #define BTN8_PIN A5 57 58 /* 59 The following keys are the iCade sequence (hold, release) 60 for each function. Send "W" to indicate UP, and "E" when 61 UP is released. 62 */ 63 #define UP_KEYS "we" 64 #define DOWN_KEYS "xz" 65 #define LEFT_KEYS "aq" 66 #define RIGHT_KEYS "dc" 67 #define BTN1_KEYS "yt" 68 #define BTN2_KEYS "uf" 69 #define BTN3_KEYS "im" 70 #define BTN4_KEYS "og" 71 #define BTN5_KEYS "hr" 72 #define BTN6_KEYS "jn" 73 #define BTN7_KEYS "kp" 74 #define BTN8_KEYS "lv" 75 76 #define DI_PIN_COUNT 12 // 12 pins used. 77 // #define DI_PIN_START 1 // First I/O pin. 78 // #define DI_PIN_END 20 // Last I/O pin. 79 80 byte myPins[DI_PIN_COUNT] = {UP_PIN, DOWN_PIN, LEFT_PIN, RIGHT_PIN, BTN1_PIN, 81 BTN2_PIN, BTN3_PIN, BTN4_PIN, 82 BTN5_PIN, BTN6_PIN, BTN7_PIN, BTN8_PIN}; 83 84 char iCadeKeymap[][DI_PIN_COUNT] = {UP_KEYS, DOWN_KEYS, LEFT_KEYS, RIGHT_KEYS, 85 BTN1_KEYS, BTN2_KEYS, BTN3_KEYS, BTN4_KEYS, 86 BTN5_KEYS, BTN6_KEYS, BTN7_KEYS, BTN8_KEYS}; 87 88 char iCadeDesc[][DI_PIN_COUNT] = {"Up", "Down", "Left", "Right", "Btn1", 89 "Btn2", "Btn3", "Btn4", 90 "Btn5", "Btn6", "Btn7", "Btn8"}; 91 92 /* We want a very short debounce delay for an arcade controller. */ 93 #define DI_DEBOUNCE_MS 10 // 100ms (1/10th second) 94 95 #define LED_PIN 17 96 //#define POWER_LED 17 97 #define LEDBLINK_MS 1000 98 99 100 /* For I/O pin status and debounce. */ 101 unsigned int digitalStatus[DI_PIN_COUNT]; // Last set PIN mode. 102 unsigned long digitalDebounceTime[DI_PIN_COUNT]; // Debounce time. 103 //unsigned long digitalCounter[DI_PIN_COUNT]; // Times button pressed. 104 unsigned int digitalDebounceRate = DI_DEBOUNCE_MS; // Debounce rate. 105 106 /* For the blinking LED (heartbeat). */ 107 unsigned int ledStatus = LOW; // Last set LED mode. 108 unsigned long ledBlinkTime = 0; // LED blink time. 109 unsigned int ledBlinkRate = LEDBLINK_MS; // LED blink rate. 110 111 unsigned int pinsOn = 0; 112 113 114 void setup() 115 { 116 // Just in case it was left on... 117 //wdt_disable(); 118 119 // Initialize the serial port. 120 Serial.begin(115200); 121 Serial.println(); 122 Serial.println("Go to your phone's Bluetooth settings to pair your device"); 123 Serial.println("then open an application that accepts keyboard input"); 124 Bluefruit.begin(); 125 // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 126 Bluefruit.setTxPower(4); 127 Bluefruit.setName("AtariFruitJoystick"); 128 129 // Configure and Start Device Information Service 130 bledis.setManufacturer("Adafruit Industries"); 131 bledis.setModel("Atari Fruit Joystick 52"); 132 bledis.begin(); 133 134 /* Start BLE HID 135 * Note: Apple requires BLE device must have min connection interval >= 20m 136 * ( The smaller the connection interval the faster we could send data). 137 * However for HID and MIDI device, Apple could accept min connection interval 138 * up to 11.25 ms. Therefore BLEHidAdafruit::begin() will try to set the min and max 139 * connection interval to 11.25 ms and 15 ms respectively for best performance. 140 */ 141 blehid.begin(); 142 143 /* Set connection interval (min, max) to your perferred value. 144 * Note: It is already set by BLEHidAdafruit::begin() to 11.25ms - 15ms 145 * min = 9*1.25=11.25 ms, max = 12*1.25= 15 ms 146 */ 147 /* Bluefruit.setConnInterval(9, 12); */ 148 149 // Set up and start advertising 150 startAdv(); 151 152 // Docs say this isn't necessary for Uno. 153 //while(!Serial) { } 154 155 showHeader(); 156 157 // Initialize watchdog timer for 2 seconds. 158 //wdt_enable(WDTO_4S); 159 160 // LOW POWER MODE! 161 // Pins default to INPUT mode. To save power, turn them all to OUTPUT 162 // initially, so only those being used will be turn on. See: 163 // http://www.pjrc.com/teensy/low_power.html 164 for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) 165 { 166 pinMode(thisPin, OUTPUT); 167 } 168 169 // Disable Unused Peripherals 170 // ADCSRA = 0; 171 172 // Initialize the pins and digitalPin array. 173 for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) 174 { 175 // Set pin to be digital input using pullup resistor. 176 pinMode(myPins[thisPin], INPUT_PULLUP); 177 // Set the current initial pin status. 178 digitalStatus[thisPin] = HIGH; //digitalRead(thisPin+DI_PIN_START); 179 // Clear debounce time. 180 digitalDebounceTime[thisPin] = 0; 181 //digitalCounter[thisPin] = 0; 182 } 183 184 // Set LED pin to output, since it has an LED we can use. 185 pinMode(LED_PIN, OUTPUT); 186 // pinMode(POWER_LED, OUTPUT); 187 // digitalWrite(POWER_LED, HIGH); 188 189 Serial.println("Ready."); 190 191 } 192 193 194 void startAdv(void) 195 { 196 // Advertising packet 197 Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); 198 Bluefruit.Advertising.addTxPower(); 199 Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD); 200 201 // Include BLE HID service 202 Bluefruit.Advertising.addService(blehid); 203 204 // There is enough room for the dev name in the advertising packet 205 Bluefruit.Advertising.addName(); 206 207 /* Start Advertising 208 * - Enable auto advertising if disconnected 209 * - Interval: fast mode = 20 ms, slow mode = 152.5 ms 210 * - Timeout for fast mode is 30 seconds 211 * - Start(timeout) with timeout = 0 will advertise forever (until connected) 212 * 213 * For recommended advertising interval 214 * https://developer.apple.com/library/content/qa/qa1931/_index.html 215 */ 216 Bluefruit.Advertising.restartOnDisconnect(true); 217 Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms 218 Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode 219 Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds 220 } 221 222 223 void loop() 224 { 225 // Tell the watchdog timer we are still alive. 226 //wdt_reset(); 227 228 #ifndef LED_OFF 229 // LED blinking heartbeat. Yes, we are alive. 230 if ( (long)(millis()-ledBlinkTime) >= 0 ) 231 { 232 // Toggle LED. 233 if (ledStatus==LOW) // If LED is LOW... 234 { 235 ledStatus = HIGH; // ...make it HIGH. 236 } else { 237 ledStatus = LOW; // ...else, make it LOW. 238 } 239 // Set LED pin status. 240 if (pinsOn==0) digitalWrite(LED_PIN, ledStatus); 241 // Reset "next time to toggle" time. 242 ledBlinkTime = millis()+ledBlinkRate; 243 } 244 #endif 245 246 // Check for serial data. 247 if (Serial.available() > 0) { 248 // If data ready, read a byte. 249 int incomingByte = Serial.read(); 250 // Parse the byte we read. 251 switch(incomingByte) 252 { 253 case '?': 254 showStatus(); 255 break; 256 default: 257 break; 258 } 259 } 260 261 262 // Loop through each Digital Input pin. 263 for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) 264 { 265 // Read the pin's current status. 266 unsigned int status = digitalRead(myPins[thisPin]); 267 268 // In pin status has changed from our last toggle... 269 if (status != digitalStatus[thisPin]) 270 { 271 blehid.keyRelease(); 272 // Remember when it changed, starting debounce mode. 273 // If not currently in debounce mode, 274 if (digitalDebounceTime[thisPin]==0) 275 { 276 // Set when we can accept this as valid (debounce is considered 277 // done if the time gets to this point with the status still the same). 278 digitalDebounceTime[thisPin] = millis()+digitalDebounceRate; 279 } 280 281 // Check to see if we are in debounce detect mode. 282 if (digitalDebounceTime[thisPin]>0) 283 { 284 // Yes we are. Have we delayed long enough yet? 285 if ( (long)(millis()-digitalDebounceTime[thisPin]) >= 0 ) 286 { 287 // Yes, so consider it switched. 288 // If pin is Active LOW, 289 if (status==LOW) 290 { 291 // Emit BUTTON PRESSED string. 292 Serial.print(iCadeDesc[thisPin]); 293 Serial.print(" pressed (sending "); 294 Serial.print(iCadeKeymap[thisPin][0]); 295 Serial.println(" to iCade)."); 296 blehid.keyPress(iCadeKeymap[thisPin][0]); 297 //Keyboard.print(iCadeKeymap[thisPin][0]); 298 //digitalCounter[thisPin]++; 299 pinsOn++; 300 #ifndef LED_OFF 301 digitalWrite(LED_PIN, HIGH); 302 #endif 303 } else { 304 // Emit BUTTON RELEASED string. 305 Serial.print(iCadeDesc[thisPin]); 306 Serial.print(" released (sending "); 307 Serial.print(iCadeKeymap[thisPin][1]); 308 Serial.println(" to iCade)."); 309 blehid.keyPress(iCadeKeymap[thisPin][1]); 310 //Keyboard.print(iCadeKeymap[thisPin][1]); 311 if (pinsOn>0) pinsOn--; 312 if (pinsOn==0) digitalWrite(LED_PIN, LOW); 313 } 314 // Remember current (last set) status for this pin. 315 digitalStatus[thisPin] = status; 316 // Reset debounce time (disable, not looking any more). 317 digitalDebounceTime[thisPin] = 0; 318 } // End of if ( (long)(millis()-digitalDebounceTime[thisPin]) >= 0 ) 319 320 } // End of if (digitalDebounceTime[thisPin]>0) 321 } 322 else // No change? Flag no change. 323 { 324 // If we were debouncing, we are no longer debouncing. 325 digitalDebounceTime[thisPin] = 0; 326 } 327 } // End of (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) 328 329 // Request CPU to enter low-power mode until an event/interrupt occurs 330 waitForEvent(); 331 } 332 333 void showHeader() 334 { 335 int i; 336 // Emit some startup stuff to the serial port. 337 Serial.println("Atari 2600 BlueCade by John Park for Adafruit. "); 338 Serial.print("Based on: iCadeTeensy "); 339 Serial.print(VERSION); 340 Serial.println(" by Allen C. Huffman (alsplace@pobox.com)"); 341 Serial.print(DI_PIN_COUNT); 342 Serial.print(" DI Pins ("); 343 for (i=0; i<DI_PIN_COUNT; i++) 344 { 345 Serial.print(myPins[i]); 346 Serial.print("="); 347 Serial.print(iCadeDesc[i]); 348 Serial.print(" "); 349 } 350 Serial.print("), "); 351 Serial.print(digitalDebounceRate); 352 Serial.println("ms Debounce."); 353 } 354 355 356 void showStatus() 357 { 358 showDigitalInputStatus(); 359 } 360 361 362 void showDigitalInputStatus() 363 { 364 Serial.print("DI: "); 365 366 for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) 367 { 368 // Read the pin's current status. 369 Serial.print(iCadeDesc[thisPin]); 370 Serial.print("="); 371 Serial.print(digitalRead(myPins[thisPin])); 372 Serial.print(" "); 373 //Serial.print(" ("); 374 //Serial.print(digitalCounter[thisPin]); 375 //Serial.print(") "); 376 } 377 Serial.println(""); 378 } 379 380 /** 381 * RTOS Idle callback is automatically invoked by FreeRTOS 382 * when there are no active threads. E.g when loop() calls delay() and 383 * there is no bluetooth or hw event. This is the ideal place to handle 384 * background data. 385 * 386 * NOTE: FreeRTOS is configured as tickless idle mode. After this callback 387 * is executed, if there is time, freeRTOS kernel will go into low power mode. 388 * Therefore waitForEvent() should not be called in this callback. 389 * http://www.freertos.org/low-power-tickless-rtos.html 390 * 391 * WARNING: This function MUST NOT call any blocking FreeRTOS API 392 * such as delay(), xSemaphoreTake() etc ... for more information 393 * http://www.freertos.org/a00016.html 394 */ 395 void rtos_idle_callback(void) 396 { 397 // Don't call any other FreeRTOS blocking API() 398 // Perform background task(s) here 399 } 400 // End of file.