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

В микроконтроллере ATmega328 4 таймера. Timer0, Timer1, Timer2 и watchdog.

Функция ISR в программе для Aruino определяет/переопределяет стандартную функцию (или вектор) обработчик прерываний. Имя функции (или вектор) обработчика прерываний зарезервировано и изменить нельзя. Каждый таймер может генерировать несколько типов прерываний. Тип прерывания и его временные параметры зависят от значений служебных битов в специальных 8-ми битных регистрах управления (портах ввода/вывода) микроконтроллера. Некоторые настройки этих регистров вы увидите  в следующих примерах. Полную информацию по портам ввода вывода можно найти в datasheet микроконтроллера ATmega328.

В следующей таблице перечислены номера прерываний и имена функций обработчиков прерываний (векторы) таймеров микроконтроллера ATmega328.

Vector Описание
6 WDT_vect Таймаут сторожевого таймера
7 TIMER2_COMPA_vect Совпадение A таймера/счетчика T2
8 TIMER2_COMPB_vect Совпадение B таймера/счетчика T2
9 TIMER2_OVF_vect Переполнение таймера/счетчика T2
10 TIMER1_CAPT_vect Захват таймера/счетчика T1
11 TIMER1_COMPA_vect Совпадение A таймера/счетчика T1
12 TIMER1_COMPB_vect Совпадение B таймера/счетчика T1
13 TIMER1_OVF_vect Переполнение таймера/счетчика T1
14 TIMER0_COMPA_vect Совпадение A таймера/счетчика T0
15 TIMER0_COMPB_vect Совпадение B таймера/счетчика T0
16 TIMER0_OVF_vect Переполнение таймера/счетчика T0

Табл. 1. Прерывания по таймерам микроконтроллера ATmega328.

В следующем примере используется 8-ми битный Timer2. Обычно он используется в функции tone(). Светодиод мигает на частоте 1 Гц.

volatile int interruptCount = 0;

ISR (TIMER2_COMPA_vect) {                                    // или так ISR(_VECTOR(7))
  interruptCount++;
  if (interruptCount == 50) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    interruptCount = 0;
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  // инициализация Timer2
  TCCR2A = 0;
  TCCR2B = 0;
  OCR2A = 155;                                               // установка регистра совпадения 500 мс
  TCCR2A = (1 << WGM21);                                     // CTC режим
  TIMSK2 |= (1 << OCIE2A);                                   // включение прерываний по совпадению
  TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);          // запуск с таймера с делителем на 1024
}

void loop() {
}

Скетч 1. Задействованы прерывания по таймеру 2.

В следующем примере используется 16-ти битный Timer1. Обычно он используется библиотекой Servo.  Светодиод мигает на частоте 1 Гц.

ISR(TIMER1_COMPA_vect) {                           // или так ISR(_VECTOR(11))
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  // инициализация Timer1
  TCCR1A = 0;
  TCCR1B = 0;
  OCR1A = 7750;                                     // установка регистра совпадения 500 мс
  TCCR1B |= (1 << WGM12);                           // CTC режим
  TIMSK1 |= (1 << OCIE1A);                          // включение прерываний по совпадению
  TCCR1B |= (1 << CS10) | (1 << CS12);              // запуск с таймера с делителем на 1024
}

void loop() {
}

Скетч 2. Задействованы прерывания по таймеру 1.

В следующем примере используется 8-ми битный Timer0.Обычно он используется в функции delay() и millis().  Светодиод мигает на частоте 1 Гц.

volatile int interruptCount = 0;

ISR (TIMER0_COMPA_vect) {                                    // или так ISR(_VECTOR(14))
  interruptCount++;
  if (interruptCount == 50) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    interruptCount = 0;
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  // инициализация Timer0
  TCCR0A = 0;
  TCCR0B = 0;
  OCR0A = 155;                                               // установка регистра совпадения 500 мс
  TCCR0A = (1 << WGM01);                                     // CTC режим
  TIMSK0 |= (1 << OCIE0A);                                   // включение прерываний по совпадению
  TCCR0B = (1 << CS02) | (1 << CS00);                        // запуск с таймера с делителем на 1024
}

void loop() {
}

Скетч 3. Задействованы прерывания по таймеру 0.

В следующем примере используется сторожевой таймер WDT (Watchdog). Обычно он используется для перезагрузки или пробуждения м-к.  Светодиод мигает на частоте 1 Гц.

ISR(WDT_vect) {                                        // или так ISR(_VECTOR(6))
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  // инициализация WDT
  cli();                                               // Запрт прерываний
  asm("wdr");                                          // Сбрасываем WDT
  WDTCSR = (1 << WDCE) | (1 << WDE);                   // Разрешить изменение значения предделителя WDT
  WDTCSR = (1 << WDP0) | (1 << WDP2);                  // Время 0,5 сек.
  WDTCSR = 0 | (1 << WDIE );                           // Запрещаем RESET и разрешаем прерывания от WDT
  sei();                                               // Разрешение прерываний
}

void loop() {
}

Скетч 4. Задействованы прерывания по таймеру WDT.

Watchdog таймер (WDT) может вызывать всего одно прерывание с интервалами 16, 32, 64, 125, 250, 500, 1000, 2000, 4000, 8000 мс. Программно, в любой момент можно сбросить в 0 счётчик таймера Watchdog и тем самым предотвратить это прерывание. 

Интервал Команда
16 мс WDTCSR = (1 << WDIE );
32 мс WDTCSR = (1 << WDP0) | (1 << WDIE );
64 мс WDTCSR = (1 << WDP1) | (1 << WDIE );
125 мс WDTCSR = (1 << WDP0) | (1 << WDP1) | (1 << WDIE );
250 мс WDTCSR = (1 << WDP2) | (1 << WDIE );
0,5 с WDTCSR = (1 << WDP0) | (1 << WDP2) | (1 << WDIE );
1 с WDTCSR = (1 << WDP1) | (1 << WDP2) | (1 << WDIE );
2 с WDTCSR = (1 << WDP0) | (1 << WDP1) | (1 << WDP2) | (1 << WDIE );
4 с WDTCSR = (1 << WDP3) | (1 << WDIE );
8 с WDTCSR = (1 << WDP0) | (1 << WDP3) | (1 << WDIE );

Табл. . Возможные интервалы времени между срабатываниями прерываний по таймеру WDT.

Например, в следующей программе прерывания возникают каждые 16 мс.

ISR(WDT_vect) {                                        // или так ISR(_VECTOR(6))
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  // инициализация WDT
  cli();                                               // Запрт прерываний
  asm("wdr");                                          // Сбрасываем WDT
  WDTCSR = (1 << WDCE) | (1 << WDE);                   // Разрешить изменение значения предделителя WDT
  WDTCSR = 0 | (1 << WDIE );                           // Запрещаем RESET и разрешаем прерывания от WDT (время 16 мсек.)
  sei();                                               // Разрешение прерываний
}

void loop() {
}

Скетч 5. Задействованы прерывания по таймеру WDT.

Прерывания вызванные WDT таймером с периодичностью 16 мс

Рис. 1. Осциллограмма снятая на ножке D13 платы Ардуино во время работы программы скетч 5. 

В микроконтроллере ATmega8 нет прерываний по WDT. В этом м-к WDT может выполнить только аппаратный RESET.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  delay(500);
  digitalWrite(LED_BUILTIN, HIGH);
  // инициализация WDT
  cli();                                               // Запрт прерываний
  asm("wdr");                                          // Сбрасываем WDT
  WDTCR |= (1 << WDCE) | (1 << WDE);                   // Разрешить изменение значения предделителя и включить WDT
  WDTCR |= (1 << WDP0) | (1 << WDP2);                  // Время 0,5 сек. и разрешение RESET по WDT
  sei();                                               // Разрешение прерываний
}

void loop() {
}

Скетч 6. Задействован RESET по таймеру WDT.