Here's some code for an interrupt-driven timer.
Call Timer_1_Initialize() in the User_Initialization routine in user_routines.c, add the call to the interrupt handler to user_routines_fast.c. Time is in the timer_ variables.
Mike
Code:
/*******************************************************************************
*
* TITLE: wallclock.h
*
* VERSION: 0.1 (Beta)
*
* DATE: 23-Sep-2006
*
* AUTHOR: Mike Luckham
* sunrise@sunsys.net
*
********************************************************************************
*
* You are free to use this source code for any non-commercial
* use. Please do not make copies of this source code, modified
* or un-modified, publicly available on the internet or
* elsewhere without permission. Thanks.
*
* Copyright ©2006-2007 Mike Luckham. All rights are reserved.
*
********************************************************************************
*
* CHANGE LOG:
*
* DATE REV DESCRIPTION
* ----------- --- ----------------------------------------------------------
* 23-Sep-2006 0.1 Mike Luckham - Original code.
*
*******************************************************************************/
#ifndef _WALLCLOCK_H
#define _WALLCLOCK_H
typedef struct {
unsigned int hours;
unsigned int minutes;
unsigned int seconds;
unsigned int msec;
unsigned int usec;
unsigned long timestamp;
} WallTime;
// Do not write to any of these variables
extern volatile unsigned long timer_msecClock; // milliseconds since system start (overflows after 49.7 days)
extern unsigned long Timer_1_MicrosecondsTimestamp(void);
#define timer_usecClock Timer_1_MicrosecondsTimestamp() // microseconds since system start (overflows after about 71 minutes)
// current time decoded
#define timer_microseconds Timer_1_Microseconds()
extern volatile unsigned int timer_milliseconds;
extern volatile unsigned int timer_100msec;
extern volatile unsigned int timer_seconds;
extern volatile unsigned int timer_minutes;
extern volatile unsigned int timer_hours;
extern void Timer_1_Reset(void);
extern void Timer_1_Initialize(void);
extern void Timer_1_Int_Handler(void);
extern void Timer_1_Load_Prescale(void);
extern unsigned int Timer_1_Microseconds(void);
extern void Timer_1_ReadTimeStruct(WallTime *);
#endif
Code:
/*******************************************************************************
*
* TITLE: wallclock.c
*
* VERSION: 0.1 (Beta)
*
* DATE: 23-Sep-2006
*
* AUTHOR: Mike Luckham
* sunrise@sunsys.net
*
* COMMENTS: This source code in this file implements a clock that is useful
* for tracking time at millisecond resolution. A timestamp value
* is useful for comparing the time between events. The timestamp
* is decoded into hours/minutes/seconds/milliseconds, and the
* current time can be read at microsecond resolution.
*
* Since the clock is interrupt-driven, it runs independently and
* maintains correct time between calls to user_routines.c by the master
* scheduler.
*
* To use the code, include wallclock.h in the user_routines.c and
* user_routines_fast.c source files, and:
*
* 1) call Timer_1_Initialize() from user_routines.c in User_Initialization()
*
* 2) add the following to user_routines_fast.c in InterruptHandlerLow():
*
* if (PIR1bits.TMR1IF && PIE1bits.TMR1IE)
* {
* PIR1bits.TMR1IF = 0; // clear the interrupt flag
* Timer_1_Int_Handler();
* }
*
* 3) when you need to measure elapsed time, store timer_msecClock into
* an unsigned long variable at the beginning of the interval, then subtract
* timer_msecClock from the saved value at the end of the interval to obtain
* the number of milliseconds which have elapsed.
*
* If you need the microsecond portion of the time, store the value returned
* by the timer_microseconds macro into an unsigned long variable. Probably this
* is of limited usefulness, although it could be useful in a busy-loop.
*
* The decoded time is available in the timer_ variables. Here is an example of
* how to use them:
*
* printf("%u:%u:%u.%u.%u\n", timer_hours, timer_minutes, timer_seconds, timer_milliseconds, timer_microseconds);
*
*
********************************************************************************
*
* You are free to use this source code for any non-commercial
* use. Please do not make copies of this source code, modified
* or un-modified, publicly available on the internet or
* elsewhere without permission. Thanks.
*
* Copyright ©2006-2007 Mike Luckham. All rights are reserved.
*
********************************************************************************
*
* CHANGE LOG:
*
* DATE REV DESCRIPTION
* ----------- --- ----------------------------------------------------------
* 23-Sep-2006 0.1 Mike Luckham - Original code.
*
*******************************************************************************/
#include "ifi_aliases.h"
#include "ifi_default.h"
#include "wallclock.h"
/********** GLOBAL CLOCK VARIABLES ***********************************************/
volatile unsigned long timer_msecClock = 0L; // milliseconds since system start (overflows after 49.7 days)
// current wall-clock time
volatile unsigned int timer_100msec = 0; // increments once every 100 milliseconds
volatile unsigned int timer_milliseconds = 0; // increments once per millisecond
volatile unsigned int timer_seconds = 0; // increments once per second
volatile unsigned int timer_minutes = 0; // increments once per minute
volatile unsigned int timer_hours = 0; // increments once per hour
// for the current microseconds count, use Timer_1_Microseconds
WallTime wTime;
// preload the timer so that the interrupt-on-overflow occurs 1000 microseconds later
// for microprocessor clock crystal 40MHz/4 = 10MHz tick rate = 65535 - 10000 (0xD8EF)
#define PRELOAD_LOW 0xD8
#define PRELOAD_HIGH 0xEF
/*******************************************************************************
* FUNCTION NAME: Timer_1_Reset
* PURPOSE: resets clock variables to zero
* CALLED FROM: user_routines.c
* ARGUMENTS: none
* RETURNS: real-time clock variables are reset
*******************************************************************************/
void Timer_1_Reset(void)
{
timer_msecClock = 0L;
timer_milliseconds = 0;
timer_seconds = 0;
timer_minutes = 0;
timer_hours = 0;
}
/*******************************************************************************
* FUNCTION NAME: Timer_1_Load_Prescale
* PURPOSE: preload the timer to overflow after 1000 microseconds
* CALLED FROM: user_routines_fast.c, user_routines.c
* ARGUMENTS: none
* RETURNS: Timer1 is preloaded
*******************************************************************************/
void Timer_1_Load_Prescale(void)
{
// NOTE: Timer specification requires MUST write high byte
// first, then low byte
TMR1H = PRELOAD_LOW; // preload the timer so that the interrupt-on-overflow occurs 1000 microseconds later
TMR1L = PRELOAD_HIGH; // for microprocessor clock crystal 40MHz/4 = 10MHz tick rate = 65535 - 10000 (0xD8EF)
}
/*******************************************************************************
* FUNCTION NAME: Timer_1_Int_Handler
* PURPOSE: Updates the real time clock variables
* CALLED FROM: user_routines_fast.c
* ARGUMENTS: none
* RETURNS: real-time clock variables are updated
*******************************************************************************/
void Timer_1_Int_Handler(void)
{
// update the millisecond-counter
// probably will be more useful than the msec/sec/min/hours/days variables
// avoid calling a function from within the Interrupt handler, other register
// context will need to be saved with a #pragma in user_routines_fast.c
// Timer_1_Load_Prescale();
TMR1H = PRELOAD_LOW; // reload the timer so that the interrupt-on-overflow occurs 1000 microseconds later
TMR1L = PRELOAD_HIGH; // for microprocessor clock crystal 40MHz/4 = 10MHz tick rate = 65535 - 10000 (0xD8EF)
timer_msecClock++;
timer_milliseconds++;
if (timer_milliseconds > 999)
{
timer_milliseconds = 0;
if (++timer_seconds > 59)
{
timer_seconds = 0;
if(++timer_minutes > 59)
{
timer_minutes = 0;
timer_hours++;
}
}
}
if ((timer_milliseconds % 100) == 0)
timer_100msec++;
}
/***********************************************************************************
* FUNCTION NAME: Timer_1_Microseconds
* PURPOSE: fetch the number of microseconds since the timer last interrupted
* CALLED FROM: user_routines.c
* ARGUMENTS: none
* RETURNS: microseconds since the last interrupt
***********************************************************************************/
unsigned int Timer_1_Microseconds(void)
{
unsigned int tmr1;
// NOTE: Timer specification requires MUST read low byte
// first, then high byte
tmr1 = (unsigned int) TMR1L + ((unsigned int) TMR1H << 8); // convert to 16-bit, then negate
tmr1 = ~tmr1 + 1; // (2's complement) to get remaining ticks until overflow
// scale to the clock frequency - see Timer_1_Load_Prescale() comments for the number
// of ticks per microsecond
return (tmr1 / 10);
}
/***********************************************************************************
* FUNCTION NAME: Timer_1_MicrosecondsTimestamp
* PURPOSE: fetch a timestamp which includes the number of microseconds
* CALLED FROM: user_routines.c
* ARGUMENTS: none
* RETURNS: microseconds since the last interrupt
***********************************************************************************/
unsigned long Timer_1_MicrosecondsTimestamp(void)
{
// WARNING: this is quite expensive to call, probably not very useful
return (timer_msecClock * 1000) + (unsigned long) Timer_1_Microseconds();
}
/*******************************************************************************
* FUNCTION NAME: Timer_1_Initialize
* PURPOSE: configure the Timer1 interrupt to overflow and interrupt after
* 1000 microseconds
* CALLED FROM: user_routines.c
* ARGUMENTS: none
* RETURNS: Timer1 is configured and TMR1IF interrupt enabled
*******************************************************************************/
void Timer_1_Initialize(void)
{
T1CONbits.TMR1ON = 0; // disable the clock while programming it
T1CONbits.TMR1CS = 0; // use internal clock (10 MHz)
T1CONbits.T1CKPS0 = 0; // set prescaler to 1:1, or OSC/4. A 40 MHz crystal divided by 4 gives 10 ticks per microsecond.
T1CONbits.T1CKPS1 = 0;
T1CONbits.RD16 = 0; // read timer using two 8-bit reads of TMR1L followed by TMR1H
// reading the timer and subtracting it's value from FFFFh gives the number of microseconds
// since the timer was last loaded
Timer_1_Load_Prescale();
Timer_1_Reset(); // reset time variables
T1CONbits.TMR1ON = 1; // enable the clock
PIE1bits.TMR1IE = 1; // enable the Timer1 overflow interrupt
}
/*******************************************************************************
* FUNCTION NAME: Timer_1_ReadTimeStruct
* PURPOSE: safely read the time values without disturbance from the
* interrupt service routines
* CALLED FROM: user_routines.c
* ARGUMENTS: none
* RETURNS: pointer to WallTime structure
*******************************************************************************/
void Timer_1_ReadTimeStruct(WallTime * wTime)
{
int intEnabled;
intEnabled = PIE1bits.TMR1IE; // disable timer interrupts while loading structure
if (intEnabled != 0)
PIE1bits.TMR1IE = 0; // disable the Timer1 overflow interrupt
wTime->hours = timer_hours;
wTime->minutes = timer_minutes;
wTime->seconds = timer_seconds;
wTime->msec = timer_milliseconds;
wTime->usec = Timer_1_Microseconds();
wTime->timestamp = Timer_1_MicrosecondsTimestamp();
if (intEnabled != 0)
PIE1bits.TMR1IE = 1; // enable the Timer1 overflow interrupt
}
Add this to the InterruptHandlerLow() routine in user_routines_fast.c
Code:
if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) // wallclock.c timer interrupt?
{
PIR1bits.TMR1IF = 0; // clear the interrupt flag
Timer_1_Int_Handler();
}