Ron Kreymborg
The AVR processors can reduce their power requirements considerably by powering down various internal systems. They do this by executing the sleep assembler instruction in various modes. The processor will remain powered down (and effectively halted) until the next interrupt. The Idle and Power Save modes are useful with TaskR when the Timer0 overflow is used as a clock tick source.
In the Mega103, idle mode leaves all peripherals running and powers down just the cpu. This is the simplest sleep mode to use but saves the least power. However, all the clocks are left running and the cpu can restart the instant an interrupt occurs, so use this mode if one of the timers is doing double duty as a real time clock.
The Power Save mode shuts down all internal circuitry including the cpu oscillator, but allows Timer0 to continue running. Use this mode if you are using Timer0 as a system tick clock as long as the tick period is around 20 mSecs or longer. Tick rates much faster than this will be approaching the restart time for the cpu clock. Don't use this mode if the timer doubles as a real time clock (see later).
Here are some example figures for shutdown and startup. Say you have a clock tick setting for Timer0 of 20 mSecs, a process that is started by this clock tick that takes 8 mSecs, and when the process concludes the sleep instruction is executed. If we start from when the Timer0 overflow interrupt routine executes, this will be what happens:
![]() |
The timer0 counter will be reloaded in the interrupt routine and the counter will immediately start its next cycle. the associated process will be queued by a call to QueTask. |
![]() |
TaskR will start the associated process from the queue which then runs for its allotted 8 mSecs. When it concludes execution returns to TaskR in the normal way. With an empty queue TaskR then executes the sleep instruction which shuts down the processor and clock oscillator. |
![]() |
Around 12 mSecs later (remember Timer0 has continued to run during this time), the Timer0 overflow will interrupt again as its 20 mSec period has elapsed. The processor now starts a normal power up sequence. After about 5 mSecs the cpu crystal oscillator will be running but not yet stable. After about another 25 mSecs the startup sequence will conclude and the cpu will start to run. After a few cpu clock cycles the Timer0 overflow interrupt will be honored which is where we started. |
Thus there is around 60 mSecs between execution of the Timer0 interrupts, not the 20 mSecs defined by the timer settings. In addition, the startup time is effected by such external variables as temperature and supply voltage. So you cannot reliably use Timer0 as a realtime clock in a Power Down mode situation.
Note that you can switch between these modes where that is convenient. For example you could use the Idle mode during a period of intense activity where interrupts are occurring very frequently (for example while performing a set of AtoD samples). When interrupts are likely to be less frequent you can switch to the Power Down mode.
#include <stdlib.h>
#include <interrupt.h>
#include <signal.h>
#include "taskr.h"
int main(void);
void InitClock(void);
void ManageEvent(void);
#define CLK0_DIVIDER 0x04 // PCK0 / 64
#define CLK0_COUNT 256-10 // roughly 20 mSec
int LED_State;
int main(void)
{
int i, j;
// PORTA
outp(0x00, PORTA); // all low
outp(0xff, DDRA); // all output
// PORTB
outp(0xff, PORTB); // all low
outp(0xff, DDRB); // all output
sbi(ACSR, ACD); // disable comparator
InitClock();
InitTaskR();
sei(); // hello world
for (i=0; i<20000; i++)
j = 123;
while (1)
{
Despatch();
}
return 0;
}
//---------------------------------------------------------------------
void InitClock(void)
{
sbi(ASSR, AS0); // clock from external crystal
outp(CLK0_DIVIDER, TCCR0);
outp(CLK0_COUNT, TCNT0);
sbi(TIMSK, TOIE0); // enable Timer0 interrupts
}
//-------------------------------------------------------------------
void ManageEvent(void)
{
int i, j;
sbi(PORTA, 5); // for scoping
if (LED_State)
sbi(PORTB, 6); // the state change
else
cbi(PORTB, 6); // the state change
LED_State = ~LED_State; // flip state
cbi(PORTA, 5); // for scoping
}
//---------------------------------------------------------------------
// The simulated event that causes the state change.
//
SIGNAL(SIG_OVERFLOW0)
{
outp(CLK0_COUNT, TCNT0); // reset counter
sbi(PORTA, 1); // for scoping
QueTask(ManageEvent);
cbi(PORTA, 1); // for scoping
}