Прерывания по таймерам в программах для микроконтроллеров в некоторых случаях могут быть очень полезны. Для примера, помигаем светодиодом с помощью прерываний по таймерам.
В микроконтроллере 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.
Рис. 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.