Pluggy Reloaded
Forum rules
Be nice. No drama.
Be nice. No drama.
Re: Pluggy Reloaded
Thanks for the offer! This project is also an excuse for me to learn EDA Tools. I'll see how far I can get with KiCad by myself and might get back to you.
Re: Pluggy Reloaded
After some more prototyping with a Pro Micro Board and messing around with the BabelFish code, I have now managed to get the game controller and the PS/2 keyboard to work simultaneously. I had to temporarily disable serial output from the Gigatron but it shouldn't be too hard to get that working again.
This is the schematic from the photo above:
I have also ordered some Micro SD breakout boards, to test out monsonites suggestion.
After testing those, I'll start laying out a PCB. For now it'll just be a carrier board for the Pro Micro and the Micro SD breakout.
While experimenting I have noticed, that for some reason the Gigatron can be partially powered through the data pin on the game controller port.
With the schematic above, if I power the Pro Micro through USB but disconnect power to the Gigatron, the Blinkenlights will stay on (the animation stops). As soon as I disconnect the data pin the LED's go out.
Since this is less than ideal, do you perhaps have an idea what I could do about this?
I can only think of pulling the data pin low in Software when there's no power from the Gigatron.
This is the schematic from the photo above:
I have also ordered some Micro SD breakout boards, to test out monsonites suggestion.
After testing those, I'll start laying out a PCB. For now it'll just be a carrier board for the Pro Micro and the Micro SD breakout.
While experimenting I have noticed, that for some reason the Gigatron can be partially powered through the data pin on the game controller port.
With the schematic above, if I power the Pro Micro through USB but disconnect power to the Gigatron, the Blinkenlights will stay on (the animation stops). As soon as I disconnect the data pin the LED's go out.
Since this is less than ideal, do you perhaps have an idea what I could do about this?
I can only think of pulling the data pin low in Software when there's no power from the Gigatron.
Re: Pluggy Reloaded
Can you explain or show how you did it? I like to fix it in BabelFish soon, as I'm about to do some experiments with the non-clone Arduino Nano that just arrived. I also plan to fix an old issue, and BabelFish needs an update for MS BASIC as well. I can try to address and test this in the same effort.I have now managed to get the game controller and the PS/2 keyboard to work simultaneously.
P.S.: Your image links aren't showing in my browser (Safari).
Re: Pluggy Reloaded
Ill post my code later today, when I'm back home.
The images from the last post should be attached.
The images from the last post should be attached.
- Attachments
-
- screenshot_2019-10-26-173654.png (169.53 KiB) Viewed 7732 times
-
- IMG_20191026_153514.jpg (256.3 KiB) Viewed 7732 times
Re: Pluggy Reloaded
What I did to get the Game Controller passthrough working again is basically just manually pulsing the game controllers latch and clock pin while reading from the data pin, and then sending the controller state to the Gigatron every Frame (except when a key of the keyboard is pressed).
I had to reduce the delay for the PS/2 interrupts, because missing a frame when sending the controller state would lead to repeated presses of a button.
To get back the Gigatron-> PC serial interface you would have to add the HSync counting functionality from waitVSync to sendFirstByte (both have to happen simultaneously right after VSync). Ill probably do this when I have the SD Card functionality fully working.
Here's the modified Babelfish.ino for the Game Controller passthrough
Ill post the SD Card code when its fully working.
I had to reduce the delay for the PS/2 interrupts, because missing a frame when sending the controller state would lead to repeated presses of a button.
To get back the Gigatron-> PC serial interface you would have to add the HSync counting functionality from waitVSync to sendFirstByte (both have to happen simultaneously right after VSync). Ill probably do this when I have the SD Card functionality fully working.
Here's the modified Babelfish.ino for the Game Controller passthrough
Code: Select all
// 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
// SD Card
#define sdChipSelectPin -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
/*----------------------------------------------------------------------+
| Pro Micro config |
+----------------------------------------------------------------------*/
// Arduino AVR Gigatron Schematic Controller PCB Gigatron
// Pro Micro 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_PROMICRO)
#define platform "ArduinoProMicro"
#define maxStorage 28672 // TODO: find out exact value
// Pins for Gigatron (must be on PORTB)
#define gigatronDataPin 8
#define gigatronLatchPin 9
#define gigatronPulsePin 10
#define gigatronPinToBitMask digitalPinToBitMask
// Pins for Controller
#define gameControllerDataPin 5
#define gameControllerLatchPin 6
#define gameControllerPulsePin 7
// 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
/*----------------------------------------------------------------------+
| |
| Built-in GT1 images |
| |
+----------------------------------------------------------------------*/
const byte TinyBASIC_gt1[] PROGMEM = {
#include "TinyBASIC_v3.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" }, // 3073 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;
// Set pin modes for game controller passthrough
#if gameControllerDataPin >= 0
pinMode(gameControllerDataPin, INPUT);
pinMode(gameControllerLatchPin, OUTPUT);
pinMode(gameControllerPulsePin, OUTPUT);
#endif
// 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
critical();
byte gameControllerData = 0;
digitalWrite(gameControllerLatchPin, HIGH);
delayMicroseconds(50);
digitalWrite(gameControllerLatchPin, LOW);
delayMicroseconds(50);
for(byte i = 0; i < 8; i++) {
gameControllerData <<= 1;
gameControllerData |= digitalRead(gameControllerDataPin);
digitalWrite(gameControllerPulsePin, HIGH);
delayMicroseconds(50);
digitalWrite(gameControllerPulsePin, LOW);
delayMicroseconds(50);
}
sendFirstByte(gameControllerData);
nonCritical();
#endif
// PS/2 keyboard events
delay(10); // 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
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
}
// Show keymapping in Loader screen (Loader must be running)
void doMapping()
{
word pos = 0x800;
char text[] = "Ctrl-F1 This help";
// 0123456789
pos = renderLine(pos, text);
text[9] = 0;
for (byte i=0; i<arrayLen(gt1Files); i++) {
byte f = i + 2;
// To save space avoid itoa() or sprintf()
text[6] = '0' + f / 10;
text[7] = ' ';
text[6+f/10] = '0' + f % 10;
pos = renderString(pos, text);
pos = renderLine(pos, gt1Files[i].name);
}
pos = renderString(pos, "Keymap: ");
pos = renderString(pos, getKeymapName());
pos = renderLine(pos, " (Change with Ctrl-Alt-Fxx)");
pos = renderString(pos, "Available:");
for (byte i=0; i<nrKeymaps; i++) {
pos = renderString(pos, " ");
pos = renderString(pos, getKeymapName(i));
}
}
void doEcho(byte value)
{
#if hasSerial
echo = value;
Serial.print(":Echo ");
Serial.println(value ? "on" : "off");
#endif
}
void doHelp()
{
#if hasSerial
Serial.println(":Commands are");
Serial.println(": V Show configuration");
Serial.println(": H Show this help");
Serial.println(": R Reset Gigatron");
Serial.println(": L Start Loader");
Serial.println(": M Show key mapping or menu in Loader screen");
Serial.println(": P[<n>] Transfer object file from PROGMEM slot <n> [1..12]");
Serial.println(": U Transfer object file from USB");
Serial.println(": .<text> Send text line as ASCII key strokes");
Serial.println(": C Toggle echo mode (default off)");
Serial.println(": T Enter terminal mode");
Serial.println(": W/A/S/D Up/left/down/right arrow");
Serial.println(": Z/X A/B button");
Serial.println(": Q/E Select/start button");
#endif
}
void doReset(int n)
{
// Soft reset: hold start for >128 frames (>2.1 seconds)
#if hasSerial
Serial.println(":Resetting Gigatron");
Serial.flush();
#endif
sendController(~buttonStart, n ? n : 128+32);
// Wait for main menu to be ready
delay(1500);
}
void doLoader()
{
// Navigate menu. 'Loader' is at the bottom
#if hasSerial
Serial.println(":Starting Loader from menu");
Serial.flush();
#endif
for (byte i=0; i<10; i++) {
sendController(~buttonDown, 2);
delay(50);
}
// Start 'Loader' application on Gigatron
sendController(~buttonA & 255, 2);
// Wait for Loader to be running
delay(1000);
}
void doLine(char *line)
{
// Pass through the line of text
for (byte i=0; line[i]; i++) {
sendController(line[i], 1);
delay(20); // Allow Gigatron software to process key code
}
// And terminal with a CR
sendController('\n', 1);
delay(50); // Allow Gigatron software to process line
}
// In terminal mode we transfer every incoming character to
// the Gigatron, with some substitutions for convenience.
// This lets you type directly into BASIC and WozMon from
// a terminal window on your PC or laptop.
//
// picomon -b 115200 /dev/tty.usbmodem1411
// screen /dev/tty.usbmodem1411 115200
void doTerminal()
{
#if hasSerial
Serial.println(":Entering terminal mode");
Serial.println(":Exit with Ctrl-D");
char next = 0, last;
byte out;
bool ansi = false;
for (;;) {
if (Serial.available()) {
last = next;
next = Serial.read();
sendEcho(next, last);
// Mappings for newline and arrow sequences
out = next;
switch (next) {
case 4: return; // Ctrl-D (EOT)
case 9: out = ~buttonB; break; // Same as PS/2 above
case '\r': out = '\n'; break;
case '\n': if (last == '\r') continue; // Swallow
case '\e': continue; // ANSI escape sequence
case '[': if (last == '\e') ansi = true; continue;
case 'A': if (ansi) out = ~buttonUp; break; // Map cursor keys to buttons
case 'B': if (ansi) out = ~buttonDown; break;
case 'C': if (ansi) out = ~buttonRight; break;
case 'D': if (ansi) out = ~buttonLeft; break;
}
sendController(out, 2);
ansi = false;
}
}
#endif
}
// Render line in Loader screen
word renderLine(word pos, const char *text)
{
pos = renderString(pos, text);
return (pos & 0xff00) + 0x600; // Goes to new line
}
// Render string in Loader screen
word renderString(word pos, const char text[])
{
// Send 6 pixel lines to Gigatron
// The ATtiny85 doesn't have sufficient RAM for separate bitmap[] and
// pixelLine[] arrays. Therefore the rendering must be redone with each
// iteration, followed by an in-place conversion to pixel colors
word p = pos;
byte x;
for (byte b=32; b; b>>=1) {
// (Re-)render line of text in bitmap
x = 0;
for (byte i=0; text[i]!=0; i++) {
// Get pixel data for character
int pixels = pgm_read_word(&tinyfont[text[i]-32]);
// Render character in bitmap
if (pixels >= 0) {
outBuffer[x++] = 0; // Regular position
outBuffer[x++] = (pixels >> 9) & 62;
outBuffer[x++] = (pixels >> 4) & 62;
outBuffer[x++] = (pixels << 1) & 62;
} else {
outBuffer[x++] = 0; // Shift down for g, j, p, q, y
outBuffer[x++] = (pixels >> 10) & 31;
outBuffer[x++] = (pixels >> 5) & 31;
outBuffer[x++] = pixels & 31;
if (text[i] == 'j') // Special case to dot the j
outBuffer[x-1] = '.';
}
}
// Convert bitmap to pixels
const byte bgColor = 32; // Blue
const byte fgColor = 63; // White
for (byte i=0; i<x; i++)
outBuffer[i] = (outBuffer[i] & b) ? fgColor : bgColor;
// Send line of pixels to Gigatron
sendGt1Segment(p, x);
// To next scanline
p += 256;
}
return pos + x;
}
// Because the Arduino doesn't have enough RAM to buffer
// a complete GT1 file, it processes these files segment
// by segment. Each segment is transmitted downstream in
// concatenated frames to the Gigatron. Between segments
// it is communicating upstream with the serial port.
void doTransfer(const byte *gt1)
{
int nextByte;
#if hasSerial
if (!waitVSync()) {
Serial.print("!Failed");
return;
}
#endif
#if hasSerial
#define readNext()\
if (gt1)\
nextByte = pgm_read_byte(gt1++);\
else {\
nextByte = nextSerial();\
if (nextByte < 0) return;\
}
#define ask(n)\
if (!gt1) {\
Serial.print(n);\
Serial.println("?");\
}
#else
#define readNext() (nextByte = pgm_read_byte(gt1++))
#define ask(n)
#endif
ask(3);
readNext();
word address = nextByte;
// Any number n of segments (n>0)
do {
// Segment start and length
readNext();
address = (address << 8) + nextByte;
readNext();
int len = nextByte ? nextByte : 256;
ask(len);
// Copy data into send buffer
for (int i=0; i<len; i++) {
readNext();
outBuffer[i] = nextByte;
}
// Check that segment doesn't cross the page boundary
if ((address & 255) + len > 256) {
#if hasSerial
Serial.println("!Data error (page overflow)");
#endif
return;
}
// Send downstream
#if hasSerial
Serial.print(":Loading ");
Serial.print(len);
Serial.print(" bytes at $");
Serial.println(address, HEX);
Serial.flush();
#endif
sendGt1Segment(address, len);
// Signal that we're ready to receive more
ask(3);
readNext();
address = nextByte;
} while (address != 0);
// Two bytes for start address
readNext();
address = nextByte;
readNext();
address = (address << 8) + nextByte;
if (address != 0) {
#if hasSerial
Serial.print(":Executing from $");
Serial.println(address, HEX);
Serial.flush();
#endif
sendGt1Execute(address, outBuffer+240);
}
}
int nextSerial()
{
#if hasSerial
unsigned long timeout = millis() + 5000;
while (!Serial.available() && millis() < timeout)
;
int nextByte = Serial.read();
if (nextByte < 0)
Serial.println("!Timeout error (no data)");
// Workaround suspected bug in USB support for ATmega32U4 (Arduino Micro,
// Leonardo, etcetera) in Arduino's USBCore.cpp. These boards don't have a
// support processor for USB handling but use an on-chip USB controller
// and a different software stack for that.
// From Atmel-7766J-USB-ATmega16U4/32U4-Datasheet_04/2016:
//
// "RXOUTI shall always be cleared before clearing FIFOCON."
//
// (An identical remark is in the datasheets for ATmega32U6/AT90USB64/128)
//
// However:
// Serial.read() ->
// CDC.cpp/Serial_::read ->
// USBCore.cpp/USB_Recv() ->
// USBCore.cpp/ReleaseRX() ->
// UEINTX = 0x6B; // FIFOCON=0 NAKINI=1 RWAL=1 NAKOUTI=0 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=1
//
// This last statement attempts to clear both bits AT ONCE. This fails to
// clear FIFOCON when host data arrives in exact multiples of 64,128,192,...
// bytes and when using double buffering with two banks of bytes, as
// USBCore.cpp does. A hangup situation occurs after reading the first
// transmitted 64 bytes. This can then only be solved by resetting the board,
// because no further host data reaches the sketch.
//
// A better fix would be to repair Arduino's USB_Recv and ReleaseRX.
// See for follow-up https://github.com/arduino/Arduino/issues/7838
// and https://github.com/kervinck/gigatron-rom/issues/36
#if defined(USBCON) && defined(UEINTX) && defined(UEBCLX)
if (!UEBCLX) // If bank empty
UEINTX &= ~(1 << FIFOCON); // Clear FIFOCON bit
#endif
return nextByte;
#endif
}
/*----------------------------------------------------------------------+
| |
| Gigatron communication |
| |
+----------------------------------------------------------------------*/
static inline void critical()
{
forbidPs2();
noInterrupts();
}
static inline void nonCritical()
{
interrupts();
allowPs2();
}
// Send a 1..256 byte code or data segment into the Gigatron by
// repacking it into Loader frames of max N=60 payload bytes each.
void sendGt1Segment(word address, int len)
{
byte n = min(N, len);
resetChecksum();
// Send segment data
critical();
for (int i=0; i<len; i+=n) {
n = min(N, len-i);
sendFrame('L', n, address+i, outBuffer+i);
}
nonCritical();
// Wait for vPulse to start so we're 100% sure to skip one frame and
// the checksum resets on the other side. (This is a bit pedantic)
while (PINB & gigatronLatchBit) // ~160 us
;
}
// Send execute command
void sendGt1Execute(word address, byte data[])
{
critical();
resetChecksum();
sendFrame('L', 0, address, data);
nonCritical();
}
// Pretend to be a game controller
// Send the same byte a few frames, just like a human user
void sendController(byte value, int n)
{
// Send controller code for n frames
// E.g. 4 frames = 3/60s = ~50 ms
critical();
for (int i=0; i<n; i++)
sendFirstByte(value);
nonCritical();
PORTB |= gigatronDataBit; // Send 1 when idle
}
void resetChecksum()
{
// Setup checksum properly
checksum = 'g';
}
void sendFrame(byte firstByte, byte len, word address, byte message[])
{
// Send one frame of data
//
// A frame has 65*8-2=518 bits, including protocol byte and checksum.
// The reasons for the two "missing" bits are:
// 1. From start of vertical pulse, there are 35 scanlines remaining
// in vertical blank. But we also need the payload bytes to align
// with scanlines where the interpreter runs, so the Gigatron doesn't
// have to shift everything it receives by 1 bit.
// 2. There is a 1 bit latency inside the 74HC595 for the data bit,
// but (obviously) not for the sync signals.
// All together, we drop 2 bits from the 2nd byte in a frame. This achieves
// byte alignment for the Gigatron at visible scanline 3, 11, 19, ... etc.
sendFirstByte(firstByte); // Protocol byte
checksum += firstByte << 6; // Keep Loader.gcl dumb
sendBits(len, 6); // Length 0, 1..60
sendBits(address&255, 8); // Low address bits
sendBits(address>>8, 8); // High address bits
for (byte i=0; i<N; i++) // Payload bytes
sendBits(message[i], 8);
byte lastByte = -checksum; // Checksum must come out as 0
sendBits(lastByte, 8);
checksum = lastByte; // Concatenate checksums
PORTB |= gigatronDataBit; // Send 1 when idle
}
void sendFirstByte(byte value)
{
// Wait vertical sync NEGATIVE edge to sync with loader
while (~PINB & gigatronLatchBit) // Ensure vSync is HIGH first
;
// Send first bit in advance
if (value & 128)
PORTB |= gigatronDataBit;
else
PORTB &= ~gigatronDataBit;
while (PINB & gigatronLatchBit) // Then wait for vSync to drop
;
// Wait for bit transfer at horizontal sync RISING edge. As this is at
// the end of a short (3.8 us) pulse following VERY shortly (0.64us) after
// vSync drop, this timing is tight. That is the reason that interrupts
// must be disabled on the microcontroller (and why 1 MHz isn't enough).
while (PINB & gigatronPulseBit) // Ensure hSync is LOW first
;
while (~PINB & gigatronPulseBit) // Then wait for hSync to rise
;
// Send remaining bits
sendBits(value, 7);
}
// Send n bits, highest first
void sendBits(byte value, byte n)
{
for (byte bit=1<<(n-1); bit; bit>>=1) {
// Send next bit
if (value & bit)
PORTB |= gigatronDataBit;
else
PORTB &= ~gigatronDataBit;
// Wait for bit transfer at horizontal sync POSITIVE edge.
while (PINB & gigatronPulseBit) // Ensure hSync is LOW first
;
while (~PINB & gigatronPulseBit) // Then wait for hSync to rise
;
}
checksum += value;
}
// Count number of hSync pulses during vPulse
// This is a way for the Gigatron to send information out
byte waitVSync()
{
word timeout = 0; // 2^16 cycles must give at least 17 ms
// Wait vertical sync NEGATIVE edge
while (~PINB & gigatronLatchBit) // Ensure vSync is HIGH first
if (!--timeout)
return 0;
while (PINB & gigatronLatchBit) // Then wait for vSync to drop
if (!--timeout)
return 0;
// Now count horizontal sync POSITIVE edges
byte count = 0;
for (;;) {
while (PINB & gigatronPulseBit) // Ensure hSync is LOW first
;
if (PINB & gigatronLatchBit) // Not in vPulse anymore
break;
while (~PINB & gigatronPulseBit) // Then wait for hSync to rise
;
count += 1;
}
return count;
}
/*----------------------------------------------------------------------+
| |
| EEPROM functions |
| |
+----------------------------------------------------------------------*/
// Send a saved file as keystrokes to the Gigatron
void sendSavedFile()
{
word i = fileStart;
do {
byte nextByte = EEPROM.read(i);
if (nextByte == 255)
break;
sendController(nextByte, 1);
delay(nextByte == 10 ? 70 : 20); // Allow Gigatron software to process byte
} while (++i < EEPROM.length());
}
Ill post the SD Card code when its fully working.
Re: Pluggy Reloaded
Thanks. When you press a game controller button now, doesn't it get sent intermittently to the Gigatron? It appears the code will alternate between calling waitVSync() and sendFirstByte() in that case. Both wait for the vertical pulse. For example, does the soft reset function work with this change?
I was thinking of looping locally, like this:
I was thinking of looping locally, like this:
Code: Select all
for (;;) {
byte serialByte = 0;
sendPulse(gameControllerLatchPin);
for (byte i=0; i<8; i++) { // Shift in all 8 bits
serialByte <<= 1;
serialByte |= digitalRead(gameControllerDataPin);
sendPulse(gameControllerPulsePin);
}
if (serialByte == 255)
break;
critical(); // Forward byte to Gigatron
sendFirstByte(serialByte);
nonCritical();
}
Re: Pluggy Reloaded
The code for the Gigatron->PC Serial link containing the call to waitVSync() is commented out, so game controller data ist sent every VSync Pulse. Should have probably deleted that part to make it more clear. As I mentioned in my last post this serial functionality is something I want to bring back by moving the HSync counting to sendFirstByte() but havent gotten around to that yet. Soft reset works with the code I posted.
Re: Pluggy Reloaded
After some consideration your solution seems to be the better option, since its more consistent with the PS/2 code and doesn't require reworking the serial code. Only downside is that while a button is held down nothing else works. But that was already the case with the PS/2 keyboard and shouldn't affect the regular use case.
Re: Pluggy Reloaded
I have this part (pass through) working now on my Arduino Nano as well, so I just pushed it into GitHub.
Edit: I found that my
is better done as
because that also keeps the data line high while idle and that prevents some trouble.
Edit: I found that my
Code: Select all
critical(); // Forward byte to Gigatron
sendFirstByte(serialByte);
nonCritical();
Code: Select all
sendController(serialByte, 1); // Forward byte to Gigatron
Re: Pluggy Reloaded
Very nice!
In the meanwhile I have managed to get the SD card file transfer finally working. You can check out the code in my fork of the git repo:
https://github.com/DerTyp321/gigatron-rom/
Turns out the issue I was having with that was a stack overflow, due to the Atmega32U4 only having 2K of SRAM. My "fix" was to shorten all the menu strings. Anybody have a better idea? Maybe putting the strings into progmem somehow?
In the meanwhile I have managed to get the SD card file transfer finally working. You can check out the code in my fork of the git repo:
https://github.com/DerTyp321/gigatron-rom/
Turns out the issue I was having with that was a stack overflow, due to the Atmega32U4 only having 2K of SRAM. My "fix" was to shorten all the menu strings. Anybody have a better idea? Maybe putting the strings into progmem somehow?