Настройка таймера мешает логике мигания светодиода
Этот пример — упрощенная версия того, что мне действительно нужно сделать, но я думаю, что он демонстрирует проблему (= мое недопонимание?). Мне нужно использовать таймер для подсчета микросекунд; мой код показывает, как я настраиваю таймер, и действительно, таймер выдает прямоугольный сигнал частотой 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++;
}
Светодиод по-прежнему не мигает.
@Bryan Hanson, 👍0
Обсуждение2 ответа
Вы включаете прерывание сравнения таймера, но у вас не определен обработчик прерываний для него. Вам не нужно устанавливать этот бит, если вы просто хотите выводить ШИМ.
Я думаю, вам следует либо удалить эту строку, либо определить обработчик прерываний для этого вектора.
TIMSK2 |= (1 << OCIE2A); // включить прерывание сравнения таймера
Когда происходит совпадение при сравнении, код пытается перейти к неопределенному обработчику прерываний, и это блокирует плату.
Спасибо... вы правы, если я закомментирую эту строку, то это сработает, и я понимаю вашу логику. Однако у меня там раньше был ISR, и он не работал, поэтому я удалил его, чтобы получить пример с минимальным количеством ошибок. Я приложу пример кода, чтобы вы могли увидеть, что я пробовал., @Bryan Hanson
Во-первых: установка 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
- Как сделать очень долгую функцию delay(), несколько часов
- Разница между «time_t» и «DateTime»
- Получение BPM из данного кода
- Создание таймера с использованием часов реального времени с указанием времени начала и остановки
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- Генерация стабильной частоты
- Как исправить ошибку компиляции для tone (), используя тот же таймер, что и другая функция
- Использовать timer0, не влияя на millis() и micros().
Уже есть функция micros() для подсчета микросекунд, которая работает. Вам не нужно ее изобретать заново. Она использует Timer0., @Delta_G
Спасибо, я знаю о micros(), но мне также нужна опорная форма сигнала и нужно, чтобы они были синхронизированы. Так что будет ISR от таймера, который считает микро., @Bryan Hanson
Даже для входа в ISR требуется больше 1 микросекунды. То, что вы предлагаете, не сработает. У вас есть микроконтроллер 16 МГц. Вы довольно ограничены в том, что вы можете сделать за микросекунду в коде., @Delta_G
Мне было интересно — есть ли источник, который дает накладные расходы на различные операции? Я вижу много комментариев о том, что операция X занимает Y времени, но я не знаю, откуда люди берут эти цифры. Спасибо., @Bryan Hanson
После компиляции посмотрите на полученный ассемблерный код... обратитесь к техническому описанию программирования микроконтроллера, чтобы узнать, сколько тактов занимает каждая инструкция., @jsotola
Думаю, я буду использовать micros() для общего подсчета и беспокоиться о фазе опорного сигнала другими способами. Спасибо еще раз., @Bryan Hanson
подайте выходной сигнал частотой 1 МГц на внешнюю схему счетчика для генерации прерывания, @jsotola