Timer Counter: counter overflow interrupt

There are several options for each of our 6 16bit timer counter channels. Depending on how you configure the options you can perform different functions such as: frequency measurement, event counting, interval measurement, pulse generation, delay timing, pwm, etc. In this example we will setup a counter that generates an interrupt on overflow and blink and use this to toggle an LED every 1 second.

Prerequisites

We only need to select our master clock before we enable the timer/counter peripheral clock.

Setup

To setup the Timer/counter TC0, we will need to do the following:

  1. Set our master clock
  2. Enable TC0 interrupt in the NVIC
  3. Enable TCO peripheral clock in the PMC
  4. Setup our TC0 options
  5. Start our clock when ready to start counting
  6. Write our TC0 handler function

1. Set out master clock using the clock init function we created in the clock tutorial

clock_init();

2. Enable TC0 Interrupt in the NVIC

NVIC_EnableIRQ(TC0_IRQn);

3. Enable TC0 peripheral clock in the PMC

REG_PMC_PCER0 |= PMC_PCER0_PID23;

4. Set the clock for TC0 to the internal master clock divided by 2, enable counter overflow interrupt, and enable the TC0 clock.

//mainclock div 2
REG_TC0_CMR0 |= TC_CMR_TCCLKS_TIMER_CLOCK1;
//enable couter overflow interrupt
REG_TC0_IER0 |= TC_IER_COVFS; 
//enable tc clock
REG_TC0_CCR0 |= TC_CCR_CLKEN;

We don't need to configure any pins because we are not accepting input on the TIOA and TIOB pins, nor are we using an external clock source.

Since our master clock is 20 MHz and the first option is to use the master clock div 2 which is 10 MHz. The TC0 clock runs at 10 MHz and will increment on the rising edge of the clock. The counter is held in a 16bit register REG_TC_CV0 and will increment at the rising edge of the TC0 clock until it hits 65535 at which case it will trigger an interrupt. If the clock is running at 10Mhz our interrupt will trigger every 6.5535ms (1second/10,000,000 x 65535). If we want to toggle an LED roughly every 1 second we can calculate how many interrupts in a second which is about 152.59 interrupts.

5. After TC0 is fully setup with clocks enabled and the settings we want, we can reset the 16bit counter, start the clock and start counting.

REG_TC0_CCR0 |= TC_CCR_SWTRG;

6. When the counter overflows it will trigger and interrupt and the code in our TC0_Handler function will run.

void TC0_Handler(void//read status register - this clears interrupt flags
    uint32_t status = REG_TC0_SR0; 
    if ((status & TC_SR_COVFS)>=1){
        //code to run on overflow
    }
}

Example

In this example we will start TC0 and generate an interrupt on a counter overflow and toggle an LED roughly every 1 second.

#include "sam.h"

void clock_init(){
//enable external crystal
REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN;
//wait for crystal to become ready
while (!(REG_PMC_SR & PMC_SR_MOSCXTS));
//select crystal
REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;
//master clock source selection - choose main clock
REG_PMC_MCKR |= PMC_MCKR_CSS_MAIN_CLK;
//wait until main clock ready
while (!(REG_PMC_SR & PMC_SR_MCKRDY));
	
//select processer prescaler (0 - no divisor)
REG_PMC_MCKR |= PMC_MCKR_PRES_CLK_1;
	
//wait until main clock ready
while (!(REG_PMC_SR & PMC_SR_MCKRDY));
}

void timerInit(){
    //Setup for TC0 - ID 23, TIOA0 - PA0 peripheral B
    //enable interrupts in NVIC for TC0
    NVIC_EnableIRQ(TC0_IRQn);
	
    //PMC setup
    //enable peripheral clock for timer counter channel0
    REG_PMC_PCER0 |= PMC_PCER0_PID23;
	
    //Interrupt Setup
    //mainclock div 2
    REG_TC0_CMR0 |= TC_CMR_TCCLKS_TIMER_CLOCK1;
    //enable couter overflow interrupt
    REG_TC0_IER0 |= TC_IER_COVFS;
    //enable tc clock
    REG_TC0_CCR0 |= TC_CCR_CLKEN;
	
    //PIO setup (not neccessary) because we won't use the pins
}

//we will use this counter to count out the number of interrupts to get to 1 second
uint8_t counter = 0;

int main(void){
    /* Initialize the SAM system */
    SystemInit();
    clock_init();
    timerInit();
	
    //Setup output on PA11 for our LED
    //set PA11 as controllable by the PIO controller (disable peripheral)
    REG_PIOA_PER |= PIO_PER_P11;
	
    //set PA11 as output
    REG_PIOA_OER |= PIO_PER_P11;
	
    //Start with LED on
    //set PA11 high
    REG_PIOA_SODR |= PIO_SODR_P11;
	
    //start our TC0
    REG_TC0_CCR0 |= TC_CCR_SWTRG;
	    
    while (1)
    {
		
    }
}

void TC0_Handler(void){
    //read status register - this clears interrupt flags
    uint32_t status = REG_TC0_SR0;
    if ((status & TC_SR_COVFS)>=1){
        //increment counter
        counter+=1;
    }
	
    if (counter>152){	
        //reset counter
        counter=0;
        //toggle led
        if ((REG_PIOA_ODSR & PIO_ODSR_P11)>=1){
            REG_PIOA_CODR |= PIO_CODR_P11; //set PA11 low
            //printString("off");
        }
        else{
            REG_PIOA_SODR |= PIO_SODR_P11; //set PA11 high
            //printString("on");
        }
    }		  		  
}