Hi, as others have said, sorry for being here late, but here goes...
I have been working on a battery powered binary clock. This code introduces several techniques, and they are NOT all interdependent. So, portions may be used separately. This project is not yet complete (are they ever??) It also includes main loop activation via. interupts (ISRs) from button presses and a modified delay2() function which calls sleep_cpu().
Of real interest to this discussion is my use of the "compare" interrupt instead of "overflow".
Secondly, I believe all current use of timer0 for time/date/clock purposes should be switched over to timer2.
In the default interrupt priority ordering, timer2 is the highest, it was designed as a real-clock-timer within the CPU
based in part on:
1. its ability to interface with external watch crystals (32.768 kHz)
2. use the prescalar with an external asynchronous clock source (signals on TOSC1 pin).
3. Wake CPU from sleep modes.
(see ATmegaxx8 datasheet for details on all of the above).
There is still some debug code/comments, and I have not created a schematic/diagram of my hardware yet. But, if anyone wants to see the whole project I will post it in exhibition.
// begin new wall-clock-time routines
#include <avr/io.h>
#include <WProgram.h>
#include "clock_timer2.h"
#include "interrupt_mgmt.h"
/*
* NOTE: Updates to init() required -- change timer2 to fast-PWM mode!!!
* and load OCR2A with 249
* set CTC mode for matchA
* enable timer2 compare match A interrupt
*/
// XXX volatile unsigned long timer2_matchA_count = 0;
volatile unsigned long timer2_millis = 0;
// The timer2_millis_update_count is a "guard" variable for timer2_millis.
// Its update is atomic. It is only updated in the timer2-compare ISR below.
// Since this ISR is uninterruptable, if another process/task detects
// an increment in timer2_millis_update_count, the ISR below must have started
// and run to completion. This means a "get" of timer2_millis was invalid.
// Simple solution, go get it again. And since this ISR is very small and
// quick compared to the timer2 count interval, any re-gets will be very-very
// infrequent. Also the elapsed time to perform the re-get is very quick.
volatile unsigned char timer2_millis_update_count = 0; // 8bit cyclical
// SIGNAL() is depricated, using ISR(). See avr/interrupt.h
#if defined(__AVR_ATmega8__)
ISR(TIMER2_COMP_vect)
#else
ISR(TIMER2_COMPA_vect)
#endif
{
// (volatile variables _ARE_ read from memory on every access)
// Using Output Compare Register "A" set to 249 (+1 = ticks).
// milliseconds increment := (1/F_CPU) * prescaler * ticks * millis-per-second
// := (1/16000000) * 64 * 250 * 1000
// := 1
// for different clock frequencies the timer2-prescaler may be
// ajusted to also call this interrupt on 1 millisecond
// boundary...
// clock(MHz) : t2-prescaler
// 16 : 64
// 8 : 32 (only available on timer2)
// for longer cycles (allowing sleep modes to extend battery
// life) the following works:
// clock(MHz) : prescaler : increment
// 16 : 1024 : 16
// ( this ONLY synchronizes with whole seconds on EVEN second boundries).
// but it is accurate.
//#define MILLIS_INCREMENT 1UL // prescalar is 64
#define MILLIS_INCREMENT 16UL // prescalar is 1024
// reducing system clock with its prescaler can save more
// but is more complex.
// this is a candidate for being ISR_NAKED and coding
// the following in assembler
timer2_millis_update_count++;
timer2_millis += MILLIS_INCREMENT;
// XXX timer2_matchA_count++;
}
volatile unsigned long millis2_spin_wait_count = 0; // debug test
volatile unsigned long millis2_count = 0; // debug test
unsigned long millis2()
{
unsigned long m;
unsigned char bc;
{
bc = timer2_millis_update_count; // get the before count
m = timer2_millis;
// if ISR changed 8bit counter --> must re-fetch multi-byte value
// else its OK just return it.
if (bc == timer2_millis_update_count) return m;
// XXX should repeat ONE time only, may need better check
// millis2_spin_wait_count++;
} while (1);
}
unsigned long get_spin_wait() // debug test
{
unsigned long w;
w = millis2_spin_wait_count;
return w;
}
unsigned long get_m2() // debug test
{
unsigned long w;
w = millis2_count;
return w;
}
volatile unsigned long micros2_spin_wait_count = 0; // debug test
volatile unsigned long micros2_count = 0; // debug test
unsigned long micros2() {
unsigned long m, t;
unsigned char bc;
// see comments in "millis2()"
micros2_count++; // debug test
{
bc = timer2_millis_update_count; // get the before count
t = TCNT2;
m = timer2_millis;
// if ISR changed 8bit counter --> must re-fetch multi-byte value
// else its OK just return it.
m = timer2_millis * 1000UL * MILLIS_INCREMENT;
if (bc == timer2_millis_update_count)
{
// Note: MILLIS_INCREMENT * 64 / c-c-perMicroS should
// be optimized to a single constant by the compiler.
return (m + (t * MILLIS_INCREMENT * 64 / clockCyclesPerMicrosecond()));
}
// XXX should repeat ONE time only, may need better check
// micros2_spin_wait_count;
} while (1);
}
/*
void trueSleep2(unsigned long ms)
*/
void delay2(unsigned long ms)
{
unsigned long start = millis2();
// for details see:
// http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html
#if defined(__AVR_ATmega8__)
// set sleep mode Power Save
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
#else
// set sleep mode External Standby
set_sleep_mode(SLEEP_MODE_EXT_STANDBY);
#endif
// set Sleep Enable Bit
cli();
sleep_enable();
while (millis2() - start <= ms && get_pcintx() == 0)
{
sei(); // always perform IMMEDIATELY prior...
sleep_cpu();
}
// clear Sleep Enable Bit
sleep_disable();
sei(); //to be safe
// clear_pcintx();
}
unsigned long get_micros2_spin_wait() // debug test
{
unsigned long w;
w = micros2_spin_wait_count;
return w;
}
unsigned long get_micros2() // debug test
{
unsigned long w;
w = micros2_count;
return w;
}
void clock_timer2_init()
{
#if defined(_T2_PRESCALE_64_)
// set timer 2 prescale factor to 64
#if defined(__AVR_ATmega8__)
sbi(TCCR2, CS22);
#else
sbi(TCCR2B, CS22);
#endif
#else
// set timer 2 prescale factor to 1024
#if defined(__AVR_ATmega8__)
sbi(TCCR2, CS22);
sbi(TCCR2, CS21);
sbi(TCCR2, CS20);
#else
sbi(TCCR2B, CS22);
sbi(TCCR2B, CS21);
sbi(TCCR2B, CS20);
#endif
#endif
// load match value for timer2
// 0::249 => 250 ticks
#if defined(__AVR_ATmega8__)
OCR2 = 249;
#else
OCR2A = 249;
#endif
// set CTC mode (clear timer on compare).
#if defined(__AVR_ATmega8__)
// set CTC mode only
sbi(TCCR2, WGM21);
#else
// configure timer 2 for fast hardware pwm
// _AND_ CTC
sbi(TCCR2A, WGM21);
sbi(TCCR2A, WGM20);
sbi(TCCR2B, WGM22);
#endif
// enable timer2 compare interrupt
#if defined(__AVR_ATmega8__)
sbi(TIMSK, OCIE2);
#else
sbi(TIMSK2, OCIE2A);
#endif
}
void clock_timer2_dis_timer0()
{
// disable timer 0 overflow interrupt
#if defined(__AVR_ATmega8__)
cbi(TIMSK, TOIE0);
#else
cbi(TIMSK0, TOIE0);
#endif
}
// end new wall-clock-time routines