Таймеры Arduino UNO — Часть 2. Работа с прерываниями таймеров
Теория
Что такое прерывание и зачем оно нужно
Прерывание — это специальный механизм микроконтроллера, позволяющий при возникновении определённого события мгновенно приостановить выполнение основного кода и выполнить специальную функцию — обработчик прерывания (ISR, Interrupt Service Routine).
Прерывания используются для задач, которые требуют высокой точности по времени, например, измерение интервалов, генерация сигналов, опрос датчиков, приём данных по UART.
Настройка аппаратного таймера на прерывания
В микроконтроллере ATmega328P можно настроить таймер так, чтобы он вызывал прерывание через заданные интервалы времени. Для этого нужно:
- Выбрать режим работы таймера (например, CTC — сброс по совпадению).
- Установить значение сравнения в регистре
OCRnA(Output Compare Register). - Разрешить прерывание в регистре
TIMSKn. - Разрешить глобальные прерывания командой
sei().
Регистр TIMSKx и векторы прерываний
TIMSK0,TIMSK1,TIMSK2— регистры разрешения прерываний для соответствующих таймеров.- Вектор прерывания — это адрес функции, которую вызовет контроллер при срабатывании события. Например, для таймера 1 в режиме CTC это
TIMER1_COMPA_vect.
Практика
Настройка Timer1 для вызова прерывания каждые 1 мс
#include <avr/interrupt.h>
volatile unsigned long counter = 0; // Счётчик миллисекунд
void setup() {
pinMode(13, OUTPUT);
// Настройка Timer1
noInterrupts(); // Отключаем прерывания
TCCR1A = 0; // Регистр управления A
TCCR1B = 0; // Регистр управления B
TCNT1 = 0; // Сброс счётчика
OCR1A = 15999; // 16 МГц / 1000 Гц / 1 = 16000 - 1
TCCR1B |= (1 << WGM12); // Режим CTC
TCCR1B |= (1 << CS10); // Делитель 1 (prescaler 1)
TIMSK1 |= (1 << OCIE1A); // Разрешаем прерывание по совпадению
interrupts(); // Включаем прерывания
}
ISR(TIMER1_COMPA_vect) {
counter++;
digitalWrite(13, !digitalRead(13)); // Мигаем светодиодом
}
void loop() {
// Основной код может выполняться параллельно
}
Мерцание светодиода в обработчике прерывания
В приведённом примере встроенный светодиод на пине 13 мигает каждые 1 мс. Однако на практике так часто мигать не имеет смысла — человеческий глаз не заметит.
Проект: Точный секундомер с использованием прерываний
Создадим секундомер, который считает время с точностью до миллисекунды, используя прерывания Timer1.
#include <avr/interrupt.h>
volatile unsigned long millisCounter = 0;
void setup() {
Serial.begin(9600);
// Настройка Timer1
noInterrupts();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 249; // 16 МГц / 64 / 1000 Гц = 250 - 1
TCCR1B |= (1 << WGM12); // Режим CTC
TCCR1B |= (1 << CS11) | (1 << CS10); // Делитель 64
TIMSK1 |= (1 << OCIE1A); // Разрешаем прерывание
interrupts();
}
ISR(TIMER1_COMPA_vect) {
millisCounter++;
}
void loop() {
static unsigned long lastPrint = 0;
if (millisCounter - lastPrint >= 1000) { // Каждую секунду
lastPrint = millisCounter;
Serial.print("Секунд прошло: ");
Serial.println(millisCounter / 1000);
}
}
Теперь секундомер работает независимо от основного кода, благодаря прерываниям, а точность не зависит от функций delay().




