//  Version 2.20 changes:
//		Fixed sci_logger_value_to_voltage() to properly account for the front-end
//		gain when executing the "read science <channel>" command.  8/1/98

//  TT8 specific includes
#include        <TT8.h>                 /* Tattletale Model 8 Definitions */
#include        <tat332.h>              /* 68332 Tattletale (7,8) Hardware Definitions */
#include        <sim332.h>              /* 68332 System Integration Module Definitions */
#include        <qsm332.h>              /* 68332 Queued Serial Module Definitions */
#include        <tpu332.h>              /* 68332 Time Processing Unit Definitions */
#include        <dio332.h>              /* 68332 Digital I/O Port Pin Definitions */
#include        <tt8pic.h>              /* Model 8 PIC Parallel Slave Port Definitions */
#include        <tt8lib.h>              /* definitions and prototypes for Model 8 library */

//  general C includes
#include        <stdio.h>
#include        <stdlib.h>

//  includes for the data logger
#include                "assert.h"
#include                "ad7716.h"
#include				"max186.h"
#include                "science.h"
#include                "eeprom.h"
#include                "engineer.h"
#include                "logtime.h"
#include                "logdisk.h"
#include                "commands.h"
#include                "logger.h"
#include                "text_log.h"
#include                "intexcpt.h"
#include                "lpsleep.h"
#include				"logdisk.h"
#include				"criterr.h"


//  includes for the disk routines
#include                <pcdisk.h>

//  'previous command' char is backspace
const uchar PREVIOUS_COMMAND = 0x08;
const float SOFTWARE_VERSION = 2.48;


//  The next few lines define the allowed clock frequency for various
//  functions, including the nominal TT8 clock, the disk access frequency
//  (too slow -> wasting power, too fast -> hardware errors), the min
//  speed to execute a sync time set or read, and the min speed to
//  read the engineering channels (which must pause interrupt servicing
//  of the science channels, since they share the SPI interface).
#ifdef DEBUG
	long TT8_clock_freq = (long)16e6;
#else
	#ifdef SIMULATE_DATA
		//  Run all out when simulating data
		long TT8_clock_freq = (long)(16e6);
	#else
		//  fast enough for conversion rate of 128 Hz when not computing triggering
		long TT8_clock_freq = (long)(2.56e6);
	#endif	
#endif
const long DISK_FREQ = (long)8e6;
const long MIN_TIME_SET_FREQ = (long)8e6;
const long MIN_ENG_READ_FREQ = (long)8e6;

//  Structure to group together the data used by the top level supervisor
//  routine which starts all of the sub-systems.
struct supervisor  {
	uchar COM_buffer[128];      //  buffer for input strings which user types
	short n_in_COM_buffer;      //  number of chars in the COM buffer
	short disks_full;
} supervisor;


//  local subroutines used only within this module
short collect_COM_input (void);
short process_COM_input (void);
void collect_data (void);
int disk_access (void);
void print_CRC (void);
char* get_stkbase (void);
#ifdef DEBUG
	void debug_init (void);
	void debug_end (void);
#endif  



void main (void)  {
	long delay;
	time_tt now;

	//  setup Model 8 for running C programs
	InitTT8(NO_WATCHDOG, TT8_TPU);
	printf ("\nStarted...\n");

	//  Init pins so as to avoid unconnected inputs, which can draw an
	//  inordinant amount of power.  This routine should be called only
	//  early in the execution sequence, as some of its settings will
	//  be overwridden by the more advanced functions of the logger.
	prep_pins_low_power ();

	//  switch to normal speed and baud.  This is the only place that we
	//  need to set the default frequency.  Every other routine which changes
	//  the frequency first saves the original frequency and then sets it
	//  back when it is done
	SimSetFSys(TT8_clock_freq);
	SerSetBaud(9600L,0L);
	printf ("\n\nGEOSense data logger.  This is software version %.2f.\n", SOFTWARE_VERSION);

	//  Init all of the sub-systems.  Be sure to init the text logging routines
	//  and the time routines first since the other routines may call them
	log_power_on_init ();
	logger_time_power_on_init ();

	//  check and log the CRC for the TOM8 monitor
	print_CRC ();

	//  log the startup now that the basic inits are done
	log_printf ("\n\nGEOSense data logger.  This is software version %.2f.\n", SOFTWARE_VERSION);
	now = ttmnow ();
	log_printf ("The current time is %s", logger_time_get_string (&now));

	#ifdef DEBUG
	printf ("\n*****************************************************************\n");
	printf ("The data logger software is currently running in debug mode.\n");
	printf ("Lots of error checking code requires a fast clock speed and has\n");
	printf ("increased power consumption beyond the norm.  We do not recommend\n");
	printf ("deployment of this code.\n");
	printf ("*****************************************************************\n");
	#endif
	#ifdef SIMULATE_DATA
	printf ("\n*****************************************************************\n");
	printf ("The data logger software is currently running in SIMULATE DATA mode.\n");
	printf ("None of the recorded data is real.\n");
	printf ("DO NOT DEPLOY THIS CODE!\n");
	printf ("*****************************************************************\n");
	#endif
	

	//  If the stack is high up in memory, then this must be the 256k version
	//  The stack for the 1 Meg version should be at 0x200000
	if (get_stkbase() == (char*)0x2C0000)  {
		#ifndef DEBUG
		printf ("\n***********************************************************\n");
		#endif
		printf ("This code was compiled for a TT8 with 256 kbytes of memory.\n");
		printf ("It will only use 256 kbytes of memory, even if run on a TT8\n");
		printf ("with 1 Mbyte.  Recompile for proper operation on a 1 M TT8.\n");
		printf ("***********************************************************\n");
	}       

	#ifdef DEBUG
	//  inits which apply only to the debug version of this program
	debug_init ();                                  
	#endif                                                                  
	init_IDE ();                    //  inits for the IDE disk access
	AD7716_power_on_init ();        //  init of the interface to the AD7716 A/D
	MAX186_power_on_init ();
	logger_disk_power_on_init ();   //  verifies availability of disk drives
	sci_logger_power_on_init ();    //  and the science logger
	eng_logger_power_on_init ();    //  and the engineering logger
	eeprom_read_setup_data (0);     //  reads the logger setup data from the eeprom

	//  REVIEW
	//  A bit of a kluge, but sometimes useful.  This allows us to remember the
	//  tick offsets as set by a sync set command through a complete code reset
	//  This is useful if our code resets itself, or if the user quits and restarts
	//  the program.  It is not useful if power has been cycled.
	if (logger_time_verify_set () == 0)
		logger_time_set_tick_offset (setup_data.tick_offset);
		
//      disk_power_on_init ();      //  not yet implemented; only important for
									//  multi-disk systems
	
	//  sets up routines to catch unexpected interrupts or exceptions
	unexpected_int_init ();
	
	//  REVIEW - we would like a better way of tracking disk errors so that we
	//  can avoid pointless and power hungry retries while still allowing
	//  a reasonable recovery from errors.
	//  This will be set non-zero if we get a disk error
	supervisor.disks_full = 0;
	
	supervisor.n_in_COM_buffer = 0;
	supervisor.COM_buffer[0] = '\0';

	//  The logger starts out suspended for 10 minutes.  It will start
	//  automatically at the end of this time.
	logger_suspend (600L);
	
	//  processing a command with no data present will force a print
	//  of the command prompt.
	process_COM_input ();

	//  This is the main data logger loop
	for (;;)  {
		//  Check to see if the logger is suspended and if it needs restarting
        if (logger_get_seconds_to_start () != 0)  {
			//  don't restart the logger if there are no disks available!
			if (supervisor.disks_full == 0)
				logger_verify_suspension ();
		}

		//  After any operation which may take a long time, such as a command
		//  or a disk access,  we check to see if the input buffer for the
		//  science logger overflowed.
		if (collect_COM_input () != 0)  {
			if (process_COM_input () != 0)
				break;
			collect_data ();
			sci_logger_overflow_check ();
		}
				
		//  Check to see if a disk access is required, and dump data to disk
		//  if necessary
		if (disk_access ())  {
			//  If we actually spun the disk, then we need to collect some data
			//  to be sure we have space in the buffer and then we have to check
			//  to see if we overflowed the buffer due to a slow disk access and
			//  reset the collection if we did.
			collect_data ();
			sci_logger_overflow_check ();
		}
		
		//  Collect data if any is waiting.  This call and others that it makes
		//  implements the heart of the data logger
		collect_data ();

		//  REVIEW - we should turn off the Serial port to minimize power usage,
		//  but we must be sure that any error messages being printed are complete
	}
	
	//  try to shut down gracefully by doing such things as depowering the analog
	//  sections.
	logger_shutdown ();
	prep_pins_low_power ();
	print_CRC ();

	#ifdef DEBUG
	debug_end ();
	#endif                                                                  
	ResetToMon ();
}





//  collect_COM_input ()
//  
//  Collects any input waiting at the serial COM port and adds it to the
//  input buffer.  Replaces the newline with a NULL.
//
//  Returns: non-zero if there is a complete line in the buffer
//
short collect_COM_input (void)  {
	uchar new_char;
	long temp;

	//  get input from user if available
	if (!SerByteAvail())  {  
		#ifndef DEBUG
			//  don't execute shut down in debug mode because it doesn't allow
			//  spontaneous printing, and we never know when we will print in
			//  debug mode.  Second observation: the "wait for xmission to complete"
			//  function doesn't seem to work properly, which is why there are places
			//  in this code with calls to DelayMilliSecs() after characters are printed
			SerShutDown (1, 1);
		#endif	
		return 0;
	}	

	//  The documentation implies that SerActivate is not necessary.  Skip
	//  at Onset indicates that it is not necessary for character reception,
	//  but it is necessary for xmission.  SerActivate powers up the voltage
	//  converters that are part of the Maxim RS232 chip.  Try allowing 2 mS
	//  for power up
	SerActivate ();
    temp = StopWatchTime ();
   	temp += 50000;
    while (StopWatchTime () < temp)
		{;}
			
	new_char = SerGetByte ();

	//  Stop science data preview as soon as the user hits a key.  Ok
	//  to call these routines even if preview is not on.
	if (supervisor.n_in_COM_buffer == 0)  {
		sci_logger_set_binary_preview (-1);
		sci_logger_set_ASCII_preview (-1);
	}
	
	//  If this character is a carriage return, then this command line is 
	//  complete.  Terminate the string and return non-zero to request
	//  processing of the command string.
	if (new_char == '\r')  {
		printf ("\n");
		//  the use user is only tapping the carriage return key and nothing
		//  else, then don't erase the previously executed command by filling
		//  in the NULL, cause this would inhibit the "execute previous cmd"
		//  function
		if (supervisor.n_in_COM_buffer != 0)
			supervisor.COM_buffer[supervisor.n_in_COM_buffer] = '\0';
		return 1;
	}
	
	//  if not a <return> process char as normal
	if ((new_char == PREVIOUS_COMMAND) && (supervisor.n_in_COM_buffer == 0))  {
		for (supervisor.n_in_COM_buffer = 0;
				supervisor.COM_buffer[supervisor.n_in_COM_buffer] != '\0';
										supervisor.n_in_COM_buffer++)  {
			ASSERT (supervisor.n_in_COM_buffer < sizeof (supervisor.COM_buffer));
//          putchar(supervisor.COM_buffer[supervisor.n_in_COM_buffer]);
			printf ("%c", supervisor.COM_buffer[supervisor.n_in_COM_buffer]);
		}       
	}  else  {
		if (new_char == CHAR_BACKSPACE)  {
			//  if the char was a backspace, then delete one char from our
			//  input buffer, but don't allow user to backspace more than
			//  was typed.  Actually overwrite the char to avoid odd behavoir
			//  if the user backspaces over a line and then asks for the previous
			//  line to be redisplayed.
			if (supervisor.n_in_COM_buffer > 0)  {
				supervisor.n_in_COM_buffer--;
				supervisor.COM_buffer[supervisor.n_in_COM_buffer] = '\0';
//          	putchar (CHAR_BACKSPACE);
				printf ("%c", CHAR_BACKSPACE);
			}
		}  else  {
			supervisor.COM_buffer[supervisor.n_in_COM_buffer] = new_char;
			//  echo the character
//                      putchar (supervisor.COM_buffer[supervisor.n_in_COM_buffer]);
			printf ("%c", supervisor.COM_buffer[supervisor.n_in_COM_buffer]);
			//  don't overflow the buffer; leave room for null at end
			if (supervisor.n_in_COM_buffer < sizeof(supervisor.COM_buffer)-1)
				supervisor.n_in_COM_buffer++;
		}               
	}               
	//  must call flush to force the char to be output from the queue
	fflush (stdout);                
	DelayMilliSecs (1);
	return 0;
}                       



//  process_COM_input ()
//
//  Looks at the data in the global input buffer and interprets it
//  as a command.  Processes the command.
//
//  Returns: 0 => no further action required
//                       non-zero => quit the program
//
short process_COM_input (void)  {
	short result = NOOP;
	
	//  Process the command, but only if there were chars put in the buffer.
	//  We must check because we don't erase previous commands (so we can
	//  implement the "echo previous cmd" function), and we don't want to
	//  execute cmds twice if the user keeps hitting carriage returns.
	if (supervisor.n_in_COM_buffer > 0)  {
		result = command_parse ((char*)supervisor.COM_buffer);
		//  The quit command returns the user to the TT8 monitor.  It is
		//  implemented as a global command so that we have the option of
		//  adding code which could clean up after the data logger code
		if (result == CMD_QUIT)
			return 1;
		
		//  delete the processed command
		supervisor.n_in_COM_buffer = 0;
	}
	
	if (result != CMD_READ_BINARY_SCIENCE)  {
		printf ("Cmd: ");
		fflush (stdout);
		DelayMilliSecs (5);
	}	
	return 0;
}




//  collect_data ()
//
//  Makes subroutine calls to the science and engineering loggers to collect
//  data as necessary.
//
void collect_data (void)  {
	static kluge = 0;

	//  Collect science data.  The argument is the maximum number
	//  of data points to process on this call.  A large number will
	//  slow the response of the logger to user inputs after the user
	//  does something which causes a lot of data to accumulate, such
	//  as a disk print.  In normal operation, the logger should find
	//  only a few points waiting.
	sci_logger_collect (20);
	eng_logger_collect ();
    //  Something of a kluge.  We want to make sure the RS232 xmitter is
    //  shut down without having to call the routine too often, so we
    //  include the shutdown call here for activation every few seconds
    //  Parmams instruct to wait for xmit to finish before shutdown and
    //  to autowake on xmit
//	if (kluge++ > 100)  {
//		SerShutDown (1, 1);
//		kluge = 0;
//	}
}




//  void disk_access ()
//
//  Checks to see if any of the sub-systems wants the disk turned on, and
//  if so, powers on the disk and calls all of the sub-system disk writing
//  routines.
//
int disk_access (void)  {
	short disk_needed;
	long freq, baud;
	short freq_change;
	dword blocks_free, blocks_total;
	short error;
	#if defined(DEBUG) || defined(SIMULATE_DATA)
	time_tt start_time, stop_time;
	long dT;
	#endif	

	//  if the disks are full, don't bother
	if (supervisor.disks_full != 0)
		return 0;
		
	disk_needed = 0;
	disk_needed += sci_logger_disk_request ();
	disk_needed += eng_logger_disk_request ();
	disk_needed += log_disk_request (NULL);
	if (disk_needed == 0)
		return 0;

	#ifdef DEBUG
	if (disk_needed)  {
		printf ("\ndisk_access starting at: ");
		cmd_print_time (NULL);
		if (sci_logger_disk_request())
			printf ("  sci_logger_disk_request positive\n");	
		if (eng_logger_disk_request ())
			printf ("  eng_logger_disk_request positive\n");	
		if (log_disk_request (NULL))
			printf ("  log_disk_request positive\n");	
	}
	#endif
	#if defined(DEBUG)
	start_time = logger_time_get_time ();
	#endif
	#if defined(SIMULATE_DATA)
	StopWatchStart ();
	#endif
		
	//  REVIEW
	//  Can we detect the disruption of the TT8 that occurs when the disk
	//  is spun up with a crappy power supply?  (or dead batteries?)
	//  REVIEW
	//  Recalculate the maximum frequency we can run the clock at during
	//  a disk access and still meet the timing requirements of the IDE
	//  interface.  We may be able to up this frequency.
	freq = SimGetFSys ();
	baud = SerGetBaud (0L, 0L);
	if (freq != DISK_FREQ)  {
		SimSetFSys (DISK_FREQ);
		SerSetBaud (baud, 0L);
		freq_change = 1;
	} else
		freq_change = 0;

	for (;;)  {
		//  Switch disks if we have less than 1 mega byte of space left.  If we have this
		//  much left, then we can be certain we will not run out while dumping the data
		//  to disk.  The downside is that we waste up to 1 MB of space on the disk.  However,
		//  on a typical 4 GB disk, this is 0.025% of the total, which isn't much.  The
		//  positive side of doing it this way is that the switch from disk to disk occurs
		//  only at this point in the code, as opposed to occuring at many different places
		//  through the code at the point of use.  By limiting the switch to this one point
		//  we make it much easier to test the code and insure that it is working.  In a
		//  system for which reliability is important, this is worth the loss of 0.025% of
		//  the disk space.  A secondary benefit is that this avoids the access time penalty
		//  which is imposed when we try to use the last few allocation units on a disk

		//  A disk failure that prevents pc_free from working will leave the
		//  blocks_free variable set at -1
		logger_disk_power_disk_on ();

		blocks_free = -1;
		critical_error_reset ();
		pc_free(logger_disk_get_partition(), &blocks_total, &blocks_free);
		//  A block is 512 bytes - resets with 40 MB left
		if (blocks_free < 81920L)  {
			//  The next_partition() routine will depower the current disk
			log_printf ("disk_access: disk '%s' has only %ld blocks free; switching.\n",
													logger_disk_get_partition(), blocks_free);
																
			printf ("disk_access: disk '%s' has only %ld blocks free; switching.\n",
													logger_disk_get_partition(), blocks_free);
													
			if (logger_disk_next_partition () < 0)  {
				//  REVIEW
				//  All out of disks.
				//  Should probably run through the disks and double check everything
				//  here just to be sure we didn't accidentally abandon a disk due to
				//  a programing error
				supervisor.disks_full = 1;
				printf ("disk_access: out of disks\n");				
				//  break through for loop so we can reset the frequency
				break;
			}
			eng_logger_disk_switch ();
		}

		//  flush the text log file to disk, switching disks if needed
		do  {
			#ifdef DEBUG
				printf ("disk_access: flushing text log\n");
			#endif	
			error = log_flush (NULL);
			if (error)  {	
				//  We might also switch disks if we get an unrecoverable error on the current disk,
				//  even though that disk is not full.
				if (logger_disk_next_partition () < 0)  {
					//  REVIEW
					//  all out of disks.
					supervisor.disks_full = 1;
					error = 1;
					break;
				}
				eng_logger_disk_switch ();
			}
		}  while (error != 0);		
		if (error != 0)
			break;
			
			
		//  flush the engineering logger to disk, switching disks if needed
		do  {
			#ifdef DEBUG
				printf ("disk_access: flushing engineering log\n");
			#endif	
			error = eng_logger_write_disk ();
			if (error)  {	
				//  This error can occur if the disk is bad or if we are out of disks
				if (logger_disk_next_partition () < 0)  {
					//  REVIEW
					//  all out of disks.
					supervisor.disks_full = 1;
					error = 1;
					break;
				}
				eng_logger_disk_switch ();
			}
		}  while (error != 0);
		if (error != 0)
			break;
		
		//  flush the science data to disk, switching disks if needed
		do  {
			#ifdef DEBUG
				printf ("disk_access: flushing science log\n");
			#endif	
			error = sci_logger_write_disk ();;
			if (error)  {	
				//  This error can occur if the disk is bad or if we are out of disks
				if (logger_disk_next_partition () < 0)  {
					//  REVIEW
					//  all out of disks.
					supervisor.disks_full = 1;
					break;
				}
				eng_logger_disk_switch ();
			}
		}  while (error != 0);
		if (error != 0)
			break;
			
		//  flush the text log file to disk one more time, just to cover any log
		//  messages which were generated while writing the science or engineering
		//  logs
		do  {
			#ifdef DEBUG
				printf ("disk_access: flushing text log\n");
			#endif	
			error = log_flush (NULL);
			if (error)  {	
				//  We might also switch disks if we get an unrecoverable error on the current disk,
				//  even though that disk is not full.
				if (logger_disk_next_partition () < 0)  {
					//  REVIEW
					//  all out of disks.
					supervisor.disks_full = 1;
					error = 1;
					break;
				}
				eng_logger_disk_switch ();
			}
		}  while (error != 0);		

		break;			
	}       

	//  power down the disk(s)
	//  REVIEW
	//  There is something funny about the way a disk actually moves
	//  data from its semiconductor buffer to the disk itself.  Some disks
	//  do not immediately flush the data from the buffer to the disk.  If
	//  the disk is powered down before it is flushed, the data is lost.
	//  We try to force a flush before we power down the disk by having 
	//  the power off routine read back the first part of the log file.
	//  This is gross and perhaps unreliable.  Investigate better
	//  solutions and fix this.
	logger_disk_power_disk_off ();
				
	//  be sure to restore the frequency and baud if needed
	if (freq_change)  {
		SimSetFSys (freq);
		SerSetBaud (baud, 0L);
	}
	
	if (supervisor.disks_full)  {
		log_printf ("disks_full is set.\n");
		log_printf ("About to enter low power mode.\n");
		#ifdef NDEBUG
			printf ("disks_full is set.\n");
			printf ("About to enter low power mode.\n");
		#endif	
		logger_disk_final_flush ();
						
		//  REVIEW - if the disks are full we should disable the
		//  logger to save power.  A more sophisticated solution
		//  might be called for.  If we do this, we may as well turn
		//  the clock down as well.  Maybe we should call a special
		//  "all_done" routine.  The concern is that we would accidently
		//  call it when we weren't really dead yet.
		logger_suspend (MAX_SUSPENSION_TIME);

		//  The following routine does not return until a character is
		//  received over the RS232 interface
		low_power_start_sleep ();
	}

	#if defined(DEBUG) && !defined(SIMULATE_DATA)
	printf ("disk_access finishing at: ");
	cmd_print_time (NULL);
	stop_time = logger_time_get_time ();
	dT = ttmcmp (stop_time, start_time);
	printf ("%ld.", dT/GetTickRate());
	printf ("%03d sec disk access time\n", (int)(1000.0*(dT%GetTickRate()) / GetTickRate()));
    #endif

	#ifdef SIMULATE_DATA
	dT = StopWatchTime ();
	log_printf ("%ld.", (long)(dT/1000000L));
	log_printf ("%03d sec disk access time\n", (int)((dT%1000000L)/1000));
	#ifndef DEBUG
	printf ("%ld.", (long)(dT/1000000L));
	printf ("%03d sec disk access time\n", (int)((dT%1000000L)/1000));
	#endif
	if ((dT/1000000L) > 60)  {
		log_printf ("disk_access: likely disk overflow\n");
		printf ("disk_access: likely disk overflow\n");
	}
	#endif

	return 1;       
}       





//  assembler routines to allow access to variables which define how
//  the memory is used
#asm
;:ts=8
;
	dseg
	public  __stkbase
	public  STKSIZ
	public  __H0_org
	public  __H0_end
	cseg
	public  _get_stkbase
	public  _get_STKSIZ
	public  _get_code_start
	public  _get_code_end

_get_stkbase:
	move.l  __stkbase,d0
	rts
	
_get_STKSIZ:
	move.l  STKSIZ,d0
	rts

_get_code_start:
	move.l  __H0_org,d0
	rts
	
_get_code_end:
	move.l  __H0_end,d0
	rts
	
#endasm


//  print_CRC
//
//      Debugging routine only, calculates a CRC so we can verify that there were
//  no accidental code overwrites during execution
//
void print_CRC (void)  {
	//  calculate a crc on our code so as to help catch overwrite bugs
	log_printf ("TOM8 monitor CRC = 0x%04X\n", CalcCRC ((uchar*)0x0, 0x176FL, 0));
	#ifndef DEBUG
		printf ("TOM8 monitor CRC = 0x%04X\n", CalcCRC ((uchar*)0x0, 0x176FL, 0));
	#endif	
//	log_printf ("Data logger CRC = 0x%04X\n", CalcCRC ((uchar*)0x2C2000, 0x225C4L, 0));
}



#ifdef DEBUG

void debug_init (void)  {
    char* cp;
    
	print_CRC ();
	cp = get_stkbase ();
	printf ("The stack base is at 0x%08lX\n", (long)cp);
	//  Skip the first 4 chars because the stkchk routine uses them
	//  to monitor for stack overflow
	cp += 4;
	while (cp < (char*)GetStackPtr ())  {
		*cp++ = (char)0xCC;
	}
	
	return;
}




void debug_end (void)  {
    char* cp;
    long delta;
    long stack_size;
    
	print_CRC ();

	//  check how much of the stack was actually used by looking for our
	//  fill character.  There is apparently no good way to id what the
	//  actual stack size is.  The linker defined public STKSIZ doesn't
	//  seem to work.
	cp = (char*)get_stkbase ();
	cp += 4;
	if ((cp == (char*)0x2C0000L) || (cp == (char*)0x202000L))
		stack_size = 0x2000;
	else    
		stack_size = 0x4000;
	
	while ((*cp == (char)0xCC) && (cp < (char*)GetStackPtr ()))
		cp++;
	delta = (long)cp - (long)get_stkbase();
	printf ("There were %ld bytes of stack space remaining.\n", delta);
	printf ("Apparent usage was %ld bytes\n", stack_size-delta);
}

#endif
		