functional code for the Teensy

This commit is contained in:
Jorj Bauer 2017-02-20 13:55:16 -05:00
parent 8e155646c9
commit 3af0b916d7
52 changed files with 2647 additions and 3 deletions

153
README.md
View File

@ -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:
<pre>
$ 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
</pre>
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)
===================

View File

@ -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;

View File

@ -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;

View File

@ -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);

1
teensy/appledisplay.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/appledisplay.cpp

1
teensy/appledisplay.h Symbolic link
View File

@ -0,0 +1 @@
../apple/appledisplay.h

1
teensy/applekeyboard.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applekeyboard.cpp

1
teensy/applekeyboard.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applekeyboard.h

1
teensy/applemmu-rom.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu-rom.h

1
teensy/applemmu.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu.cpp

1
teensy/applemmu.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu.h

1
teensy/applevm.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applevm.cpp

1
teensy/applevm.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applevm.h

1
teensy/bios-font.h Symbolic link
View File

@ -0,0 +1 @@
../bios-font.h

421
teensy/bios.cpp Normal file
View File

@ -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; i<BIOS_MAXFILES; i++) {
// Put end terminators in place; strncpy won't copy over them
fileDirectory[i][BIOS_MAXPATH] = '\0';
}
}
BIOS::~BIOS()
{
}
bool BIOS::runUntilDone()
{
int8_t prevAction = ACT_EXIT;
bool volumeDidChange = 0;
while (1) {
switch (prevAction = GetAction(prevAction)) {
case ACT_EXIT:
goto done;
case ACT_REBOOT:
ColdReboot();
goto done;
case ACT_RESET:
WarmReset();
goto done;
case ACT_MONITOR:
((AppleVM *)g_vm)->Monitor();
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; i<NUM_ACTIONS; i++) {
char buf[25];
if (i == ACT_DISK1 || i == ACT_DISK2) {
sprintf(buf, titles[i], ((AppleVM *)g_vm)->DiskName(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, "<Prev>");
} else {
g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 10, 50, "<Prev>");
}
uint8_t i;
for (i=0; i<BIOS_MAXFILES; i++) {
if (i < fileCount) {
g_display->drawString((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), "<Next>");
} else {
g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), "<Next>");
}
}
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;
}
}
}

38
teensy/bios.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef __BIOS_H
#define __BIOS_H
#include <Arduino.h>
#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

1
teensy/cpu.cpp Symbolic link
View File

@ -0,0 +1 @@
../cpu.cpp

1
teensy/cpu.h Symbolic link
View File

@ -0,0 +1 @@
../cpu.h

1
teensy/diskii-rom.h Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii-rom.h

1
teensy/diskii.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii.cpp

1
teensy/diskii.h Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii.h

1
teensy/display-bg.h Symbolic link
View File

@ -0,0 +1 @@
../display-bg.h

1
teensy/filemanager.h Symbolic link
View File

@ -0,0 +1 @@
../filemanager.h

1
teensy/font.h Symbolic link
View File

@ -0,0 +1 @@
../apple/font.h

1
teensy/globals.cpp Symbolic link
View File

@ -0,0 +1 @@
../globals.cpp

1
teensy/globals.h Symbolic link
View File

@ -0,0 +1 @@
../globals.h

1
teensy/mmu.h Symbolic link
View File

@ -0,0 +1 @@
../mmu.h

1
teensy/nibutil.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/nibutil.cpp

1
teensy/nibutil.h Symbolic link
View File

@ -0,0 +1 @@
../apple/nibutil.h

1
teensy/physicaldisplay.h Symbolic link
View File

@ -0,0 +1 @@
../physicaldisplay.h

1
teensy/physicalkeyboard.h Symbolic link
View File

@ -0,0 +1 @@
../physicalkeyboard.h

1
teensy/physicalpaddles.h Symbolic link
View File

@ -0,0 +1 @@
../physicalpaddles.h

1
teensy/physicalspeaker.h Symbolic link
View File

@ -0,0 +1 @@
../physicalspeaker.h

1
teensy/slot.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/slot.cpp

1
teensy/slot.h Symbolic link
View File

@ -0,0 +1 @@
../apple/slot.h

126
teensy/teensy-clock.cpp Normal file
View File

@ -0,0 +1,126 @@
#include <string.h> // memset
#include <TimeLib.h>
#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
}

30
teensy/teensy-clock.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef __TEENSYCLOCK_H
#define __TEENSYCLOCK_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#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

72
teensy/teensy-crash.h Normal file
View File

@ -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")));

560
teensy/teensy-display.cpp Normal file
View File

@ -0,0 +1,560 @@
#include <ctype.h> // 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<blocks; i++) {
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
}
if ((pix % 16) != 0) {
for (int i=0; i<(pix % 16); i++)
{
pulse_low(P_WR, B_WR);
}
}
}
void TeensyDisplay::redraw()
{
cbi(P_CS, B_CS);
clrXY();
sbi(P_RS, B_RS);
moveTo(0, 0);
for (int y=0; y<240; y++) {
for (int x=0; x<320; x++) {
uint8_t r = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 0]);
uint8_t g = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 1]);
uint8_t b = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 2]);
uint16_t color16 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 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