Altair 8800 Emulator - with ADM-31, ADM-3A, Kaypro, Hazeltine 1500 and Osborne I terminal emulation
#include <Preferences.h>
#include "src/machine.h"
#define FORMAT_ON_FAIL true
#define SPIFFS_MOUNT_PATH "/flash"
#define SDCARD_MOUNT_PATH "/SD"
#define USE_TEXTUAL_DISPLAYCONTROLLER
#include "disks/CPM22/cpm22_dsk.h"
#include "disks/CPM22/games_dsk.h"
#include "disks/CPM22/turbopascal3_dsk.h"
#include "disks/CPM22/wordstar3_dsk.h"
#include "disks/CPM22/wordstar4_dsk.h"
#include "disks/CPM22/multiplan_dsk.h"
#include "disks/CPM22/dbaseii_dsk.h"
#include "disks/CPM22/BDSC_dsk.h"
#include "disks/CPM22/langs_dsk.h"
#include "disks/CPM14/cpm141_dsk.h"
#include "disks/CPM3/cpm3_disk1_dsk.h"
#include "disks/CPM3/cpm3_disk2_dsk.h"
#include "disks/CPM3/cpm3_build_dsk.h"
#include "disks/AltairDOS/DOS_1_dsk.h"
#include "disks/AltairDOS/DOS_2_dsk.h"
#include "disks/basic/basic5_dsk.h"
#include "minidisks/CPM22/cpm22_disk1_minidsk.h"
#include "minidisks/CPM22/cpm22_disk2_minidsk.h"
#include "minidisks/basic/basic300_5F_minidisk.h"
#define DISKFORMAT Disk_338K
#define DRIVE_A cpm22_dsk
#define DRIVE_B games_dsk
#define DRIVE_C "diskC.dsk"
#define DRIVE_D "diskD.dsk"
constexpr int DefaultCPU = 1;
constexpr int DefaultTermIndex = 2;
constexpr int MaxTermIndex = 7;
constexpr int DefaultKbdLayIndex = 2;
constexpr int DefaultRowsCount = 0;
const char * ColorsStr[] = { "Green/Black", "Yellow/Black", "White/Black", "Black/White", "Yellow/Blue", "Black/Yellow" };
constexpr int DefaultColorsIndex = 0;
constexpr int MaxColorsIndex = 5;
#ifdef USE_TEXTUAL_DISPLAYCONTROLLER
#else
#endif
Machine altair;
Mits88Disk diskDrive(&altair, DISKFORMAT);
SIO SIO0(&altair, 0x00);
SIO SIO1(&altair, 0x10);
SIO SIO2(&altair, 0x12);
Preferences preferences;
char const * basepath = nullptr;
{
return (
TermType) preferences.getInt(
"termEmu", DefaultTermIndex);
}
void setupTerminalEmu()
{
}
int getRowsCount()
{
return preferences.getInt("rowsCount", DefaultRowsCount);
}
void setupRowsCount()
{
int rows = getRowsCount();
if (rows <= 0)
else
Terminal.printf("\e[1;%dr", rows);
}
int getKbdLayoutIndex()
{
return preferences.getInt("kbdLay", DefaultKbdLayIndex);
}
void setupKbdLayout()
{
PS2Controller.
keyboard()->
setLayout( SupportedLayouts::layouts()[getKbdLayoutIndex()] );
}
bool getRealCPUSpeed()
{
return preferences.getBool("realSpeed", false);
}
void setupRealCPUSpeed()
{
altair.setRealSpeed(getRealCPUSpeed());
}
int getCPU()
{
return preferences.getInt("CPU", DefaultCPU);
}
bool getEmuCRT()
{
return preferences.getBool("emuCRT", false);
}
int getColorsIndex()
{
return preferences.getInt("colors", DefaultColorsIndex);
}
void setupTerminalColors()
{
int colorsIndex = getColorsIndex();
}
void emulator_menu()
{
bool resetRequired = false;
bool changedRowsCount = false;
for (bool loop = true; loop; ) {
diskDrive.flush();
Terminal.
write(
"\r\n\n\e[97m\e[40m ** Emulator Menu **\e[K\r\n\e[K\n");
Terminal.
write(
"\e[93m Z \e[37m Reset\e[K\n\r");
Terminal.
write(
"\e[93m S \e[37m Send Disk to Serial\e[K\n\r");
Terminal.
write(
"\e[93m R \e[37m Get Disk from Serial\e[K\n\r");
Terminal.
write(
"\e[93m F \e[37m Format FileSystem\e[K\n\r");
Terminal.printf("\e[93m U \e[37m CPU: \e[33m%s\e[K\n\r", getCPU() == 1 ? "Z80" : "i8080");
Terminal.printf("\e[93m P \e[37m Real CPU Speed: \e[33m%s\e[K\n\r", getRealCPUSpeed() ? "YES" : "NO");
Terminal.printf("\t\t\t\t\t\e[93m T \e[37m Terminal: \e[33m%s\e[K\n\r", SupportedTerminals::names()[(int)getTerminalEmu()] );
Terminal.printf("\t\t\t\t\t\e[93m K \e[37m Keyboard Layout: \e[33m%s\e[K\n\r", SupportedLayouts::names()[getKbdLayoutIndex()] );
#ifndef USE_TEXTUAL_DISPLAYCONTROLLER
Terminal.printf("\t\t\t\t\t\e[93m G \e[37m CRT Mode: \e[33m%s\e[K\n\r", getEmuCRT() ? "YES" : "NO");
#endif
Terminal.printf("\t\t\t\t\t\e[93m C \e[37m Colors: \e[33m%s\e[K\n\r", ColorsStr[getColorsIndex()] );
Terminal.printf(
"\t\t\t\t\t\e[93m L \e[37m Rows: \e[33m%d\e[K\n\r", getRowsCount() <= 0 ? Terminal.
getRows() : getRowsCount());
Terminal.
write(
"\t\t\t\t\t\e[93m O \e[37m Reset Configuration\e[K\n\r");
Terminal.
write(
"\n\n\e[97mPlease select an option (ENTER to exit): \e[K\e[92m");
int ch = toupper(Terminal.
read());
switch (ch) {
case 'S':
case 'R':
{
Terminal.
write(
"Select a drive (A, B, C, D...): ");
char drive = toupper(Terminal.
read());
if (ch == 'S') {
Terminal.printf("\n\rSending drive %c...\n\r", drive);
diskDrive.sendDiskImageToStream(drive - 'A', &Serial);
} else if (ch == 'R') {
Terminal.printf("\n\rReceiving drive %c...\n\r", drive);
diskDrive.receiveDiskImageFromStream(drive - 'A', &Serial);
}
break;
}
case 'F':
Terminal.
write(
"\e[91mFormat deletes all disks and files in your SD or SPIFFS!\e[92m\r\nAre you sure? (Y/N)");
if (toupper(Terminal.
read()) ==
'Y') {
Terminal.
write(
"\n\rFormatting...");
FileBrowser::format(FileBrowser::getDriveType(basepath), 0);
resetRequired = true;
}
break;
case 'Z':
Terminal.
write(
"Resetting the Altair. Are you sure? (Y/N)");
if (toupper(Terminal.
read()) ==
'Y')
ESP.restart();
break;
case 'P':
preferences.putBool("realSpeed", !getRealCPUSpeed());
setupRealCPUSpeed();
break;
case 'G':
preferences.putBool("emuCRT", !getEmuCRT());
resetRequired = true;
break;
case 'U':
preferences.putInt("CPU", getCPU() ^ 1);
resetRequired = true;
break;
case 'T':
{
int termIndex = (int)getTerminalEmu() + 1;
if (termIndex > MaxTermIndex)
termIndex = 0;
preferences.putInt("termEmu", termIndex);
break;
}
case 'K':
{
int kbdLayIndex = getKbdLayoutIndex() + 1;
if (kbdLayIndex >= SupportedLayouts::count())
kbdLayIndex = 0;
preferences.putInt("kbdLay", kbdLayIndex);
setupKbdLayout();
break;
}
case 'C':
{
int colorsIndex = getColorsIndex() + 1;
if (colorsIndex > MaxColorsIndex)
colorsIndex = 0;
preferences.putInt("colors", colorsIndex);
break;
}
case 'L':
{
int rows = getRowsCount() + 1;
if (rows < 24)
rows = 24;
if (rows > 25)
rows = 0;
preferences.putInt("rowsCount", rows);
changedRowsCount = true;
break;
}
case 'O':
preferences.clear();
resetRequired = true;
break;
default:
loop = false;
break;
}
}
if (resetRequired) {
Terminal.
write(
"Reset required. Reset now? (Y/N)");
if (toupper(Terminal.
read()) ==
'Y') {
diskDrive.detachAll();
ESP.restart();
}
}
if (changedRowsCount) {
setupRowsCount();
Terminal.
write(
"Clear screen required. Settings applied.\r\n\e\n");
}
setupTerminalColors();
setupTerminalEmu();
}
void setup()
{
preferences.begin("altair8800", false);
Serial.begin(115200);
PS2Controller.
begin(PS2Preset::KeyboardPort0);
DisplayController.
begin();
if (getEmuCRT())
else
Terminal.
begin(&DisplayController);
}
void attachDisk(
int drive,
void const *
data)
{
auto filename = (
char const *)
data;
auto dskimage = (uint8_t
const *)
data;
if (dskimage[0] >= 0x80) {
auto newfilename = String(basepath) + String("/disk") + String((char)('A' + drive)) + String(".dsk");
Terminal.printf("\r\nAttaching disk %c to %s...", 'A' + drive, newfilename.c_str());
diskDrive.attachFileFromImage(drive, newfilename.c_str(), dskimage);
} else {
diskDrive.attachReadOnlyBuffer(drive, dskimage);
}
} else {
if (filename[0] < 0x80) {
Terminal.printf("\r\nAttaching disk %c to %s...", 'A' + drive, filename);
diskDrive.attachFile(drive, (String(basepath) + String("/") + String(filename)).c_str());
}
}
}
void loop()
{
if (FileBrowser::mountSDCard(FORMAT_ON_FAIL, SDCARD_MOUNT_PATH))
basepath = SDCARD_MOUNT_PATH;
else if (FileBrowser::mountSPIFFS(FORMAT_ON_FAIL, SPIFFS_MOUNT_PATH))
basepath = SPIFFS_MOUNT_PATH;
attachDisk(0, DRIVE_A);
attachDisk(1, DRIVE_B);
attachDisk(2, DRIVE_C);
attachDisk(3, DRIVE_D);
SIO0.attachStream(&Terminal);
SIO1.attachStream(&Terminal);
SIO2.attachStream(&Serial);
altair.attachRAM(65536);
altair.load(Altair88DiskBootROMAddr, Altair88DiskBootROM, sizeof(Altair88DiskBootROM));
altair.setMenuCallback(emulator_menu);
setupTerminalColors();
setupRowsCount();
Terminal.
write(
"\e[97m\e[44m");
Terminal.
write(
" /* * * * * * * * * * * * * * * * * * * *\e[K\r\n");
Terminal.
write(
" A L T A I R 8 8 0 0 \e[K\r\n");
Terminal.
write(
" \e[37mby Fabrizio Di Vittorio - www.fabgl.com\e[97m\e[K\r\n");
Terminal.
write(
" * * * * * * * * * * * * * * * * * * * */\e[K\r\n\e[K\n");
Terminal.printf("\e[33mFree Memory :\e[32m %d bytes\e[K\r\n", heap_caps_get_free_size(MALLOC_CAP_32BIT));
int64_t total, used;
FileBrowser::getFSInfo(FileBrowser::getDriveType(basepath), 0, &total, &used);
Terminal.printf("\e[33mFile System :\e[32m %lld KiB used, %lld KiB free\e[K\r\n", used / 1024, (total - used) / 1024);
Terminal.printf("\e[33mKbd Layout : \e[32m%s\e[K\r\n", SupportedLayouts::names()[getKbdLayoutIndex()] );
Terminal.printf("\e[33mCPU : \e[32m%s\e[92m\e[K\r\n\e[K\n", getCPU() == 1 ? "Z80" : "i8080");
Terminal.printf("Press \e[93m[F12]\e[92m or \e[93m[PAUSE]\e[92m to display emulator menu\e[K\r\n");
setupTerminalColors();
setupKbdLayout();
setupTerminalEmu();
setupRealCPUSpeed();
CPU cpu = (getCPU() == 1 ? CPU::Z80 : CPU::i8080);
altair.run(cpu, Altair88DiskBootROMRun);
}
void setLayout(KeyboardLayout const *layout)
Sets keyboard layout.
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
static void begin(gpio_num_t port0_clkGPIO, gpio_num_t port0_datGPIO, gpio_num_t port1_clkGPIO=GPIO_UNUSED, gpio_num_t port1_datGPIO=GPIO_UNUSED)
Initializes PS2 device controller.
The PS2 device controller class.
void connectLocally()
Permits using of terminal locally.
int getRows()
Returns the number of lines.
void clear(bool moveCursor=true)
Clears the screen.
size_t write(const uint8_t *buffer, size_t size)
Sends specified number of codes to the display.
void localWrite(uint8_t c)
Injects keys into the keyboard queue.
int read()
Reads codes from keyboard.
void enableCursor(bool value)
Enables or disables cursor.
void setColorForAttribute(CharStyle attribute, Color color, bool maintainStyle)
Selects a color for the specified attribute.
void setTerminalType(TermType value)
Sets the terminal type to emulate.
void setBackgroundColor(Color color, bool setAsDefault=true)
Sets the background color.
bool begin(BaseDisplayController *displayController, int maxColumns=-1, int maxRows=-1, Keyboard *keyboard=nullptr)
Initializes the terminal.
void flush(bool waitVSync)
Waits for all codes sent to the display has been processed.
void setForegroundColor(Color color, bool setAsDefault=true)
Sets the foreground color.
An ANSI-VT100 compatible display terminal.
Represents the VGA bitmapped controller.
void begin(gpio_num_t redGPIO, gpio_num_t greenGPIO, gpio_num_t blueGPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
This is the 8 colors (5 GPIOs) initializer.
void setResolution(char const *modeline=nullptr, int viewPortWidth=-1, int viewPortHeight=-1, bool doubleBuffered=false)
Sets fixed resolution.
Represents the VGA text-only controller.
GlyphOptions & Underline(bool value)
Helper method to set or reset underlined.
GlyphOptions & Italic(bool value)
Helper method to set or reset italic.
GlyphOptions & Bold(bool value)
Helper method to set or reset bold.
This file is the all in one include file. Application can just include this file to use FabGL library...
#define VGA_640x200_70HzRetro
This file contains some utility classes and functions.
TermType
This enum defines supported terminals.
Color
This enum defines named colors.