Неточный счетчик на основе внутреннего генератора микроконтроллера Atmel SAMC21

Я программирую SAMC21 для системы управления, и мне нужен счетчик миллисекунд, аналогичный функции millis в Arduino, чтобы отслеживать сэмплы и т. д. Для этого я сначала настроил тактовый канал для внутреннего генератора (48 МГц), выход которого я делю на 48. Затем я устанавливаю 8-битный таймер/счетчик с периодом 100. Каждый раз, когда TC достигает 100, он переполняется, и я использую обратный вызов для вызова функции, которая просто увеличивает мою переменную счетчика миллисекунд (volatile uint32_t). Это работает нормально, но счетчик работает медленнее, чем должен быть, и задерживает от 5 до 15 секунд каждую минуту. Почему это так и как я могу сделать это более точным?

Примечание. Я использую драйвер UART через отладочный USB-накопитель для считывания значения счетчика.

Вот код, который вызывается соответствующим образом в 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);
}
Вы видели таблицу данных и точность, которую она обещает?

Ответы (1)

На самом деле вы прерываете каждые 100 мкс, а не миллисекунду, как следует из вашего названия. Это много прерываний для системы 48 МГц.

Кроме того, код намекает на довольно сложную структуру обработки прерываний, которая существует «за кулисами», с обратными вызовами, которые «регистрируются» и «разрешаются». Таким образом, хотя ваша фактическая функция обратного вызова короткая и приятная, общие накладные расходы на выполнение любого отдельного прерывания могут быть довольно высокими.

Вы также намекаете на использование UART для связи, и я предполагаю, что его драйвер также основан на прерываниях.

Все это заставляет меня предположить, что вы приближаетесь к пределу количества прерываний, которые может обработать эта система, и что некоторые прерывания вашего таймера просто пропускаются. Это было бы особенно верно, если бы прерывания UART имели более высокий приоритет, чем прерывания таймера, что является обычным случаем. Попробуйте увеличить период прерывания таймера до 200 мкс или 1000 мкс. (В своем обратном вызове увеличьте millisпеременную на 2 или 10 соответственно — тогда остальная часть системы не заметит разницы.)

На самом деле я только что нашел предложение в таблице данных о том, что тактовый генератор делит источник на 12, поэтому он фактически работает на частоте 4 МГц. Я реализовал соответственно с коэффициентом деления на часах 40 и периодом 100. Теперь он достаточно точен и теряет около 2 секунд каждые 4 минуты. Это ожидаемо или вы думаете, что я мог бы сделать это еще лучше?
Что ж, теперь у вас погрешность <1%, что может быть связано с точностью часов (если это источник часов RC), но для меня это все еще звучит довольно высоко. Возможно, вы все еще пропускаете некоторые прерывания.