Timer Counter: RC compare interrupt

There is no delay include file for Atsam4s unless you are using ASF, so that means we have to make our own.

We will make a few changes to the previous timer counter program. Instead of triggering an interrupt when the counter overflows its 16 bit limit, we will set a value in the TC0_RC0 register and when the counter hits that value then it will trigger our interrupt. If we set the interrupt to trigger at a known rate then we can easily create our delay function. The changes that we have to make to the timer counter setup from the previous section are the following:

//remove this line
//enable counter overflow interrupt
//REG_TC0_IER0 |= TC_IER_COVFS;

//add these lines
//compare resets counter and clock
REG_TC0_CMR0 |= TC_CMR_CPCTRG;
//enable RC compare interrupt
REG_TC0_IER0 |= TC_IER_CPCS;

We will also have to set the value that the counter will generate an interrupt in the REG_TC0_RC0 register. Since our main clock is 20mhz and the timer counter clock is running at main clock / 2 = 10mhz then we have:

1 second / 10,000,000 = 0.0000001

So every 0.0000001 seconds or 0.0001 ms or 0.1us or 100ns the timer counter increments by 1.

If we want to generate an interrupt every 1 ms we should set our TC0_RC0 register to 10000.

REG_TC0_RC0 = 10000; //1 second / 10mhz = 1ms	

Using this we will create a function to delay for a variable number of miliseconds. The function will pass an unsigned 32 bit variable for the number of milliseconds to delay for. That gives us 4294967295 ms maximum or ~49 days. That's probably a sufficient delay. If you're going to delay that long you might want to use a sleep function. The added benifit of using a timer/counter delay is that it won't stop other interrupts from working. A blocking style delay might stop your interrupts.

#include "sam.h"
#include "basic_uart.h"

void clock_init(){
	REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN;
	while (!(REG_PMC_SR & PMC_SR_MOSCXTS));
	REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;
	REG_PMC_MCKR |= MC_MCKR_CSS_MAIN_CLK;
	while (!(REG_PMC_SR & PMC_SR_MCKRDY));
	REG_PMC_MCKR |= PMC_MCKR_PRES_CLK_1;
	while (!(REG_PMC_SR & PMC_SR_MCKRDY));
}

void timerInit(){
    //Setup for TC0 - ID 23
	
    //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
    REG_TC0_CMR0 |= TC_CMR_TCCLKS_TIMER_CLOCK1; //mainclock div 2
    //REG_TC0_IER0 |= TC_IER_COVFS; //enable couter overflow interrupt
    REG_TC0_IER0 |= TC_IER_CPCS; //enable RC compare interrupt
    REG_TC0_CMR0 |= TC_CMR_CPCTRG; //compare resets counter and clock
    REG_TC0_CCR0 |= TC_CCR_CLKEN; //enable tc clock
	
    //PIO setup (not necessary) because we won't use the pins
}


volatile uint32_t counter = 0; // global variable for delay

void delay_ms(uint32_t delayInMs){

    REG_TC0_RC0 = 10000; //1 second / 10mhz = 1ms
    //enable tc clock
    REG_TC0_CCR0 |= TC_CCR_CLKEN;
    //start timer
    REG_TC0_CCR0 |= TC_CCR_SWTRG;

    while (counter <= delayInMs){
	
    }

    //disable tc clock
    REG_TC0_CCR0 |= TC_CCR_CLKDIS;
    //reset counter
    counter = 0;
}

int main(void){
    /* Initialize the SAM system */
    SystemInit();
    clock_init();
    UART_Init();
    timerInit();
	
    //Setup output on PA11 for our LED
    REG_PIOA_PER|= PIO_PER_P11; //set PA11 as controllable by the PIO controller (disable peripheral)
    REG_PIOA_OER |= PIO_PER_P11; //set PA11 as output
    //Start Pin off
    REG_PIOA_SODR |= PIO_SODR_P11; //set PA30 high
			
    /* Replace with your application code */
    while (1)
        {			
        delay_ms(1000);
        REG_PIOA_CODR|= PIO_CODR_P11;
        delay_ms(1000);
        REG_PIOA_SODR |= PIO_SODR_P11;
    }
}

void TC0_Handler(void){
    //read status register - this clears interrupt flags
    uint32_t status = REG_TC0_SR0; 
    if ((status & TC_SR_CPCS)>=1){
        //increment counter
        counter+=1;		
    } 	
}