2017-02-20 18:55:16 +00:00
# include <Arduino.h>
# include <SPI.h>
# include <TimeLib.h>
2020-07-04 20:28:07 +00:00
# include <TimerOne.h>
2017-02-20 18:55:16 +00:00
# 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"
2018-01-07 19:43:17 +00:00
# include "appleui.h"
2018-02-18 01:44:04 +00:00
# include "teensy-prefs.h"
2020-07-04 12:03:58 +00:00
# include "teensy-println.h"
2018-02-18 01:44:04 +00:00
2017-02-20 18:55:16 +00:00
# define RESETPIN 39
2018-02-07 15:20:26 +00:00
# define BATTERYPIN 32
2017-02-20 18:55:16 +00:00
# define SPEAKERPIN A21
# include "globals.h"
# include "teensy-crash.h"
2018-01-03 01:28:47 +00:00
uint32_t nextInstructionMicros ;
uint32_t startMicros ;
2017-02-20 18:55:16 +00:00
BIOS bios ;
2018-02-07 15:20:26 +00:00
// How many microseconds per cycle
# define SPEEDCTL ((float)1000000 / (float)g_speed)
2017-02-20 18:55:16 +00:00
static time_t getTeensy3Time ( ) { return Teensy3Clock . get ( ) ; }
2017-07-14 00:31:04 +00:00
# define ESP_TXD 51
# define ESP_CHPD 52
# define ESP_RST 53
# define ESP_RXD 40
# define ESP_GPIO0 41
# define ESP_GPIO2 42
2017-02-20 18:55:16 +00:00
void setup ( )
{
Serial . begin ( 230400 ) ;
2020-06-28 13:21:27 +00:00
/*
while ( ! Serial ) {
yield ( ) ;
} */
delay ( 100 ) ; // let the power settle
2017-02-20 18:55:16 +00:00
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 ) {
2020-07-04 12:03:58 +00:00
println ( " RTC set from Teensy " ) ;
2017-02-20 18:55:16 +00:00
} else {
2020-07-04 12:03:58 +00:00
println ( " Error while setting RTC " ) ;
2017-02-20 18:55:16 +00:00
}
pinMode ( RESETPIN , INPUT ) ;
digitalWrite ( RESETPIN , HIGH ) ;
2017-08-14 23:48:37 +00:00
analogReference ( EXTERNAL ) ; // 3.3v external, or 1.7v internal. We need 1.7 internal for the battery level, which means we're gonna have to do something about the paddles :/
2017-02-20 18:55:16 +00:00
analogReadRes ( 8 ) ; // We only need 8 bits of resolution (0-255) for battery & paddles
analogReadAveraging ( 4 ) ; // ?? dunno if we need this or not.
2017-02-26 16:00:41 +00:00
analogWriteResolution ( 12 ) ;
2017-02-20 18:55:16 +00:00
pinMode ( SPEAKERPIN , OUTPUT ) ; // analog speaker output, used as digital volume control
pinMode ( BATTERYPIN , INPUT ) ;
2020-07-04 12:03:58 +00:00
println ( " creating virtual hardware " ) ;
2017-02-20 18:55:16 +00:00
g_speaker = new TeensySpeaker ( SPEAKERPIN ) ;
2020-07-04 12:03:58 +00:00
println ( " fm " ) ;
2017-02-20 18:55:16 +00:00
// 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.
2020-07-04 12:03:58 +00:00
println ( " display " ) ;
2017-02-20 18:55:16 +00:00
g_display = new TeensyDisplay ( ) ;
2020-07-04 12:03:58 +00:00
println ( " UI " ) ;
2018-01-07 19:43:17 +00:00
g_ui = new AppleUI ( ) ;
2017-02-20 18:55:16 +00:00
// Next create the virtual CPU. This needs the VM's MMU in order to
// run, but we don't have that yet.
2020-07-04 12:03:58 +00:00
println ( " cpu " ) ;
2017-02-20 18:55:16 +00:00
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).
2020-07-04 12:03:58 +00:00
println ( " vm " ) ;
2017-02-20 18:55:16 +00:00
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.
2020-07-04 12:03:58 +00:00
println ( " [setMMU] " ) ;
2017-02-20 18:55:16 +00:00
g_cpu - > SetMMU ( g_vm - > getMMU ( ) ) ;
// And the physical keyboard needs hooks in to the virtual keyboard...
2020-07-04 12:03:58 +00:00
println ( " keyboard " ) ;
2017-02-20 18:55:16 +00:00
g_keyboard = new TeensyKeyboard ( g_vm - > getKeyboard ( ) ) ;
2020-07-04 12:03:58 +00:00
println ( " paddles " ) ;
2017-07-13 13:53:28 +00:00
g_paddles = new TeensyPaddles ( A23 , A24 , 1 , 1 ) ;
2017-02-20 18:55:16 +00:00
// Now that all the virtual hardware is glued together, reset the VM
2020-07-04 12:03:58 +00:00
println ( " Resetting VM " ) ;
2017-02-20 18:55:16 +00:00
g_vm - > Reset ( ) ;
g_display - > redraw ( ) ;
2017-02-26 16:00:41 +00:00
// g_display->blit();
2017-02-20 18:55:16 +00:00
2020-07-04 12:03:58 +00:00
println ( " Reading prefs " ) ;
2017-02-20 18:55:16 +00:00
readPrefs ( ) ; // read from eeprom and set anything we need setting
2018-01-03 01:28:47 +00:00
startMicros = nextInstructionMicros = micros ( ) ;
2017-02-20 18:55:16 +00:00
2017-02-26 16:00:41 +00:00
// Debugging: insert a disk on startup...
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
2017-08-27 11:51:21 +00:00
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
2018-01-03 01:28:47 +00:00
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
2017-02-20 18:55:16 +00:00
2018-02-07 15:20:26 +00:00
// pinMode(56, OUTPUT);
// pinMode(57, OUTPUT);
2017-02-27 00:59:51 +00:00
2018-01-03 01:28:47 +00:00
Serial . print ( " Free RAM: " ) ;
2020-07-04 12:03:58 +00:00
println ( FreeRamEstimate ( ) ) ;
2018-01-03 01:28:47 +00:00
2020-07-04 12:03:58 +00:00
println ( " free-running " ) ;
2018-01-07 19:43:17 +00:00
2020-07-04 20:28:07 +00:00
Timer1 . initialize ( 3 ) ;
Timer1 . attachInterrupt ( runCPU ) ;
Timer1 . start ( ) ;
2017-02-20 18:55:16 +00:00
}
// 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 ( )
{
2020-07-04 20:28:07 +00:00
Timer1 . stop ( ) ;
2017-02-27 00:59:51 +00:00
2017-02-20 18:55:16 +00:00
// 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
2018-02-07 15:20:26 +00:00
if ( g_debugMode = = D_NONE ) {
2017-02-20 18:55:16 +00:00
g_display - > debugMsg ( " " ) ;
}
// clear the CPU next-step counters
g_cpu - > cycles = 0 ;
nextInstructionMicros = micros ( ) ;
startMicros = micros ( ) ;
2017-12-31 22:21:34 +00:00
// Drain the speaker queue (FIXME: a little hacky)
2018-01-03 01:28:47 +00:00
g_speaker - > maintainSpeaker ( - 1 , - 1 ) ;
2017-02-20 18:55:16 +00:00
// Force the display to redraw
2018-02-18 01:44:04 +00:00
g_display - > redraw ( ) ;
2017-02-20 18:55:16 +00:00
( ( AppleDisplay * ) ( g_vm - > vmdisplay ) ) - > modeChange ( ) ;
// Poll the keyboard before we start, so we can do selftest on startup
g_keyboard - > maintainKeyboard ( ) ;
2017-02-27 00:59:51 +00:00
2020-07-04 20:28:07 +00:00
Timer1 . start ( ) ;
2017-02-20 18:55:16 +00:00
}
2017-08-15 00:38:04 +00:00
//bool debugState = false;
//bool debugLCDState = false;
2017-02-20 18:55:16 +00:00
2018-02-07 15:20:26 +00:00
2017-02-27 00:59:51 +00:00
void runCPU ( )
2017-02-20 18:55:16 +00:00
{
2020-07-04 20:28:07 +00:00
g_inInterrupt = true ;
// Debugging: to watch when the speaker is triggered...
// static bool debugState = false;
// debugState = !debugState;
// digitalWrite(56, debugState);
// Relatively critical timing: CPU needs to run ahead at least 4
// cycles, b/c we're calling this interrupt (runCPU, that is) just
// about 1/3 as fast as we should; and the speaker is updated
// directly from within it, so it needs to be real-ish time.
if ( micros ( ) > nextInstructionMicros ) {
// Debugging: to watch when the CPU is triggered...
// static bool debugState = false;
// debugState = !debugState;
// digitalWrite(56, debugState);
2017-02-27 00:59:51 +00:00
2020-07-04 20:28:07 +00:00
uint8_t executed = g_cpu - > Run ( 24 ) ;
// 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%.
nextInstructionMicros = startMicros + ( ( double ) g_cpu - > cycles * ( double ) SPEEDCTL ) ;
( ( AppleVM * ) g_vm ) - > cpuMaintenance ( g_cpu - > cycles ) ;
2017-02-26 16:00:41 +00:00
}
2020-07-04 20:28:07 +00:00
g_inInterrupt = false ;
2017-02-27 00:59:51 +00:00
}
void loop ( )
{
if ( digitalRead ( RESETPIN ) = = LOW ) {
// This is the BIOS interrupt. We immediately act on it.
biosInterrupt ( ) ;
}
g_keyboard - > maintainKeyboard ( ) ;
2017-08-15 00:38:04 +00:00
//debugLCDState = !debugLCDState;
//digitalWrite(57, debugLCDState);
2017-02-27 00:59:51 +00:00
doDebugging ( ) ;
2017-08-15 00:38:04 +00:00
// Only redraw if the CPU is caught up; and then we'll suspend the
// CPU to draw a full frame.
2017-08-27 12:07:24 +00:00
// Note that this breaks audio, b/c it's real-time and requires the
// CPU running to change the audio line's value. So we need to EITHER
//
// - delay the audio line by at least the time it takes for one
// display update, OR
// - lock display updates so the CPU can update the memory, but we
// keep drawing what was going to be displayed
2020-07-04 20:28:07 +00:00
//
// The Timer1.stop()/start() is bad. Using it, the display doesn't
// tear; but the audio is also broken. Taking it out, audio is good
// but the display tears. So there's a global - g_prioritizeDisplay -
// which lets the user pick which they want.
if ( g_prioritizeDisplay )
Timer1 . stop ( ) ;
2018-02-18 01:44:04 +00:00
g_ui - > blit ( ) ;
2017-08-30 17:28:48 +00:00
g_vm - > vmdisplay - > lockDisplay ( ) ;
if ( g_vm - > vmdisplay - > needsRedraw ( ) ) {
AiieRect what = g_vm - > vmdisplay - > getDirtyRect ( ) ;
g_vm - > vmdisplay - > didRedraw ( ) ;
g_display - > blit ( what ) ;
}
g_vm - > vmdisplay - > unlockDisplay ( ) ;
2020-07-04 20:28:07 +00:00
if ( g_prioritizeDisplay )
Timer1 . start ( ) ;
2018-01-03 01:28:47 +00:00
2018-02-07 15:20:26 +00:00
static unsigned long nextBattCheck = millis ( ) + 30 ; // debugging
2017-02-20 18:55:16 +00:00
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?
2018-02-07 15:20:26 +00:00
nextBattCheck = millis ( ) + 3 * 1000 ; // check every 3 seconds
2017-02-20 18:55:16 +00:00
2017-08-14 23:48:37 +00:00
// This is a bit disruptive - but the external 3.3v will drop along with the battery level, so we should use the more stable (I hope) internal 1.7v.
2017-07-18 06:13:12 +00:00
// The alternative is to build a more stable buck/boost regulator for reference...
analogReference ( INTERNAL ) ;
2017-02-20 18:55:16 +00:00
batteryLevel = analogRead ( BATTERYPIN ) ;
2017-07-18 06:13:12 +00:00
analogReference ( EXTERNAL ) ;
/* LiIon charge to a max of 4.2v; and we should not let them discharge below about 3.5v.
* With a resistor voltage divider of Z1 = 39 k , Z2 = 10 k we ' re looking at roughly 20.4 % of
* those values : ( 10 / 49 ) * 4.2 = 0.857 v , and ( 10 / 49 ) * 3.5 = 0.714 v . Since the external
* voltage reference flags as the battery drops , we can ' t use that as an absolute
* reference . So using the INTERNAL 1.1 v reference , that should give us a reasonable
* range , in theory ; the math shows the internal reference to be about 1.27 v ( assuming
* the resistors are indeed 39 k and 10 k , which is almost certainly also wrong ) . But
* then the high end would be 172 , and the low end is about 142 , which matches my
* actual readings here very well .
2017-07-14 00:31:04 +00:00
*
2017-07-18 06:13:12 +00:00
* Actual measurements :
* 3.46 v = 144 - 146
* 4.21 v = 172
2017-02-20 18:55:16 +00:00
*/
2018-02-07 15:20:26 +00:00
#if 0
2017-07-14 00:31:04 +00:00
Serial . print ( " battery: " ) ;
2020-07-04 12:03:58 +00:00
println ( batteryLevel ) ;
2017-07-14 00:31:04 +00:00
# endif
2017-07-18 06:13:12 +00:00
if ( batteryLevel < 146 )
batteryLevel = 146 ;
2017-08-14 23:48:37 +00:00
if ( batteryLevel > 168 )
batteryLevel = 168 ;
2017-02-20 18:55:16 +00:00
2017-08-14 23:48:37 +00:00
batteryLevel = map ( batteryLevel , 146 , 168 , 0 , 100 ) ;
2018-01-07 19:43:17 +00:00
g_ui - > drawPercentageUIElement ( UIePowerPercentage , batteryLevel ) ;
2017-02-20 18:55:16 +00:00
}
2017-02-26 16:00:41 +00:00
}
void doDebugging ( )
{
char buf [ 25 ] ;
2018-02-07 15:20:26 +00:00
switch ( g_debugMode ) {
2017-02-26 16:00:41 +00:00
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 ;
2017-02-20 18:55:16 +00:00
}
2017-02-26 16:00:41 +00:00
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 ;
2020-07-04 12:03:58 +00:00
case D_SHOWDSK :
{
uint8_t sd = ( ( AppleVM * ) g_vm ) - > disk6 - > selectedDrive ( ) ;
sprintf ( buf , " s %d t %d " ,
sd ,
( ( AppleVM * ) g_vm ) - > disk6 - > headPosition ( sd ) ) ;
g_display - > debugMsg ( buf ) ;
}
break ;
2017-02-20 18:55:16 +00:00
}
}
void readPrefs ( )
{
2018-02-18 01:44:04 +00:00
TeensyPrefs np ;
prefs_t p ;
if ( np . readPrefs ( & p ) ) {
g_volume = p . volume ;
g_displayType = p . displayType ;
g_debugMode = p . debug ;
2020-07-04 20:28:07 +00:00
g_prioritizeDisplay = p . priorityMode ;
2018-02-18 01:44:04 +00:00
g_speed = ( p . speed * ( 1023000 / 2 ) ) ; // steps of half normal speed
if ( g_speed < ( 1023000 / 2 ) )
g_speed = ( 1023000 / 2 ) ;
if ( p . disk1 [ 0 ] ) {
( ( AppleVM * ) g_vm ) - > insertDisk ( 0 , p . disk1 ) ;
2017-02-20 18:55:16 +00:00
}
2018-02-18 01:44:04 +00:00
if ( p . disk2 [ 0 ] ) {
( ( AppleVM * ) g_vm ) - > insertDisk ( 1 , p . disk2 ) ;
2017-02-20 18:55:16 +00:00
}
2018-02-18 01:44:04 +00:00
if ( p . hd1 [ 0 ] ) {
( ( AppleVM * ) g_vm ) - > insertHD ( 0 , p . hd1 ) ;
}
2017-02-20 18:55:16 +00:00
2018-02-18 01:44:04 +00:00
if ( p . hd2 [ 0 ] ) {
( ( AppleVM * ) g_vm ) - > insertHD ( 1 , p . hd2 ) ;
}
}
2017-02-20 18:55:16 +00:00
}
void writePrefs ( )
{
2018-02-18 01:44:04 +00:00
TeensyPrefs np ;
prefs_t p ;
2017-02-20 18:55:16 +00:00
2018-02-18 01:44:04 +00:00
g_display - > clrScr ( ) ;
g_display - > drawString ( M_SELECTED , 80 , 100 , " Writing prefs... " ) ;
g_display - > flush ( ) ;
2017-02-20 18:55:16 +00:00
2018-02-18 01:44:04 +00:00
p . magic = PREFSMAGIC ;
p . prefsSize = sizeof ( prefs_t ) ;
p . version = PREFSVERSION ;
2017-02-20 18:55:16 +00:00
2018-02-18 01:44:04 +00:00
p . volume = g_volume ;
p . displayType = g_displayType ;
p . debug = g_debugMode ;
2020-07-04 20:28:07 +00:00
p . priorityMode = g_prioritizeDisplay ;
2018-02-18 01:44:04 +00:00
p . speed = g_speed / ( 1023000 / 2 ) ;
strcpy ( p . disk1 , ( ( AppleVM * ) g_vm ) - > DiskName ( 0 ) ) ;
strcpy ( p . disk2 , ( ( AppleVM * ) g_vm ) - > DiskName ( 1 ) ) ;
strcpy ( p . hd1 , ( ( AppleVM * ) g_vm ) - > HDName ( 0 ) ) ;
strcpy ( p . hd2 , ( ( AppleVM * ) g_vm ) - > HDName ( 1 ) ) ;
2017-02-27 00:59:51 +00:00
2020-07-04 20:28:07 +00:00
Timer1 . stop ( ) ;
2018-02-18 01:44:04 +00:00
bool ret = np . writePrefs ( & p ) ;
2020-07-04 20:28:07 +00:00
Timer1 . start ( ) ;
2017-02-20 18:55:16 +00:00
}