/******************************************************************************* ** 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. ** ***************************************************************************** ** ** 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. ** *******************************************************************************/ #include /* Tattletale Model 8 Definitions */ #include /* 68332 Tattletale (7,8) Hardware Definitions */ #include /* 68332 System Integration Module Definitions */ #include /* 68332 Queued Serial Module Definitions */ #include /* 68332 Time Processing Unit Definitions */ #include /* 68332 Digital I/O Port Pin Definitions */ #include /* Model 8 PIC Parallel Slave Port Definitions */ #include /* definitions and prototypes for Model 8 library */ #include /* common convenient user I/O routines */ #include #include #include "lpsleep.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 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 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 }; extern SleepPowerOptions SleepOpts; /******************************************************************************* ** 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 ). ** ** 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 // 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); fflush(stdout); #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] ", canLPSTOP, can535v); fflush(stdout); #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 STOPAGE // 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 got 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(); } /******************************************************************************* ** SleepUntil Setup to sleep until a specific time *******************************************************************************/ void SleepUntil(time_tt WakeupTime) { SLPResult result; LowPowerSleep(0L); // puts("\nCalling LPSleeptill..."); PConfInp(F,1); // this tracks the RS232 input (1 is idle, 0 is break) result = LowPowerSleepTill(WakeupTime); // 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() */