/* FILE: atmega32_rtl.c DESCRIPTION: Real-time Loop (RTL) framework for the Atmega32 using Timer/Counter0 Output Compare facility. Toggles PB0 once per second to give indication of life. Also includes a task to keep track of time in a structure. This could be displayed, but in order to keep this framework simple, that task is left to you! NOTES: - Accuracy of time-keeping depends on the accuracy of the timing element, of course. - This code contains code that depends on the system clock frequency. F_CPU of 1 MHz is assumed. AUTHOR: L. Wyard-Scott, University of Alberta COPYRIGHT: Public Domain - Use at your own risk! DATE: 14 March 2008 REVISION: 0.0 HISTORY: 0.0 - 14 March 2008 - Creation. POTENTIAL FUTURE IMPROVEMENTS: - Addition of code to detect RTL failure. - Inclusion of month determination in time calculation. - Modify timer0_init to use F_CPU. */ #include #include #include #include #include #include #include #include #include /* Time-handling structure. */ typedef struct { uint16_t thousandths; uint8_t seconds; uint8_t minutes; uint8_t hours; uint16_t days; uint16_t year; } time_struct; /********************************************************************** Global Variables **********************************************************************/ volatile uint8_t NEWTICK = 0; /* Flag to indicate when a thousandth of a second has passed. The volatile keyword is required since the variable is changed in an ISR and its effects need to affect the main program. */ time_struct clock_time; /* A global time variable. */ /********************************************************************** Function: time_increment Description: Increments the time pointed to by *timestruct by one thousandth of a second, allowing carry into longer units of time as needed. Requires: time_struct *timestruct - a pointer to a time structure. Returns: Nothing. (Time in *timestruct is updated.) Notes: This routine has not been fully tested. **********************************************************************/ void time_increment(time_struct *timestruct) { if (++timestruct->thousandths == 1000) { timestruct->thousandths = 0; if (++timestruct->seconds == 60) { timestruct->seconds = 0; if (++timestruct->minutes == 60) { timestruct->minutes = 0; if (++timestruct->hours == 24) { timestruct->hours = 0; if (timestruct->year % 4) { /* Not a leap-year. */ if (++timestruct->days == 365) { timestruct->days = 0; timestruct->year++; } } else { /* This is a leap-year. */ if (++timestruct->days == 366) { timestruct->days = 0; timestruct->year++; } } } } } } } /********************************************************************** Function: ISR(TIMER0_COMP_vect) Description: Interrupt service routine for Timer0's output compare facility. This facility is used to generate periodic interrupts - in this case at a frequency of 1kHz. Requires: Nothing. Returns: NEW_TICK (global variable) is set to indicate a new tick has passed. Notes: This routine contains code specific to the clock frequency. F_CPU of 1 MHz is assumed. **********************************************************************/ ISR(TIMER0_COMP_vect) { /* The flag that generated this interrupt (OCF0 in TIFR) is automatically cleared. */ /* Schedule the next interrupt 1 ms in the future. See timer0_init for calculation of this number. */ OCR0 += 125; /* Flag the main program that a tick has passed. */ NEWTICK = 1; } /********************************************************************** Function: timer0_init Description: Initializes timer0 to generate periodic interrupts at a rate of 1kHz (a tick time of 1 ms) using the OC0 facility. No output to the OC0 pin (PB3)is made - the OC0 signal is used only to generate interrupts. Requires: Nothing. Returns: Nothing. Notes: This routine contains code specific to the clock frequency. F_CPU of 1 MHz is assumed. **********************************************************************/ void timer0_init(void) { /* Disconnect OC0 from PB3, use I/O clock at divide-by-8. */ TCCR0 = 0x02; /* Prepare to generate interrupts every 1 millisecond (f = 1kHz): F_CPU = 1 MHz: 1 MHz / 8 (prescaler) = 125 kHz. i.e. A count of 125 indicates 1 ms has passed. Since this is an 8-bit timer, this count needs to be kept in the range 1-255. This is why the prescaler is set to divide-by-8. The result, too, should be an integer, otherwise error is introduced to the time-keeping by rounding. */ OCR0 = 125; /* Enable OC0 interrupts by setting OCIE0 in TIMSK */ TIMSK |= 0x02; } /********************************************************************** Main Program. **********************************************************************/ int main(void) { /***************************************************************** Initialization. *****************************************************************/ timer0_init(); /* Clear the clock time (not really necessary since the global variable is initialized to 0, but you may want to initialize the values to something other than 0. */ clock_time.thousandths = 0; clock_time.seconds = 0; clock_time.minutes = 0; clock_time.hours = 0; clock_time.days = 0; clock_time.year = 0; /* Make PB0 an output - this is used for demonstration purposes only. */ PORTB &= 0xFE; /* Make low to start. */ DDRB |= 0x01; /* Change to output. */ /***************************************************************** The real time loop. It is convenient to write tasks as functions so that they can easily be removed by comments. *****************************************************************/ sei(); /* Enable interrupts. */ while (1) { time_increment(&clock_time); /* Keep track of time. */ /* A simple heartbeat task. Flashes an LED on PB0 at a frequency of 2Hz. Used for demonstration purposes only. */ PORTB = (PORTB & 0xFE) | (clock_time.seconds & 0x01); /* YOUR TASKS HERE! */ /* Block until a new tick occurs. */ while (!NEWTICK); /* Clear the flag. */ NEWTICK = 0; } return(0); }