/ 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.