diff --git a/README.md b/README.md index aae4d92..38974c6 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,159 @@ The MD5 sums of those two files are: From those, the appropriate headers will be automatically generated by "make roms" (or any other target that relies on the ROMs). +Building (for the Teensy) +========================= + +The directory 'teensy' contains 'teensy.ino' - the Arduino development +environment project file. You'll need to open that up and compile from +within. + +However. + +I built this on a Mac, and I used a lot of symlinks because of +limitations in the Arduino IDE. There's no reason that shouldn't work +under Linux, but I have absolutely no idea what Windows will make of +it. I would expect trouble. No, I won't accept pull requests that +remove the symlinks and replace them with the bare files. Sorry. + +Also, you'll have to build the ROM headers (above) with 'make roms' +before you can build the Teensy .ino file. + +If anyone knows how to make the Arduino development environment do any +form of scripting that could be used to generate those headers, I'd +gladly adopt that instead of forcing folks to run the Perl script via +Makefile. And if you have a better way of dealing with subfolders of +code, with the Teensy-specific code segregated as it is, I'm all ears... + +I compile this with optimization set to "Faster" for the Teensy 3.6 at +180MHz. There's no need to overclock the CPU -- but it does give +better video performance, all the way up to 240MHz. Do as you see fit +:) + +Environment and Libraries +------------------------- + +I built this with arduino 1.8.1 and TeensyDuino 1.35. + + https://www.pjrc.com/teensy/td_download.html + +These libraries I'm using right from Teensy's environment: TimerOne; +SPI; EEPROM; Time; Keypad. + +There's an error in the Time library. On Macs, where the filesystem is +case-insensitive, you'll have to do something like this: + +
+  $ mv /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/Time.h /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/_Time.h
+
+ +I'm also using these libraries that don't come with TeensyDuino: + +### uSDFS ### + +This has long filename support, where the Teensy SD library +doesn't. It's pretty messy code, though, and I hope to abandon it +eventually. + + https://github.com/WMXZ-EU/uSDFS + +### RingBuffer ### + +My library for dealing with ring and circular buffers. Simplistic. +You'll need v1.2.0 or later. + + https://github.com/JorjBauer/RingBuffer + +### RadioHead ### + +This library comes with TeensyDuino, but I found the version that's +with 1.35 hangs. Using the stock v1.70 seems to be fine. + + http://www.airspayce.com/mikem/arduino/RadioHead + + + + +Running (on the Teensy) +======================= + +The reset/menu button brings up a BIOS menu: + + Resume + Reset + Cold Reboot + Drop to Monitor + Display: RGB + Debug: off + Insert/Eject Disk 1 + Insert/Eject Disk 2 + Volume + + Volume - + +Reset +----- + +This is the same as control-reset on the actual hardware. If you +want to execute the Apple //e self-test, then hold down the two +joystick buttons; hit the reset/menu key; and select "Reset". + +Cold Reboot +----------- + +This resets much of the hardware to a default state and forces a +reboot. (You can get the self-test using this, too.) + +Drop to Monitor +--------------- + +"Drop to Monitor" tries fairly hard to get you back to a monitor +prompt. Useful for debugging, probably not for much else. + +Display +------- + +"Display" has four values, and they're only really implemented for +text and hi-res modes (not for lo-res modes). To describe them, I have +to talk about the details of the Apple II display system. + +In hires modes, the Apple II can only display certain colors in +certain horizontal pixel columns. Because of how the composite video +out works, the color "carries over" from one pixel to its neighbor; +multiple pixels turned on in a row makes them all white. Which means +that, if you're trying to display a picture in hires mode, you get +color artifacts on the edges of white areas. + +The Apple Color Composite Monitor had a button on it that turned on +"Monochrome" mode, with the finer resolution necessary to display the +pixels without the color cast. So its two display modes would be the +ones I call "NTSC-like" and "Black and White." + +There are two other video modes. The "RGB" mode (the default, because +it's my preference) shows the color pixels as they're actually drawn +in to memory. That means there can't be a solid field of, for example, +orange; there can only be vertical stripes of orange with black +between them. + +The last mode is "Monochrome" which looks like the original "Monitor +II", a black-and-green display. + +Debug +----- + +This has several settings: + + off + Show FPS + Show mem free + Show paddles + Show PC + Show cycles + Show battery + Show time + +... these are all fairly self-explanatory. + + Building (on a Mac) =================== diff --git a/filemanager.h b/filemanager.h index cbc9e5f..1bde8d2 100644 --- a/filemanager.h +++ b/filemanager.h @@ -18,7 +18,7 @@ class FileManager { virtual const char *fileName(int8_t fd) = 0; - virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx) = 0; + virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen) = 0; virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false) = 0; virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false) = 0; virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false) = 0; diff --git a/opencv/opencv-filemanager.cpp b/opencv/opencv-filemanager.cpp index 80882ba..c10e539 100644 --- a/opencv/opencv-filemanager.cpp +++ b/opencv/opencv-filemanager.cpp @@ -61,7 +61,7 @@ const char *OpenCVFileManager::fileName(int8_t fd) return cachedNames[fd]; } -int8_t OpenCVFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx) +int8_t OpenCVFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint8_t maxlen) { // not used in this version return -1; diff --git a/opencv/opencv-filemanager.h b/opencv/opencv-filemanager.h index d415b37..82133d0 100644 --- a/opencv/opencv-filemanager.h +++ b/opencv/opencv-filemanager.h @@ -14,7 +14,7 @@ class OpenCVFileManager : public FileManager { virtual const char *fileName(int8_t fd); - virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx); + virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen); virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false); virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false); virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false); diff --git a/teensy/appledisplay.cpp b/teensy/appledisplay.cpp new file mode 120000 index 0000000..a506d9b --- /dev/null +++ b/teensy/appledisplay.cpp @@ -0,0 +1 @@ +../apple/appledisplay.cpp \ No newline at end of file diff --git a/teensy/appledisplay.h b/teensy/appledisplay.h new file mode 120000 index 0000000..19355ca --- /dev/null +++ b/teensy/appledisplay.h @@ -0,0 +1 @@ +../apple/appledisplay.h \ No newline at end of file diff --git a/teensy/applekeyboard.cpp b/teensy/applekeyboard.cpp new file mode 120000 index 0000000..b083db5 --- /dev/null +++ b/teensy/applekeyboard.cpp @@ -0,0 +1 @@ +../apple/applekeyboard.cpp \ No newline at end of file diff --git a/teensy/applekeyboard.h b/teensy/applekeyboard.h new file mode 120000 index 0000000..9376c82 --- /dev/null +++ b/teensy/applekeyboard.h @@ -0,0 +1 @@ +../apple/applekeyboard.h \ No newline at end of file diff --git a/teensy/applemmu-rom.h b/teensy/applemmu-rom.h new file mode 120000 index 0000000..a5461fe --- /dev/null +++ b/teensy/applemmu-rom.h @@ -0,0 +1 @@ +../apple/applemmu-rom.h \ No newline at end of file diff --git a/teensy/applemmu.cpp b/teensy/applemmu.cpp new file mode 120000 index 0000000..db957ef --- /dev/null +++ b/teensy/applemmu.cpp @@ -0,0 +1 @@ +../apple/applemmu.cpp \ No newline at end of file diff --git a/teensy/applemmu.h b/teensy/applemmu.h new file mode 120000 index 0000000..70bbed0 --- /dev/null +++ b/teensy/applemmu.h @@ -0,0 +1 @@ +../apple/applemmu.h \ No newline at end of file diff --git a/teensy/applevm.cpp b/teensy/applevm.cpp new file mode 120000 index 0000000..e5faf7f --- /dev/null +++ b/teensy/applevm.cpp @@ -0,0 +1 @@ +../apple/applevm.cpp \ No newline at end of file diff --git a/teensy/applevm.h b/teensy/applevm.h new file mode 120000 index 0000000..a4dae41 --- /dev/null +++ b/teensy/applevm.h @@ -0,0 +1 @@ +../apple/applevm.h \ No newline at end of file diff --git a/teensy/bios-font.h b/teensy/bios-font.h new file mode 120000 index 0000000..c09ff71 --- /dev/null +++ b/teensy/bios-font.h @@ -0,0 +1 @@ +../bios-font.h \ No newline at end of file diff --git a/teensy/bios.cpp b/teensy/bios.cpp new file mode 100644 index 0000000..2428b0a --- /dev/null +++ b/teensy/bios.cpp @@ -0,0 +1,421 @@ +#include "bios.h" + +#include "applevm.h" +#include "physicalkeyboard.h" +#include "teensy-keyboard.h" +#include "cpu.h" +#include "teensy-filemanager.h" +#include "teensy-display.h" + +#include "globals.h" + +enum { + ACT_EXIT = 0, + ACT_RESET = 1, + ACT_REBOOT = 2, + ACT_MONITOR = 3, + ACT_DISPLAYTYPE = 4, + ACT_DEBUG = 5, + ACT_DISK1 = 6, + ACT_DISK2 = 7, + ACT_VOLPLUS = 8, + ACT_VOLMINUS = 9, + + NUM_ACTIONS = 10 +}; + +const char *titles[NUM_ACTIONS] = { "Resume", + "Reset", + "Cold Reboot", + "Drop to Monitor", + "Display: %s", + "Debug: %s", + "%s Disk 1", + "%s Disk 2", + "Volume +", + "Volume -" +}; + +// FIXME: abstract the pin # rather than repeating it here +#define RESETPIN 39 + +extern int16_t g_volume; // FIXME: external global. icky. +extern uint8_t debugMode; // and another. :/ +// FIXME: and these need abstracting out of the main .ino ! +enum { + D_NONE = 0, + D_SHOWFPS = 1, + D_SHOWMEMFREE = 2, + D_SHOWPADDLES = 3, + D_SHOWPC = 4, + D_SHOWCYCLES = 5, + D_SHOWBATTERY = 6, + D_SHOWTIME = 7 +}; + +const char *staticPathConcat(const char *rootPath, const char *filePath) +{ + static char buf[MAXPATH]; + strncpy(buf, rootPath, sizeof(buf)-1); + strncat(buf, filePath, sizeof(buf)-strlen(buf)-1); + + return buf; +} + + +BIOS::BIOS() +{ + strcpy(rootPath, "/A2DISKS/"); + + selectedFile = -1; + for (int8_t i=0; iMonitor(); + goto done; + case ACT_DISPLAYTYPE: + g_displayType++; + g_displayType %= 4; // FIXME: abstract max # + break; + case ACT_DEBUG: + debugMode++; + debugMode %= 8; // FIXME: abstract max # + break; + case ACT_DISK1: + if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') { + ((AppleVM *)g_vm)->ejectDisk(0); + } else { + if (SelectDiskImage()) { + ((AppleVM *)g_vm)->insertDisk(0, staticPathConcat(rootPath, fileDirectory[selectedFile]), false); + goto done; + } + } + break; + case ACT_DISK2: + if (((AppleVM *)g_vm)->DiskName(1)[0] != '\0') { + ((AppleVM *)g_vm)->ejectDisk(1); + } else { + if (SelectDiskImage()) { + ((AppleVM *)g_vm)->insertDisk(1, staticPathConcat(rootPath, fileDirectory[selectedFile]), false); + goto done; + } + } + break; + case ACT_VOLPLUS: + g_volume += 10; + if (g_volume > 255) { + g_volume = 255; + } + volumeDidChange = true; + break; + case ACT_VOLMINUS: + g_volume -= 10; + if (g_volume < 0) { + g_volume = 0; + } + volumeDidChange = true; + break; + } + } + + done: + // Undo whatever damage we've done to the screen + g_display->redraw(); + g_display->blit(); + + // return true if any persistent setting changed that we want to store in eeprom + return volumeDidChange; +} + +void BIOS::WarmReset() +{ + g_cpu->Reset(); +} + +void BIOS::ColdReboot() +{ + g_vm->Reset(); + g_cpu->Reset(); +} + +uint8_t BIOS::GetAction(int8_t selection) +{ + while (1) { + DrawMainMenu(selection); + while (!((TeensyKeyboard *)g_keyboard)->kbhit() && + (digitalRead(RESETPIN) == HIGH)) { + ; + // Wait for either a keypress or the reset button to be pressed + } + + if (digitalRead(RESETPIN) == LOW) { + // wait until it's no longer pressed + while (digitalRead(RESETPIN) == HIGH) + ; + delay(100); // wait long enough for it to debounce + // then return an exit code + return ACT_EXIT; + } + + switch (((TeensyKeyboard *)g_keyboard)->read()) { + case DARR: + selection++; + selection %= NUM_ACTIONS; + break; + case UARR: + selection--; + if (selection < 0) + selection = NUM_ACTIONS-1; + break; + case RET: + if (isActionActive(selection)) + return selection; + break; + } + } +} + +bool BIOS::isActionActive(int8_t action) +{ + // don't return true for disk events that aren't valid + switch (action) { + case ACT_EXIT: + case ACT_RESET: + case ACT_REBOOT: + case ACT_MONITOR: + case ACT_DISPLAYTYPE: + case ACT_DEBUG: + case ACT_DISK1: + case ACT_DISK2: + return true; + + case ACT_VOLPLUS: + return (g_volume < 255); + case ACT_VOLMINUS: + return (g_volume > 0); + } + + /* NOTREACHED */ + return false; +} + +void BIOS::DrawMainMenu(int8_t selection) +{ + ((TeensyDisplay *)g_display)->clrScr(); + g_display->drawString(M_NORMAL, 0, 12, "BIOS Configuration"); + for (int i=0; iDiskName(i - ACT_DISK1)[0] ? "Eject" : "Insert"); + } else if (i == ACT_DISPLAYTYPE) { + switch (g_displayType) { + case m_blackAndWhite: + sprintf(buf, titles[i], "B&W"); + break; + case m_monochrome: + sprintf(buf, titles[i], "Mono"); + break; + case m_ntsclike: + sprintf(buf, titles[i], "NTSC-like"); + break; + case m_perfectcolor: + sprintf(buf, titles[i], "RGB"); + break; + } + } else if (i == ACT_DEBUG) { + switch (debugMode) { + case D_NONE: + sprintf(buf, titles[i], "off"); + break; + case D_SHOWFPS: + sprintf(buf, titles[i], "Show FPS"); + break; + case D_SHOWMEMFREE: + sprintf(buf, titles[i], "Show mem free"); + break; + case D_SHOWPADDLES: + sprintf(buf, titles[i], "Show paddles"); + break; + case D_SHOWPC: + sprintf(buf, titles[i], "Show PC"); + break; + case D_SHOWCYCLES: + sprintf(buf, titles[i], "Show cycles"); + break; + case D_SHOWBATTERY: + sprintf(buf, titles[i], "Show battery"); + break; + case D_SHOWTIME: + sprintf(buf, titles[i], "Show time"); + break; + } + } else { + strcpy(buf, titles[i]); + } + + if (isActionActive(i)) { + g_display->drawString(selection == i ? M_SELECTED : M_NORMAL, 10, 50 + 14 * i, buf); + } else { + g_display->drawString(selection == i ? M_SELECTDISABLED : M_DISABLED, 10, 50 + 14 * i, buf); + } + } + + // draw the volume bar + uint16_t volCutoff = 300.0 * (float)((float) g_volume / 256.0); + for (uint8_t y=200; y<=210; y++) { + ((TeensyDisplay *)g_display)->moveTo(10, y); + for (uint16_t x = 0; x< 300; x++) { + ((TeensyDisplay *)g_display)->drawNextPixel( x <= volCutoff ? 0xFFFF : 0x0010 ); + } + } +} + + +// return true if the user selects an image +// sets selectedFile (index; -1 = "nope") and fileDirectory[][] (names of up to BIOS_MAXFILES files) +bool BIOS::SelectDiskImage() +{ + int8_t sel = 0; + int8_t page = 0; + + while (1) { + DrawDiskNames(page, sel); + + while (!((TeensyKeyboard *)g_keyboard)->kbhit()) + ; + switch (((TeensyKeyboard *)g_keyboard)->read()) { + case DARR: + sel++; + sel %= BIOS_MAXFILES + 2; + break; + case UARR: + sel--; + if (sel < 0) + sel = BIOS_MAXFILES + 1; + break; + case RET: + if (sel == 0) { + page--; + if (page < 0) page = 0; + // else sel = BIOS_MAXFILES + 1; + } + else if (sel == BIOS_MAXFILES+1) { + page++; + //sel = 0; + } else { + if (strcmp(fileDirectory[sel-1], "../") == 0) { + // Go up a directory (strip a directory name from rootPath) + stripDirectory(); + page = 0; + //sel = 0; + continue; + } else if (fileDirectory[sel-1][strlen(fileDirectory[sel-1])-1] == '/') { + // Descend in to the directory. FIXME: file path length? + strcat(rootPath, fileDirectory[sel-1]); + sel = 0; + page = 0; + continue; + } else { + selectedFile = sel - 1; + return true; + } + } + break; + } + } +} + +void BIOS::stripDirectory() +{ + rootPath[strlen(rootPath)-1] = '\0'; // remove the last character + + while (rootPath[0] && rootPath[strlen(rootPath)-1] != '/') { + rootPath[strlen(rootPath)-1] = '\0'; // remove the last character again + } + + // We're either at the previous directory, or we've nulled out the whole thing. + + if (rootPath[0] == '\0') { + // Never go beyond this + strcpy(rootPath, "/"); + } +} + +void BIOS::DrawDiskNames(uint8_t page, int8_t selection) +{ + uint8_t fileCount = GatherFilenames(page); + ((TeensyDisplay *)g_display)->clrScr(); + g_display->drawString(M_NORMAL, 0, 12, "BIOS Configuration - pick disk"); + + if (page == 0) { + g_display->drawString(selection == 0 ? M_SELECTDISABLED : M_DISABLED, 10, 50, ""); + } else { + g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 10, 50, ""); + } + + uint8_t i; + for (i=0; idrawString((i == selection-1) ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), fileDirectory[i]); + } else { + g_display->drawString((i == selection-1) ? M_SELECTDISABLED : M_DISABLED, 10, 50+14*(i+1), "-"); + } + + } + + // FIXME: this doesn't accurately say whether or not there *are* more. + if (fileCount == BIOS_MAXFILES || fileCount == 0) { + g_display->drawString((i+1 == selection) ? M_SELECTDISABLED : M_DISABLED, 10, 50 + 14 * (i+1), ""); + } else { + g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), ""); + } +} + + +uint8_t BIOS::GatherFilenames(uint8_t pageOffset) +{ + uint8_t startNum = 10 * pageOffset; + uint8_t count = 0; // number we're including in our listing + + while (1) { + char fn[BIOS_MAXPATH]; + // FIXME: add po, nib + int8_t idx = g_filemanager->readDir(rootPath, "dsk", fn, startNum + count, BIOS_MAXPATH); + + if (idx == -1) { + return count; + } + + idx++; + + strncpy(fileDirectory[count], fn, BIOS_MAXPATH); + count++; + + if (count >= BIOS_MAXFILES) { + return count; + } + } +} + diff --git a/teensy/bios.h b/teensy/bios.h new file mode 100644 index 0000000..67be37e --- /dev/null +++ b/teensy/bios.h @@ -0,0 +1,38 @@ +#ifndef __BIOS_H +#define __BIOS_H + +#include + +#define BIOS_MAXFILES 10 // number of files in a page of listing +#define BIOS_MAXPATH 40 // maximum length of a single filename that we'll support + +class BIOS { + public: + BIOS(); + ~BIOS(); + + // return true if a persistent change needs to be stored in EEPROM + bool runUntilDone(); + + private: + uint8_t GetAction(int8_t prevAction); + bool isActionActive(int8_t action); + void DrawMainMenu(int8_t selection); + + void WarmReset(); + void ColdReboot(); + + bool SelectDiskImage(); + void DrawDiskNames(uint8_t page, int8_t selection); + uint8_t GatherFilenames(uint8_t pageOffset); + + void stripDirectory(); + + private: + int8_t selectedFile; + char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1]; + + char rootPath[255-BIOS_MAXPATH]; +}; + +#endif diff --git a/teensy/cpu.cpp b/teensy/cpu.cpp new file mode 120000 index 0000000..89beeaf --- /dev/null +++ b/teensy/cpu.cpp @@ -0,0 +1 @@ +../cpu.cpp \ No newline at end of file diff --git a/teensy/cpu.h b/teensy/cpu.h new file mode 120000 index 0000000..e9d4bdf --- /dev/null +++ b/teensy/cpu.h @@ -0,0 +1 @@ +../cpu.h \ No newline at end of file diff --git a/teensy/diskii-rom.h b/teensy/diskii-rom.h new file mode 120000 index 0000000..0a3edae --- /dev/null +++ b/teensy/diskii-rom.h @@ -0,0 +1 @@ +../apple/diskii-rom.h \ No newline at end of file diff --git a/teensy/diskii.cpp b/teensy/diskii.cpp new file mode 120000 index 0000000..b773c2b --- /dev/null +++ b/teensy/diskii.cpp @@ -0,0 +1 @@ +../apple/diskii.cpp \ No newline at end of file diff --git a/teensy/diskii.h b/teensy/diskii.h new file mode 120000 index 0000000..781bcbe --- /dev/null +++ b/teensy/diskii.h @@ -0,0 +1 @@ +../apple/diskii.h \ No newline at end of file diff --git a/teensy/display-bg.h b/teensy/display-bg.h new file mode 120000 index 0000000..3043c6a --- /dev/null +++ b/teensy/display-bg.h @@ -0,0 +1 @@ +../display-bg.h \ No newline at end of file diff --git a/teensy/filemanager.h b/teensy/filemanager.h new file mode 120000 index 0000000..b5f9cfe --- /dev/null +++ b/teensy/filemanager.h @@ -0,0 +1 @@ +../filemanager.h \ No newline at end of file diff --git a/teensy/font.h b/teensy/font.h new file mode 120000 index 0000000..ce7b944 --- /dev/null +++ b/teensy/font.h @@ -0,0 +1 @@ +../apple/font.h \ No newline at end of file diff --git a/teensy/globals.cpp b/teensy/globals.cpp new file mode 120000 index 0000000..3fef46a --- /dev/null +++ b/teensy/globals.cpp @@ -0,0 +1 @@ +../globals.cpp \ No newline at end of file diff --git a/teensy/globals.h b/teensy/globals.h new file mode 120000 index 0000000..caa372d --- /dev/null +++ b/teensy/globals.h @@ -0,0 +1 @@ +../globals.h \ No newline at end of file diff --git a/teensy/mmu.h b/teensy/mmu.h new file mode 120000 index 0000000..cc56dfb --- /dev/null +++ b/teensy/mmu.h @@ -0,0 +1 @@ +../mmu.h \ No newline at end of file diff --git a/teensy/nibutil.cpp b/teensy/nibutil.cpp new file mode 120000 index 0000000..29b7732 --- /dev/null +++ b/teensy/nibutil.cpp @@ -0,0 +1 @@ +../apple/nibutil.cpp \ No newline at end of file diff --git a/teensy/nibutil.h b/teensy/nibutil.h new file mode 120000 index 0000000..0bd1862 --- /dev/null +++ b/teensy/nibutil.h @@ -0,0 +1 @@ +../apple/nibutil.h \ No newline at end of file diff --git a/teensy/physicaldisplay.h b/teensy/physicaldisplay.h new file mode 120000 index 0000000..8df37c8 --- /dev/null +++ b/teensy/physicaldisplay.h @@ -0,0 +1 @@ +../physicaldisplay.h \ No newline at end of file diff --git a/teensy/physicalkeyboard.h b/teensy/physicalkeyboard.h new file mode 120000 index 0000000..75e8d2a --- /dev/null +++ b/teensy/physicalkeyboard.h @@ -0,0 +1 @@ +../physicalkeyboard.h \ No newline at end of file diff --git a/teensy/physicalpaddles.h b/teensy/physicalpaddles.h new file mode 120000 index 0000000..8f5c173 --- /dev/null +++ b/teensy/physicalpaddles.h @@ -0,0 +1 @@ +../physicalpaddles.h \ No newline at end of file diff --git a/teensy/physicalspeaker.h b/teensy/physicalspeaker.h new file mode 120000 index 0000000..65d0f7e --- /dev/null +++ b/teensy/physicalspeaker.h @@ -0,0 +1 @@ +../physicalspeaker.h \ No newline at end of file diff --git a/teensy/slot.cpp b/teensy/slot.cpp new file mode 120000 index 0000000..375ae63 --- /dev/null +++ b/teensy/slot.cpp @@ -0,0 +1 @@ +../apple/slot.cpp \ No newline at end of file diff --git a/teensy/slot.h b/teensy/slot.h new file mode 120000 index 0000000..e877fef --- /dev/null +++ b/teensy/slot.h @@ -0,0 +1 @@ +../apple/slot.h \ No newline at end of file diff --git a/teensy/teensy-clock.cpp b/teensy/teensy-clock.cpp new file mode 100644 index 0000000..2341d8a --- /dev/null +++ b/teensy/teensy-clock.cpp @@ -0,0 +1,126 @@ +#include // memset +#include + +#include "teensy-clock.h" +#include "applemmu.h" // for FLOATING + +/* + * http://apple2online.com/web_documents/prodos_technical_notes.pdf + * + * When ProDOS calls a clock card, the card deposits an ASCII string + * in the GETLN input buffer in the form: 07,04,14,22,46,57. The + * string translates as the following: + * + * 07 = the month, July + * 04 = the day of the week (00 = Sun) + * 14 = the date (00 to 31) + * 22 = the hour (00 to 23) + * 46 = the minute (00 to 59) + * 57 = the second (00 to 59) +*/ + +static void timeToProDOS(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, + uint8_t proDOStimeOut[4]) +{ + proDOStimeOut[0] = ((year % 100) << 1) | (month >> 3); + proDOStimeOut[1] = ((month & 0x0F) << 5) | (day & 0x1F); + proDOStimeOut[2] = hour & 0x1F; + proDOStimeOut[3] = minute & 0x3F; +} + +TeensyClock::TeensyClock(AppleMMU *mmu) +{ + this->mmu = mmu; +} + +TeensyClock::~TeensyClock() +{ +} + +void TeensyClock::Reset() +{ +} + +uint8_t TeensyClock::readSwitches(uint8_t s) +{ + // When any switch is read, we'll put the current time in the prodos time buffer + + tmElements_t tm; + breakTime(now(), tm); + + // Put the date/time in the official ProDOS buffer + uint8_t prodosOut[4]; + timeToProDOS(tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, prodosOut); + mmu->write(0xBF90, prodosOut[0]); + mmu->write(0xBF91, prodosOut[1]); + mmu->write(0xBF92, prodosOut[2]); + mmu->write(0xBF93, prodosOut[3]); + + // and also generate a date/time that contains seconds, but not a + // year, which it also consumes + char ts[18]; + sprintf(ts, "%.2d,%.2d,%.2d,%.2d,%.2d,%.2d", + tm.Month, + tm.Wday - 1, // Sunday should be 0, not 1 + tm.Day, + tm.Hour, + tm.Minute, + tm.Second); + + uint8_t i = 0; + while (ts[i]) { + mmu->write(0x200 + i, ts[i] | 0x80); + i++; + } + + return FLOATING; +} + +void TeensyClock::writeSwitches(uint8_t s, uint8_t v) +{ + // printf("unimplemented write to the clock - 0x%X\n", v); +} + +// FIXME: this assumes slot #7 +void TeensyClock::loadROM(uint8_t *toWhere) +{ + memset(toWhere, 0xEA, 256); // fill the page with NOPs + + // ProDOS only needs these 4 bytes to recognize that a clock is present + toWhere[0x00] = 0x08; // PHP + toWhere[0x02] = 0x28; // PLP + toWhere[0x04] = 0x58; // CLI + toWhere[0x06] = 0x70; // BVS + + // Pad out those bytes so they will return control well. The program + // at c700 becomes + // + // C700: PHP ; push to stack + // NOP ; filler (filled in by memory clear) + // PLP ; pop from stack + // RTS ; return + // CLI ; required to detect driver, but not used + // NOP ; filled in by memory clear + // BVS ; required to detect driver, but not used + + toWhere[0x03] = 0x60; // RTS + + // And it needs a small routing here to read/write it: + // 0x08: read + toWhere[0x08] = 0x4C; // JMP $C710 + toWhere[0x09] = 0x10; + toWhere[0x0A] = 0xC7; + + // 0x0b: write + toWhere[0x0B] = 0x8D; // STA $C0F0 (slot 7's first switch) + toWhere[0x0C] = 0xF0; + toWhere[0x0D] = 0xC0; + toWhere[0x0E] = 0x60; // RTS + + // simple read + toWhere[0x10] = 0xAD; // LDA $C0F0 (slot 7's first switch) + toWhere[0x11] = 0xF0; + toWhere[0x12] = 0xC0; + toWhere[0x13] = 0x60; // RTS +} + diff --git a/teensy/teensy-clock.h b/teensy/teensy-clock.h new file mode 100644 index 0000000..489dd9a --- /dev/null +++ b/teensy/teensy-clock.h @@ -0,0 +1,30 @@ +#ifndef __TEENSYCLOCK_H +#define __TEENSYCLOCK_H + +#ifdef TEENSYDUINO +#include +#else +#include +#include +#endif + +#include "slot.h" +#include "applemmu.h" + +class TeensyClock : public Slot { + public: + TeensyClock(AppleMMU *mmu); + virtual ~TeensyClock(); + + virtual void Reset(); + + virtual uint8_t readSwitches(uint8_t s); + virtual void writeSwitches(uint8_t s, uint8_t v); + + virtual void loadROM(uint8_t *toWhere); + + private: + AppleMMU *mmu; +}; + +#endif diff --git a/teensy/teensy-crash.h b/teensy/teensy-crash.h new file mode 100644 index 0000000..fab4026 --- /dev/null +++ b/teensy/teensy-crash.h @@ -0,0 +1,72 @@ +/* + * https://forum.pjrc.com/threads/186-Teensy-3-fault-handler-demonstration?highlight=crash+handler + * + * call this from setup(): + * enableFaultHandler(); + * + * On crash you see something like: + * !!!! Crashed at pc=0x490, lr=0x55B. + * + * which you can interpret thusly: + * $ arm-none-eabi-addr2line -s -f -C -e main.elf 0x490 0x55B + * Print:: println(char const*) + * Print.h:47 + * main + * main_crash.cpp:86 + */ + +#define SCB_SHCSR_USGFAULTENA (uint32_t)1<<18 +#define SCB_SHCSR_BUSFAULTENA (uint32_t)1<<17 +#define SCB_SHCSR_MEMFAULTENA (uint32_t)1<<16 + +#define SCB_SHPR1_USGFAULTPRI *(volatile uint8_t *)0xE000ED20 +#define SCB_SHPR1_BUSFAULTPRI *(volatile uint8_t *)0xE000ED19 +#define SCB_SHPR1_MEMFAULTPRI *(volatile uint8_t *)0xE000ED18 + +// enable bus, usage, and mem fault handlers. +void enableFaultHandler() +{ + SCB_SHCSR |= SCB_SHCSR_BUSFAULTENA | SCB_SHCSR_USGFAULTENA | SCB_SHCSR_MEMFAULTENA; +} + + +extern "C" { +void __attribute__((naked)) _fault_isr () { + uint32_t* sp=0; + // this is from "Definitive Guide to the Cortex M3" pg 423 + asm volatile ( "TST LR, #0x4\n\t" // Test EXC_RETURN number in LR bit 2 + "ITE EQ\n\t" // if zero (equal) then + "MRSEQ %0, MSP\n\t" // Main Stack was used, put MSP in sp + "MRSNE %0, PSP\n\t" // else Process stack was used, put PSP in sp + : "=r" (sp) : : "cc"); + + Serial.print("!!!! Crashed at pc=0x"); + Serial.print(sp[6], 16); + Serial.print(", lr=0x"); + Serial.print(sp[5], 16); + Serial.println("."); + + Serial.flush(); + + // allow USB interrupts to preempt us: + SCB_SHPR1_BUSFAULTPRI = (uint8_t)255; + SCB_SHPR1_USGFAULTPRI = (uint8_t)255; + SCB_SHPR1_MEMFAULTPRI = (uint8_t)255; + + while (1) { + digitalWrite(13, HIGH); + delay(100); + digitalWrite(13, LOW); + delay(100); + + asm volatile ( + "WFI" // Wait For Interrupt. + ); + } +} +} + +void hard_fault_isr(void) __attribute__ ((alias("_fault_isr"))); +void memmanage_fault_isr(void) __attribute__ ((alias("_fault_isr"))); +void bus_fault_isr(void) __attribute__ ((alias("_fault_isr"))); +void usage_fault_isr(void) __attribute__ ((alias("_fault_isr"))); diff --git a/teensy/teensy-display.cpp b/teensy/teensy-display.cpp new file mode 100644 index 0000000..b51b446 --- /dev/null +++ b/teensy/teensy-display.cpp @@ -0,0 +1,560 @@ +#include // isgraph +#include "teensy-display.h" + +#include "bios-font.h" +#include "display-bg.h" + +#define RS 16 +#define WR 17 +#define CS 18 +#define RST 19 + +// Ports C&D of the Teensy connected to DB of the display +#define DB_0 15 +#define DB_1 22 +#define DB_2 23 +#define DB_3 9 +#define DB_4 10 +#define DB_5 13 +#define DB_6 11 +#define DB_7 12 +#define DB_8 2 +#define DB_9 14 +#define DB_10 7 +#define DB_11 8 +#define DB_12 6 +#define DB_13 20 +#define DB_14 21 +#define DB_15 5 + +#define disp_x_size 239 +#define disp_y_size 319 + +#define setPixel(color) { LCD_Write_DATA(((color)>>8),((color)&0xFF)); } // 565 RGB + +#include "globals.h" +#include "applevm.h" + +// RGB map of each of the lowres colors +const uint16_t loresPixelColors[16] = { 0x0000, // 0 black + 0xC006, // 1 magenta + 0x0010, // 2 dark blue + 0xA1B5, // 3 purple + 0x0480, // 4 dark green + 0x6B4D, // 5 dark grey + 0x1B9F, // 6 med blue + 0x0DFD, // 7 light blue + 0x92A5, // 8 brown + 0xF8C5, // 9 orange + 0x9555, // 10 light gray + 0xFCF2, // 11 pink + 0x07E0, // 12 green + 0xFFE0, // 13 yellow + 0x87F0, // 14 aqua + 0xFFFF // 15 white +}; + +TeensyDisplay::TeensyDisplay() +{ + pinMode(DB_8, OUTPUT); + pinMode(DB_9, OUTPUT); + pinMode(DB_10, OUTPUT); + pinMode(DB_11, OUTPUT); + pinMode(DB_12, OUTPUT); + pinMode(DB_13, OUTPUT); + pinMode(DB_14, OUTPUT); + pinMode(DB_15, OUTPUT); + pinMode(DB_0, OUTPUT); + pinMode(DB_1, OUTPUT); + pinMode(DB_2, OUTPUT); + pinMode(DB_3, OUTPUT); + pinMode(DB_4, OUTPUT); + pinMode(DB_5, OUTPUT); + pinMode(DB_6, OUTPUT); + pinMode(DB_7, OUTPUT); + + P_RS = portOutputRegister(digitalPinToPort(RS)); + B_RS = digitalPinToBitMask(RS); + P_WR = portOutputRegister(digitalPinToPort(WR)); + B_WR = digitalPinToBitMask(WR); + P_CS = portOutputRegister(digitalPinToPort(CS)); + B_CS = digitalPinToBitMask(CS); + P_RST = portOutputRegister(digitalPinToPort(RST)); + B_RST = digitalPinToBitMask(RST); + + pinMode(RS,OUTPUT); + pinMode(WR,OUTPUT); + pinMode(CS,OUTPUT); + pinMode(RST,OUTPUT); + + // begin initialization + + sbi(P_RST, B_RST); + delay(5); + cbi(P_RST, B_RST); + delay(15); + sbi(P_RST, B_RST); + delay(15); + + cbi(P_CS, B_CS); + + LCD_Write_COM_DATA(0x00,0x0001); // oscillator start [enable] + LCD_Write_COM_DATA(0x03,0xA8A4); // power control [%1010 1000 1010 1000] == DCT3, DCT1, BT2, DC3, DC1, AP2 + LCD_Write_COM_DATA(0x0C,0x0000); // power control2 [0] + LCD_Write_COM_DATA(0x0D,0x080C); // power control3 [VRH3, VRH2, invalid bits] + LCD_Write_COM_DATA(0x0E,0x2B00); // power control4 VCOMG, VDV3, VDV1, VDV0 + LCD_Write_COM_DATA(0x1E,0x00B7); // power control5 nOTP, VCM5, VCM4, VCM2, VCM1, VCM0 + // LCD_Write_COM_DATA(0x01,0x2B3F); // driver control output REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0 + // Change that: TB = 0 please + LCD_Write_COM_DATA(0x01,0x293F); // driver control output REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0 + // LCD_Write_COM_DATA(0x01,0x693F); // driver control output RL, REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0 + + + LCD_Write_COM_DATA(0x02,0x0600); // LCD drive AC control B/C, EOR + LCD_Write_COM_DATA(0x10,0x0000); // sleep mode 0 + // Change the (Y) order here to match above (TB=0) + //LCD_Write_COM_DATA(0x11,0x6070); // Entry mode DFM1, DFM0, TY0, ID1, ID0 + //LCD_Write_COM_DATA(0x11,0x6050); // Entry mode DFM1, DFM0, TY0, ID0 + LCD_Write_COM_DATA(0x11,0x6078); // Entry mode DFM1, DFM0, TY0, ID1, ID0, AM + + LCD_Write_COM_DATA(0x05,0x0000); // compare reg1 + LCD_Write_COM_DATA(0x06,0x0000); // compare reg2 + LCD_Write_COM_DATA(0x16,0xEF1C); // horiz porch (default) + LCD_Write_COM_DATA(0x17,0x0003); // vertical porch + LCD_Write_COM_DATA(0x07,0x0233); // display control VLE1, GON, DTE, D1, D0 + LCD_Write_COM_DATA(0x0B,0x0000); // frame cycle control + LCD_Write_COM_DATA(0x0F,0x0000); // gate scan start posn + LCD_Write_COM_DATA(0x41,0x0000); // vertical scroll control1 + LCD_Write_COM_DATA(0x42,0x0000); // vertical scroll control2 + LCD_Write_COM_DATA(0x48,0x0000); // first window start + LCD_Write_COM_DATA(0x49,0x013F); // first window end (0x13f == 319) + LCD_Write_COM_DATA(0x4A,0x0000); // second window start + LCD_Write_COM_DATA(0x4B,0x0000); // second window end + LCD_Write_COM_DATA(0x44,0xEF00); // horiz ram addr posn + LCD_Write_COM_DATA(0x45,0x0000); // vert ram start posn + LCD_Write_COM_DATA(0x46,0x013F); // vert ram end posn + LCD_Write_COM_DATA(0x30,0x0707); // γ control + LCD_Write_COM_DATA(0x31,0x0204);// + LCD_Write_COM_DATA(0x32,0x0204);// + LCD_Write_COM_DATA(0x33,0x0502);// + LCD_Write_COM_DATA(0x34,0x0507);// + LCD_Write_COM_DATA(0x35,0x0204);// + LCD_Write_COM_DATA(0x36,0x0204);// + LCD_Write_COM_DATA(0x37,0x0502);// + LCD_Write_COM_DATA(0x3A,0x0302);// + LCD_Write_COM_DATA(0x3B,0x0302);// + LCD_Write_COM_DATA(0x23,0x0000);// RAM write data mask1 + LCD_Write_COM_DATA(0x24,0x0000); // RAM write data mask2 + LCD_Write_COM_DATA(0x25,0x8000); // frame frequency (OSC3) + LCD_Write_COM_DATA(0x4f,0x0000); // Set GDDRAM Y address counter + LCD_Write_COM_DATA(0x4e,0x0000); // Set GDDRAM X address counter +#if 0 + // Set data access speed optimization (?) + LCD_Write_COM_DATA(0x28, 0x0006); + LCD_Write_COM_DATA(0x2F, 0x12BE); + LCD_Write_COM_DATA(0x12, 0x6CEB); +#endif + + LCD_Write_COM(0x22); // RAM data write + sbi(P_CS, B_CS); + + // LCD initialization complete + + + setColor(255, 255, 255); + + clrScr(); + +} + +TeensyDisplay::~TeensyDisplay() +{ +} + +void TeensyDisplay::_fast_fill_16(int ch, int cl, long pix) +{ + *(volatile uint8_t *)(&GPIOD_PDOR) = ch; + *(volatile uint8_t *)(&GPIOC_PDOR) = cl; + uint16_t blocks = pix / 16; + + for (uint16_t i=0; i> 3); + setPixel(color16); + } + } + + if (g_vm) { + drawDriveDoor(0, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0'); + drawDriveDoor(1, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0'); + } + + cbi(P_CS, B_CS); + clrXY(); + sbi(P_RS, B_RS); +} + +void TeensyDisplay::clrScr() +{ + cbi(P_CS, B_CS); + clrXY(); + sbi(P_RS, B_RS); + _fast_fill_16(0, 0, ((disp_x_size+1)*(disp_y_size+1))); + sbi(P_CS, B_CS); +} + +// The display flips X and Y, so expect to see "x" as "vertical" +// and "y" as "horizontal" here... +void TeensyDisplay::setYX(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) +{ + LCD_Write_COM_DATA(0x44, (y2<<8)+y1); // Horiz start addr, Horiz end addr + LCD_Write_COM_DATA(0x45, x1); // vert start pos + LCD_Write_COM_DATA(0x46, x2); // vert end pos + LCD_Write_COM_DATA(0x4e,y1); // RAM address set (horiz) + LCD_Write_COM_DATA(0x4f,x1); // RAM address set (vert) + LCD_Write_COM(0x22); +} + +void TeensyDisplay::clrXY() +{ + setYX(0, 0, disp_y_size, disp_x_size); +} + +void TeensyDisplay::setColor(byte r, byte g, byte b) +{ + fch=((r&248)|g>>5); + fcl=((g&28)<<3|b>>3); +} + +void TeensyDisplay::setColor(uint16_t color) +{ + fch = (uint8_t)(color >> 8); + fcl = (uint8_t)(color & 0xFF); +} + +void TeensyDisplay::fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) +{ + if (x1>x2) { + swap(uint16_t, x1, x2); + } + if (y1 > y2) { + swap(uint16_t, y1, y2); + } + + cbi(P_CS, B_CS); + setYX(x1, y1, x2, y2); + sbi(P_RS, B_RS); + _fast_fill_16(fch,fcl,((long(x2-x1)+1)*(long(y2-y1)+1))); + sbi(P_CS, B_CS); +} + +void TeensyDisplay::drawPixel(uint16_t x, uint16_t y) +{ + cbi(P_CS, B_CS); + setYX(x, y, x, y); + setPixel((fch<<8)|fcl); + sbi(P_CS, B_CS); + clrXY(); +} + +void TeensyDisplay::drawPixel(uint16_t x, uint16_t y, uint16_t color) +{ + cbi(P_CS, B_CS); + setYX(x, y, x, y); + setPixel(color); + sbi(P_CS, B_CS); + clrXY(); +} + +void TeensyDisplay::drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) +{ + uint16_t color16 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3); + + cbi(P_CS, B_CS); + setYX(x, y, x, y); + setPixel(color16); + sbi(P_CS, B_CS); + clrXY(); +} + +void TeensyDisplay::LCD_Writ_Bus(uint8_t ch, uint8_t cl) +{ + *(volatile uint8_t *)(&GPIOD_PDOR) = ch; + *(volatile uint8_t *)(&GPIOC_PDOR) = cl; + pulse_low(P_WR, B_WR); +} + +void TeensyDisplay::LCD_Write_COM(uint8_t VL) +{ + cbi(P_RS, B_RS); + LCD_Writ_Bus(0x00, VL); +} + +void TeensyDisplay::LCD_Write_DATA(uint8_t VH, uint8_t VL) +{ + sbi(P_RS, B_RS); + LCD_Writ_Bus(VH,VL); +} + +void TeensyDisplay::LCD_Write_DATA(uint8_t VL) +{ + sbi(P_RS, B_RS); + LCD_Writ_Bus(0x00, VL); +} + +void TeensyDisplay::LCD_Write_COM_DATA(uint8_t com1, uint16_t dat1) +{ + LCD_Write_COM(com1); + LCD_Write_DATA(dat1>>8, dat1); +} + +void TeensyDisplay::moveTo(uint16_t col, uint16_t row) +{ + cbi(P_CS, B_CS); + + // FIXME: eventually set drawing to the whole screen and leave it that way + + // set drawing to the whole screen + // setYX(0, 0, disp_y_size, disp_x_size); + LCD_Write_COM_DATA(0x4e,row); // RAM address set (horiz) + LCD_Write_COM_DATA(0x4f,col); // RAM address set (vert) + + LCD_Write_COM(0x22); +} + +void TeensyDisplay::drawNextPixel(uint16_t color) +{ + // Anything inside this object should call setPixel directly. This + // is primarily for the BIOS. + setPixel(color); +} + +void TeensyDisplay::blit() +{ + uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep + + // remember these are "starts at pixel number" values, where 0 is the first. + #define HOFFSET 18 + #define VOFFSET 13 + + // Define the horizontal area that we're going to draw in + LCD_Write_COM_DATA(0x45, HOFFSET); // offset by 20 to center it... + LCD_Write_COM_DATA(0x46, 279+HOFFSET); + + // position the "write" address + LCD_Write_COM_DATA(0x4e,0+VOFFSET); // row + LCD_Write_COM_DATA(0x4f,HOFFSET); // col + + // prepare the LCD to receive data bytes for its RAM + LCD_Write_COM(0x22); + + // send the pixel data + sbi(P_RS, B_RS); + uint16_t pixel; + for (uint8_t y=0; y<192; y++) { // Drawing 192 of the 240 rows + pixel = y * (320/2)-1; + for (uint16_t x=0; x<280; x++) { // Drawing 280 of the 320 pixels + uint8_t colorIdx; + if (!(x & 0x01)) { + pixel++; + colorIdx = videoBuffer[pixel] >> 4; + } else { + colorIdx = videoBuffer[pixel] & 0x0F; + } + LCD_Writ_Bus(loresPixelColors[colorIdx]>>8,loresPixelColors[colorIdx]); + } + } + cbi(P_CS, B_CS); + + // draw overlay, if any + if (overlayMessage[0]) { + // reset the viewport in order to draw the overlay... + LCD_Write_COM_DATA(0x45, 0); + LCD_Write_COM_DATA(0x46, 319); + + drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage); + } +} + +void TeensyDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c) +{ + int8_t xsize = 8, + ysize = 0x0C, + offset = 0x20; + uint16_t temp; + + c -= offset;// font starts with a space + + uint16_t offPixel, onPixel; + switch (mode) { + case M_NORMAL: + onPixel = 0xFFFF; + offPixel = 0x0010; + break; + case M_SELECTED: + onPixel = 0x0000; + offPixel = 0xFFFF; + break; + case M_DISABLED: + default: + onPixel = 0x7BEF; + offPixel = 0x0000; + break; + case M_SELECTDISABLED: + onPixel = 0x7BEF; + offPixel = 0xFFE0; + break; + } + + temp=(c*ysize); + for (int8_t y_off = 0; y_off <= ysize; y_off++) { + moveTo(x, y + y_off); + uint8_t ch = pgm_read_byte(&BiosFont[temp]); + for (int8_t x_off = 0; x_off <= xsize; x_off++) { + if (ch & (1 << (7-x_off))) { + setPixel(onPixel); + } else { + setPixel(offPixel); + } + } + temp++; + } +} + +void TeensyDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str) +{ + int8_t xsize = 8; // width of a char in this font + + for (int8_t i=0; i watermark) { + // black... + bgr = bgg = bgb = 0; + } + + for (int x=0; x<11; x++) { + uint8_t *p = &appleBitmap[(y * 10 + (x-1))*4]; + // It's RGBA; blend w/ background color + + uint8_t r,g,b; + float alpha = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 3]) / 255.0; + + r = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 0]) + * alpha + (bgr * (1.0 - alpha)); + g = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 1]) + * alpha + (bgr * (1.0 - alpha)); + b = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 2]) + * alpha + (bgr * (1.0 - alpha)); + + + drawPixel(x+xoff, y+yoff, r, g, b); + } + } +} + diff --git a/teensy/teensy-display.h b/teensy/teensy-display.h new file mode 100644 index 0000000..00163d3 --- /dev/null +++ b/teensy/teensy-display.h @@ -0,0 +1,81 @@ +#ifndef __TEENSY_DISPLAY_H +#define __TEENSY_DISPLAY_H + +#include +#include "physicaldisplay.h" + +enum { + M_NORMAL = 0, + M_SELECTED = 1, + M_DISABLED = 2, + M_SELECTDISABLED = 3 +}; + +#define regtype volatile uint8_t +#define regsize uint8_t + +#define cbi(reg, bitmask) *reg &= ~bitmask +#define sbi(reg, bitmask) *reg |= bitmask +#define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask); +#define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask); + +#define cport(port, data) port &= data +#define sport(port, data) port |= data + +#define swap(type, i, j) {type t = i; i = j; j = t;} + +class UTFT; +class BIOS; + +class TeensyDisplay : public PhysicalDisplay { + friend class BIOS; + + public: + TeensyDisplay(); + virtual ~TeensyDisplay(); + + virtual void blit(); + virtual void redraw(); + + void clrScr(); + + virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c); + virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str); + + virtual void drawDriveDoor(uint8_t which, bool isOpen); + virtual void drawDriveStatus(uint8_t which, bool isRunning); + virtual void drawBatteryStatus(uint8_t percent); + + protected: + void moveTo(uint16_t col, uint16_t row); + void drawNextPixel(uint16_t color); + + + private: + regtype *P_RS, *P_WR, *P_CS, *P_RST, *P_SDA, *P_SCL, *P_ALE; + regsize B_RS, B_WR, B_CS, B_RST, B_SDA, B_SCL, B_ALE; + + uint8_t fch, fcl; // high and low foreground colors + + void _fast_fill_16(int ch, int cl, long pix); + + void setYX(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + void clrXY(); + + void setColor(byte r, byte g, byte b); + void setColor(uint16_t color); + void fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + void drawPixel(uint16_t x, uint16_t y); + void drawPixel(uint16_t x, uint16_t y, uint16_t color); + void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b); + + void LCD_Writ_Bus(uint8_t VH,uint8_t VL); + void LCD_Write_COM(uint8_t VL); + void LCD_Write_DATA(uint8_t VH,uint8_t VL); + void LCD_Write_DATA(uint8_t VL); + void LCD_Write_COM_DATA(uint8_t com1,uint16_t dat1); + + bool needsRedraw; +}; + +#endif diff --git a/teensy/teensy-filemanager.cpp b/teensy/teensy-filemanager.cpp new file mode 100644 index 0000000..104d3de --- /dev/null +++ b/teensy/teensy-filemanager.cpp @@ -0,0 +1,275 @@ +#include +#include +#include "ff.h" // File System +#include "teensy-filemanager.h" +#include // strcpy + + +// FIXME: globals are yucky. +DIR dir; +FILINFO fno; +FIL fil; + +static TCHAR *char2tchar( const char *charString, int nn, TCHAR *output) +{ + int ii; + for (ii=0; ii= MAXFILES) + return -1; + + + // No, so we'll add it to the end + strncpy(cachedNames[numCached], name, MAXPATH-1); + cachedNames[numCached][MAXPATH-1] = '\0'; // safety: ensure string terminator + fileSeekPositions[numCached] = 0; + + numCached++; + return numCached-1; +} + +void TeensyFileManager::closeFile(int8_t fd) +{ + // invalid fd provided? + if (fd < 0 || fd >= numCached) + return; + + // clear the name + cachedNames[fd][0] = '\0'; +} + +const char *TeensyFileManager::fileName(int8_t fd) +{ + if (fd < 0 || fd >= numCached) + return NULL; + + return cachedNames[fd]; +} + +// suffix may be comma-separated +int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen) +{ + // ... open, read, save next name, close, return name. Horribly + // inefficient but hopefully won't break the sd layer. And if it + // works then we can make this more efficient later. + + // First entry is always "../" + if (startIdx == 0) { + strcpy(outputFN, "../"); + return 0; + } + + int8_t idxCount = 1; + TCHAR buf[MAXPATH]; + char2tchar(where, MAXPATH, buf); + buf[strlen(where)-1] = '\0'; // this library doesn't want trailing slashes + FRESULT rc = f_opendir(&dir, buf); + if (rc) { + Serial.printf("f_opendir '%s' failed: %d\n", where, rc); + return -1; + } + + while (1) { + rc = f_readdir(&dir, &fno); + if (rc || !fno.fname[0]) { + // No more - all done. + f_closedir(&dir); + return -1; + } + + if (fno.fname[0] == '.' || fno.fname[0] == '_' || fno.fname[0] == '~') { + // skip MAC fork files and any that have been deleted :/ + continue; + } + + // skip anything that has the wrong suffix + char fn[MAXPATH]; + tchar2char(fno.fname, MAXPATH, fn); + if (suffix && !(fno.fattrib & AM_DIR) && strlen(fn) >= 3) { + const char *fsuff = &fn[strlen(fn)-3]; + if (strstr(suffix, ",")) { + // multiple suffixes to check + bool matchesAny = false; + const char *p = suffix; + while (p && strlen(p)) { + if (!strncasecmp(fsuff, p, 3)) { + matchesAny = true; + break; + } + p = strstr(p, ",")+1; + } + if (matchesAny) + continue; + } else { + // one suffix to check + if (strcasecmp(fsuff, suffix)) + continue; + } + } + + if (idxCount == startIdx) { + if (fno.fattrib & AM_DIR) { + strcat(fn, "/"); + } + strncpy(outputFN, fn, maxlen); + f_closedir(&dir); + return idxCount; + } + + idxCount++; + } + + /* NOTREACHED */ +} + +void TeensyFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib) +{ + if (fd < 0 || fd >= numCached) + return; + + fileSeekPositions[fd] = block * (isNib ? 416 : 256); +} + + +bool TeensyFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib) +{ + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, read, close. + TCHAR buf[MAXPATH]; + char2tchar(cachedNames[fd], MAXPATH, buf); + FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ); + if (rc) { + Serial.println("failed to open"); + return false; + } + + rc = f_lseek(&fil, fileSeekPositions[fd]); + if (rc) { + Serial.println("readTrack: seek failed"); + f_close(&fil); + return false; + } + + UINT v; + f_read(&fil, toWhere, isNib ? 0x1a00 : (256 * 16), &v); + f_close(&fil); + return (v == (isNib ? 0x1a00 : (256 * 16))); +} + +bool TeensyFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib) +{ + // open, seek, read, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, read, close. + TCHAR buf[MAXPATH]; + char2tchar(cachedNames[fd], MAXPATH, buf); + FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ); + if (rc) { + Serial.println("failed to open"); + return false; + } + + rc = f_lseek(&fil, fileSeekPositions[fd]); + if (rc) { + Serial.println("readBlock: seek failed"); + f_close(&fil); + return false; + } + UINT v; + f_read(&fil, toWhere, isNib ? 416 : 256, &v); + f_close(&fil); + return (v == (isNib ? 416 : 256)); +} + +bool TeensyFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib) +{ + // open, seek, write, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // can't write just a single block of a nibblized track + if (isNib) + return false; + + // open, seek, write, close. + TCHAR buf[MAXPATH]; + char2tchar(cachedNames[fd], MAXPATH, buf); + FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE); + rc = f_lseek(&fil, fileSeekPositions[fd]); + UINT v; + f_write(&fil, fromWhere, 256, &v); + f_close(&fil); + return (v == 256); +} + +bool TeensyFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib) +{ + // open, seek, write, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, write, close. + TCHAR buf[MAXPATH]; + char2tchar(cachedNames[fd], MAXPATH, buf); + FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE); + rc = f_lseek(&fil, fileSeekPositions[fd]); + UINT v; + f_write(&fil, fromWhere, isNib ? 0x1a00 : (256*16), &v); + f_close(&fil); + return (v == (isNib ? 0x1a00 : (256*16))); +} + diff --git a/teensy/teensy-filemanager.h b/teensy/teensy-filemanager.h new file mode 100644 index 0000000..910ab3b --- /dev/null +++ b/teensy/teensy-filemanager.h @@ -0,0 +1,30 @@ +#ifndef __TEENSYFILEMANAGER_H +#define __TEENSYFILEMANAGER_H + +#include "filemanager.h" +#include + +class TeensyFileManager : public FileManager { + public: + TeensyFileManager(); + virtual ~TeensyFileManager(); + + virtual int8_t openFile(const char *name); + virtual void closeFile(int8_t fd); + + virtual const char *fileName(int8_t fd); + + virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen); + virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false); + virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false); + virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false); + virtual bool writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false); + virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false); + + private: + int8_t numCached; + char cachedNames[MAXFILES][MAXPATH]; + unsigned long fileSeekPositions[MAXFILES]; +}; + +#endif diff --git a/teensy/teensy-keyboard.cpp b/teensy/teensy-keyboard.cpp new file mode 100644 index 0000000..f1c3e9d --- /dev/null +++ b/teensy/teensy-keyboard.cpp @@ -0,0 +1,231 @@ +#include +#include "teensy-keyboard.h" +#include +#include + +const byte ROWS = 5; +const byte COLS = 13; + +char keys[ROWS][COLS] = { + { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', DEL }, + { ESC, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']' }, + { _CTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', RET }, + { LSHFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', RSHFT, 0 }, + { LOCK, '`', TAB, '\\', LA, ' ', RA, LARR, RARR, DARR, UARR, 0, 0 } +}; + +uint8_t rowsPins[ROWS] = { 33, 34, 35, 36, 37 }; +uint8_t colsPins[COLS] = { 0, 1, 3, 4, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; +Keypad keypad(makeKeymap(keys), rowsPins, colsPins, ROWS, COLS); +RingBuffer buffer(10); // 10 keys should be plenty, right? + +static uint8_t shiftedNumber[] = { '<', // , + '_', // - + '>', // . + '?', // / + ')', // 0 + '!', // 1 + '@', // 2 + '#', // 3 + '$', // 4 + '%', // 5 + '^', // 6 + '&', // 7 + '*', // 8 + '(', // 9 + 0, // (: is not a key) + ':' // ; +}; + +TeensyKeyboard::TeensyKeyboard(VMKeyboard *k) : PhysicalKeyboard(k) +{ + keypad.setDebounceTime(5); + + leftShiftPressed = false; + rightShiftPressed = false; + ctrlPressed = false; + capsLock = true; + leftApplePressed = false; + rightApplePressed = false; + + numPressed = 0; +} + +TeensyKeyboard::~TeensyKeyboard() +{ +} + +void TeensyKeyboard::pressedKey(uint8_t key) +{ + numPressed++; + + if (key & 0x80) { + // it's a modifier key. + switch (key) { + case _CTRL: + ctrlPressed = 1; + break; + case LSHFT: + leftShiftPressed = 1; + break; + case RSHFT: + rightShiftPressed = 1; + break; + case LOCK: + capsLock = !capsLock; + break; + case LA: + leftApplePressed = 1; + break; + case RA: + rightApplePressed = 1; + break; + } + return; + } + + if (key == ' ' || key == DEL || key == ESC || key == RET || key == TAB) { + buffer.addByte(key); + return; + } + + if (key >= 'a' && + key <= 'z') { + if (ctrlPressed) { + buffer.addByte(key - 'a' + 1); + return; + } + if (leftShiftPressed || rightShiftPressed || capsLock) { + buffer.addByte(key - 'a' + 'A'); + return; + } + buffer.addByte(key); + return; + } + + // FIXME: can we control-shift? + if (key >= ',' && key <= ';') { + if (leftShiftPressed || rightShiftPressed) { + buffer.addByte(shiftedNumber[key - ',']); + return; + } + buffer.addByte(key); + return; + } + + if (leftShiftPressed || rightShiftPressed) { + uint8_t ret = 0; + switch (key) { + case '=': + ret = '+'; + break; + case '[': + ret = '{'; + break; + case ']': + ret = '}'; + break; + case '\\': + ret = '|'; + break; + case '\'': + ret = '"'; + break; + case '`': + ret = '~'; + break; + } + if (ret) { + buffer.addByte(ret); + return; + } + } + + // Everything else falls through. + buffer.addByte(key); +} + +void TeensyKeyboard::releasedKey(uint8_t key) +{ + numPressed--; + if (key & 0x80) { + // it's a modifier key. + switch (key) { + case _CTRL: + ctrlPressed = 0; + break; + case LSHFT: + leftShiftPressed = 0; + break; + case RSHFT: + rightShiftPressed = 0; + break; + case LA: + leftApplePressed = 0; + break; + case RA: + rightApplePressed = 0; + break; + } + } +} + +bool TeensyKeyboard::kbhit() +{ + if (keypad.getKeys()) { + for (int i=0; ikeyDepressed(keypad.key[i].kchar); + break; + case RELEASED: + vmkeyboard->keyReleased(keypad.key[i].kchar); + break; + case HOLD: + case IDLE: + break; + } + } + } + } +} diff --git a/teensy/teensy-keyboard.h b/teensy/teensy-keyboard.h new file mode 100644 index 0000000..7a10d73 --- /dev/null +++ b/teensy/teensy-keyboard.h @@ -0,0 +1,34 @@ +#ifndef __TEENSY_KEYBOARD_H +#define __TEENSY_KEYBOARD_H + +#include "physicalkeyboard.h" + +class TeensyKeyboard : public PhysicalKeyboard { + public: + TeensyKeyboard(VMKeyboard *k); + virtual ~TeensyKeyboard(); + + // Interface used by the VM... + virtual void maintainKeyboard(); + + // Interface used by the BIOS... + bool kbhit(); + int8_t read(); + + + private: + void pressedKey(uint8_t key); + void releasedKey(uint8_t key); + + private: + bool leftShiftPressed; + bool rightShiftPressed; + bool ctrlPressed; + bool capsLock; + bool leftApplePressed; + bool rightApplePressed; + + int8_t numPressed; +}; + +#endif diff --git a/teensy/teensy-paddles.cpp b/teensy/teensy-paddles.cpp new file mode 100644 index 0000000..0434a75 --- /dev/null +++ b/teensy/teensy-paddles.cpp @@ -0,0 +1,53 @@ +#include "teensy-paddles.h" + +/* C061: Open Apple (Paddle 0 button pressed if &= 0x80) + * C062: Closed Apple (Paddle 1 button pressed if &= 0x80) + * C064: PADDLE0 (sets bit 0x80 when value reached, increments of 11 us) + * C065: PADDLE1 (sets bit 0x80 when value reached, increments of 11 us) + * C070: "start reading paddle data" - "may take up to 3 milliseconds" + */ + +#define PADDLE0 A24 +#define PADDLE1 A23 + +#include "globals.h" + +TeensyPaddles::TeensyPaddles() +{ + pinMode(PADDLE0, INPUT); + pinMode(PADDLE1, INPUT); +} + +TeensyPaddles::~TeensyPaddles() +{ +} + +uint8_t TeensyPaddles::paddle0() +{ + uint8_t raw = 255 - analogRead(PADDLE0); + return raw; + + // 40 .. 200 on the old joystick + if (raw >200) raw = 200; + if (raw < 40) raw = 40; + + return map(raw, 40, 200, 0, 255); +} + +uint8_t TeensyPaddles::paddle1() +{ + uint8_t raw = analogRead(PADDLE1); + return raw; + + // 60..200 on the old joystick + if (raw >200) raw = 200; + if (raw < 60) raw = 60; + + return map(raw, 60, 200, 0, 255); +} + +void TeensyPaddles::startReading() +{ + g_vm->triggerPaddleInCycles(0, 12 * paddle0()); + g_vm->triggerPaddleInCycles(1, 12 * paddle1()); +} diff --git a/teensy/teensy-paddles.h b/teensy/teensy-paddles.h new file mode 100644 index 0000000..daae610 --- /dev/null +++ b/teensy/teensy-paddles.h @@ -0,0 +1,13 @@ +#include + +#include "physicalpaddles.h" + +class TeensyPaddles : public PhysicalPaddles { + public: + TeensyPaddles(); + virtual ~TeensyPaddles(); + + virtual uint8_t paddle0(); + virtual uint8_t paddle1(); + virtual void startReading(); +}; diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp new file mode 100644 index 0000000..8b5cdda --- /dev/null +++ b/teensy/teensy-speaker.cpp @@ -0,0 +1,33 @@ +#include +#include "teensy-speaker.h" + +extern int16_t g_volume; + +TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker() +{ + speakerState = false; + speakerPin = pinNum; + pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control + nextTransitionAt = 0; +} + +TeensySpeaker::~TeensySpeaker() +{ +} + +void TeensySpeaker::toggleAtCycle(uint32_t c) +{ + // FIXME: could tell here if we dropped something - is nextTransitionAt already set? If so, we missed a toggle :( + nextTransitionAt = c; +} + +void TeensySpeaker::maintainSpeaker(uint32_t c) +{ + if (nextTransitionAt && c >= nextTransitionAt) { + nextTransitionAt = 0; + + speakerState = !speakerState; + // FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor? + analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095 + } +} diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h new file mode 100644 index 0000000..49910c5 --- /dev/null +++ b/teensy/teensy-speaker.h @@ -0,0 +1,22 @@ +#ifndef __TEENSY_SPEAKER_H +#define __TEENSY_SPEAKER_H + +#include "physicalspeaker.h" + +class TeensySpeaker : public PhysicalSpeaker { + public: + TeensySpeaker(uint8_t pinNum); + virtual ~TeensySpeaker(); + + virtual void toggleAtCycle(uint32_t c); + virtual void maintainSpeaker(uint32_t c); + + private: + bool speakerState; + uint8_t speakerPin; + + uint32_t nextTransitionAt; + +}; + +#endif diff --git a/teensy/teensy.ino b/teensy/teensy.ino new file mode 100644 index 0000000..9b4dda2 --- /dev/null +++ b/teensy/teensy.ino @@ -0,0 +1,440 @@ +#include +#include +#include // uSDFS +#include +#include +#include +#include "bios.h" +#include "cpu.h" +#include "applevm.h" +#include "teensy-display.h" +#include "teensy-keyboard.h" +#include "teensy-speaker.h" +#include "teensy-paddles.h" +#include "teensy-filemanager.h" + +#define RESETPIN 39 +#define BATTERYPIN A19 +#define SPEAKERPIN A21 + +//#define DEBUGCPU + +#include "globals.h" +#include "teensy-crash.h" + +volatile float nextInstructionMicros; +volatile float startMicros; + +FATFS fatfs; /* File system object */ +BIOS bios; + +uint8_t videoBuffer[320*240/2]; + +enum { + D_NONE = 0, + D_SHOWFPS = 1, + D_SHOWMEMFREE = 2, + D_SHOWPADDLES = 3, + D_SHOWPC = 4, + D_SHOWCYCLES = 5, + D_SHOWBATTERY = 6, + D_SHOWTIME = 7 +}; +uint8_t debugMode = D_NONE; + +static time_t getTeensy3Time() { return Teensy3Clock.get(); } + +/* Totally messing around with the RadioHead library */ +#include +#include +#include +RHSoftwareSPI spi; + +#define RF_CSN 40 +#define RF_MOSI 41 +#define RF_IRQ 42 +#define RF_CE 53 +#define RF_SCK 52 +#define RF_MISO 51 + +RH_NRF24 nrf24(RF_CE, RF_CSN, spi); + +void setup() +{ + Serial.begin(230400); + + /* while (!Serial) { + ; // wait for serial port to connect. Needed for Leonardo only + } + Serial.println("hi"); + */ + delay(100); // let the serial port connect if it's gonna + + enableFaultHandler(); + + // set the Time library to use Teensy 3.0's RTC to keep time + setSyncProvider(getTeensy3Time); + delay(100); // don't know if we need this + if (timeStatus() == timeSet) { + Serial.println("RTC set from Teensy"); + } else { + Serial.println("Error while setting RTC"); + } + + spi.setPins(RF_MISO, RF_MOSI, RF_SCK); + if (!nrf24.init()) + Serial.println("init failed"); + // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm + if (!nrf24.setChannel(1)) + Serial.println("setChannel failed"); + if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) + Serial.println("setRF failed"); + Serial.println("nrf24 initialized"); + + TCHAR *device = (TCHAR *)_T("0:/"); + f_mount (&fatfs, device, 0); /* Mount/Unmount a logical drive */ + + pinMode(RESETPIN, INPUT); + digitalWrite(RESETPIN, HIGH); + + analogReference(EXTERNAL); // 3.3v external, instead of 1.7v internal + analogReadRes(8); // We only need 8 bits of resolution (0-255) for battery & paddles + analogReadAveraging(4); // ?? dunno if we need this or not. + + pinMode(SPEAKERPIN, OUTPUT); // analog speaker output, used as digital volume control + pinMode(BATTERYPIN, INPUT); + + Serial.println("creating virtual hardware"); + g_speaker = new TeensySpeaker(SPEAKERPIN); + + Serial.println(" fm"); + // First create the filemanager - the interface to the host file system. + g_filemanager = new TeensyFileManager(); + + // Construct the interface to the host display. This will need the + // VM's video buffer in order to draw the VM, but we don't have that + // yet. + Serial.println(" display"); + g_display = new TeensyDisplay(); + + // Next create the virtual CPU. This needs the VM's MMU in order to + // run, but we don't have that yet. + Serial.println(" cpu"); + g_cpu = new Cpu(); + + // Create the virtual machine. This may read from g_filemanager to + // get ROMs if necessary. (The actual Apple VM we've built has them + // compiled in, though.) It will create its virutal hardware (MMU, + // video driver, floppy, paddles, whatever). + Serial.println(" vm"); + g_vm = new AppleVM(); + + // Now that the VM exists and it has created an MMU, we tell the CPU + // how to access memory through the MMU. + Serial.println(" [setMMU]"); + g_cpu->SetMMU(g_vm->getMMU()); + + // And the physical keyboard needs hooks in to the virtual keyboard... + Serial.println(" keyboard"); + g_keyboard = new TeensyKeyboard(g_vm->getKeyboard()); + + Serial.println(" paddles"); + g_paddles = new TeensyPaddles(); + + // Now that all the virtual hardware is glued together, reset the VM + Serial.println("Resetting VM"); + g_vm->Reset(); + + g_display->redraw(); + g_display->blit(); + + Serial.println("Reading prefs"); + readPrefs(); // read from eeprom and set anything we need setting + + Serial.println("free-running"); + + startMicros = 0; + nextInstructionMicros = micros(); + Timer1.initialize(3); + Timer1.attachInterrupt(runCPU); + Timer1.start(); +} + +/* We're running the timer that calls this at 1/3 "normal" speed, and + * then asking runCPU to run 48 steps (individual opcodes executed) of + * the CPU before returning. Then we figure out how many cycles + * elapsed during that run, and keep track of how many cycles we now + * have to "drain off" (how many extra ran during this attempt -- we + * expected at least 3, but might have gotten more). Then the next + * call here from the interrupt subtracts 3 cycles, on the assumption + * that 3 have passed, and we're good to go. + * + * This approach is reasonable: the 6502 instruction set takes an + * average of 4 clock cycles to execute. This compromise keeps us from + * chewing up the entire CPU on interrupt overhead, allowing us to + * focus on refreshing the LCD as fast as possible while sacrificing + * some small timing differences. Experimentally, paddle values seem + * to still read well up to 48 steps. At 2*48, the paddles drift at + * the low end, meaning there's probably an issue with timing. + */ +void runCPU() +{ + // static bool outputState = false; + // outputState = !outputState; + // digitalWrite(56, outputState); + + if (micros() >= nextInstructionMicros) { +#ifdef DEBUGCPU + g_cpu->Run(1); +#else + g_cpu->Run(24); +#endif + + // The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think + // the next instruction should run based on how long the execution + // was ((1000/1023) * numberOfCycles) - which is about 97.8%. + +#ifdef DEBUGCPU + // ... have to slow down so the printing all works + nextInstructionMicros = startMicros + (float)g_cpu->cycles * 50; +#else + nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978; +#endif + +#ifdef DEBUGCPU + { + uint8_t p = g_cpu->flags; + Serial.printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n", + g_vm->getMMU()->read(g_cpu->pc), + g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp, + p & (1<<7) ? 'N':' ', + p & (1<<6) ? 'V':' ', + p & (1<<4) ? 'B':' ', + p & (1<<3) ? 'D':' ', + p & (1<<2) ? 'I':' ', + p & (1<<1) ? 'Z':' ', + p & (1<<0) ? 'C':' ' + ); + } +#endif + } + + g_speaker->maintainSpeaker(g_cpu->cycles); + ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); +} + +// FIXME: move these memory-related functions elsewhere... + +// This only gives you an estimated free mem size. It's not perfect. +uint32_t FreeRamEstimate() +{ + uint32_t stackTop; + uint32_t heapTop; + + // current position of the stack. + stackTop = (uint32_t) &stackTop; + + // current position of heap. + void* hTop = malloc(1); + heapTop = (uint32_t) hTop; + free(hTop); + + // The difference is the free, available ram. + return stackTop - heapTop; +} + +#include "malloc.h" + +int heapSize(){ + return mallinfo().uordblks; +} + +void biosInterrupt() +{ + // Shut down the CPU + Timer1.stop(); + + // wait for the interrupt button to be released + while (digitalRead(RESETPIN) == LOW) + ; + + // invoke the BIOS + if (bios.runUntilDone()) { + // if it returned true, we have something to store persistently in EEPROM. + writePrefs(); + } + + // if we turned off debugMode, make sure to clear the debugMsg + if (debugMode == D_NONE) { + g_display->debugMsg(""); + } + + // clear the CPU next-step counters + g_cpu->cycles = 0; + nextInstructionMicros = micros(); + startMicros = micros(); + + // Force the display to redraw + ((AppleDisplay*)(g_vm->vmdisplay))->modeChange(); + + // Poll the keyboard before we start, so we can do selftest on startup + g_keyboard->maintainKeyboard(); + + // Restart the CPU + Timer1.start(); +} + + +void loop() +{ + static uint16_t ctr = -1; + + /* testing the fault handler? uncomment this and it'll crash. */ + // *((int*)0x0) = 1; + + static unsigned long nextBattCheck = 0; + static int batteryLevel = 0; // static for debugging code! When done + // debugging, this can become a local + // in the appropriate block below + if (millis() >= nextBattCheck) { + // FIXME: what about rollover? + nextBattCheck = millis() + 1 * 1000; // once a minute? maybe? FIXME: Right now 1/sec + + // FIXME: scale appropriately. + batteryLevel = analogRead(BATTERYPIN); + + /* 205 is "near dead, do something about it right now" - 3.2v and lower. + * What's the top end? 216-ish? + * + * The reading fluctuates quite a lot - we should probably capture + * more and average it over a longer period before showing + * anything (FIXME) + */ + if (batteryLevel < 205) + batteryLevel = 205; + if (batteryLevel > 216) + batteryLevel = 216; + + batteryLevel = map(batteryLevel, 205, 216, 0, 100); + ((AppleVM *)g_vm)->batteryLevel( batteryLevel ); + } + + if ((++ctr & 0xFF) == 0) { + if (digitalRead(RESETPIN) == LOW) { + // This is the BIOS interrupt. We immediately act on it. + biosInterrupt(); + } + + if (g_vm->vmdisplay->needsRedraw()) { + // make sure to clear the flag before drawing; there's no lock + // on didRedraw, so the other thread might update it + g_vm->vmdisplay->didRedraw(); + g_display->blit(); + } + + g_keyboard->maintainKeyboard(); + + { + char buf[25]; + switch (debugMode) { + case D_SHOWFPS: + // display some FPS data + static uint32_t startAt = millis(); + static uint32_t loopCount = 0; + loopCount++; + time_t lenSecs; + lenSecs = (millis() - startAt) / 1000; + if (lenSecs >= 5) { + sprintf(buf, "%lu FPS", loopCount / lenSecs); + g_display->debugMsg(buf); + startAt = millis(); + loopCount = 0; + } + break; + case D_SHOWMEMFREE: + sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize()); + g_display->debugMsg(buf); + break; + case D_SHOWPADDLES: + sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1()); + g_display->debugMsg(buf); + break; + case D_SHOWPC: + sprintf(buf, "%X", g_cpu->pc); + g_display->debugMsg(buf); + break; + case D_SHOWCYCLES: + sprintf(buf, "%lX", g_cpu->cycles); + g_display->debugMsg(buf); + break; + case D_SHOWBATTERY: + sprintf(buf, "BAT %d", analogRead(BATTERYPIN)); + g_display->debugMsg(buf); + break; + case D_SHOWTIME: + sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second()); + g_display->debugMsg(buf); + break; + } + } + } +} + +typedef struct _prefs { + uint32_t magic; + int16_t volume; +} prefs; + +// Fun trivia: the Apple //e was in production from January 1983 to +// November 1993. And the 65C02 in them supported weird BCD math modes. +#define MAGIC 0x01831093 + +void readPrefs() +{ + prefs p; + uint8_t *pp = (uint8_t *)&p; + + Serial.println("reading prefs"); + + for (uint8_t i=0; i 4095) { + p.volume = 4095; + } + if (p.volume < 0) { + p.volume = 0; + } + + g_volume = p.volume; + return; + } + + // use defaults + g_volume = 0; +} + +// Writes to EEPROM slow down the Teensy 3.6's CPU to 120MHz automatically. Disable our timer +// while we're doing it and we'll just see a pause. +void writePrefs() +{ + Timer1.stop(); + + Serial.println("writing prefs"); + + prefs p; + uint8_t *pp = (uint8_t *)&p; + + p.magic = MAGIC; + p.volume = g_volume; + + for (uint8_t i=0; i