Contador basado en oscilador interno Atmel SAMC21 MCU inexacto

Estoy programando un SAMC21 para un sistema de control y necesito un contador de milisegundos similar a la función de milis en arduino para realizar un seguimiento de las muestras, etc. Para hacer esto, primero configuro un canal de reloj para el oscilador interno (48MHz) cuya salida I divido por 48. Luego configuro un temporizador/contador de 8 bits con un período de 100. Cada vez que el TC llega a 100 se desborda y uso una devolución de llamada para llamar a una función que simplemente incrementa mi variable de contador de milis (volátil uint32_t). Esto funciona bien, pero el contador es bastante más lento de lo que debería y se retrasa entre 5 y 15 segundos cada minuto. ¿Por qué es así y cómo podría hacerlo más preciso?

Nota: utilizo el controlador UART sobre el USB de depuración para leer el valor del contador.

Aquí está el código que se llama apropiadamente en 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);
}
¿Ha echado un vistazo a la hoja de datos y la precisión que promete?

Respuestas (1)

En realidad, está tomando una interrupción cada 100 µs, no milisegundos como su nombre lo indica. Son muchas interrupciones para un sistema de 48 MHz.

Además, el código sugiere una estructura de manejo de interrupciones bastante elaborada que existe "detrás de escena", con devoluciones de llamadas "registradas" y "habilitadas". Entonces, si bien su función de devolución de llamada real es corta y agradable, la sobrecarga general para tomar cualquier interrupción individual aún puede ser bastante alta.

También insinúa el uso de un UART para la comunicación, y supongo que su controlador también se basa en interrupciones.

Todo esto me lleva a sugerir que se está acercando al límite en la cantidad de interrupciones que este sistema puede manejar, y que algunas de las interrupciones de su temporizador simplemente se están perdiendo. Esto sería especialmente cierto si las interrupciones UART tienen una prioridad más alta que las interrupciones del temporizador, que es el caso habitual. Intente aumentar el período de interrupción del temporizador a 200 µs o 1000 µs. (En su devolución de llamada, incremente la millisvariable en 2 o 10, respectivamente; luego, el resto del sistema no notará la diferencia).

De hecho, acabo de encontrar una oración en la hoja de datos que dice que el generador de reloj divide la fuente por 12, por lo que en realidad funciona a 4MHz. Lo implementé en consecuencia con un factor de división en el reloj de 40 y un período de 100. Ahora es bastante preciso y pierde alrededor de 2 segundos cada 4 minutos. ¿Es eso de esperar o crees que podría hacerlo aún mejor?
Bueno, ahora tiene un error de <1%, lo que podría atribuirse a la precisión del reloj (si es una fuente de reloj RC), pero todavía me parece bastante alto. Es posible que aún te falten algunas interrupciones.