Включение прерывания Timer1 CompareA мгновенно вызывает прерывание?

Я изучаю прерывания Arduino и не могу объяснить поведение минималистичного проекта, управляемого прерываниями. Проект следующий: Arduino UNO подключается к двум светодиодам на портах 9 и 10 и кнопке на порту 2. Кнопка прослушивается на предмет прерываний RISING, которые, как ожидается, вызовут следующую 3-секундную последовательность: LED1 HIGH (одна секунда); оба светодиода HIGH (одна секунда); LED2 HIGH (одна секунда); во время которого нажатие кнопки не должно иметь никакого эффекта.

#define led1Pin 10
#define led2Pin 9
#define buttonPin 2


void setup() {

  cli();//остановим прерывания

  pinMode(led1Pin, OUTPUT);
  pinMode(led2Pin, OUTPUT);
  pinMode(buttonPin, INPUT);
  digitalWrite(led1Pin, LOW);
  digitalWrite(led2Pin, LOW);
  attachInterrupt(digitalPinToInterrupt(buttonPin), f, RISING);

  TCCR0A = 0;// устанавливаем регистр TCCR2A в 0
  TCCR0B = 0;// устанавливаем регистр TCCR2B в 0 (устанавливает значения прескалера timer0 на «отключить таймер»)
  TCCR2A = 0;// устанавливаем регистр TCCR2A в 0
  TCCR2B = 0;// устанавливаем регистр TCCR2B в 0 (устанавливает значения прескалера timer2 на «отключить таймер»)

  //устанавливаем прерывание таймера 1 на 1 Гц
  TCCR1A = 0;// устанавливаем регистр TCCR1A в 0
  TCCR1B = 0;// устанавливаем регистр TCCR1B в 0

  // устанавливаем регистр сравнения совпадений с шагом 1 Гц
  OCR1A = 15624;// = (16*10^6) / (1*1024) - 1
  TCCR1B |= (1 << WGM12);   // включаем режим CTC 1 (сравните с OCR1A)
  TCCR1B |= (1 << CS12) | (1 << CS10);// CS12|CS10 => Прескалер 1024
  TIMSK1 = 0;//отключаем прерывание сравнения таймера

  sei();
}

volatile int index=0;

volatile byte previousState=LOW;
volatile byte state = LOW;

void loop() {
  if (state && !previousState) {
    digitalWrite(led1Pin, HIGH); index=1;
    TCNT1H = 0;
    TCNT1L = 0; // инициализируем значение счетчика равным 0
    TIMSK1 |= (1 << OCIE1A); // включаем прерывание сравнения таймера
    previousState = HIGH;
  }
}

void f() {
  state = HIGH;
}

ISR(TIMER1_COMPA_vect){ // прерывание таймера 1 1 Гц
  switch(index) {
  case 1:
    digitalWrite(led2Pin, HIGH); index=2;
    break;
  case 2:
    digitalWrite(led1Pin, LOW); index=3;
    break;
  case 3:
    digitalWrite(led2Pin, LOW); index=0;
    state=LOW; previousState=LOW;
    TIMSK1 = 0;//отключаем прерывание сравнения таймера
    break;
  default:
    break;
  }

}

Код адаптирован из https://www.instructables.com/id/Arduino-Timer-Interrupts/

При нажатии кнопки state устанавливается в значение HIGH, что предполагает, что previousState и state были >LOW, должен заставить цикл выполнить набор действий один раз, поскольку он установит previousState HIGH.

Эти действия следующие: установите для led1Pin значение HIGH, установите для TCNT1H и TCNT1L значение 0, что следует установить счетчик Timer1 на 0, а затем установить для TIMSK1 значение 0b00000010, то есть 1 << OCIE1A, чтобы принять прерывание, когда оно будет запущено, предположительно через одну секунду после инструкции, которая устанавливает счетчик в 0, поскольку таймер запускает прерывания с частотой 1 Гц.

Но хотя ожидаемое поведение — светодиод 1 горит на одну секунду, оба светодиода горят на одну секунду, затем светодиод 2 горит на одну секунду, наблюдаемое поведение, согласно моим тестам, следующее:

  • Первое нажатие кнопки после сброса Arduino: светодиод 1 и светодиод 2 горят на одну секунду, светодиод 2 горит на одну секунду.

  • Нажатия кнопок игнорируются до тех пор, пока не погаснет светодиод 2.

  • Последующие нажатия кнопок: либо правильное поведение (светится светодиод 1, горят оба светодиода, горят светодиод 2); или режим «первое нажатие» (горят оба светодиода, горит светодиод 2). В обоих случаях цикл иногда мгновенно перезапускается ([необязательно: горит светодиод 1;] горят оба светодиода; горит светодиод 2; горит светодиод 1; горят оба светодиода; горит светодиод 2), при этом каждое состояние занимает одну секунду.

Недетерминированный характер проблемы позволяет мне думать, что я неправильно справляюсь с прерываниями. Проект ведет себя так, как будто включение прерываний сравнения таймеров мгновенно запускает одно из них, несмотря на то, что перед этим соответствующие регистры были установлены в 0. Мне не удалось найти информацию по этому поводу, которая могла бы объяснить проблему.

Существует ли асинхронное поведение при установке регистров TCNT1H и TCNT1L в 0? Можно ли как-то буферизовать прерывание сравнения? И почему цикл иногда выполняется дважды?

, 👍0


1 ответ


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

1

Проект ведет себя так, как если бы прерывания сравнения по таймеру включались мгновенно. вызвал один

Это действительно может случиться, и, скорее всего, именно это вы и испытываете.

Объяснение: прерывание срабатывает, когда одновременно установлены три бита:

  • «флаг прерывания», сигнализирующий об обнаружении определенного события.
  • бит разрешения прерывания, связанный с этим конкретным прерыванием
  • бит «глобального разрешения прерываний», который устанавливается с помощью sei() инструкция

В вашем случае соответствующий флаг прерывания — OCF1A (Timer/Counter1, Выходное сравнение с флагом совпадения), в регистре TIFR1 (Таймер/Счетчик1 Регистр флага прерывания). Этот флаг поднимается при «сравнении таймера». событие, т. е. когда значение таймера достигает значения, хранящегося в OCR1A. Если прерывание срабатывает, флаг автоматически очищается, когда процессор начинает выполнение соответствующего ISR. Альтернативно, флаг может быть очищается вручную путем перезаписи логической 1 (да, это своего рода назад). Стоит отметить, что флаг записывает события сравнения. независимо от того, разрешено ли прерывание.

Обычно вы ожидаете, что флаг прерывания будет последним из этих трех. биты, которые необходимо установить, чтобы прерывание запускалось событием сравнения. Но это не обязательно так. В вашей программе, вероятно, Флаг прерывания поднимается через одну секунду после запуска. Глобальное прерывание Enable устанавливается вашим sei() в конце setup(). Таким образом, при этом точка, как только вы устанавливаете бит разрешения прерывания, прерывание срабатывает немедленно.

Стандартное решение этой проблемы — сбросить флаг прерывания. прямо перед установкой бита разрешения прерывания:

TIFR1  |= _BV(OCF1A);   // очищаем флаг сравнения таймеров
TIMSK1 |= _BV(OCIE1A);  // включаем прерывание сравнения таймера
,