/*******************************************************************************
**	NewSleep.c				Tattletale Model 8 New Low Power Sleep Example
**	
**	First release:			Monday, November 17, 1997
**	Latest release:			Monday, November 17, 1997
*****************************************************************************
**	
**	Developed by: John H. Godley dba Peripheral Issues
**	P.O.Box 543, Mashpee, MA 02649-0543 USA
**	jhgodley@periph.com - http://www.periph.com
**	
**	Copyright (C) 1994-1997 Onset Computer Corp.
**	Portions (C) 1997 Peripheral Issues
**	All rights reserved.
**	
*****************************************************************************
**	
**	Copyright and License Information
**	
**	Onset Computer and Peripheral Issues (hereafter, Authors) grants you
**	(hereafter, Licensee) a non-exclusive, non-transferable license to use 
**	the software source code contained in this single source file. Licensee 
**	may distribute binary derivative works using this software to third 
**	parties without fee or restrictions.
**	
**	THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 
**	OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
**	THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
**	PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS SHALL NOT BE LIABLE FOR ANY 
**	DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 
**	DISTRIBUTING THE BINARY SOFTWARE OR ITS DERIVATIVES.
**	
**	By using or copying this Software, Licensee agrees to abide by the
**	copyright law and all other applicable laws of the U.S. including, but
**	not limited to, export control laws, and the terms of this license. The
**	Authors shall have the right to terminate this license immediately by 
**	written notice upon Licensee's breach of, or non-compliance with, any 
**	of its terms. Licensee may be held legally responsible for any copyright
**	infringement or damages resulting from Licensee's failure to abide by
**	the terms of this license. 
**	
**	This example code demonstrate techniques for operating the Model 8 on a
**	periodic schedule while also reducing the power drain to an absolute
**	minimum during the idle periods. Running at 16MHz from a 10 volt supply,
**	the results can be quite dramatic - 500 mW active and less than 5 mW idle.
**	For applications with idle periods longer than 10 seconds and external
**	hardware that can tolerate 3.3 volt operation, the idle power can be
**	dropped to below 2 mW.
**	
*****************************************************************************
**	
**	Program Information
**	
**	This example supersedes the earlier LPSLEEP.C which appears in the Model 8
**	Users Manual. It provides much more reliable operation, especially when
**	used in conjunction with external asynchronous interrupts. To achieve
**	this, it features enhanced communication between the calling functions and
**	sleep routines, but is somewhat more restrictive in the selection of
**	timing intervals, though as you will see, it actually provides more
**	flexible, more predictable, and more useful operation.
**	
**	To make best use of the these functions, you must understand the nature of
**	relative sleep as a technique for scheduling, and you must also understand
**	the limitations imposed by the hardware when the Model 8 enters its lowest
**	power modes.
**	
**	
**	Sleep() and SleepTill()
**	-----------------------
**	Built into the libraries is the function Sleep() which performs relative
**	sleeps using ticks as the interval specifier, and in turn calling library
**	function SleepTill() to do all of the real work. For these functions, each
**	tick can refer to any interval between 25uS (rate 40,000) and 1 second
**	(rate 1), as specified by the SetTickRate() and GetTickRate() library
**	functions. After the call to InitTT8(), the tick rate defaults to 40,000,
**	which is the clock frequency that feeds into the Model 8's PIC
**	microcontroller.
**	
**	Even with a rate of 40000, the usable granularity of all of the built-in
**	time functions is dependent on the communications rate between the 68332
**	and the PIC, and this varies with operating frequency. At 16MHz, this is
**	typically 1.6 milliseconds, but may be as much as 2.0 mS, which gives an
**	effective maximum usable rate of 625 to 500. At 8MHz the maximum is 3 mS
**	(rate 333) and at 4MHz it's 3.8 (rate 260).
**	
**	The Sleep() function must begin with a call to Sleep() with an argument of
**	zero ticks. This tells Sleep() to take the current time as the reference
**	start time. Future calls to Sleep() will specify an actual tick count, and
**	that tells Sleep() how long, relative to the last Sleep(), to wait before
**	returning. In this way, you can set up a periodic operation that is will
**	occur at regular times independent of the time it takes for the operation
**	to conclude - as long as it is less than the total sleep period.
**	
**	Sleep() calls SleepTill() to manage the delay, but SleepTill() can also be
**	called to wait for some arbitrary future time to arrive. Either way, while
**	waiting, the 68332 continuously polls the PIC for the current time to see
**	if it's time to finish - and burns up full power in the process.
**	
**	
**	LowPowerSleep() and LowPowerSleepTill()
**	---------------------------------------
**	Like their full power library counterparts, these functions wait for a
**	future time to arrive, but unlike the library versions, they do everything
**	possible to drop the power as low as possible while waiting. The longer
**	the time you wait between wakeups, the more power you save, and this makes
**	it imperative that there be some external interrupt mechanism to break out
**	from the low power sleep. This of course makes everything much more
**	complex.
**	
**	One of the ways we've tamed this complexity is to limit some of the
**	operating conditions over which LowPowerSleep() will work. You can't run
**	the system at frequencies above 16MHz or below 1.28MHz (because PIC
**	communications break down) and you must setup and use a tick rate of 100
**	which gives a granularity of 10 mS, which is only a little bit worse than
**	the maximum practical granularity of 2 mS. As you'll see, you will
**	probably elect to limit your use of LowPowerSleep() to integral seconds
**	anyway, as that yields the best low power modes.
**	
**	
**	LPSTOP and Interrupts
**	---------------------
**	LPSTOP is an instruction built into the 68332 that turns off the clock to
**	the CPU and all of the internal submodules until an external interrupt
**	comes along to wake it up. This saves an enormous amount of power, but it
**	does require some time to setup, some time to recover, and some practical
**	considerations how the wakeup interrupt is allowed to behave. Once the
**	68332 enters LPSTOP, it takes anywhere from 8.5 to 20 millisecond before
**	the CPU will resume execution.
**	
**	For timed wakeups, the PIC is the only wakeup source, and it is limited to
**	firing off alarm interrupts at integral seconds. If the target wake time
**	is less than the next one second transition, the sleep functions can not
**	take advantage of LPSTOP. If the target wake time occurs in the next
**	second, but there's not enough time to setup the alarm (typically 30mS),
**	the sleep functions can again not take advantage of LPSTOP. If you hope to
**	drop to 3.3 volts during sleep, the setup time increases to 500mS.
**	
**	During timed wakeups, your calling function can specify any of four
**	external interrupts that can wake the sleep up early. When this happens,
**	the sleep functions will return an error code indicating that the sleep
**	did not run through to completion, and you should examine the hardware to
**	determine the cause and the application specific behavior to apply.
**	
**	One of the selectable interrupts connects to the RS232 driver input signal
**	and happens to toggle the interrupt line as the serial character comes in.
**	This is very convenient, but it also demonstrates one of the least
**	friendly ways to effect the wakeup. The following describes what happens
**	with this RS232 interrupt, but the scenarios apply to any external
**	interrupt that does not assert and stay asserted until the 68332 can
**	provide some kind of acknowledgment.
**	
**	At 9600 baud, an entire character transmission will take about 1 mS, and
**	this allows for three possible outcomes. The interrupt could be completely
**	ignored, which is not a problem for an operator who can type a few more
**	keys, but could be very confusing for a separate attached computer. The
**	interrupt could be partially recognized, which puts the 68332 into a sort
**	of limbo state, where it draws nearly full power while waiting for
**	another, more valid interrupt. Finally the interrupt could be recognized
**	enough to generate what's called a spurious interrupt, which tells the CPU
**	that something happened, but there's not a clue what it was.
**	
**	The LowPowerSleep functions handles the spurious interrupt condition by
**	keeping it from crashing your program, and returning an interrupt occurred
**	error code. When your program looks at the hardware for the interrupt
**	source and finds nothing there, it must have been a spurious interrupt,
**	and you should take whatever action is appropriate for your mission.
**	
**	The LowPowerSleep functions can do nothing about the non-recognized, and
**	partially recognized interrupts since they do not regain control of the
**	CPU until a valid interrupt comes along. So...
**	
**	YOU CANNOT EXPECT RELIABLE UNATTENDED SLEEP OPERATION WITH EXTERNAL
**	INTERRUPTS THAT DO NOT STAY ASSERTED UNTIL THEY RECEIVE AN ACKNOWLEDGE!
**	
**	Got that? The failure rate may be one in ten, one in a thousand, or one in
**	a billion, but it will ultimately fail. In the case of the RS232 interrupt
**	when connected to another computer, the solution is to have the
**	interrupting computer assert break until it receives an acknowleding
**	character from your program.
**	
**	The recommended way of awakening using an external interrupt uses a
**	flip-flop and one of the 332's I/O lines. When the external device wants
**	to interrupt the 332 it clocks the flip-flop which sets the IRQx line
**	active. When the 332 has awakened due to IRQx the corresponding handler
**	then resets the flip-flop, disabling the interrupt. This guarantees the
**	line will be asserted and the 332 will properly recognize it.
**	
**	
**	Sleep Intervals
**	---------------
**	As stated above, the LowPowerSleep functions limit the rate setting to a
**	fixed value of 100, or 10mS granularity. For maximum power savings, you
**	will want to voluntarily limit yourself to intervals that are an even
**	number of seconds, and you will also want to keep enabled the
**	sleepZeroEvenSec setting in the SleepPowerOptions flags which forces your
**	LowPowerSleep(0) call to wait for the next seconds transition.
**	
**	Sleep intervals on integral seconds allow the low power sleep functions to
**	spend the maximum portion of idle periods in LPSTOP, but this is only
**	guaranteed if you also keep the sleepZeroEvenSec setting.
**	
**	
**	Using LowPowerSleep
**	-------------------
**	The LowPowerSleep functions are combined in this source module along with
**	example code that exercises them. The macro LPS_EXAMPLE_CODE is declared
**	at the top of the source, and if defined, builds a complete executable
**	program to let you run and test the sleep functions. When LPS_EXAMPLE_CODE
**	is commented out, you can use the low power sleep functions in your own
**	applications without having to modify the source. To do this, you will
**	need to copy the PUBLIC DECLARATIONS section into a project header file,
**	or into the source modules that access low power sleep.
**	
**	
**	Running the Example Code
**	------------------------
**	The example program uses a simple single keystroke menu system to test the
**	sleep functions. At startup and after each command you are presented the
**	menu of commands as shown:
**	
**		Model 8 Low Power Sleep Test Menu:
**		  D --- Set Date and Time
**		  U --- Sleep Until ...
**		  L --- Sleep Loop ...
**		  T --- Torture Loop ...
**		  O --- Sleep Options
**		  3 --- Toggle 3V Sleep
**		  K --- Toggle Keyboard Early Wake
**		  S --- Toggle Spurious Interrupt Early Wake
**		  Z --- Toggle Sleep Zero to Even Seconds
**		  X --- Exit
**		Test Selector [D] ?  
**	
**	[D] Set Date and Time: lets you set the Model 8 clock to the current time
**	so that future alarms will correspond to verifiable time.
**	
**	[U] Sleep Until: is designed to wake at a specific absolute time in the
**	future. It takes user input of data and time and converts it to the proper
**	format for use by LPSleepTill() which is the function that calls LPSTOP.
**	Provided the TT8 is connected to the computer hitting a key will bring it
**	out of sleep.
**	
**	[L] SleepFor: is designed to wakeup at regular intervals, relative to the
**	last wakeup time. It takes its inputs in seconds, and goes through its
**	sleep - wake cycle repeatedly until a key is hit. It also calls
**	LPSleepTill() to do its LPSTOP sleep setup.
**	
**	[T] Torture Loop: attempts to stress the sleep functions with non-integral
**	intervals and delays that force the time to go out of synchronization.
**	
**	[O] Sleep Options: displays the current sleep options that are in effect.
**	
**	[3] Toggle 3V Sleep: toggles the current setting of the flag that tells
**	sleep to drop into 3.3 volt mode if there's enough time.
**	
**	[K] Toggle Keyboard Early Wake: toggles the current setting of the flag
**	that tells sleep to pay attention to IRQ1 interrupts which come from the
**	RS232 driver. See [S] Toggle Spurious below.
**	
**	[S] Toggle Spurious Interrupt Early Wake: toggles the current setting of
**	the flag that tells sleep to handle spurious interrupts. If keyboard early
**	wake is enabled while this is disabled, you may be able to force a failure
**	by pounding on the keyboard. The implication is that these should be
**	enabled/disabled in a coordinated fashion.
**	
**	[Z] Toggle Sleep Zero to Even Seconds: toggles the current setting of the
**	flag that tells sleep to wait up to a full second before returning from
**	the first LowPowerSleep(0) call, which lets the sleep functions achieve
**	maximum power savings.
**	
*******************************************************************************/

#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 */
#include	<userio.h>		/* common convenient user I/O routines */

#include	<time.h>
#include	<stdio.h>

#define	LPS_MAX_FSYS	16000000	// the maximum clock speed for lpsleep
#define	LPS_MIN_FSYS	1280000			// the minimum clock speed for lpsleep
#define	LPS_V3_FSYS		4000000		// don't run faster than this at 3.3V
#define	LPS_TICK_RATE	100L			// the only tick rate supported for lpsleep
#define	LPS_STOP_MIN_T	3			// we need 30mS to attempt LPSTOP
#define	LPS_VCHG_MIN_T	50			// we need 500mS to attempt V3 switch


//	The following code allows you to disable compilation of the portions of
//	this source module that exist simply to demonstrate the usage of low power
//	sleep. With this commented out, you can incorporate these sleep functions
//	into your program without modifying this source module, by just copying
//	the PUBLIC DECLARATIONS section into a project header file, or your source
//	modules that reference the low power sleep functions.
//#define	LPS_EXAMPLE_CODE			// comment out to disable the example code

// 	The following macro controls conditional compilation of diagnostic messages
//	deep inside the LowPowerSleepTill() function. Be aware that the default 
//	setting which shuts down the RS232 driver will cause some UARTS and comm
//	programs to sometimes garble characters. Comment out for production code.
#define	LPS_PRINTF_DIAG				// comment out to disable printf diagnostics

// 	The following macro controls conditional compilation of diagnostic signals
//	fired off deep inside the LowPowerSleepTill() function. You can watch port
//	pin D3 (PS[A8], SB(44) to get an idea of how the time and options settings
//	affect the ratio of low power sleep (D3 low) to high power sleep (D3 high).
//	Comment this out for production code.
//#define	LPS_SCOPE_DIAG				// comment out to disable scope diagnostics

//
//	P U B L I C   D E C L A R A T I O N S
//
typedef enum
	{
	sleepOK = 0,			// normal sleep, returned on seconds transition
	sleepMissed,			// sleep target passed before call to sleep
	sleepInterrupted,		// sleep terminated by an external interrupt
	cantSleepBadFSys,		// wont even try if <1.28MHz or > 16MHz
	cantSleepBadTickRate,	// low power sleep demands tick rate of 100
	cantSleepIRQ3Busy		// IRQ3 is being asserted so we can't sleep
	}	SLPResult;

typedef struct
	{
	unsint		sleepAt3V			:1;	// reduce power by reducing voltage
	unsint		sleepADshdn			:1;	// force the AD into shutdown
	unsint		sleepRSshdn			:1;	// force the RS232 driver to shutdown
	unsint		sleepZeroEvenSec	:1;	// sleep zero waits for next second
	unsint		sleepSpuriousRupt	:1;	// break on (catch) spurious interrupts
	unsint		sleepHandleIRQ1		:1;	// break on IRQ1 (RS232) interrupts
	unsint		sleepHandleIRQ2		:1;	// break on IRQ2 (unknown) interrupts
	unsint		sleepHandleIRQ4		:1;	// break on IRQ4 (unknown) interrupts
	unsint		sleepHandleIRQ6		:1;	// break on IRQ6/TP15 interrupts
	}	SleepPowerOptions;	
	
extern SleepPowerOptions	SleepOpts;

//	LowPowerSleep		- Sleep for a number of ticks relative to the last sleep.
SLPResult LowPowerSleep(ulong wakesecs);
//	LowPowerSleepTill	- Sleep until a specified time.
SLPResult LowPowerSleepTill(time_tt wakeup);


//
//	P R I V A T E   D E C L A R A T I O N S
//
void SpurRuptHandler(void);
void IRQ1RuptHandler(void);
void IRQ2RuptHandler(void);
void IRQ3RuptHandler(void);
void IRQ4RuptHandler(void);
void IRQ6RuptHandler(void);
void LPStop (void) = { 0xF800, 0x01C0, 0x2000 };
void SPStop (void) = { 0x4E72, 0x2000 };
void RTE (void) = { 0x4E73 };

SleepPowerOptions	SleepOpts = { 1, 1, 1, 1, 0, 0, 0, 0, 0 };

/*******************************************************************************
**	LowPowerSleep		Sleep for a number of ticks relative to the last sleep.
**	
**	LowPowerSleep suspends operation until a time interval relative to the 
**	last invocation has elapsed. It is used primarily to schedule periodic
**	actions which have both and active phase and a dormant phase, and allows 
**	the ration of active to dormant time to vary without affecting overall
**	timing, so long as the combination of the two is always less than the
**	sleep period.
**	
**	LowPowerSleep takes a single argument, which is the number of ticks to
**	sleep (10mS/tick) relative to the last LowPowerSleep call. You should
**	always make a preliminary call to LowPowerSleep with an argument of zero
**	before beginning your periodic cycles to establish the starting reference.
**	
**	LowPowerSleep always returns one of six possible result codes from the list
**	in the SLPResult enumeration.
**
**	sleepOK					This is the desired result code and means that
**							the sleep concluded successfully and the it's
**							safe to assume that it's time to begin the next
**							active phase. (see the jitter notes below)
**
**	sleepMissed				This code indicates that your call to LowPowerSleep
**							came after the requested interval. You will need to
**							take some corrective action to bring you timing
**							back in sync, and possibly increase the loop time.
**
**	sleepInterrupted		This code indicates that you elected to allow
**							external interrupt to force early termination of
**							the sleep, and that one happened. You will most
**							likely need to take some action to acknowledge and
**							dismiss the interrupt source, handle the event, and
**							bring the timing back in sync.
**
**	cantSleepBadFSys		This code tells you the LowPowerSleep refused the
**							request because you are running the processor at
**							a clock frequency that's not supported.
**
**	cantSleepBadTickRate	This code tells you the LowPowerSleep refused the
**							request because you are running with a tick rate
**							that's not supported. Use SetTickRate(LPS_TICK_RATE)
**							to setup the 10mS ticks required by this function.
**
**	cantSleepIRQ3Busy		This code tells you the LowPowerSleep refused the
**							request because there is a hardware malfunction that
**							could prevent it from ever waking up. You should not
**							experience this return code unless your software is
**							also setting up the PIC microcontroller to issue
**							interrupts. This is not done by any of the standard
**							Model 8 library functions.
**
**	NOTES:
**
**	1. The actual return time will have some amount of jitter between successive
**	calls. This will typically be less than 10 mS for LowPowerSleep that does
**	not drop down to 3.3 volts, and less than 500 mS for LowPowerSleep that does
**	enter 3.3 volts. The jitter is not cumulative, and does not affect overall
**	timing.
**
**	2. External interrupts will only be recognized for sleep calls which enter
**	LPSTOP. This implies two things: For sleep intervals with integral seconds, 
**	interrupts may not be recognized for LPS_STOP_MIN_T (30ms typ). For sleep
**	intervals which do not end on integral seconds, interrupts may not be
**	recognized for up to a second.
*******************************************************************************/
SLPResult LowPowerSleep(ulong ticks)
	{
	static time_tt		LastSleep;		// for relative time reference
	static short		inited = FALSE;	// until the first time through
	time_tt				tmtemp;			// gp time variable
	long				saveFSys;		// for sanity testing
	SLPResult			result;			// for error checking and reporting
	
//
//	CONFIRM THAT THE SYSTEM IS SETUP CORRECTLY TO SUPPORT SLEEP
//		The sleep functions demand that communications with the PIC are
//		invariantly reliable. This is only guaranteed over the operating
//		frequencies and the tick rate specified below.
//
	saveFSys = SimGetFSys();
	if (saveFSys < LPS_MIN_FSYS || saveFSys > LPS_MAX_FSYS)
		return cantSleepBadFSys;		// can't guarantee time calls
	if (GetTickRate() != LPS_TICK_RATE)
		return cantSleepBadTickRate;	// can't use low power sleep

//
//	SLEEP 0 (OR FIRST INVOCATION) IS CHANCE FOR SANITY CHECKS AND SETUP
//		Sleep is a relative time function, and requires initialization to
//		provide the reference relation. Take this opportunity to enforce
//		the PIC alarm state to a known and predictable behavior.
//
	if (ticks == 0 || ! inited)	// a request to reset the interval counter
		{
		inited = TRUE;		// get that out of the way

		PicAckCmpAlrm();	// don't let old alarms to trip us up
		SetAlarmSecs(time(0) - 1);	// force behind
		PicAckCmpAlrm();			// in case we just missed
		
		//	Here we establish the reference time, but since we're reading
		//	this asynchronously, we may capture the time just before the
		//	next transition. So we wait for the next time transition, and
		//	use that as our starting reference.
		tmtemp = LastSleep = ttmnow();	// get reference time now
		if (SleepOpts.sleepZeroEvenSec)
			{
			while (tmtemp.secs == LastSleep.secs)
				LastSleep = ttmnow();	// wait for secs transition
			}
		else
			{
			while (ttmcmp(tmtemp, LastSleep) == 0)
				LastSleep = ttmnow();	// wait for ticks transition
			}
		return sleepOK;			// this is always ok
		}

//
//	ADD THE TICKS REQUEST TO THE LAST SLEEP AS A TT8 TIME STRUCTURE
//		This becomes the argument to LowPowerSleepTill()
	tmtemp.secs = 0;		// we just specify ticks
	tmtemp.ticks = ticks;	// which may be larger than the tick rate
	tmtemp = ttmadd(LastSleep, tmtemp);

	result = LowPowerSleepTill(tmtemp);

//
//	DID WE KEEP SYNCHRONIZATION ?
//		We can maintain synchronization for all but external interrupts
	if (result == sleepOK || result == sleepMissed)
		LastSleep = tmtemp;

	return result;
	
	}	/* LowPowerSleep() */


/*******************************************************************************
**	LowPowerSleepTill		Sleep until a specified time.
**	
**	LowPowerSleepTill suspends operation until a specified future time arrives
**	to revive the system. It is used primarily as the engine for the above
**	LowPowerSleep function, but may also be useful for long term delays and
**	power reduction.
**	
**	LowPowerSleepTill takes a single argument, which is the wakeup time in
**	seconds and ticks expressed as a time_tt structure (defined in <tt8lib.h>).
**	
**	LowPowerSleepTill always returns one of six possible result codes from the 
**	list in the SLPResult enumeration. These are fully described in the 
**	LowPowerSleep function above.
*******************************************************************************/
SLPResult LowPowerSleepTill(time_tt wakeup)
	{
	time_tt				now;		// holds the current time at various points
	long				diffticks;	// difference between now and wakeup time
	short				canLPSTOP;	// true if we have to time to LPSTOP
	short				can535v;	// true if we have to time to change voltage
	short				need5vWake;	// true if we have to switch up to 5V on wake
	short				started3v;	// true if we entered at 3.3V
	short				sawPICalarm;// true if alarm active when we woke
	short				saveRupts;	// save a copy of entry interrupt mask
	long				saveFSys;	// for testing, and to restore on exit
	SLPResult			result;			// for error checking and reporting
	vfptr				saveSpur, saveIRQ1, saveIRQ2, saveIRQ4, saveIRQ6;
	ushort				saveSCCR1, saveQMCR, saveSPCR1, saveTMCR;
	uchar				saveSPCR3, savePFPAR, saveDDRF;
	enum				{ CmpAlarmMask = 0x82, PREnables = 0x23 };

//
//	CONFIRM THAT THE SYSTEM IS SETUP CORRECTLY TO SUPPORT SLEEP
//		The sleep functions demand that communications with the PIC are
//		invariantly reliable. This is only guaranteed over the operating
//		frequencies and the tick rate specified below.
	saveFSys = SimGetFSys();
	if (saveFSys < LPS_MIN_FSYS || saveFSys > LPS_MAX_FSYS)
		return cantSleepBadFSys;		// can't guarantee time calls
	if (GetTickRate() != LPS_TICK_RATE)
		return cantSleepBadTickRate;	// can't use low power sleep

//
//	MAKE SURE OUR HARDWARE CAN ACCEPT AN ALARM INTERRUPT
//		This isn't going to be a problem unless some other part of your
//		program is mucking around with PIC alarms (don't do it!), but if
//		that's the case, it could really foul things up.
	PConfInp(F,3);		// lets look at the IRQ line that wakes us
	if (Pin(F,3) == 0)	// big trouble if it's already asserting
		{
		PicAckCmpAlrm();			// attempt a repair
		SetAlarmSecs(0L);			// force behind
		PicAckCmpAlrm();			// in case we just missed
		result = cantSleepIRQ3Busy;	// we don't have enough context to fix this!
		goto sleepExit;
		}

//
//	WE'VE GOT TIME TO ATTEMPT LPSTOP POWER SAVING...
//	WE'RE COMMITTING NOW, SO DISABLE INTERRUPTS UNTIL WE COMPLETE
	saveRupts = GetInterruptMask();		// we'll restore on exit
	SetInterruptMask(NO_RUPTS_MASK);	// defined in <tt8lib.h>
//
//	CHECK THE TIME NOW IN RELATION TO THE DESIRED WAKEUP
	now = ttmnow();
	diffticks = ttmcmp(wakeup, now);

#ifdef LPS_PRINTF_DIAG
	printf(" N[%03lu:%02lu] W[%03lu:%02lu] D[%02ld] ", now, wakeup, diffticks);	
#endif

//	JUST RETURN OK IF WE'RE RIGHT AT THE TARGET WAKEUP NOW
	if (diffticks == 0)
		{
		result = sleepOK;
		goto sleepExit;
		}

//	MISSED THE WAKE TIME, WE'RE OUT OF SYNC
	if (diffticks < 0)
		{
		result = sleepMissed;		// we've already gone past
		goto sleepExit;
		}

//
//	DO WE HAVE THE TIME TO DO ANY MEANINGFUL POWER REDUCTIONS
//		We have to be sure we can get a seconds transition interrupt
	canLPSTOP = can535v = FALSE;	// assume so until proved different
	if (wakeup.secs > (now.secs + 1))	// plenty of time for all reductions
		canLPSTOP = can535v = TRUE;
	else if (wakeup.secs == (now.secs + 1))	// maybe, we've got between 0 and 1
		{
		canLPSTOP = now.ticks < (LPS_TICK_RATE - LPS_STOP_MIN_T);
		can535v = now.ticks < (LPS_TICK_RATE - LPS_VCHG_MIN_T);
		}

#ifdef LPS_PRINTF_DIAG
	printf(" [LP=%d] [V3=%d]\n", canLPSTOP, can535v);
	fflush (stdout);	// print diagnostics before sleeping [prm]
#endif
//	WE DON'T HAVE TIME, WAIT OUT THE REMAINDER IN A HIGH POWER LOOP
	if (! canLPSTOP)
		{
		while (ttmcmp(wakeup, ttmnow()) >= 0)
			;
		result = sleepOK;	
		goto sleepExit;
		}

//
//	We know from the testing above that we've got time to schedule an
//	alarm interrupt from the PIC. We won't actually respond to the
//	interrupt until we enable interrupts as an atomic intrinsic part
//	of the LPSTOP instruction.
//
	SetAlarmSecs(wakeup.secs);		// do this first
	InstallHandler(IRQ3RuptHandler, Level_3_Interrupt, 0);
	PConfInp(F,3);		// make sure DDR will revert from IRQ to input
	PConfBus(F,3);		// now set up to accept interrupts

//	GRAB COPIES OF SYSTEM REGISTERS WE'RE LIKELY TO CHANGE
	saveSCCR1 = *SCCR1;
	saveQMCR = *QMCR;
	saveSPCR1 = *SPCR1;
	saveTMCR = *TMCR;
	saveSPCR3 = *SPCR3;
	savePFPAR = *PFPAR;		// the PConfInp(F,3) above is ok before save...
	saveDDRF = *DDRF;		// ...as caller better not be using it

//	ISSUE COMMANDS TO STOP SUBMODULES WHICH MAY TAKE SOME TIME TO COMPLETE
	if (_SPCR1->SPE)
		_SPCR3->HALT = 1;	// tell QSPI to stop on next queue boundary
	_SCCR1->RE = 0;		// turn SCI receiver off
	PSet(D,7);			// we will want transmit to idle...
	PConfOutp(D,7);		// ...after we turn it off
	_SCCR1->TE = 0;		// turn SCI transmitter off
	*TMCR |= M_STOP;	// turn off the TPU (do this as word operation)

//	SHUTDOWN THE OPTIONAL POWER CONSUMERS
	if (SleepOpts.sleepADshdn)
		Max186PowerDown();

//	INSTALL THE INTERRUPT HANDLERS
	if (SleepOpts.sleepSpuriousRupt)
		saveSpur = 	InstallHandler(SpurRuptHandler, Spurious_Interrupt, 0);
	if (SleepOpts.sleepHandleIRQ1)
		{
		saveIRQ1 = 	InstallHandler(IRQ1RuptHandler, Level_1_Interrupt, 0);
		PConfInp(F,1);		// make sure DDR will revert from IRQ to input
		PConfBus(F,1);		// now set up to accept interrupts
		}
	if (SleepOpts.sleepHandleIRQ2)
		{
		saveIRQ2 = 	InstallHandler(IRQ2RuptHandler, Level_2_Interrupt, 0);
		PConfInp(F,2);		// make sure DDR will revert from IRQ to input
		PConfBus(F,2);		// now set up to accept interrupts
		}
	if (SleepOpts.sleepHandleIRQ4)
		{
		saveIRQ4 = 	InstallHandler(IRQ4RuptHandler, Level_4_Interrupt, 0);
		PConfInp(F,4);		// make sure DDR will revert from IRQ to input
		PConfBus(F,4);		// now set up to accept interrupts
		}
	if (SleepOpts.sleepHandleIRQ6)
		{
		saveIRQ6 = 	InstallHandler(IRQ6RuptHandler, Level_6_Interrupt, 0);
		PConfInp(F,6);		// make sure DDR will revert from IRQ to input
		PConfBus(F,6);		// now set up to accept interrupts
		}

//	COMPLETE THE SUBMODULE STOPPAGE
// 		give SCI transmitter and QSPI time to conclude (but not too much)
	if ((! (_SCSR->TC)) || (_SPCR1->SPE && (! _SPSR->HALTA)))
		DelayMilliSecs(1l);	// could be as much as two
	*QMCR |= M_QSTOP;		// ready or not...

	if (SleepOpts.sleepRSshdn)	// now we turn off the RS-232 driver
		SerShutDown(TRUE, TRUE);		// bit set it up to auto-wake


//	CHECK FOR REQUEST TO DROP TO 3.3 VOLTS
	need5vWake = 0;		// assume we won't need this, test and update below
	PConfInp(F,0);		// lets look at the SEL5V line
	started3v = (Pin(F,0) == 0);		// we're already 3V if it's zero
	if (SleepOpts.sleepAt3V && can535v && (! started3v))
		{
		need5vWake = 1;		// we will need to restore
		if (saveFSys > LPS_V3_FSYS)	// we don't want to push speed at 3.3v
			SimSetFSys(LPS_V3_FSYS);
		VRegSwitchTo(v3, (ushort) 64000, NO_RUPTS_MASK);	// delay (mS) = 8 * loopdelay / fKHz
		}

//	==+==+==+==+==+==+==+==+==+==+==+==+==
//	!!! THIS IS IT - WE'RE GOING TO SLEEP
//	==+==+==+==+==+==+==+==+==+==+==+==+==
	PicOrRF(PREnables, CmpAlarmMask);	// enable PIC alarm interrupts
#ifdef LPS_SCOPE_DIAG
	PClear(D,3);
#endif
	LPStop();		// this is direct inline assembler for LPSTOP #$2000
					// and the #$2000 says enable any and all interrupts
					
//	==+==+==+==+==+==+==+==+==+==+==+==+==
//	!!! THAT WAS THAT - NOW WE'RE AWAKE
//	==+==+==+==+==+==+==+==+==+==+==+==+==
	SetInterruptMask(NO_RUPTS_MASK);	// just the one, thank you
#ifdef LPS_SCOPE_DIAG
	PSet(D,3);
#endif
	PConfInp(F,3);		// lets look at the IRQ line that maybe woke us
	sawPICalarm = Pin(F,3) == 0;	// did we get our alarm

//
//	DEAL WITH THE POSSIBILITY THAT WE GOT AN EXTERNAL INTERRUPT
//		This would not be so necessary if we only had to deal with alarm
//		interrupts, but since that's not the case, and since by definition
//		an asynchronous interrupt could whack us at any time before the
//		alarm, we've got to cover all the possibilities.
	PicAckCmpAlrm();			// turn off the IRQ3 interrupt
	PicAndRF(PREnables, ~CmpAlarmMask);	// disable PIC alarm interrupts
	if (! sawPICalarm)
		{
		PicAckCmpAlrm();		// again, in case we just missed
		SetAlarmSecs(0L);		// force it behind us
		}

//	If we're at 3.3v, the first thing to do is restore the correct voltage
//	Expect a delay of 32ms at 16MHz, 400ms at 1.28MHz
	if (need5vWake)
		VRegSwitchTo(v5,(ushort) 64000, NO_RUPTS_MASK);	// delay (mS) = 8 * loopdelay / fKHz

//	We're back up to 5 volts, so it's ok to ramp speed back up if needed
	if (saveFSys != SimGetFSys())
		SimSetFSys(saveFSys);

//	BEGIN THE RESTORATION PROCESS

	if (SleepOpts.sleepSpuriousRupt)
		InstallHandler(saveSpur, Spurious_Interrupt, 0);
	if (SleepOpts.sleepHandleIRQ1)
		{
		PConfInp(F,1);
		InstallHandler(saveIRQ1, Level_1_Interrupt, 0);
		}
	if (SleepOpts.sleepHandleIRQ2)
		{
		PConfInp(F,2);
		InstallHandler(saveIRQ2, Level_2_Interrupt, 0);
		}
	if (SleepOpts.sleepHandleIRQ4)
		{
		PConfInp(F,4);
		InstallHandler(saveIRQ4, Level_4_Interrupt, 0);
		}
	if (SleepOpts.sleepHandleIRQ6)
		{
		PConfInp(F,6);
		InstallHandler(saveIRQ6, Level_6_Interrupt, 0);
		}

	*QMCR = saveQMCR;
	*SCCR1 = saveSCCR1;
	*SPCR1 = saveSPCR1;
	*SPCR3 = saveSPCR3;
	*TMCR = saveTMCR;
	*DDRF = saveDDRF;
	*PFPAR = savePFPAR;

//	FINISH UP
//		We may have to kill some time in high power sleep for non-integral
//		seconds loops.
	if (sawPICalarm)
		{
		result = sleepOK;
		while (ttmcmp(wakeup, ttmnow()) >= 0)
			;
		}
	else
		result = sleepInterrupted;

sleepExit:
	SetInterruptMask(saveRupts);	// finally safe to re-enable
	return result;

	}	/* LowPowerSleepTill() */


/*******************************************************************************
**	SpurRuptHandler		// Handle a spurious interrupt from sleep
**	
**	This is here to deal with the problem of an external interrupt signal
**	asserting, but then going away before the CPU can wake up from LPSTOP
**	(about 8ms) and determine what the interrupt signal was. Without this, 
**	the program would bomb out to the TOM8 monitor with an interrupt/
**	exception message.
**	
**	It's actually very easy to get here from an IRQ1 (RS232 input) signal
**	which is going to toggle at high rates as it's just a serial data stream. 
**	If you're going to allow serial data to break the LPSTOP, you should make
**	sure to send a serial BREAK signal which is in excess of 50ms to prevent
**	CPU confusion. If your sleep terminates early with no other IRQ lines
**	asserting, it probably came from here.
*******************************************************************************/
void SpurRuptHandler(void)	{	RTE();	}

/*******************************************************************************
**	IRQxRuptHandlers		// Handle external interrupt requests
**	
**	We just perform the minimum handling, which is to revert the interrupt
**	inputs to simple digital inputs, effectively ending the interrupt request
**	that would keep us from concluding our sleep function.
**	
**	Whoever called us will have to be responsible for actually looking at the
**	I/O lines and doing whatever is appropriate to making the interrupt go away
**	before the next sleep call.
*******************************************************************************/
void IRQ1RuptHandler(void)	{	PConfInp(F,1);	RTE();	}
void IRQ2RuptHandler(void)	{	PConfInp(F,2);	RTE();	}
void IRQ3RuptHandler(void)	{	PConfInp(F,3);	RTE();	}
void IRQ4RuptHandler(void)	{	PConfInp(F,4);	RTE();	}
void IRQ6RuptHandler(void)	{	PConfInp(F,6);	RTE();	}



/*******************************************************************************
********************************************************************************
********************************************************************************
**	EVERYTHING BELOW IS PART OF THE EXAMPLE CODE TO EXERCISE LOW POWER SLEEP
********************************************************************************
********************************************************************************
*******************************************************************************/
#ifdef	LPS_EXAMPLE_CODE			// build the example


//
//	EXAMPLE PROTOTYPES
//
void ShowOptions(void);
void SetDateTime(void);
void SleepUntil(void);
void SleepLoop(void);
void TortureLoop(void);
void PrepPinsLowPower(void);

/*******************************************************************************
**	main		Tattletale Model 8 Main Entry Point
**		
**	The first action code in a Model 8 program must be a call to InitTT8(),
**	usually with the default arguments (NO_WATCHDOG, TT8_TPU) which disable
**	the software watchdog, and install the TPU code tailored for the TT8.
**	
**	The last action code in a Model 8 program should be a return from main(),
**	or a call to Reset() or ResetToMon(). Return or Reset() will act like
**	a conventional power up or reset and run whatever program is in rom.
**	ResetToMon() will force the Model 8 to restart in the TOM8 monitor.
*******************************************************************************/
main()
	{
	char	selection = 'D';

/*
**	STANDARD TATTLETALE MODEL 8 LIBRARY & HARDWARE INITIALIZATION
*/
	InitTT8(NO_WATCHDOG, TT8_TPU);	/* setup Model 8 for running C programs */

	printf("\nTattletale Model 8 Low Power Sleep Examples\n");

	PrepPinsLowPower();				// see function for notes on this

	SetTimeSecs(0L, 0L);			// just for known starting point
	SetTickRate(LPS_TICK_RATE);		// mandatory for low power sleep

#ifdef LPS_SCOPE_DIAG
	PConfOutp(D,3);
	PSet(D,3);
#endif

/*
**	LOOP FOR TEST COMMAND
*/
	while (1)
		{
		SimSetFSys(16000000);
		SerSetBaud(9600L, 0L);		/* to be safe */
		puts("\nModel 8 Low Power Sleep Test Menu:");
		puts("  D --- Set Date and Time");
		puts("  U --- Sleep Until ...");
		puts("  L --- Sleep Loop ...");
		puts("  T --- Torture Loop ...");
		puts("  O --- Sleep Options");
		puts("  3 --- Toggle 3V Sleep");
		puts("  K --- Toggle Keyboard Early Wake");
		puts("  S --- Toggle Spurious Interrupt Early Wake");
		puts("  Z --- Toggle Sleep Zero to Even Seconds");
		puts("  X --- Exit");
		
		if (! QueryChar("Test Selector", selection, "DULTOFZ3KSXQ", &selection))
			Reset();		/* ctlr-C'd */

		switch (selection)
			{
			case 'D' :	SetDateTime();		break;
			case 'U' :	SleepUntil();		break;
			case 'L' :	SleepLoop();		break;
			case 'T' :	TortureLoop();		break;
			case 'O' :
				ShowOptions();
				break;
			case '3' :
				SleepOpts.sleepAt3V = ! SleepOpts.sleepAt3V;
				ShowOptions();
				break;
			case 'K' :
				SleepOpts.sleepHandleIRQ1 = ! SleepOpts.sleepHandleIRQ1;
				ShowOptions();
				break;
			case 'S' :
				SleepOpts.sleepSpuriousRupt = ! SleepOpts.sleepSpuriousRupt;
				ShowOptions();
				break;
			case 'Z' :
				SleepOpts.sleepZeroEvenSec = ! SleepOpts.sleepZeroEvenSec;
				ShowOptions();
				break;
			case 'Q' :
			case 'X' :	Reset();
			}
		}

	return (0);		// never get here, but keeps compiler happy
	
	}	/* main() */

/*******************************************************************************
**	ShowOptions		Display the current options and settings
*******************************************************************************/
void ShowOptions(void)
	{
	printf("\n\nCurrent Options:\n");
	printf("  3V Sleep %sabled\n", SleepOpts.sleepAt3V
		? "En" : "Dis");
	printf("  Key Wake %sabled\n", SleepOpts.sleepHandleIRQ1
		? "En" : "Dis");
	printf("  Spurious Wake %sabled\n", SleepOpts.sleepSpuriousRupt
		 ? "En" : "Dis");
	printf("  Sleep Zero Seconds %sabled\n", SleepOpts.sleepZeroEvenSec
		 ? "En" : "Dis");
	}	/* ShowOptions() */


/*******************************************************************************
**	SetDateTime		Simple function to set the real time clock
*******************************************************************************/
void SetDateTime(void)
	{
	struct tm 	t;

	printf("\nSet Current Date and Time (Alt-D from CrossCut)");
	if (! QueryDateTime("\n<return> for default", TRUE, &t))
		return;
	SetTimeTM(&t, NULL);

	}	/* SetDateTime() */


/*******************************************************************************
**	SleepUntil		Setup to sleep until a specific time
*******************************************************************************/
void SleepUntil(void)
	{
	struct tm 	*t;
	time_tt		alarmTime, deftofs = { 15, 0 };	/* default secs from now */
	SLPResult	result;

	alarmTime = ttmadd(ttmnow(), deftofs);	/* establish default alarm */
	t = localtime(&alarmTime.secs);		/* convert for query routine */
	
	if (! QueryDateTime("\n\nSet Wakeup Date and Time", FALSE, t))
		return;
	alarmTime.secs = mktime(t);
	alarmTime.ticks = 0;

	puts("\nSleeping...");
	PConfInp(F,1);	// this tracks the RS232 input (1 is idle, 0 is break)
	result = LowPowerSleepTill(alarmTime);	

	if (Pin(F,1) == 0)
		printf("...Awakened rudely, code %d\n", result);
	else
		printf("...Awakened normally, code %d\n", result);

	while (Pin(F,1) == 0)
		;	// wait for break to go away
	SerInFlush();

	}	/* SleepUntil() */


/*******************************************************************************
**	SleepLoop		Sleep in a loop until break received
*******************************************************************************/
void SleepLoop(void)
	{
	static ulong	ticks = 500;		// start with 5 seconds
	ulong			iterations = 0;
	SLPResult		result;
	
	if (! QueryNum("\nSleep interval (ticks (10ms))", "%ld", "%ld", &ticks))
		return;
	if (ticks == 0)
		return;

	puts("\nSend break to exit sleep");
	PConfInp(F,1);	// this tracks the RS232 input (1 is idle, 0 is break)

	LowPowerSleep(0L);
	while (Pin(F,1) == 1)
		{
		result = LowPowerSleep(ticks);
		printf("%ld: Woke, code %d\n", iterations++, result);
		if (result != sleepOK)
			break;
		}
	while (Pin(F,1) == 0)
		;	// wait for break to go away
	SerInFlush();
	
	}	/* SleepLoop() */


/*******************************************************************************
**	TortureLoop		Torture sleep mode in a loop until break received
*******************************************************************************/
void TortureLoop(void)
	{
	ulong		ticks = 75;			// 0.75 seconds
	ulong		iterations = 0;
	SLPResult	result;
	ulong		delay = 0;

	SleepOpts.sleepAt3V = TRUE;
	SleepOpts.sleepHandleIRQ1 = TRUE;
	SleepOpts.sleepSpuriousRupt = TRUE;

	puts("\nReset to exit\n");

	LowPowerSleep(0L);
	while (1)
		{

		result = LowPowerSleep(ticks);
		printf("%ld: Woke, code %d, delay %dmS\n", iterations++, result, delay);

		DelayMilliSecs(delay);

		delay = (delay + 66) % 1000;
		}

	}	/* TortureLoop() */


/*******************************************************************************
**	PrepPinsLowPower		Prepare port lines for lowest power operation
**	
**	This routine (as delivered) sets up the port and tpu lines for a stock
**	Model 8 to force possible floating I/O pins (possible source of extra
**	current drain in CMOS circuits) to outputs.  The technique used samples
**	each of the I/O lines as inputs, then configures and writes the sampled
**	value back as outputs.  For this example code, we use a brute force pin-
**	by-pin approach which allows for ease of modification.  Real applications
**	can save quite a bit of code by condensing the pin operations to byte
**	wide configuration of the various ports and putting the tpu settings
**	into a loop.
*******************************************************************************/
void PrepPinsLowPower(void)
	{

/*
**	68332 PORT F (IRQ's OR I/O)
*/
	// the following two pins are used by interrupts in this program
	PConfInp(F,3);						/* irq3/led (has pullup) */
	PConfInp(F,1); 						/* rxd1 /IRQ1            */

	PConfOutp(F,7);	PClear(F,7);				/* A-D shutdown */
	PConfInp(F,6);						/* connected to, and pulled by TP15 */
	PConfInp(F,5);						/* picirq (PIC asserts)  */
	PInpToOut(F,4);						/* nc                    */
	PInpToOut(F,2);						/* nc                    */
	PConfInp(F,0);						/* sel5vpic (has pullup) */

/*
**	68332 PORT E (BUS SIGNALS OR I/O)
*/
	PInpToOut(E,7);						/* nc */
	PInpToOut(E,6);						/* nc */
	PInpToOut(E,5);						/* nc */
	PInpToOut(E,4);						/* nc */
	PInpToOut(E,3);						/* nc */
	PInpToOut(E,2);						/* nc */
	PInpToOut(E,1);						/* nc */
	PInpToOut(E,0);						/* nc */

/*
**	68332 PORT D (QSM OR I/O)
*/
	PConfBus(D,7);						/* txd RS-232 */
	PConfOutp(D,6);	PSet(D,6);			/* pcs3 a-d cs active low */
	PInpToOut(D,5);						/* pcs2/nc */
	PInpToOut(D,4);						/* pcs1/nc */
	PInpToOut(D,3);						/* pcs0/nc */
	PConfOutp(D,2);	PClear(D,2);		/* sck */
	PConfOutp(D,1);	PClear(D,2);		/* mosi */
	PInpToOut(D,0);						/* miso */

/*
**	68332 TPU PINS
*/
	TPUSetPin(0, 0);					/* nc */
	TPUSetPin(1, 0);					/* nc */
	TPUSetPin(2, 0);					/* nc */
	TPUSetPin(3, 0);					/* nc */
	TPUSetPin(4, 0);					/* nc */
	TPUSetPin(5, 0);					/* nc */
	TPUSetPin(6, 0);					/* nc */
	TPUSetPin(7, 0);					/* nc */
	TPUSetPin(8, 0);					/* nc */
	TPUSetPin(9, 0);					/* nc */
	TPUSetPin(10, 0);					/* nc */
	TPUSetPin(11, 0);					/* nc */
	TPUSetPin(12, 0);					/* nc */
	TPUSetPin(13, 1);					/* txd2 */
	TPUGetPin(14);						/* rxd2 (active in shutdown) */
	TPUSetPin(15, 0);					/* connected to F,6 */

	}	/* PrepPinsLowPower() */

#endif	// LPS_EXAMPLE_CODE
