Saturday, December 26, 2015

Timer0 interrupt for pic18f

Normal simple program work in an order entity in forever loop, the execution of function is one by one, from start to the end and back to the start again. Although the program is executed in a predictable manner, but the control of function's occurrence is hard in forever loop. For example, several tasks need to be run at different timings, task1 every 10ms, task 2 every 100ms, and task 3 every 1s. A timer is needed to keep track of the program execution and run the task at each time interval.
To enable precise control of tasks, timer needs to work independently from forever loop and interrupt the forever loop at pre-set time interval.

The below tutorial uses two leds to show the tasks execution in 1s and 2s.
Timer0 is used where a 10ms flags is raised at each interrupt. At each 10ms, a counter is increased, when the 1s is reached, led1 will blinks, for the next second, led2 will blinks.

There are three ways to set up the timer0 interrupt which are high interrupt, and low interrupt.
In this tutorial, high interrupt timer0 is shown.

For starter, always read the datasheet but microchip wikidot has prepared a good explanation for developer.

The timer0 register, always refer back when setting the timer0 module.

The basic structure of timer0 is

It can serves as timer or a counter, the timer source is MCU frequency divided by 4 while pin interrupt is T0CK1.

To enter timer mode, register TMR0CS need to be cleared.

Another important register is prescaler

Clear PSA to assign prescalar to timer0.  Then select the suitable prescaler bit.

I like to do the timer calculation in hertz, for 10ms, it is 100Hz.
So the calculation is
100 = (Fosc/4)/(PSx rate)/timer_value;

my clock frequency is 48Mhz, PSx rate I choose 64 as it can be divided nicely with 48Mhz.

100 = (12,000,000)/64/timer_value;
100 = 18750/timer_value;
timer_value=1875;

So in this case, 16 bits timer is needed, to load the suitable value,
we need take 0xFFFF - 1875, which is 63755.
Another point worth notice is "Any write to the TMR0 register will cause a 2 instruction cycle (2TCY) inhibit", so 63755 needs add with 2. The correct reload value is 63757.

The setting of timer0 module is as follow:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Reset Timer0 to 0x0000
TMR0H = 0;              
TMR0L = 0;

// Clear Timer0 overflow flag
INTCONbits.TMR0IF = 0;  
INTCONbits.TMR0IE = 0;

//Set up timer0 see abive
T0CONbits.TMR0ON=0;         // do not on timer0 first
T0CONbits.T08BIT=0;            // select 16 bits timer
T0CONbits.T0CS=0;               // timer0
T0CONbits.PSA=0;                 // on prescalar
T0CONbits.T0PS=5;               // divide by 64

INTCON2bits.TMR0IP=1;      // enable priority interrupt
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Refer to c18 compiler header file for timer,
(located at "C:\Program Files (x86)\Microchip\mplabc18\v3.47\src\pmc_common\Timers")
I rewrote the timer file and includes following function.

void Timer0_Init(void);
void Timer0_Start(void);
void Timer0_Stop(void);
void Timer0_Write(unsigned int timer0);

When doing initialisation for timer0 at main file,
    //------------------------------------------------------------------------------
    //  Init PIC periphery
    //------------------------------------------------------------------------------
RCONbits.IPEN=1;                  // enable priority interrupt
Timer0_Init();                           // initialise timer0
Timer0_Write(63661);             // load the value to timer0
Timer0_Start();                        // start the timer0
INTCONbits.GIEH=1;            // enable high priority interrupt


Before the interrupt service routine, following interrupt vector table need to be initialised,
//----------------------------------------------------------------------------
// High priority interrupt vector
void InterruptHandlerHigh() ;
#pragma code InterruptVectorHigh = 0x08       // 0x08 for high interrupt vector
void InterruptVectorHigh(void) {
  _asm
    goto InterruptHandlerHigh                             //jump to interrupt routine
  _endasm
}

Interrupt service routine:
//----------------------------------------------------------------------------
// High priority interrupt routine
#pragma code
#pragma interrupt InterruptHandlerHigh
//
// Timer0 overflow
void InterruptHandlerHigh() 
{
    if (INTCONbits.TMR0IF) {
INTCONbits.TMR0IF = 0;                 //clear interrupt flag

//set timer int on overflow
    Timer0_Write(63661);                      //write the timer0 value

counter++;
if(counter>=100)                              //count until 1second
{
counter=0;
flag_1sec=1;

}

if(flag_1sec==1)                             //led1 toggle every 1 second
{
flag_1sec=0;
flag_2sec++;
flag_led1=1;
}

if(flag_2sec==2)                           //led2 toggle every 2 second
{
flag_2sec=0;
flag_led2=1;
}
}
}

At the interrupt service routine, I do a counter until 100 to get 1 sec timing, then I raise a flag for led1 at every 1 second; at 2 seconds interval, I raise another flag for led2.

After that, I service the flag for led1, led2 at main routine. (Just a simple blinking)

The overall source code is shown at github.