Timers

Welcome back, this is the continuation of my PIC tutorials. Today’s tutorial is about Timers. In the last code example we made a LED blink using the compilers built in __delay_ms() routine. As I stated before the __delay_ms() is very inefficient and kind of a hacked together delay. Although using the Timer is a little bit more difficult and requires some basic math to get the delay that you require it gives you the ability to do other things while you are counting clock cycles. In this particular tutorial I will be using timer0 on the PIC16F18323. Below is the full code that will be used, and like the last tutorial i will go through and explain all the parts in detail.

/* 
 * File:   main.c
 * Author: Bill Heaster
 * TheCreator aT ApexLogic d0T Net
 * 
 * 
 * This is the basic blink an LED Hello world program. 
 * Revision 1.0
 *
 * Created on October 5, 2015, 5:00 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>


// CONFIG1
#pragma config FEXTOSC = HS     // FEXTOSC External Oscillator mode Selection bits (HS (crystal oscillator) above 4 MHz)
#pragma config RSTOSC = EXT1X   // Power-up default value for COSC bits (EXTOSC operating per FEXTOSC bits)
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config MCLRE = ON       // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF      // Debugger enable bit (Background debugger disabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

#define _XTAL_FREQ 8000000 //let the compiler know what our crystal is so our _delay functions properly.

#define OUTPIN LATCbits.LATC3

unsigned char counter;

void interrupt timer0ISR()
{
    if(TMR0IF && TMR0IE) //if the overflow bit and the interrupts are enabled
    {
        TMR0IF =0;//reset the overflow bit
    }
    
    ++counter; //increment the counter
    
}

void init()
{
    //set all pins to digital by disabling analog functions.
    ANSELA =0x00;
    ANSELC = 0x00;
    
    //This firmware uses pin RC3 as the output pin
    //set the selected pin to output 0=output 1=input
    TRISCbits.TRISC3=0;
    
    //set the pin low before the main program loop starts
    OUTPIN =0;
    
    //setup timer 
    //clear both registers
    T0CON0 = 0x00;
    T0CON1 = 0x00;
    
    T016BIT = 0; //setup as 8-bit counter

   
    T0CKPS0 =1; //pre -scalar 1:8192
    T0CKPS2 = 1;
    T0CKPS3 = 1;
    
    T0CS1 = 1; //Fosc /4
 
    
    T0EN = 1;
    TMR0IE = 1;
    GIE = 1;
    
}


int main(int argc, char** argv) {

    init();
    unsigned char cnt = 0;
    unsigned char maxCnt = 10;
    
    while(1)
    {
        if(counter > 0) //if the counter is greater than zero then the overflow occurred, this means that that 256 captures of the pre scaled clock edge occurred. 
        {
            counter =0;   
            OUTPIN ^=1;        
            
        }
    }

    
    
    
    
    
    
    return (EXIT_SUCCESS);
}


The config bits are the same as the first tutorial. As I stated before these config parameters will work for most projects. The next section is something new however, lets take a look.

void interrupt timer0ISR()
{
    if(TMR0IF && TMR0IE) //if the overflow bit and the interrupts are enabled
    {
        TMR0IF =0;//reset the overflow bit
    }
    
    ++counter; //increment the counter
    
}

This is an interrupt routine or ISR. It is similar to a regular function but it is called directly by the hardware instead of through software. you can tell that this is an interrupt routine by the word “interrupt” following the declaration of the void function. The name can be anything you want it to be. In this particular case the name is timer0ISR. Once the hardware interrupt is triggered (in this case by the timer) the routine will run. The first thing that is done in the routine is to make sure the interrupt occur because our counter overflowed. The TIMER0IF bit is where the interrupt flag is set and cleared. The TMR0IE is the interrupt enabled bit which will be discussed in the init() later on. If the interrupt flag and the interrupts are both enabled then we know that the counter has overflowed, by clearing the interrupt flag to 0 we reset the bit and allow another overflow to occur. It is common practice to not perform complex tasks during the interrupt in this example we are just incrementing the counter variable that was declared globally. The reason for this is that if you were to be doing something at high speeds you could miss the next interrupt while preforming tasks within our interrupt function…if that makes any sense.

void init()
{
    //set all pins to digital by disabling analog functions.
    ANSELA =0x00;
    ANSELC = 0x00;
    
    //This firmware uses pin RC3 as the output pin
    //set the selected pin to output 0=output 1=input
    TRISCbits.TRISC3=0;
    
    //set the pin low before the main program loop starts
    OUTPIN =0;
    
    //setup timer 
    //clear both registers
    T0CON0 = 0x00;
    T0CON1 = 0x00;
    
    T016BIT = 0; //setup as 8-bit counter
    
  
    T0CKPS0 =1; //pre -scalar 1:8192
    T0CKPS2 = 1;
    T0CKPS3 = 1;
    
    T0CS1 = 1; //Fosc /4
 
    
    T0EN = 1;
    TMR0IE = 1;
    GIE = 1;
    
}

Like before we are using an init() or a setup() to configure all the pin states and registers prior to the main loop starting. Some steps, such as clearing the ANSELC and ANSELA to disable the multiplexed analog functions are the same as the previous tutorial. Same thing with the TRISC register which is used to set the direction of the pin we are using (RC3). T0CON0 and t0CON1 are the control registers for timer 0. These two registers are used to control how the timer will behave in our program. The T016bit bit is used to tell whether to use an 8 bit or 16 bit timer. This will dictate when the timer will overflow. If it is 8 bit which is what we are using, then the timers will overflow every 256 captures. The next step is to set the pre-scalar. The pre-scalar will basically skip clock cycles and only increment the timer0 internal counter when the certain pre-scalar states it to. In this example the pre-scalar is set to 1:8192. This means that every 8192 clock cycles the timer0 internal counter will increment once. I will explain how to calculate the timing output of our timer based on pre-scalar values in the upcoming section. The next step is to set the oscillator, for this example i have set it to the most simple , which is the crystal frequency /4. Once we setup how the timer will function it is required to turn on all the interrupt bits. T0EN will turn the timer on, TMR0IE will turn on the timers interrupt, finally GIE is used as kind of a master switch to allow any and all interrupts to function correctly.


int main(int argc, char** argv) {

    init();

    
    while(1)
    {
        if(counter > 0) //if the counter is greater than zero then the overflow occured, this means that that 256 captures of the pre scaled clock edge occured. 
        {
            counter =0;   //reset counter
            OUTPIN ^=1;   //toggle output Pin   
            
        }
    }

    
    
    
    
    
    
    return (EXIT_SUCCESS);
}

Now on to the main section of the program. As before init() or setup() is called at the beginning to do a one time configuration of our pins, interrupts, registers, etc. Again we enter a while(1) loop that should never end. inside this loop we check if the global variable counter is greater than 0. If this is the case then that means that the interrupt routine has run 256 times (with an 8-bit timer). Notice there was never a call for the ISR routine to run. Once the timer is turned on in the init() routine it begins counting. Once the overflow occurs the interrupt flag is set and the ISR routine runs incrementing the counter variable > 0. With the way the timer was configured should make the light blink for 1.04 seconds with an 8Mhz crystal. The following is the math i was talking about to setup the timers.

Timer pre-scalar calculations

Internal Cycle = 1/(crystal Frequency /4) = Ic
Time(seconds) per cycle = 1(Ic/Pre_scalar) = TsC
overflow time = TsC * 256 = time between interrupts

Overflow time is what will give the time between when the interrupts occur. Since I was using an 8Mhz crystal I made a table using all the available pre-scalar’s for the PIC16F18323.

1:1		.1ms
1:4		.5ms
1:8		1.02ms
1:16		2.04ms
1:32		4.09ms
1:64		8.19ms
1:128		16.38ms
1:256		32.76ms
1:512		65.53ms
1:1024		131.07ms
1:2048		262.14ms
1:4096		524.28ms
1:8192		1.04 seconds
1:16384		2.09 seconds
1:32768		4.19 seconds

Stay tuned to look at some SPI action in the next installment.