Electronic – Atmel SAMC21 MCU internal oscillator based counter inaccurate

control systemcountermicrocontrollertimer

I am programming a SAMC21 for a control system and I need a milliseconds counter similar to the millis function in arduino to keep track of samples, etc. To do this I first set up a clock channel for the internal oscillator (48MHz) whose output I divide by 48. I then set up an 8 bit Timer/Counter with a period of 100. Everytime the TC reaches 100 it overflows and I use a callback to call a function which simply increments my millis counter variable (volatile uint32_t). This works fine but the counter is quite slower than it should be and delays by between 5 and 15s every minute. Why is that so and how could I make it more accurate?

Note: I use the UART driver over the Debug USB to read the counter value.

Here is the code which is called appropriately in main:

 void configure_gclock_generator(void) // Configuration of internal oscillator and channel
 {
     struct system_gclk_gen_config gclock_gen_conf;
     system_gclk_gen_get_config_defaults(&gclock_gen_conf);
     gclock_gen_conf.source_clock    = SYSTEM_CLOCK_SOURCE_OSC48M;
     gclock_gen_conf.division_factor = 48;
     system_gclk_gen_set_config(GCLK_GENERATOR_1, &gclock_gen_conf);
     system_gclk_gen_enable(GCLK_GENERATOR_1);
 }
 void configure_gclock_channel(void)
 {
     struct system_gclk_chan_config gclk_chan_conf;
     system_gclk_chan_get_config_defaults(&gclk_chan_conf);
     gclk_chan_conf.source_generator = GCLK_GENERATOR_1;
     system_gclk_chan_set_config(TC3_GCLK_ID, &gclk_chan_conf);
     system_gclk_chan_enable(TC3_GCLK_ID);
 }

void configure_tc(void) // COnfiguration of TC on internal oscillator
{
    struct tc_config config_tc;
    tc_get_config_defaults(&config_tc);
    config_tc.counter_size = TC_COUNTER_SIZE_8BIT;
    config_tc.clock_source = GCLK_GENERATOR_1;
    //config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
    config_tc.counter_8_bit.period = 100;
    //config_tc.counter_16_bit.compare_capture_channel[0] = 48;
    tc_init(&tc_instance, CONF_TC_MODULE, &config_tc);
    tc_enable(&tc_instance);
}

void millis_count(struct tc_module *const module_inst) { //millis increment function
    millis++;
    if (millis>=1000000000) {
        millis=millis-start;
        start=0;
    }
}

void configure_tc_callbacks(void) //Set up of Callback for millis function.
{
    tc_register_callback(&tc_instance, millis_count,TC_CALLBACK_OVERFLOW);
    //tc_register_callback(&tc_instance, millis_count,TC_CALLBACK_CC_CHANNEL0);
    tc_enable_callback(&tc_instance, TC_CALLBACK_OVERFLOW);
    //tc_enable_callback(&tc_instance, TC_CALLBACK_CC_CHANNEL0);
}

Best Answer

You're actually taking an interrupt every 100 µs, not millisecond as your naming implies. That's a lot of interrupts for a 48 MHz system.

Furthermore, the code hints at a rather elaborate interrupt handling structure that exists "behind the scenes", with callbacks being "registered" and "enabled". So while your actual callback function is short and sweet, the overall overhead for taking any individual interrupt may still be rather high.

You also hint at using a UART for communication, and I'm guessing that its driver is also interrupt-based.

All of this leads me to suggest that you are approaching the limit on the number of interrupts that this system can handle, and that some of your timer interrupts are simply being missed. This would be especially true if the UART interrupts have a higher priority than the timer interrupts, which is the usual case. Try increasing the timer interrupt period to 200 µs or 1000 µs. (In your callback, increment the millis variable by 2 or 10, respectively — then the rest of the system won't know the difference.)