mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-11-25 19:31:36 +00:00
functional code for the Teensy
This commit is contained in:
parent
8e155646c9
commit
3af0b916d7
153
README.md
153
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:
|
||||
|
||||
<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)
|
||||
===================
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
1
teensy/appledisplay.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/appledisplay.cpp
|
1
teensy/appledisplay.h
Symbolic link
1
teensy/appledisplay.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/appledisplay.h
|
1
teensy/applekeyboard.cpp
Symbolic link
1
teensy/applekeyboard.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applekeyboard.cpp
|
1
teensy/applekeyboard.h
Symbolic link
1
teensy/applekeyboard.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applekeyboard.h
|
1
teensy/applemmu-rom.h
Symbolic link
1
teensy/applemmu-rom.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applemmu-rom.h
|
1
teensy/applemmu.cpp
Symbolic link
1
teensy/applemmu.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applemmu.cpp
|
1
teensy/applemmu.h
Symbolic link
1
teensy/applemmu.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applemmu.h
|
1
teensy/applevm.cpp
Symbolic link
1
teensy/applevm.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applevm.cpp
|
1
teensy/applevm.h
Symbolic link
1
teensy/applevm.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/applevm.h
|
1
teensy/bios-font.h
Symbolic link
1
teensy/bios-font.h
Symbolic link
@ -0,0 +1 @@
|
||||
../bios-font.h
|
421
teensy/bios.cpp
Normal file
421
teensy/bios.cpp
Normal 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
38
teensy/bios.h
Normal 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
1
teensy/cpu.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../cpu.cpp
|
1
teensy/cpu.h
Symbolic link
1
teensy/cpu.h
Symbolic link
@ -0,0 +1 @@
|
||||
../cpu.h
|
1
teensy/diskii-rom.h
Symbolic link
1
teensy/diskii-rom.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/diskii-rom.h
|
1
teensy/diskii.cpp
Symbolic link
1
teensy/diskii.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/diskii.cpp
|
1
teensy/diskii.h
Symbolic link
1
teensy/diskii.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/diskii.h
|
1
teensy/display-bg.h
Symbolic link
1
teensy/display-bg.h
Symbolic link
@ -0,0 +1 @@
|
||||
../display-bg.h
|
1
teensy/filemanager.h
Symbolic link
1
teensy/filemanager.h
Symbolic link
@ -0,0 +1 @@
|
||||
../filemanager.h
|
1
teensy/font.h
Symbolic link
1
teensy/font.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/font.h
|
1
teensy/globals.cpp
Symbolic link
1
teensy/globals.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../globals.cpp
|
1
teensy/globals.h
Symbolic link
1
teensy/globals.h
Symbolic link
@ -0,0 +1 @@
|
||||
../globals.h
|
1
teensy/mmu.h
Symbolic link
1
teensy/mmu.h
Symbolic link
@ -0,0 +1 @@
|
||||
../mmu.h
|
1
teensy/nibutil.cpp
Symbolic link
1
teensy/nibutil.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/nibutil.cpp
|
1
teensy/nibutil.h
Symbolic link
1
teensy/nibutil.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/nibutil.h
|
1
teensy/physicaldisplay.h
Symbolic link
1
teensy/physicaldisplay.h
Symbolic link
@ -0,0 +1 @@
|
||||
../physicaldisplay.h
|
1
teensy/physicalkeyboard.h
Symbolic link
1
teensy/physicalkeyboard.h
Symbolic link
@ -0,0 +1 @@
|
||||
../physicalkeyboard.h
|
1
teensy/physicalpaddles.h
Symbolic link
1
teensy/physicalpaddles.h
Symbolic link
@ -0,0 +1 @@
|
||||
../physicalpaddles.h
|
1
teensy/physicalspeaker.h
Symbolic link
1
teensy/physicalspeaker.h
Symbolic link
@ -0,0 +1 @@
|
||||
../physicalspeaker.h
|
1
teensy/slot.cpp
Symbolic link
1
teensy/slot.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/slot.cpp
|
1
teensy/slot.h
Symbolic link
1
teensy/slot.h
Symbolic link
@ -0,0 +1 @@
|
||||
../apple/slot.h
|
126
teensy/teensy-clock.cpp
Normal file
126
teensy/teensy-clock.cpp
Normal 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
30
teensy/teensy-clock.h
Normal 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
72
teensy/teensy-crash.h
Normal 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
560
teensy/teensy-display.cpp
Normal 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 = 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<strlen(str); i++) {
|
||||
drawCharacter(mode, x, y, str[i]);
|
||||
x += xsize; // fixme: any inter-char spacing?
|
||||
}
|
||||
}
|
||||
|
||||
void TeensyDisplay::drawDriveDoor(uint8_t which, bool isOpen)
|
||||
{
|
||||
// location of drive door for left drive
|
||||
|
||||
uint16_t xoff = 55;
|
||||
uint16_t yoff = 216;
|
||||
|
||||
// location for right drive
|
||||
|
||||
if (which == 1) {
|
||||
xoff += 134;
|
||||
}
|
||||
|
||||
for (int y=0; y<20; y++) {
|
||||
for (int x=0; x<43; x++) {
|
||||
uint8_t r, g, b;
|
||||
if (isOpen) {
|
||||
r = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 0]);
|
||||
g = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 1]);
|
||||
b = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 2]);
|
||||
} else {
|
||||
r = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 0]);
|
||||
g = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 1]);
|
||||
b = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 2]);
|
||||
}
|
||||
drawPixel(x+xoff, y+yoff, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeensyDisplay::drawDriveStatus(uint8_t which, bool isRunning)
|
||||
{
|
||||
// location of status indicator for left drive
|
||||
uint16_t xoff = 125;
|
||||
uint16_t yoff = 213;
|
||||
|
||||
// and right drive
|
||||
if (which == 1)
|
||||
xoff += 135;
|
||||
|
||||
for (int y=0; y<2; y++) {
|
||||
for (int x=0; x<6; x++) {
|
||||
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeensyDisplay::drawBatteryStatus(uint8_t percent)
|
||||
{
|
||||
uint16_t xoff = 300;
|
||||
uint16_t yoff = 222;
|
||||
|
||||
// DEBUGGING: disabling this; it's drawing it a *lot*
|
||||
return;
|
||||
|
||||
// the area around the apple is 12 wide
|
||||
// it's exactly 11 high
|
||||
// the color is 210/202/159
|
||||
|
||||
float watermark = ((float)percent / 100.0) * 11;
|
||||
|
||||
for (int y=0; y<11; y++) {
|
||||
uint8_t bgr = 210;
|
||||
uint8_t bgg = 202;
|
||||
uint8_t bgb = 159;
|
||||
if (11-y > 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
81
teensy/teensy-display.h
Normal file
81
teensy/teensy-display.h
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef __TEENSY_DISPLAY_H
|
||||
#define __TEENSY_DISPLAY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#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
|
275
teensy/teensy-filemanager.cpp
Normal file
275
teensy/teensy-filemanager.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
#include <Arduino.h>
|
||||
#include <wchar.h>
|
||||
#include "ff.h" // File System
|
||||
#include "teensy-filemanager.h"
|
||||
#include <string.h> // 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<nn; ii++) {
|
||||
output[ii] = (TCHAR)charString[ii];
|
||||
if (!charString[ii])
|
||||
break;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static char * tchar2char( const TCHAR * tcharString, int nn, char * charString)
|
||||
{ int ii;
|
||||
for(ii = 0; ii<nn; ii++)
|
||||
{ charString[ii] = (char)tcharString[ii];
|
||||
if(!charString[ii]) break;
|
||||
}
|
||||
return charString;
|
||||
}
|
||||
|
||||
|
||||
TeensyFileManager::TeensyFileManager()
|
||||
{
|
||||
numCached = 0;
|
||||
}
|
||||
|
||||
TeensyFileManager::~TeensyFileManager()
|
||||
{
|
||||
}
|
||||
|
||||
int8_t TeensyFileManager::openFile(const char *name)
|
||||
{
|
||||
// See if there's a hole to re-use...
|
||||
for (int i=0; i<numCached; i++) {
|
||||
if (cachedNames[i][0] == '\0') {
|
||||
strncpy(cachedNames[i], name, MAXPATH-1);
|
||||
cachedNames[i][MAXPATH-1] = '\0'; // safety: ensure string terminator
|
||||
fileSeekPositions[i] = 0;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// check for too many open files
|
||||
if (numCached >= 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)));
|
||||
}
|
||||
|
30
teensy/teensy-filemanager.h
Normal file
30
teensy/teensy-filemanager.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef __TEENSYFILEMANAGER_H
|
||||
#define __TEENSYFILEMANAGER_H
|
||||
|
||||
#include "filemanager.h"
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
231
teensy/teensy-keyboard.cpp
Normal file
231
teensy/teensy-keyboard.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
#include <Arduino.h>
|
||||
#include "teensy-keyboard.h"
|
||||
#include <Keypad.h>
|
||||
#include <RingBuffer.h>
|
||||
|
||||
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; i<LIST_MAX; i++) {
|
||||
if ( keypad.key[i].stateChanged ) {
|
||||
switch (keypad.key[i].kstate) {
|
||||
case PRESSED:
|
||||
pressedKey(keypad.key[i].kchar);
|
||||
break;
|
||||
case RELEASED:
|
||||
releasedKey(keypad.key[i].kchar);
|
||||
break;
|
||||
case HOLD:
|
||||
case IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging: also allow USB serial to act as a keyboard
|
||||
if (Serial.available()) {
|
||||
buffer.addByte(Serial.read());
|
||||
}
|
||||
|
||||
return buffer.hasData();
|
||||
}
|
||||
|
||||
int8_t TeensyKeyboard::read()
|
||||
{
|
||||
if (buffer.hasData()) {
|
||||
return buffer.consumeByte();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a non-buffered interface to the physical keyboard, as used
|
||||
// by the VM.
|
||||
void TeensyKeyboard::maintainKeyboard()
|
||||
{
|
||||
if (keypad.getKeys()) {
|
||||
for (int i=0; i<LIST_MAX; i++) {
|
||||
if ( keypad.key[i].stateChanged ) {
|
||||
switch (keypad.key[i].kstate) {
|
||||
case PRESSED:
|
||||
vmkeyboard->keyDepressed(keypad.key[i].kchar);
|
||||
break;
|
||||
case RELEASED:
|
||||
vmkeyboard->keyReleased(keypad.key[i].kchar);
|
||||
break;
|
||||
case HOLD:
|
||||
case IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
teensy/teensy-keyboard.h
Normal file
34
teensy/teensy-keyboard.h
Normal file
@ -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
|
53
teensy/teensy-paddles.cpp
Normal file
53
teensy/teensy-paddles.cpp
Normal file
@ -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());
|
||||
}
|
13
teensy/teensy-paddles.h
Normal file
13
teensy/teensy-paddles.h
Normal file
@ -0,0 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "physicalpaddles.h"
|
||||
|
||||
class TeensyPaddles : public PhysicalPaddles {
|
||||
public:
|
||||
TeensyPaddles();
|
||||
virtual ~TeensyPaddles();
|
||||
|
||||
virtual uint8_t paddle0();
|
||||
virtual uint8_t paddle1();
|
||||
virtual void startReading();
|
||||
};
|
33
teensy/teensy-speaker.cpp
Normal file
33
teensy/teensy-speaker.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include <Arduino.h>
|
||||
#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
|
||||
}
|
||||
}
|
22
teensy/teensy-speaker.h
Normal file
22
teensy/teensy-speaker.h
Normal file
@ -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
|
440
teensy/teensy.ino
Normal file
440
teensy/teensy.ino
Normal file
@ -0,0 +1,440 @@
|
||||
#include <Arduino.h>
|
||||
#include <TimerOne.h>
|
||||
#include <ff.h> // uSDFS
|
||||
#include <SPI.h>
|
||||
#include <EEPROM.h>
|
||||
#include <TimeLib.h>
|
||||
#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 <SPI.h>
|
||||
#include <RH_NRF24.h>
|
||||
#include <RHSoftwareSPI.h>
|
||||
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<sizeof(prefs); i++) {
|
||||
*pp++ = EEPROM.read(i);
|
||||
}
|
||||
|
||||
if (p.magic == MAGIC) {
|
||||
// looks valid! Use it.
|
||||
Serial.println("prefs valid! Restoring volume");
|
||||
if (p.volume > 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<sizeof(prefs); i++) {
|
||||
EEPROM.write(i, *pp++);
|
||||
}
|
||||
|
||||
Timer1.start();
|
||||
}
|
1
teensy/vm.h
Symbolic link
1
teensy/vm.h
Symbolic link
@ -0,0 +1 @@
|
||||
../vm.h
|
1
teensy/vmdisplay.h
Symbolic link
1
teensy/vmdisplay.h
Symbolic link
@ -0,0 +1 @@
|
||||
../vmdisplay.h
|
1
teensy/vmkeyboard.h
Symbolic link
1
teensy/vmkeyboard.h
Symbolic link
@ -0,0 +1 @@
|
||||
../vmkeyboard.h
|
Loading…
Reference in New Issue
Block a user