mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-11-05 11:05:41 +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 |