Прерывание таймера Attiny не делает прерывание каждые 1000 мс

Я делаю секундомер с помощью attiny85, идея заключалась в том, чтобы использовать прерывания таймера для подсчета секунд. моя теория заключалась в следующем: поскольку я настроил Attiny85 на частоту 1 МГц, я могу использовать предпродажную частоту 1024; 1000000 / 1024 = 976,5625 Гц, 976,5625 Гц = 1,024 мс, 125 мс / 1,024 мс = 122,07, поэтому мне нужно сосчитать до 122, чтобы получить прерывание 125 мс, затем в функции ISR я могу сосчитать до 8, чтобы обойти 1 секунду, ( 122 * 1,024 ) * 8 = 999,424 мс

В чем проблема? ну, проблема в том, что я не получаю что-то близкое к 999,424 мс, я сравниваю его с секундомером моего телефона, и attiny, кажется, запускается хорошо, но затем он становится примерно в 2-3 раза медленнее, чем секундомер моего телефона

#include <TM1637Display.h>

#define CLK 4
#define DIO 3
#define button 1

TM1637Display display (CLK, DIO);
int intr_count = 0;
byte dots = 0b01000000;

volatile int seg;
volatile int min;
volatile bool start = false;

void setup() {
  display.setBrightness(5);
  display.clear();
  pinMode(button, INPUT);

  cli();

  TCCR0A  = 0;
  TCCR0B  = 0;
  TCCR0B |= B00000101;
  TIMSK  |= B00010000;

  OCR0A   = 122;

  sei();
}

void start_counting(){
  if (digitalRead(button) == HIGH){
    start = !start;
    if(start == false){
// display.clear();
      seg = 0;
      min = 0;
    }
    while(digitalRead(button) == HIGH);
  }
}

void loop() {
  start_counting();
  display.showNumberDecEx(min, dots, true, 2, 0);
  display.showNumberDec(seg, true, 2, 2);
  
}

ISR(TIMER0_COMPA_vect) {
  TCNT0 = 0;
  if(start == true){
    
    if (intr_count == 8) {
      
      seg++;
      
      if(seg == 60){
        min++;
        min = min % 60;
      }

      seg = seg % 60;

      intr_count = 0;
    } else {
      intr_count++;
    }
  }
}

, 👍2

Обсуждение

Вы запускаете это от внутреннего генератора? *"2-3 медленнее"* 2-3 _что_ медленнее? Миллисекунды? И это намного медленнее в течение какого количества реального времени?, @timemage

@timemage да, я запускаю attiny от внутреннего генератора. "2 - 3" _секунды_ медленнее, я должен сказать, сдвиг времени, например запуск обоих секундомеров в одно и то же время, когда мой телефон **00:10** секундомер attiny **00:08**, когда мой телефон **1:00** секундомер attiny **00:57**, в первые секунды кажется, что секундомеры синхронизированы, но через пару секунд мой телефон, кажется, "считает секунды" намного быстрее, @Isael Guillén

display.showNumberDec(seg, true, 2, 2); Это, например, неатомарная операция чтения int16_t. Он мог быть наполовину обновлен ISR во время вызова. Лучше всего приостанавливать прерывания при создании копии seg и отображать ее., @6v6gt

"(122 * 1,024) * 8 = 999,424 мс". Я предполагаю, что тогда OCR0A должен быть 121, так как он начинается с 0., @6v6gt

@ 6v6gt Да. Я тоже это заметил, но, скорее всего, из-за точности самого радиоуправляемого генератора. Также имеет смысл использовать режим счетчика, который автоматически сбрасывает TCNT, а не делать это программно, но я не знаю, насколько это будет важно в конечном итоге. Я, вероятно, не буду серьезно рассматривать его еще 10+ часов., @timemage

Я загрузил код на Attiny45 с использованием внешнего генератора 16 МГц, время также дрейфует, хотя и меньше, чем с внутренним 1 МГц Attiny85., @Isael Guillén

Похоже, вам пора купить модуль RTC (часы реального времени). Это будет точно., @chrisl

Вам потребуется внешний низкочастотный кварцевый осциллятор с частотой 32,768 кГц, чтобы получить точную тактовую частоту 1 Гц и использовать его для настройки таймера для генерации тактовой частоты 1 с, которая вам нужна. Прочтите таблицу данных о низкочастотном кварцевом генераторе и о том, как его настроить., @hcheung

Если ответ Эдгара не просто отвечает на него, какой дрейф вы наблюдаете с внешними 16 МГц, и это кристалл, резонатор и т. Д.?, @timemage


1 ответ


Лучший ответ:

4

Я вижу три проблемы с этим подходом.

Во-первых, вы используете некалиброванное время очень низкого качества. источник. Частота внутреннего RC-генератора хороша с точностью до всего несколько процентов. Он также очень нестабилен и сильно зависит от температура. Использование внешнего керамического генератора на 16 МГц должно дать вам частоту, которая не хуже, чем 0,5% от, с типичным ошибка около 0,1%. Однако он все еще несколько нестабилен. Если вы вместо этого используете кварцевый осциллятор, то вы можете ожидать ошибку около десяти раз меньше (в пределах 100 ppm), и очень хорошая стабильность. В зависимости от ваших требований к качеству, 100 частей на миллион может быть недостаточно. достаточно хорошо, и в этом случае у вас все еще есть возможность измерить скорость дрейфа и откалибруйте ее (см. ниже).

Вторая проблема, которая уже поднималась 6v6g, заключается в том, что ваш таймер считает от нуля до 122 включительно, что дает период 123 такта таймера. Посмотрите на временные диаграммы в техпаспорт, если вам нужно убедить себя в этом. Если вы хотите период из 122, вы должны установить OCR0A на 121.

Третья проблема заключается в том, что вы сбрасываете таймер с помощью ISR. Этот может работать в этой программе, но это очень хрупкий подход, который стоит избегая. Проблема в том, что все, что делает микроконтроллер, занимает время, и ваш ISR может даже быть задержан другим прерыванием. Если таймер увеличивается после поднятия флага прерывания, но до того, как вы сбросьте его, вам не хватает одного счета. Если вы хотите иметь непрерывный шкале времени, никогда не сбрасывайте таймер. Вы можете:

  • дайте ему работать непрерывно, без помех (и выполните OCR0A += 122 в течение ISR),

  • или перезагрузите его, используя соответствующую генерацию сигнала режим (CTC или быстрый PWM).

Наконец, вот трюк, который вы можете использовать для калибровки секундомера. Вместо подсчета прерываний до тех пор, пока счетчик не достигнет 8 (что предполагает период прерывания ровно 1/8 с), увеличить a счетчик наносекунд внутри ISR и считать одну секунду, как только у вас есть один миллиард наносекунд:

const uint32_t nanoseconds_per_interrupt = 124928000;

ISR(TIMER0_COMPA_vect) {
    static uint32_t nanoseconds;
    nanoseconds += nanoseconds_per_interrupt;
    if (nanoseconds >= 1000000) {
        nanoseconds -= 1000000;
        seconds++;
        // затем обновить минуты, часы... если необходимо
    }
}

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

Значение 124928000, которое я написал выше, предполагает, что ISR вызывается каждый раз. 122×1024 цикла тактовой частоты 1 МГц. Вы можете изменить его, чтобы он соответствовал другую тактовую частоту или другую настройку OCR0A. Хитрость заключается в следующем: как только вы измерите скорость дрейфа секундомера, вы сможете настроить это число, чтобы удалить этот дрейф. Например, если вы измеряете секундомер и обнаружите, что он работает на 0,04% медленнее, тогда вы увеличиваете это число на 0,04% (что дало бы 124977971), и дрейф исчез.

ОБНОВЛЕНИЕ: в комментарии 6v6gt указал на четвертую проблему в вашем коде, что, вероятно, является самой большой проблемой. Ваша логика подсчета прерываний это:

if (intr_count == 8) {
    intr_count = 0;
} else {
    intr_count++;
}

Это счет от 0 до 8 включительно и цикл с периодом 9 прерываний. Более безопасная идиома для цикла с периодом 8 будет:

if (++intr_count >= 8) {
    intr_count = 0;
}

Хотя я бы рекомендовал считать наносекунды, если вы хотите возможность калибровать часы.

,

( 122 * 1.024 ) * 8 = 999,424 мс Возможно, следующее из OP также неверно по той же причине, по которой был неверен OCR0A: if (intr_count == 8) { . . Это вызов 7 (вызовы, выполняемые с 0) ISR, который указывает на конец полной секунды, а не вызов 8, @6v6gt

@ 6v6gt: Действительно, вы правы, и похоже, что это должно привести к огромной ошибке. Добавил это к ответу., @Edgar Bonet

Спасибо, Эдгар, за исчерпывающий ответ. :), @Nick Gammon