<commit_before>
// Interfacing with the Gigatron TTL microcomputer using a microcontroller
//
//               4. Game Controller
//                  (optional)
//                       |
//                       v
//                 +-----------+
//   Gigatron      |           |     PC/laptop over USB (optional)
//  controller <---| BabelFish |<--- 1. Send GT1 files
//   port J4       |           |     2. Serial console
//                 +-----------+
//                       ^
//                       |
//               3. PS/2 keyboard
//                  (optional)
//
// This sketch serves several purpuses:
// 1. Transfer a GT1 file into the Gigatron for execution.
//    This can be done with two methods: over USB or from PROGMEM
//    Each has their advantages and disadvantages:
//    a. USB can accept a regular GT1 file but needs a (Python)
//       program on the PC/laptop as sender (sendFile.py).
//    b. PROGMEM only requires the Arduino IDE on the PC/laptop,
//       but needs the GT1 file as a hexdump (C notation).
// 2. Hookup a PS/2 keyboard for typing on the Gigatron
// 3. Forward text from USB to Gigatron as keystrokes
//    For example to get a long BASIC program loaded into BASIC
// 4. Controlling the Gigatron over USB from a PC/laptop
// 5. Passing through of game controller signals
// 6. Receive data from Gigatron and store it in the EEPROM area
//
// Select one of the supported platforms in the Tools->Board menu.
//
// Supported:
//      - Arduino/Genuino Uno
//      - Arduino Nano
//      - Arduino/Genuino Micro
//      - ATtiny85 (8 MHz)
//
// (not every microcontroller platform supports all functions)

/*----------------------------------------------------------------------+
 |                                                                      |
 |      Preset configuarations                                          |
 |                                                                      |
 +----------------------------------------------------------------------*/

/*----------------------------------------------------------------------+
 |      Arduino Uno config                                              |
 +----------------------------------------------------------------------*/

// Arduino AVR    Gigatron Schematic Controller PCB              Gigatron
// Uno     Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4)
// ------- ------ -------- --------- ---------- ---------------- --------
// Pin 13  PORTB5 None     SER_DATA  11 SER INP 14 SER           2
// Pin 12  PORTB4 7 vSync  SER_LATCH  0 PAR/SER None             3
// Pin 11  PORTB3 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4

#if defined(ARDUINO_AVR_UNO)
 #define platform "ArduinoUno"
 #define maxStorage 32256

 // Pinout reference:
 // https://i2.wp.com/marcusjenkins.com/wp-content/uploads/2014/06/ARDUINO_V2.png

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  13
 #define gigatronLatchPin 12
 #define gigatronPulsePin 11
 #define gigatronPinToBitMask digitalPinToBitMask

 // Pins for Controller
 #define gameControllerDataPin -1

 // Pins for PS/2 keyboard (Arduino Uno)
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      Arduino Nano config                                             |
 +----------------------------------------------------------------------*/

// Arduino   AVR    Gigatron Schematic Controller PCB              Gigatron Controller
// Nano      Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4) DB9
// -------   ------ -------- --------- ---------- ---------------- -------- -------
// Pin J2-15 PORTB5 None     SER_DATA  11 SER INP 14 SER           2        None
// Pin J1-15 PORTB4 7 vSync  SER_LATCH  0 PAR/SER None             3        3
// Pin J1-14 PORTB3 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4        4
// Pin J1-13 PORTB2 None     None      None       None             None     2

#if defined(ARDUINO_AVR_NANO)
 // at67's setup
 #define platform "ArduinoNano"
 #define maxStorage 30720

 // Pinout reference:
 // http://lab.dejaworks.com/wp-content/uploads/2016/08/Arduino-Nano-1024x500.png
 // Note that pin 11 and 12 are wrong on some versions of these diagrams

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  13 // PB5
 #define gigatronLatchPin 12 // PB4
 #define gigatronPulsePin 11 // PB3
 #define gigatronPinToBitMask digitalPinToBitMask // Regular Arduino pin numbers

 // Pins for Controller
 #define gameControllerDataPin 10 // PB2

 // Pins for PS/2 keyboard
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      Arduino Micro config                                            |
 +----------------------------------------------------------------------*/

// See also: https://forum.gigatron.io/viewtopic.php?f=4&t=33

// Arduino AVR    Gigatron Schematic Controller PCB              Gigatron
// Micro   Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4)
// ------- ------ -------- --------- ---------- ---------------- --------
// SCLK    PORTB1 None     SER_DATA  11 SER INP 14 SER           2
// MISO    PORTB3 7 vSync  SER_LATCH  0 PAR/SER None             3
// MOSI    PORTB2 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4

// --------------------+
//               Reset |
// Arduino       +---+ |
//  Micro        | O | |
//               +---+ |
//                     |
//               2 4 6 |
//        ICSP-> o o o |
//        Port  .o o o |
//               1 3 5 |
// --------------------+

#if defined(ARDUINO_AVR_MICRO)
 // WattSekunde's setup
 #define platform "ArduinoMicro"
 #define maxStorage 28672

 // Pinout reference:
 // http://1.bp.blogspot.com/-xqhL0OrJcxo/VJhVxUabhCI/AAAAAAABEVk/loDafkdqLxM/s1600/micro_pinout.png

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  PB1
 #define gigatronLatchPin PB3
 #define gigatronPulsePin PB2
 // These are not regular Arduino pin numbers
 #define gigatronPinToBitMask(pin) (1 << (pin))

 // Pins for Controller
 #define gameControllerDataPin -1

 // Pins for PS/2 keyboard
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      ATtiny85 config                                                 |
 +----------------------------------------------------------------------*/

// Used settings in Arduino IDE 1.8.1:
//   Board:       ATtiny
//   Processor:   ATtiny85
//   Clock:       8 MHz (internal) --> Use "Burn Bootloader" to configure this
//   Programmer:  Arduino as ISP
// See also:
//   https://create.arduino.cc/projecthub/arjun/programming-attiny85-with-arduino-uno-afb829

//                       +------+
//              ~RESET --|1.   8|-- Vcc
// PS/2 data       PB3 --|2    7|-- PB2  Serial data out
// PS/2 clock      PB4 --|3    6|-- PB1  Serial latch in
//                 GND --|4    5|-- PB0  Serial pulse in
//                       +------+
//                       ATtiny85

#if defined(ARDUINO_attiny)
 #define platform "ATtiny85"
 #define maxStorage 8192

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  PB2
 #define gigatronLatchPin PB1
 #define gigatronPulsePin PB0
 // These are not regular Arduino pin numbers
 #define gigatronPinToBitMask(pin) (1 << (pin))

 // Pins for Controller
 #define gameControllerDataPin -1 // XXX Maybe use ~RESET for this some day

 // Pins for PS/2 keyboard
 #define keyboardClockPin PB4
 #define keyboardDataPin  PB3

 // Link to PC/laptop
 #define hasSerial 0
#endif

/*----------------------------------------------------------------------+
 |                                                                      |
 |      Built-in GT1 images                                             |
 |                                                                      |
 +----------------------------------------------------------------------*/

const byte TinyBASIC_gt1[] PROGMEM = {
  #include "TinyBASIC_v2.h"
};
const byte WozMon_gt1[]    PROGMEM = {
  #include "WozMon.h"
};
const byte Terminal_gt1[]  PROGMEM = {
  #include "Terminal.h"
};
const byte Blinky_gt1[]    PROGMEM = {
  #include "Blinky.h"
};
const byte bricks_gt1[]    PROGMEM = {
  #include "bricks.h"
};
const byte lines_gt1[]     PROGMEM = {
  #include "lines.h"
};
const byte life3_gt1[]     PROGMEM = {
  #include "life3.h"
};
const byte starfield_gt1[] PROGMEM = {
  #include "starfield.h"
};
const byte tetris_gt1[]    PROGMEM = {
  #include "tetris.h"
};

const struct { const byte *gt1; const char *name; } gt1Files[] = {
  { TinyBASIC_gt1, "BASIC"                    }, // 3048 bytes
#if maxStorage >= 10000
  { WozMon_gt1,    "WozMon"                   }, // 595 bytes
  { Terminal_gt1,  "Terminal"                 }, // 256 bytes
  { Blinky_gt1,    "Blinky"                   }, // 17 bytes
  { lines_gt1,     "Lines demo [at67]"        }, // 304 bytes
  { life3_gt1,     "Game of Life demo [at67]" }, // 441 bytes
  { starfield_gt1, "Starfield demo [at67]"    }, // 817 bytes
#endif
#if maxStorage >= 30000
  { bricks_gt1,    "Bricks game [xbx]"        }, // 1607 bytes
  { tetris_gt1,    "Tetris game [at67]"       }, // 9868 bytes
#endif
  { NULL,          "-SAVED-"                  },
};

/*----------------------------------------------------------------------+
 |                                                                      |
 |      End config section                                              |
 |                                                                      |
 +----------------------------------------------------------------------*/

/*
 *  Bit masks for pins
 */
byte gigatronDataBit;
byte gigatronLatchBit;
byte gigatronPulseBit;

/*
 *  Loader protocol
 */
#define N 60         // Payload bytes per transmission frame
byte checksum;       // Global is simplest
byte outBuffer[256]; // sendFrame() will read up to index 299 but that's ok.
                     // outBuffer[] is global, because having it on the stack
                     // can cause trouble on the ATtiny85 (not fully clear why)
/*
 *  Game controller button mapping
 */
#define buttonRight  1
#define buttonLeft   2
#define buttonDown   4
#define buttonUp     8
#define buttonStart  16
#define buttonSelect 32
#define buttonB      64
#define buttonA      128
// Note: The kit's game controller gives inverted signals.

/*
 *  Font data
 */
const int tinyfont[96] PROGMEM = {
  #include "tinyfont.h"
};

/*
 *  Terminal mode for upstream host
 */
static bool echo = false;

/*
 *  Non-volatile memory
 */
#include <EEPROM.h>

struct EEPROMlayout {
  byte keymapIndex;
  byte savedFile[];
};

#define fileStart offsetof(struct EEPROMlayout, savedFile)
static word saveIndex = fileStart; // Write pointer into EEPROM for file (BASIC)
static word EEPROM_length;

#define arrayLen(a) ((int) (sizeof(a) / sizeof((a)[0])))
extern const byte nrKeymaps; // From in PS2.ino

/*
 *  Setup runs once when the Arduino wakes up
 */
void setup()
{
  gigatronDataBit  = gigatronPinToBitMask(gigatronDataPin);
  gigatronLatchBit = gigatronPinToBitMask(gigatronLatchPin);
  gigatronPulseBit = gigatronPinToBitMask(gigatronPulsePin);

  // Enable output pin (pins are set to input by default)
  PORTB |= gigatronDataBit; // Send 1 when idle
  DDRB = gigatronDataBit;

  // Open upstream communication
  #if hasSerial
    Serial.begin(115200);
    doVersion();
  #endif

  // Cache for speed
  EEPROM_length = EEPROM.length();

  // In case we power on together with the Gigatron, this is a
  // good pause to wait for the video loop to have started
  delay(350);

  // Note that it takes 500~750 ms for PS/2 keyboards to boot
  keyboard_setup();

  prompt();
}

/*
 *  Loop runs repeatedly
 */
void loop()
{
  // Check Gigatron's vPulse for incoming data
  static byte inByte, inBit, inLen;

  critical();
  byte newValue = waitVSync();
  nonCritical();

  switch (newValue) {

    case 9:                            // Received one bit
      inByte |= inBit;
      // !!! FALL THROUGH !!!

    case 7:                            // Received zero bit
      inBit <<= 1;

      if (saveIndex >= EEPROM_length)  // EEPROM overflow
        if (inLen > 0)                 // Only break if this can't be a new program
          sendController(3, 10);       // Send long Ctrl-C back

      if (inBit != 0)
        break;

      EEPROM.write(saveIndex++, inByte);// Store every full byte

      #if hasSerial
        if (inByte == 10)
          Serial.print('\r');
        Serial.print((char)inByte);    // Forward to host
        noInterrupts();
      #endif

      if (inByte == 10) {              // End of line?
        EEPROM.write(saveIndex, 255);  // Terminate, in case this was the last line
        if (inLen == 0)                // An empty line clears the old program
          saveIndex = fileStart;
        inLen = 0;
      } else
        inLen++;

      inByte = 0;                      // Prepare for next byte
      inBit = 1;
      break;

    default:
      inByte = 0;                      // Reset incoming data state
      inBit = 1;
      inLen = 0;
      break;
  }

  // Game controller pass through
  #if gameControllerDataPin >= 0
    digitalWrite(gigatronDataPin, digitalRead(gameControllerDataPin));
  #endif

  // PS/2 keyboard events
  delay(15);                           // Allow PS/2 interrupts for a reasonable window
  byte key = keyboard_getState();
  if (key != 255) {
    byte f = fnKey(key ^ 64);          // Ctrl+Fn key?
    if (f) {
      if (f == 1)
        doMapping();                   // Ctrl-F1 is help
      else if (f-2 < arrayLen(gt1Files)) {
        if (gt1Files[f-2].gt1)
          doTransfer(gt1Files[f-2].gt1); // Send built-in GT1 file to Gigatron
        else
          sendSavedFile();
      }
    }
    for (;;) {                         // Focus all attention on PS/2 until state is idle again
      if (!fnKey(key ^ 64)) {          // Filter away the Ctrl+Fn combinations here
        critical();
        sendFirstByte(key);            // Synchronize with vPulse and send ASCII code
        nonCritical();
      }
      if (key == 255)                  // Break after returning to the idle state
        break;
      delay(15);                       // Allow PS/2 interrupts, so we can receive break codes
      key = keyboard_getState();       // This typically returns the same key for a couple of frames
    }
  }

  // Commands from upstream USB (PC/laptop)
  #if hasSerial
    static char line[32], next = 0, last;
    static byte lineIndex = 0;
    if (Serial.available()) {
      last = next;
      char next = Serial.read();
      sendEcho(next, last);
      if (lineIndex < sizeof line)
        line[lineIndex++] = next;
      if (next == '\r' || next == '\n') {
        line[lineIndex-1] = '\0';
        doCommand(line);
        lineIndex = 0;
      }
    }
  #endif
}

void prompt()
{
  #if hasSerial
    Serial.println(detectGigatron() ? ":Gigatron OK" : "!Gigatron offline");
    Serial.println("Cmd?");
  #endif
}

bool detectGigatron()
{
  unsigned long timeout = millis() + 85;
  long T[4] = {0, 0, 0, 0};

  // Sample the sync signals coming out of the controller port
  while (millis() < timeout) {
    byte pinb = PINB; // capture SER_PULSE and SER_LATCH at the same time
    T[(pinb & gigatronLatchBit ? 2 : 0) + (pinb & gigatronPulseBit ? 1 : 0)]++;
  }

  float S = T[0] + T[1] + T[2] + T[3] + .1;     // Avoid zero division (pedantic)
  float vSync = (T[0] + T[1]) / ( 8 * S / 521); // Adjusted vSync signal
  float hSync = (T[0] + T[2]) / (96 * S / 800); // Standard hSync signal

  // Check that vSync and hSync characteristics look normal
  return 0.95 <= vSync && vSync <= 1.20 && 0.90 <= hSync && hSync <= 1.10;
}

void sendEcho(char next, char last)
{
  #if hasSerial
    if (echo)
      switch (next) {
        case 127:  Serial.print("\b \b"); break;
        case '\n': if (last == '\r')      break; // else FALL THROUGH
        case '\r': Serial.println();      break;
        default: Serial.print(next);
      }
  #endif
}

void doCommand(char line[])
{
  int arg = line[0] ? atoi(&line[1]) : 0;
  switch (toupper(line[0])) {
  case 'V': doVersion();                      break;
  case 'H': doHelp();                         break;
  case 'R': doReset(arg);                     break;
  case 'L': doLoader();                       break;
  case 'M': doMapping();                      break;
  case 'P': if (0 <= arg && arg < arrayLen(gt1Files))
              doTransfer(gt1Files[arg].gt1);  break;
  case 'U': doTransfer(NULL);                 break;
  case '.': doLine(&line[1]);                 break;
  case 'C': doEcho(!echo);                    break;
  case 'T': doTerminal();                     break;
  case 'W': sendController(~buttonUp,     2); break;
  case 'A': sendController(~buttonLeft,   2); break;
  case 'S': sendController(~buttonDown,   2); break;
  case 'D': sendController(~buttonRight,  2); break;
  case 'Z': sendController(~buttonA & 255,2); break;
  case 'X': sendController(~buttonB,      2); break;
  case 'Q': sendController(~buttonSelect, 2); break;
  case 'E': sendController(~buttonStart,  2); break;
  case 0: /* Empty line */                    break;
  default:
    #if hasSerial
      Serial.println("!Unknown command (type 'H' for help)");
    #endif
    ;
  }
  prompt();
}

void doVersion()
{
  #if hasSerial
    Serial.println(":BabelFish platform=" platform);
    Serial.println(":Pins:");
    #define V(s) #s
    #define Q(s) V(s)
    Serial.println(": Gigatron data=" Q(gigatronDataPin) " latch=" Q(gigatronLatchPin) " pulse=" Q(gigatronPulsePin));
    Serial.println(": Keyboard clock=" Q(keyboardClockPin) " data=" Q(keyboardDataPin));
    Serial.println(": Controller data=" Q(gameControllerDataPin));
    Serial.println(":EEPROM:");
    Serial.print(": size=");
    Serial.print(EEPROM.length());
    Serial.print(" mapping=");
    Serial.print(getKeymapName());
    Serial.println(":PROGMEM slots:");
    for (byte i=0; i<arrayLen(gt1Files); i++) {
      Serial.print(": P");
      Serial.print(i);
      Serial.print(") ");
      Serial.println(gt1Files[i].name);
    }
    doEcho(echo);
    Serial.println(":Type 'H' for help");
  #endif
}<commit_msg>BabelFish: Fixed missing newline for 'V' command [bug]<commit_after>
// Interfacing with the Gigatron TTL microcomputer using a microcontroller
//
//               4. Game Controller
//                  (optional)
//                       |
//                       v
//                 +-----------+
//   Gigatron      |           |     PC/laptop over USB (optional)
//  controller <---| BabelFish |<--- 1. Send GT1 files
//   port J4       |           |     2. Serial console
//                 +-----------+
//                       ^
//                       |
//               3. PS/2 keyboard
//                  (optional)
//
// This sketch serves several purpuses:
// 1. Transfer a GT1 file into the Gigatron for execution.
//    This can be done with two methods: over USB or from PROGMEM
//    Each has their advantages and disadvantages:
//    a. USB can accept a regular GT1 file but needs a (Python)
//       program on the PC/laptop as sender (sendFile.py).
//    b. PROGMEM only requires the Arduino IDE on the PC/laptop,
//       but needs the GT1 file as a hexdump (C notation).
// 2. Hookup a PS/2 keyboard for typing on the Gigatron
// 3. Forward text from USB to Gigatron as keystrokes
//    For example to get a long BASIC program loaded into BASIC
// 4. Controlling the Gigatron over USB from a PC/laptop
// 5. Passing through of game controller signals
// 6. Receive data from Gigatron and store it in the EEPROM area
//
// Select one of the supported platforms in the Tools->Board menu.
//
// Supported:
//      - Arduino/Genuino Uno
//      - Arduino Nano
//      - Arduino/Genuino Micro
//      - ATtiny85 (8 MHz)
//
// (not every microcontroller platform supports all functions)

/*----------------------------------------------------------------------+
 |                                                                      |
 |      Preset configuarations                                          |
 |                                                                      |
 +----------------------------------------------------------------------*/

/*----------------------------------------------------------------------+
 |      Arduino Uno config                                              |
 +----------------------------------------------------------------------*/

// Arduino AVR    Gigatron Schematic Controller PCB              Gigatron
// Uno     Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4)
// ------- ------ -------- --------- ---------- ---------------- --------
// Pin 13  PORTB5 None     SER_DATA  11 SER INP 14 SER           2
// Pin 12  PORTB4 7 vSync  SER_LATCH  0 PAR/SER None             3
// Pin 11  PORTB3 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4

#if defined(ARDUINO_AVR_UNO)
 #define platform "ArduinoUno"
 #define maxStorage 32256

 // Pinout reference:
 // https://i2.wp.com/marcusjenkins.com/wp-content/uploads/2014/06/ARDUINO_V2.png

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  13
 #define gigatronLatchPin 12
 #define gigatronPulsePin 11
 #define gigatronPinToBitMask digitalPinToBitMask

 // Pins for Controller
 #define gameControllerDataPin -1

 // Pins for PS/2 keyboard (Arduino Uno)
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      Arduino Nano config                                             |
 +----------------------------------------------------------------------*/

// Arduino   AVR    Gigatron Schematic Controller PCB              Gigatron Controller
// Nano      Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4) DB9
// -------   ------ -------- --------- ---------- ---------------- -------- -------
// Pin J2-15 PORTB5 None     SER_DATA  11 SER INP 14 SER           2        None
// Pin J1-15 PORTB4 7 vSync  SER_LATCH  0 PAR/SER None             3        3
// Pin J1-14 PORTB3 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4        4
// Pin J1-13 PORTB2 None     None      None       None             None     2

#if defined(ARDUINO_AVR_NANO)
 // at67's setup
 #define platform "ArduinoNano"
 #define maxStorage 30720

 // Pinout reference:
 // http://lab.dejaworks.com/wp-content/uploads/2016/08/Arduino-Nano-1024x500.png
 // Note that pin 11 and 12 are wrong on some versions of these diagrams

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  13 // PB5
 #define gigatronLatchPin 12 // PB4
 #define gigatronPulsePin 11 // PB3
 #define gigatronPinToBitMask digitalPinToBitMask // Regular Arduino pin numbers

 // Pins for Controller
 #define gameControllerDataPin 10 // PB2

 // Pins for PS/2 keyboard
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      Arduino Micro config                                            |
 +----------------------------------------------------------------------*/

// See also: https://forum.gigatron.io/viewtopic.php?f=4&t=33

// Arduino AVR    Gigatron Schematic Controller PCB              Gigatron
// Micro   Name   OUT bit            CD4021     74HC595 (U39)    DB9 (J4)
// ------- ------ -------- --------- ---------- ---------------- --------
// SCLK    PORTB1 None     SER_DATA  11 SER INP 14 SER           2
// MISO    PORTB3 7 vSync  SER_LATCH  0 PAR/SER None             3
// MOSI    PORTB2 6 hSync  SER_PULSE 10 CLOCK   11 SRCLK 12 RCLK 4

// --------------------+
//               Reset |
// Arduino       +---+ |
//  Micro        | O | |
//               +---+ |
//                     |
//               2 4 6 |
//        ICSP-> o o o |
//        Port  .o o o |
//               1 3 5 |
// --------------------+

#if defined(ARDUINO_AVR_MICRO)
 // WattSekunde's setup
 #define platform "ArduinoMicro"
 #define maxStorage 28672

 // Pinout reference:
 // http://1.bp.blogspot.com/-xqhL0OrJcxo/VJhVxUabhCI/AAAAAAABEVk/loDafkdqLxM/s1600/micro_pinout.png

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  PB1
 #define gigatronLatchPin PB3
 #define gigatronPulsePin PB2
 // These are not regular Arduino pin numbers
 #define gigatronPinToBitMask(pin) (1 << (pin))

 // Pins for Controller
 #define gameControllerDataPin -1

 // Pins for PS/2 keyboard
 #define keyboardClockPin 3 // Pin 2 or 3 for IRQ
 #define keyboardDataPin  4 // Any available free pin

 // Link to PC/laptop
 #define hasSerial 1
#endif

/*----------------------------------------------------------------------+
 |      ATtiny85 config                                                 |
 +----------------------------------------------------------------------*/

// Used settings in Arduino IDE 1.8.1:
//   Board:       ATtiny
//   Processor:   ATtiny85
//   Clock:       8 MHz (internal) --> Use "Burn Bootloader" to configure this
//   Programmer:  Arduino as ISP
// See also:
//   https://create.arduino.cc/projecthub/arjun/programming-attiny85-with-arduino-uno-afb829

//                       +------+
//              ~RESET --|1.   8|-- Vcc
// PS/2 data       PB3 --|2    7|-- PB2  Serial data out
// PS/2 clock      PB4 --|3    6|-- PB1  Serial latch in
//                 GND --|4    5|-- PB0  Serial pulse in
//                       +------+
//                       ATtiny85

#if defined(ARDUINO_attiny)
 #define platform "ATtiny85"
 #define maxStorage 8192

 // Pins for Gigatron (must be on PORTB)
 #define gigatronDataPin  PB2
 #define gigatronLatchPin PB1
 #define gigatronPulsePin PB0
 // These are not regular Arduino pin numbers
 #define gigatronPinToBitMask(pin) (1 << (pin))

 // Pins for Controller
 #define gameControllerDataPin -1 // XXX Maybe use ~RESET for this some day

 // Pins for PS/2 keyboard
 #define keyboardClockPin PB4
 #define keyboardDataPin  PB3

 // Link to PC/laptop
 #define hasSerial 0
#endif

/*----------------------------------------------------------------------+
 |                                                                      |
 |      Built-in GT1 images                                             |
 |                                                                      |
 +----------------------------------------------------------------------*/

const byte TinyBASIC_gt1[] PROGMEM = {
  #include "TinyBASIC_v2.h"
};
const byte WozMon_gt1[]    PROGMEM = {
  #include "WozMon.h"
};
const byte Terminal_gt1[]  PROGMEM = {
  #include "Terminal.h"
};
const byte Blinky_gt1[]    PROGMEM = {
  #include "Blinky.h"
};
const byte bricks_gt1[]    PROGMEM = {
  #include "bricks.h"
};
const byte lines_gt1[]     PROGMEM = {
  #include "lines.h"
};
const byte life3_gt1[]     PROGMEM = {
  #include "life3.h"
};
const byte starfield_gt1[] PROGMEM = {
  #include "starfield.h"
};
const byte tetris_gt1[]    PROGMEM = {
  #include "tetris.h"
};

const struct { const byte *gt1; const char *name; } gt1Files[] = {
  { TinyBASIC_gt1, "BASIC"                    }, // 3048 bytes
#if maxStorage >= 10000
  { WozMon_gt1,    "WozMon"                   }, // 595 bytes
  { Terminal_gt1,  "Terminal"                 }, // 256 bytes
  { Blinky_gt1,    "Blinky"                   }, // 17 bytes
  { lines_gt1,     "Lines demo [at67]"        }, // 304 bytes
  { life3_gt1,     "Game of Life demo [at67]" }, // 441 bytes
  { starfield_gt1, "Starfield demo [at67]"    }, // 817 bytes
#endif
#if maxStorage >= 30000
  { bricks_gt1,    "Bricks game [xbx]"        }, // 1607 bytes
  { tetris_gt1,    "Tetris game [at67]"       }, // 9868 bytes
#endif
  { NULL,          "-SAVED-"                  },
};

/*----------------------------------------------------------------------+
 |                                                                      |
 |      End config section                                              |
 |                                                                      |
 +----------------------------------------------------------------------*/

/*
 *  Bit masks for pins
 */
byte gigatronDataBit;
byte gigatronLatchBit;
byte gigatronPulseBit;

/*
 *  Loader protocol
 */
#define N 60         // Payload bytes per transmission frame
byte checksum;       // Global is simplest
byte outBuffer[256]; // sendFrame() will read up to index 299 but that's ok.
                     // outBuffer[] is global, because having it on the stack
                     // can cause trouble on the ATtiny85 (not fully clear why)
/*
 *  Game controller button mapping
 */
#define buttonRight  1
#define buttonLeft   2
#define buttonDown   4
#define buttonUp     8
#define buttonStart  16
#define buttonSelect 32
#define buttonB      64
#define buttonA      128
// Note: The kit's game controller gives inverted signals.

/*
 *  Font data
 */
const int tinyfont[96] PROGMEM = {
  #include "tinyfont.h"
};

/*
 *  Terminal mode for upstream host
 */
static bool echo = false;

/*
 *  Non-volatile memory
 */
#include <EEPROM.h>

struct EEPROMlayout {
  byte keymapIndex;
  byte savedFile[];
};

#define fileStart offsetof(struct EEPROMlayout, savedFile)
static word saveIndex = fileStart; // Write pointer into EEPROM for file (BASIC)
static word EEPROM_length;

#define arrayLen(a) ((int) (sizeof(a) / sizeof((a)[0])))
extern const byte nrKeymaps; // From in PS2.ino

/*
 *  Setup runs once when the Arduino wakes up
 */
void setup()
{
  gigatronDataBit  = gigatronPinToBitMask(gigatronDataPin);
  gigatronLatchBit = gigatronPinToBitMask(gigatronLatchPin);
  gigatronPulseBit = gigatronPinToBitMask(gigatronPulsePin);

  // Enable output pin (pins are set to input by default)
  PORTB |= gigatronDataBit; // Send 1 when idle
  DDRB = gigatronDataBit;

  // Open upstream communication
  #if hasSerial
    Serial.begin(115200);
    doVersion();
  #endif

  // Cache for speed
  EEPROM_length = EEPROM.length();

  // In case we power on together with the Gigatron, this is a
  // good pause to wait for the video loop to have started
  delay(350);

  // Note that it takes 500~750 ms for PS/2 keyboards to boot
  keyboard_setup();

  prompt();
}

/*
 *  Loop runs repeatedly
 */
void loop()
{
  // Check Gigatron's vPulse for incoming data
  static byte inByte, inBit, inLen;

  critical();
  byte newValue = waitVSync();
  nonCritical();

  switch (newValue) {

    case 9:                            // Received one bit
      inByte |= inBit;
      // !!! FALL THROUGH !!!

    case 7:                            // Received zero bit
      inBit <<= 1;

      if (saveIndex >= EEPROM_length)  // EEPROM overflow
        if (inLen > 0)                 // Only break if this can't be a new program
          sendController(3, 10);       // Send long Ctrl-C back

      if (inBit != 0)
        break;

      EEPROM.write(saveIndex++, inByte);// Store every full byte

      #if hasSerial
        if (inByte == 10)
          Serial.print('\r');
        Serial.print((char)inByte);    // Forward to host
        noInterrupts();
      #endif

      if (inByte == 10) {              // End of line?
        EEPROM.write(saveIndex, 255);  // Terminate, in case this was the last line
        if (inLen == 0)                // An empty line clears the old program
          saveIndex = fileStart;
        inLen = 0;
      } else
        inLen++;

      inByte = 0;                      // Prepare for next byte
      inBit = 1;
      break;

    default:
      inByte = 0;                      // Reset incoming data state
      inBit = 1;
      inLen = 0;
      break;
  }

  // Game controller pass through
  #if gameControllerDataPin >= 0
    digitalWrite(gigatronDataPin, digitalRead(gameControllerDataPin));
  #endif

  // PS/2 keyboard events
  delay(15);                           // Allow PS/2 interrupts for a reasonable window
  byte key = keyboard_getState();
  if (key != 255) {
    byte f = fnKey(key ^ 64);          // Ctrl+Fn key?
    if (f) {
      if (f == 1)
        doMapping();                   // Ctrl-F1 is help
      else if (f-2 < arrayLen(gt1Files)) {
        if (gt1Files[f-2].gt1)
          doTransfer(gt1Files[f-2].gt1); // Send built-in GT1 file to Gigatron
        else
          sendSavedFile();
      }
    }
    for (;;) {                         // Focus all attention on PS/2 until state is idle again
      if (!fnKey(key ^ 64)) {          // Filter away the Ctrl+Fn combinations here
        critical();
        sendFirstByte(key);            // Synchronize with vPulse and send ASCII code
        nonCritical();
      }
      if (key == 255)                  // Break after returning to the idle state
        break;
      delay(15);                       // Allow PS/2 interrupts, so we can receive break codes
      key = keyboard_getState();       // This typically returns the same key for a couple of frames
    }
  }

  // Commands from upstream USB (PC/laptop)
  #if hasSerial
    static char line[32], next = 0, last;
    static byte lineIndex = 0;
    if (Serial.available()) {
      last = next;
      char next = Serial.read();
      sendEcho(next, last);
      if (lineIndex < sizeof line)
        line[lineIndex++] = next;
      if (next == '\r' || next == '\n') {
        line[lineIndex-1] = '\0';
        doCommand(line);
        lineIndex = 0;
      }
    }
  #endif
}

void prompt()
{
  #if hasSerial
    Serial.println(detectGigatron() ? ":Gigatron OK" : "!Gigatron offline");
    Serial.println("Cmd?");
  #endif
}

bool detectGigatron()
{
  unsigned long timeout = millis() + 85;
  long T[4] = {0, 0, 0, 0};

  // Sample the sync signals coming out of the controller port
  while (millis() < timeout) {
    byte pinb = PINB; // capture SER_PULSE and SER_LATCH at the same time
    T[(pinb & gigatronLatchBit ? 2 : 0) + (pinb & gigatronPulseBit ? 1 : 0)]++;
  }

  float S = T[0] + T[1] + T[2] + T[3] + .1;     // Avoid zero division (pedantic)
  float vSync = (T[0] + T[1]) / ( 8 * S / 521); // Adjusted vSync signal
  float hSync = (T[0] + T[2]) / (96 * S / 800); // Standard hSync signal

  // Check that vSync and hSync characteristics look normal
  return 0.95 <= vSync && vSync <= 1.20 && 0.90 <= hSync && hSync <= 1.10;
}

void sendEcho(char next, char last)
{
  #if hasSerial
    if (echo)
      switch (next) {
        case 127:  Serial.print("\b \b"); break;
        case '\n': if (last == '\r')      break; // else FALL THROUGH
        case '\r': Serial.println();      break;
        default: Serial.print(next);
      }
  #endif
}

void doCommand(char line[])
{
  int arg = line[0] ? atoi(&line[1]) : 0;
  switch (toupper(line[0])) {
  case 'V': doVersion();                      break;
  case 'H': doHelp();                         break;
  case 'R': doReset(arg);                     break;
  case 'L': doLoader();                       break;
  case 'M': doMapping();                      break;
  case 'P': if (0 <= arg && arg < arrayLen(gt1Files))
              doTransfer(gt1Files[arg].gt1);  break;
  case 'U': doTransfer(NULL);                 break;
  case '.': doLine(&line[1]);                 break;
  case 'C': doEcho(!echo);                    break;
  case 'T': doTerminal();                     break;
  case 'W': sendController(~buttonUp,     2); break;
  case 'A': sendController(~buttonLeft,   2); break;
  case 'S': sendController(~buttonDown,   2); break;
  case 'D': sendController(~buttonRight,  2); break;
  case 'Z': sendController(~buttonA & 255,2); break;
  case 'X': sendController(~buttonB,      2); break;
  case 'Q': sendController(~buttonSelect, 2); break;
  case 'E': sendController(~buttonStart,  2); break;
  case 0: /* Empty line */                    break;
  default:
    #if hasSerial
      Serial.println("!Unknown command (type 'H' for help)");
    #endif
    ;
  }
  prompt();
}

void doVersion()
{
  #if hasSerial
    Serial.println(":BabelFish platform=" platform);
    Serial.println(":Pins:");
    #define V(s) #s
    #define Q(s) V(s)
    Serial.println(": Gigatron data=" Q(gigatronDataPin) " latch=" Q(gigatronLatchPin) " pulse=" Q(gigatronPulsePin));
    Serial.println(": Keyboard clock=" Q(keyboardClockPin) " data=" Q(keyboardDataPin));
    Serial.println(": Controller data=" Q(gameControllerDataPin));
    Serial.println(":EEPROM:");
    Serial.print(": size=");
    Serial.print(EEPROM.length());
    Serial.print(" mapping=");
    Serial.println(getKeymapName());
    Serial.println(":PROGMEM slots:");
    for (byte i=0; i<arrayLen(gt1Files); i++) {
      Serial.print(": P");
      Serial.print(i);
      Serial.print(") ");
      Serial.println(gt1Files[i].name);
    }
    doEcho(echo);
    Serial.println(":Type 'H' for help");
  #endif
}
{'fraction_non_alphanumeric': 0.1738302721544443, 'fraction_numerical': 0.028207534522053896, 'mean_word_length': 1.505441354292624, 'pattern_counts': {'":': 22, '<': 41, '<?xml version=': 0, '>': 21, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 62, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6692319', 'n_tokens_mistral': 11953, 'n_tokens_neox': 10853, 'n_words': 3407}