CPUSleep?

Discussion specific to the DIP and TQFP packaged ZX devices like the ZX-40, ZX-44, ZX-32 and ZX-328 series. The differences between these devices is primarily the packaging and pinout so most issues will apply to all devices.
Post Reply
mdown
Posts: 62
Joined: 03 February 2006, 5:46 AM
Location: Dallas, Texas
Contact:

CPUSleep?

Post by mdown »

Is there a good example anyware of how to use CPUSleep()?

Thanks,

MikeD
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: CPUSleep?

Post by dkinzer »

mdown wrote:Is there a good example anyware of how to use CPUSleep()?
It can be as simple as:

Code: Select all

Call CPUSleep()
Note that the actual sleep mode that is used depends on the settings of the SM2, SM1 and SM0 bits of Register.MCUCR or Register.SMCR. The former register is used for mega32-based and mega128-based ZX devices while the latter is used for all other ZX devices.

Which of the available modes to use depends on what you're trying to accomplish. You'll have to read the Power Management and Sleep Modes section of the applicable ATmega datasheet to understand the benefits and ramifications of each mode.

For the sake of example, let's say that you want to use Power-down mode on a ZX-44a. The following code would suffice:

Code: Select all

Register.SMCR = &H04
Call CPUSleep()
In contrast, the code needed for the same mode on the ZX-44 is:

Code: Select all

Register.MCUCR = (Register.MCUCR And &H8f) Or &H20
Call CPUSleep()
The more complicated code is required when dealing with Register.MCUCR because it contains other bits that you likely do not want to change.
- Don Kinzer
mdown
Posts: 62
Joined: 03 February 2006, 5:46 AM
Location: Dallas, Texas
Contact:

Post by mdown »

Basically what I am trying to accomplish is to put the cpu into power-save mode if i remember right it is sm2=0 sm1=1 sm0=1 and then use timer2 to wake the unit after a period of time.
mdown
Posts: 62
Joined: 03 February 2006, 5:46 AM
Location: Dallas, Texas
Contact:

Post by mdown »

I have the power mode I want &H03 and the cpu goes to sleep, I cant get it to wake up from the timer though.

What I am trying to do is put the cpu to sleep during a long wait cycle then wakeup after the cycle is finished say 60 min or so and resume execution where it left off.

-Mike
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

mdown wrote:I have the power mode I want &H03 and the cpu goes to sleep, I can't get it to wake up from the timer though.
In order to use Timer2 to wake up the CPU you must enable one of the Timer2 interrupts. Of course, there must be an interrupt handler for any interrupt that is enabled and the VM only provides an interrupt handler for the compare interrupt since that is used for the software UART (channels 3-6).

The sample code below shows how to use Timer2 to awaken the processor. Although it is written for a mega644-based ZX, the same ideas can be used for other ZX devices. Of course, this is useful only if you aren't using any of the serial channels Com3 through Com6.

The sample code configures the timer for the maximum delay available - about 18mS. You could effectively create longer delays by counting wakeups and then taking action after a certain number. For example, 3375 wakeups would be about a minute. Longer delays could also be achieved by using an external clock for Timer2 - either a 32768Hz crystal on TOSC1/TOSC2 or an external signal fed to TOSC1.

The sample code uses the A.7 pin to generate an output signal indicating when the processor is sleeping. An oscilloscope or logic analyzer on this pin can be used to confirm the sleep time.

It is important to note that the RTC will not keep accurate time when any sleep mode is used since RTC interrupts will not be serviced when the processor is sleeping. If accurate timekeeping is required an external RTC will need to be used.

Code: Select all

'
' N.B.: this code is for mega644-based ZX devices.  Changes will
'       be required for other ZX devices.
'
Sub Main()
    Call Sleep(0.5)        ' allow sign-on message to display
    Call PutPin(A.7, 1)    ' configure a debug output
    Do
        ' select the desired sleep mode
        Register.SMCR = &H06   ' power-save mode

        ' reset all Timer2 interrupt flags
        Call SetBits(Register.TIFR2, &H07, &H07)

        ' enable the Timer2 compare A match interrupt
        Call SetBits(Register.TIMSK2, &H02, &H02)

        ' prepare Timer2 for a wake-up delay (about 18mS)
        Register.TCNT2 = 0     ' divide by 256
        Register.TCCR2A = &H02 ' WGM21
        Register.TCCR2B = 7    ' 1024 pre-scaler
        
        ' sleep for a while
        Call PutPin(A.7, 0)
        Call CPUSleep()
        Call PutPin(A.7, 1)

        ' stop the timer, disable Timer2 interrupt
        Register.TCCR2B = 0
        Call SetBits(Register.TIMSK2, &H02, &H00)

'        ' report re-awakening
'        Debug.Print "I'm awake at "; CStr(Register.RTCTick)
'        Call Sleep(0.5)        ' to allow message to display
    Loop
End Sub
- Don Kinzer
gandalf1
Posts: 7
Joined: 17 May 2007, 13:18 PM

Post by gandalf1 »

Another thing to look for in using CPUSleep() is to make sure to enable the sleep mode as well as set the particular sleep mode that you desire.

In the mega32 chip, this is the most-significant-bit of the MCUCR register (bit 7, called SE [sleep enable] in the Atmel documentation). By default it is 0 (no sleep, even if CPUSleep() command is issued), and must be set to 1 to enable sleep mode.

By my reckoning, then, I would suggest trying this modification to Don's code above:

Code: Select all

Register.MCUCR = (Register.MCUCR And &H0f) Or &HA0 
Call CPUSleep()
The part in parenthesis will zero the 4 most significant bits in the MCUCR, while preserving the state of the 4 lower bits, then the &HA0 command will set bit 7 to 1 and the next bits (6,5, and 4) to 010, respectively, i.e., to power mode 2 [power-down mode].

Since the MCUCR defaults to 0 on power-up (at least on my chips), it's important to set bit 7 to 1 to enable sleep mode. See page 30 of the Atmel mega32 documentation (in the downloads section) for details.

I haven't yet confirmed this, but will be playing with the CPUSleep() feature over the next few days. I'll try to follow up with what I find.

Thanks for a great forum, all.

-Jon (gandalf1)
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

gandalf1 wrote:Another thing to look for in using CPUSleep() is to make sure to enable the sleep mode as well as set the particular sleep mode that you desire.
Actually, setting the sleep enable bit is not necessary - that is done as part of the CPUSleep() call. There is no drawback to doing so; it's just that it isn't necessary. When the processor awakens from the sleep, the sleep enable bit is turned off before the VM executes the next instruction.

It is important to note that in many cases it is desirable (and sometimes necessary) to use the SetBits() subroutine to perform a read-modify-write on an I/O port. Using that call is guaranteed to perform the operation atomically. In comparison, using a sequence like

Code: Select all

Register.MCUCR = (Register.MCUCR And &H0f) Or &HA0 
is not guaranteed to be atomic. By this I mean that another task can get control after the value of Register.MCUCR is read but before it written back. Also, using SetBits() prevents an interrupt handler from modifying the target register in the midst of the read-modify-write.

The operation intended by the code above can be performed using SetBits() thusly:

Code: Select all

Call SetBits(Register.MCUCR, &Hf0, &Ha0)
In this case, the mask value given as the second parameter specifies that only the upper four bits of Register.MCUCR are to be modified (using the corresponding bits of the value of the third parameter).
- Don Kinzer
gandalf1
Posts: 7
Joined: 17 May 2007, 13:18 PM

Post by gandalf1 »

Thanks for the clarification, Don.

I suppose these are some of the idiosyncracies of having two systems at work: the Atmel at the hardware level and the ZBASIC firmware layer on top of it. Thanks for clarifying this issue.

Moving forward, I think I will take your suggestion and simply use the SetBits() command for any and all register modifications--this seems to be the safest bet.

Thanks again for the clarification.

-Jon
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

Since the register names and bit definitions are sometimes different between various AVR CPUs, it is convenient to use conditional compilation to write code that will run on different ZX models. One such example is setting the sleep mode for mega32-based and mega644-based ZX devices. The code below illustrates how this can be done using conditional compilation.

Code: Select all

' select Power-Down sleep mode
#if Option.CPUType = "mega32"
    Call SetBits(Register.MCUCR, &H70, &H20)
#elseif Option.CPUType = "mega644"
    Call SetBits(Register.SMCR, &H0e, &H04)
#else
    #error Target CPU not supported
#endif
Note, particularly, the #else case. If you compile this code for a ZX device that is not based on either the mega32 or the mega644 you'll get a compile error reminding you that some code changes are required.
- Don Kinzer
mdown
Posts: 62
Joined: 03 February 2006, 5:46 AM
Location: Dallas, Texas
Contact:

Post by mdown »

Can a pin be used as an the interrupt say from a 555?

-Mike
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

mdown wrote:Can a pin be used as an the interrupt say from a 555?
Sure. Any signal that has the right characteristics can be used irrespective of the source.

The mega32-based ZX devices have three possible interrupts inputs: INT0, INT1 and INT2. The mega644-based devices have these plus the ability to interrupt on a state change of any I/O pin. The ZX-1281 has some pin change interrupts plus additional INT3 through INT7 inputs.
- Don Kinzer
mdown
Posts: 62
Joined: 03 February 2006, 5:46 AM
Location: Dallas, Texas
Contact:

Post by mdown »

So on a 644 we can use any pin? is there a comand similar to WaitForInterrupt() that we would call before calling CPUSleep()?

-Mike
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

mdown wrote:So on a 644 we can use any pin?
Yes. The mega644 supports a "pin change" interrupt on all I/O pins. WaitForInterrupt() can be instructed to wait for a pin change interrupt. The System Library Manual describes how to do this.
mdown wrote:is there a comand similar to WaitForInterrupt() that we would call before calling CPUSleep()?
All that is necessary is to arrange for the pin change interrupt to occur. This means, ultimately, that the particular pin change interrupt needs to be enabled before calling CPUSleep(). Invoking WaitForInterrupt() will do this but I believe that you may also manipulate the CPU registers directly to accomplish the same goal without the need to have the extra task stack.

I haven't tried this code but the general idea should work. The code is written to awaken via a pin change interrupt on A.0. Note, particularly, that either logic transition (zero to one or one to zero) will trigger the interrupt.

Code: Select all

' 
' N.B.: this code is for mega644-based ZX devices.
' 
Sub Main() 
    Call Sleep(0.5)        ' allow sign-on message to display 
    Call PutPin(A.7, 1) 
    Do 
        ' select the desired sleep mode 
        Register.SMCR = &H06   ' power-save mode 

        ' clear the pin change interrupt flag for Port A 
        Register.PCIFR = &H01 

        ' enable the pin change interrupt on A.0 
        Call SetBits(Register.PCMSK0, &H01, &H01) 
        Call SetBits(Register.PCICR, &H01, &H01) 

        ' sleep for a while 
        Call PutPin(A.7, 0) 
        Call CPUSleep() 
        Call PutPin(A.7, 1) 

        ' disable the pin change interrupt on Port A
         Call SetBits(Register.PCICR, &H01, &H00) 

'        ' report re-awakening 
'        Debug.Print "I'm awake at "; CStr(Register.RTCTick) 
'        Call Sleep(0.5)        ' to allow message to display 
    Loop 
End Sub
You will probably want to include code prior to calling CPUSleep() that will trigger the external timer to produce the pin change at a pre-determined later time. How that might be done depends on what that external circuitry looks like. A 555 timer, for example, configured for one-shot operation could be triggered by a signal from the ZX. It would probably make sense to trigger the external timer prior to enabling the pin change interrupt so that the leading edge of the generated pulse does not generate an interrupt.
- Don Kinzer
Post Reply