Настройка таймера мешает логике мигания светодиода

Этот пример — упрощенная версия того, что мне действительно нужно сделать, но я думаю, что он демонстрирует проблему (= мое недопонимание?). Мне нужно использовать таймер для подсчета микросекунд; мой код показывает, как я настраиваю таймер, и действительно, таймер выдает прямоугольный сигнал частотой 1 МГц на выводе 11, если setup_timer2() активен.

Однако, когда setup_timer2() активен, светодиод не мигает. Если я закомментирую setup_timer2(), светодиод мигает, как и ожидалось. В моем более сложном скетче, похоже, что все, что я помещаю в цикл, блокируется при активации таймера.

Похоже, что активация таймера мешает логике мигания, и я не могу понять, почему. Относительно новичок в этом деле, так что вполне возможно, что-то простое.


// Логика управления встроенным светодиодом, полученная из
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158

const int on_board_LED_pin = 13;
const int on_board_LED_interval = 3000;
const int on_board_LED_duration = 500;
byte on_board_LED_state = LOW;
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Starting Demo");
  pinMode(on_board_LED_pin, OUTPUT);
  pinMode(11, OUTPUT); // прямоугольный сигнал частотой 1 МГц на этом выводе, если таймер активирован
  setup_timer2();
}

void loop() {
  currentMillis = millis();
  toggle_on_board_LED();
}

void toggle_on_board_LED() {
  // изменить состояние по мере необходимости
  if (on_board_LED_state == LOW) {
    if (currentMillis - previousMillis >= on_board_LED_interval) {
      on_board_LED_state = HIGH;
      previousMillis += on_board_LED_interval;
    }
  }
  if (on_board_LED_state == HIGH) {
    if (currentMillis - previousMillis >= on_board_LED_duration) {
      on_board_LED_state = LOW;
      previousMillis += on_board_LED_duration;
    }
  }
  // реализовать состояние
  digitalWrite(on_board_LED_pin, on_board_LED_state);
}

void setup_timer2() {
  cli();       //остановить прерывания глобально
  TCCR2A = 0;  // установить весь регистр TCCR0A в 0
  TCCR2B = 0;  // то же самое для TCCR0B
  TCNT2 = 0;   //инициализируем значение счетчика до 0
  OCR2A = 0;
  TCCR2A |= (1 << WGM21);   // Режим CTC
  TCCR2A |= (1 << COM2A0);  // переключить вывод OC2A при совпадении сравнения (= вывод 11)
  TCCR2B |= (1 << CS21);    // предварительное масштабирование на 8
  TIMSK2 |= (1 << OCIE2A);  // включить прерывание сравнения таймера
  sei();                    // разрешить прерывания глобально
}

РЕДАКТИРОВАТЬ

Если я добавлю следующее объявление и ISR:

volatile unsigned long u_sec;

ISR(TIMER2_COMPA_vect) {
  volatile unsigned long u_sec;
  u_sec++;
}

Светодиод по-прежнему не мигает.

, 👍0

Обсуждение

Уже есть функция micros() для подсчета микросекунд, которая работает. Вам не нужно ее изобретать заново. Она использует Timer0., @Delta_G

Спасибо, я знаю о micros(), но мне также нужна опорная форма сигнала и нужно, чтобы они были синхронизированы. Так что будет ISR от таймера, который считает микро., @Bryan Hanson

Даже для входа в ISR требуется больше 1 микросекунды. То, что вы предлагаете, не сработает. У вас есть микроконтроллер 16 МГц. Вы довольно ограничены в том, что вы можете сделать за микросекунду в коде., @Delta_G

Мне было интересно — есть ли источник, который дает накладные расходы на различные операции? Я вижу много комментариев о том, что операция X занимает Y времени, но я не знаю, откуда люди берут эти цифры. Спасибо., @Bryan Hanson

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

Думаю, я буду использовать micros() для общего подсчета и беспокоиться о фазе опорного сигнала другими способами. Спасибо еще раз., @Bryan Hanson

подайте выходной сигнал частотой 1 МГц на внешнюю схему счетчика для генерации прерывания, @jsotola


2 ответа


1

Вы включаете прерывание сравнения таймера, но у вас не определен обработчик прерываний для него. Вам не нужно устанавливать этот бит, если вы просто хотите выводить ШИМ.

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

TIMSK2 |= (1 << OCIE2A);  // включить прерывание сравнения таймера

Когда происходит совпадение при сравнении, код пытается перейти к неопределенному обработчику прерываний, и это блокирует плату.

,

Спасибо... вы правы, если я закомментирую эту строку, то это сработает, и я понимаю вашу логику. Однако у меня там раньше был ISR, и он не работал, поэтому я удалил его, чтобы получить пример с минимальным количеством ошибок. Я приложу пример кода, чтобы вы могли увидеть, что я пробовал., @Bryan Hanson


1

Во-первых: установка OCR2A на 0 неверна: если вы хотите, чтобы прерывание срабатывать каждую микросекунду (16 циклов ЦП, 2 цикла таймера), вы должны установить OCR2A до 1.

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

ISR(TIMER2_COMPA_vect){}

Если вы попробуете это, вы можете заметить, что это все еще слишком много. Разборка ISR выдает следующее:

__vector_7:
    push r1
    push r0
    in   r0, SREG
    push r0
    clr  r1
    pop  r0
    out  SREG, r0
    pop  r0
    pop  r1
    reti

Все это занимает 19 циклов ЦП. Добавьте 4 цикла, которые ЦП тратит на вход прерывание и 3 цикла вектора прерывания (a jmp инструкция) и вы получаете... слишком много дел за микросекунду (что всего 16 циклов). В этот момент вы можете задаться вопросом: зачем так много инструкций для пустой функции? Оказывается, это все шаблон. Все вверх clr r1 — это пролог прерывания, предназначенный для сохранения контекста ЦП и очистите регистр r1 (который требуется для программирования gcc AVR) модель). Остальное — эпилог, призванный восстановить сохраненный контекст и вернуться из прерывания.

Мы можем сделать ISR еще короче следующим образом:

EMPTY_INTERRUPT(TIMER2_COMPA_vect)

Это компилируется в одну инструкцию reti, которая выполняется за 3 процессора циклов. Теперь вся обработка прерывания занимает всего 11 циклов... и программа работает! С некоторыми оговорками:

  • обработка этих прерываний съедает почти 69% мощности ЦП

  • поскольку выполнение других ISR занимает более одной микросекунды, наш пустая ISR пропустит много прерываний (не то чтобы это имело значение...)

Вкратце: вам, вероятно, следует избегать прерывания работы процессора каждый раз, микросекунда.

,

Спасибо, Эдгар, это действительно полезно!, @Bryan Hanson