ПРЕРЫВАНИЯ ПО ТАЙМЕРУ

Прерывание позволяет “отвлекаться” от выполнения программы, выполнить необходимый блок кода, а затем вернуться обратно в программу. Может быть использовано для опроса датчиков с высокой частотой, генерации сигналов, точного тайминга для переключений и проч. У микроконтроллера есть пара десятков прерываний по различным событиям, одно из них – прерывание по таймеру. Таймер является аппаратной единицей микроконтроллера, и его счёт идёт параллельно выполнению программы, т.е. не тратит процессорное время. Также таймер имеет аппаратные выходы, состоянием которых он может управлять самостоятельно без участия вычислительного ядра (именно так и генерируется ШИМ).

Таймер 0 используется для работы системных функций времени delay(), delayMicroseconds() и millis() и micros(), поэтому перенастройка таймера 0 под свои нужды приведёт к некорректной работе функций времени, но при желании можно их скорректировать следующим образом:

#define delay(x * div)
#define delayMicroseconds(x * div)
#define millis() (millis() / div)
#define micros() (micros() / div)
// где div - делитель (или множитель), подбирать из степеней двойки

Таймер 1 используется в некоторых библиотеках, например Servo. При перенастройке таймера 1 под свои нужды Servo перестанет работать.

Таймер 2 используется для генерации звука стандартной функцией tone(), причём перенастройка таймера не “сломает” tone – вызов функции перенастроит таймер под нужды функции.

Различные библиотеки могут использовать таймеры для своих целей, если вам нужны прерывания по таймерам совместно с работой других библиотек – смотрите, не используют ли они эти же таймеры!

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

БИБЛИОТЕКА GYVERTIMERS

Настройка и контроль прерываний по аппаратным таймерам AVR:

  • Поддерживаются все три таймера на ATmega328 и шесть таймеров на ATmega2560;
  • Настройка периода (мкс) и частоты (Гц) прерываний:
    • 8 бит таймеры: 31 Гц – 1 МГц (32 258 мкс – 1 мкс);
    • 16 бит таймеры: 0.11 Гц – 1 МГц (9 000 000 мкс – 1 мкс);
  • Автоматическая корректировка настройки периода от частоты тактирования (F_CPU);
  • Функция возвращает точный установившийся период/частоту для отладки (частота ограничена разрешением таймера);
  • Поддержка многоканального режима работы: один таймер вызывает 2 (ATmega328) или 3 (ATmega2560, таймеры 1, 3, 4, 5) прерывания с настраиваемым сдвигом по фазе 0-360 градусов;
  • Настраиваемое действие аппаратного вывода таймера по прерыванию: высокий сигнал, низкий сигнал, переключение. Позволяет генерировать меандр (одно- и двухтактный);
  • Контроль работы таймера: старт/стоп/пауза/продолжить/инициализация;
  • Разработано by Egor ‘Nich1con’ Zaharov специально для AlexGyver

Поддерживаемые платформы: платы на ATmega328p (Arduino Nano/UNO/Pro Mini), ATmega2560 (Arduino Mega).

ДОКУМЕНТАЦИЯ


Ещё немного теории


Таймер-счётчик является аппаратной единицей МК и работает параллельно вычислительному ядру, т.е. никакие задержки и длинные вычисления не влияют на его работу. На ATmega328p (Arduino Nano/UNO/Pro Mini) у нас есть три таймера, а на ATmega2560 (Arduino Mega) – целых шесть. У таймера есть несколько каналов прерываний и аппаратные выводы, позволяющие генерировать ШИМ сигнал.

Библиотека GyverTimers позволяет генерировать прерывания по таймеру с заданной частотой на выбранном канале таймера или на нескольких каналах сразу со сдвигом по фазе: прерывания будут происходить с одинаковой частотой, но “сдвинуты” друг относительно друга. Также можно задать действие для вывода таймера: включить/выключить/переключить: таймер будет управлять выбранным пином независимо от вычислительного ядра МК, таким образом можно генерировать меандр (квадратный сигнал), причём как однотактный, так и двух- и трёхтактный с настраиваемым сдвигом по фазе.

Для Arduino Nano/UNO/Pro Mini доступно три таймера: Timer0, Timer1, Timer2. Для Arduino MEGA – пять: Timer0, Timer1, Timer2, Timer3, Timer4, Timer5. В библиотеке таймеры описаны как объекты, обращение происходит как обычно через точку. Например Timer1.stop();

Таблица таймеров ATmega328p


Таймер Разрядность Частоты Периоды Выходы Пин Arduino Пин МК
Timer0 8 бит 31 Гц – 1 МГц 32 258 – 1 мкс CHANNEL_A D6 PD6
CHANNEL_B D5 PD5
Timer1 16 бит 0.11 Гц – 1 МГц 9 000 000 – 1 мкс CHANNEL_A D9 PB1
CHANNEL_B D10 PB2
Timer2 8 бит 31 Гц – 1 МГц 32 258 – 1 мкс CHANNEL_A D11 PB3
CHANNEL_B D3 PD3

Таблица таймеров ATmega2560


Таймер Разрядность Частоты Периоды Выходы Пин Arduino Пин МК
Timer0 8 бит 31 Гц – 1 МГц 32 258 – 1 мкс CHANNEL_A 13 PB7
CHANNEL_B 4 PG5
Timer1 16 бит 0.11 Гц – 1 МГц 9 000 000 – 1 мкс CHANNEL_A 11 PB5
CHANNEL_B 12 PB6
CHANNEL_C 13 PB7
Timer2 8 бит 31 Гц – 1 МГц 32 258 – 1 мкс CHANNEL_A 10 PB4
CHANNEL_B 9 PH6
Timer3 16 бит 0.11 Гц – 1 МГц 9 000 000 – 1 мкс CHANNEL_A 5 PE3
CHANNEL_B 2 PE4
CHANNEL_C 3 PE5
Timer4 16 бит 0.11 Гц – 1 МГц 9 000 000 – 1 мкс CHANNEL_A 6 PH3
CHANNEL_B 7 PH4
CHANNEL_C 8 PH5
Timer5 16 бит 0.11 Гц – 1 МГц 9 000 000 – 1 мкс CHANNEL_A 46 PL3
CHANNEL_B 45 PL4
CHANNEL_C 44 PL5

Настройка частоты/периода


  • setPeriod(период); – установка периода в микросекундах и запуск таймера. Возвращает реальный период в мкс (точность ограничена разрешением таймера).
  • setFrequency(частота); – установка частоты в Герцах и запуск таймера. Возвращает реальную частоту в Гц (точность ограничена разрешением таймера).
  • setFrequencyFloat(частота float); – установка частоты в Герцах и запуск таймера, разрешены десятичные дроби. Возвращает реальную частоту (точность ограничена разрешением таймера).

Контроль работы таймера


  • pause(); – приостановить счёт таймера, не сбрасывая счётчик
  • resume(); – продолжить счёт после паузы
  • stop(); – остановить счёт и сбросить счётчик
  • restart(); – перезапустить таймер (сбросить счётчик)

Прерывания


  • enableISR(канал, фаза); – запустить прерывания на выбранном канале с выбранным сдвигом фазы. Если ничего не указывать, будет выбран канал A и фаза 0
    • Канал – CHANNEL_A, CHANNEL_B или CHANNEL_С (см. таблицу выше!)
    • Фаза – численное значение 0-359
  • disableISR(канал); – отключить прерывания на выбранном канале. Если ничего не указывать, будет выбран канал A

Библиотека даёт прямой доступ к прерыванию без “Ардуиновских” attachInterrupt, что позволяет сократить время вызова функции-обработчика прерывания. Прерывание с настроенной частотой будет обрабатываться в блоке вида ISR(канал) {}, пример:

ISR(TIMER1_A) {
// ваш код
}

ISR(TIMER1_B) {
// ваш код
}

ISR(TIMER2_B) {
// ваш код
}

ISR(TIMER0_A) {
// ваш код
}

Аппаратные выходы


  • outputEnable(канал, режим); – включить управление аппаратным выходом таймера
    • Канал: CHANNEL_A или CHANNEL_B (+ CHANNEL_C у ATmega2560, см. таблицу таймеров).
    • Режим: TOGGLE_PIN, CLEAR_PIN, SET_PIN (переключить/выключить/включить пин по срабатыванию таймера)
  • outputDisable(канал); – отключить выход таймера
    • Канал: CHANNEL_A или CHANNEL_B (+ CHANNEL_C у Mega2560, см. таблицу таймеров)
  • outputState(канал, состояние); – вручную сменить состояние канала. Например для установки каналов в разное состояние для запуска генерации двухтактного меандра.
    • Канал: CHANNEL_A или CHANNEL_B (+ CHANNEL_C у ATmega2560, см. таблицу таймеров).
    • Состояние: HIGH или LOW

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

Настройка по умолчанию


При помощи метода setDefault() можно сбросить настройки таймера на “Ардуиновские” умолчания: частоту и режим работы.

Таймер описан как объект: Timer0, Timer1… Timer5. Обращение через точку, например Timer1.stop() . Методы:

setPeriod(период) // установка периода в микросекундах и запуск таймера. Возвращает реальный период (точность ограничена разрешением таймера).
setFrequency(частота) // установка частоты в Герцах и запуск таймера. Возвращает реальную частоту (точность ограничена разрешением таймера).
setFrequencyFloat(частота float) // установка частоты в Герцах и запуск таймера, разрешены десятичные дроби. Возвращает реальную частоту (точность ограничена разрешением таймера).
enableISR(источник, фаза) // включить прерывания, канал прерываний CHANNEL_A или CHANNEL_B (+ CHANNEL_C у Mega2560), сдвиг фазы 0-359 (без указания параметров будет включен канал А и сдвиг фазы 0).
disableISR(источник) // выключить прерывания, канал CHANNEL_A или CHANNEL_B. Счёт таймера не останавливается (без указания параметров будет выключен канал А).
pause() // приостановить счёт таймера, не сбрасывая счётчик
resume() // продолжить счёт после паузы
stop() // остановить счёт и сбросить счётчик
restart() // перезапустить таймер (сбросить счётчик)
setDefault() // установить параметры таймера по умолчанию ("Ардуино-умолчания")
outputEnable(канал, режим) // канал: включить выход таймера CHANNEL_A или CHANNEL_B (+ CHANNEL_C у Mega2560). Режим: TOGGLE_PIN, CLEAR_PIN, SET_PIN (переключить/выключить/включить пин по прерыванию)
outputDisable(канал) // отключить выход таймера CHANNEL_A или CHANNEL_B (+ CHANNEL_C у Mega2560, см. такблицу таймеров)
outputState(канал, состояние) // сменить состояние канала: HIGH / LOW

ПРИМЕРЫ


// Демонстрация всех функций библиотеки

#include "GyverTimers.h"

void setup() {
  // Перенастроить таймер и задать ему период или частоту
  // Все функции возвращают реальный период / частоту, которые могут отличаться от введенных
  Timer2.setPeriod(1000);           // Задать конкретный период 1000 мкс (~ 1000 гц), вернет реальный период в мкс
  Timer0.setFrequency(250);         // Задать частоту прерываний таймера в Гц, вернет реальную частоту в герцах
  Timer1.setFrequencyFloat(50.20);  // Задать частоту более точно, в дробных числах, актуально для низких частот и таймера 1
  // С этого момента таймер уже перенастроен и гоняет с выьранной частотой / периодом

  // Подключить прерывание таймера, с этого момента прерывания начнут вызываться
  Timer0.enableISR();               // Подключить стандартное прерывание, канал А, без сдига фаз
  Timer2.enableISR(CHANNEL_B, 180); // Подключить прерывание таймера 2, канал B, начальная фаза - 180 градусов
  Timer1.enableISR(CHANNEL_A, 60);  // Подключить прерывание канала А, задать фазу для канала А доступно только для таймера 1!
  Timer1.enableISR(CHANNEL_B, 120); // Подключить второе прерывание таймера 1, и задать фазовый сдвиг для этого потока
  // Прерывание уже начнет вызываться

  // Если вдруг прерывание нужно отключить, не останавливая таймер
  Timer1.disableISR(CHANNEL_B);
  // С этого момента прерывание B больше не будет вызываться

  // Если нужно приостановить таймер ПОЛНОСТЬЮ, аппаратно
  Timer2.pause();
  // С этого момента таймер стоит на месте, содержимое счетчика остается нетронутым

  // Теперь таймер можно вернуть в строй
  Timer2.resume();
  // Таймер продолжил считать с того же места

  // Если нужно полностью остановить таймер и сбросить содержимое счетчика
  Timer1.stop();
  // Таймер стоит, счетчик сброшен

  // Возвращаем таймер в строй
  Timer1.restart();
  // Таймер перезапущен, начал считать с начала

  // Если нужно вернуть стандартные Arduino - настройки таймера
  Timer0.setDefault();
  // Теперь таймер работает в станлартном режиме
}

// векторы прерываний
ISR(TIMER1_A) {

}

ISR(TIMER1_B) {

}

ISR(TIMER2_B) {

}

ISR(TIMER0_A) {

}

void loop() {

}
// Пример простой генерации прерываний аппаратным таймером
#include "GyverTimers.h"

void setup() {
  Serial.begin(9600);
  Timer1.setFrequency(3);               // Высокоточный таймер 1 для первого прерывания, частота - 3 Герца
  //Timer1.setPeriod(333333);           // то же самое! Частота 3 Гц это период 333 333 микросекунд
  //Timer1.setFrequencyFloat(4.22);     // Если нужна дробная частота в Гц  
  Timer1.enableISR();                   // Запускаем прерывание (по умолч. канал А)

  // запустим второй таймер
  Timer2.setPeriod(1000000);     // Устанавливаем период таймера 1000000 мкс -> 1 гц
  Timer2.enableISR(CHANNEL_A);   // Или просто .enableISR(), запускаем прерывание на канале А таймера 2
  pinMode(13, OUTPUT);           // будем мигать
}

void loop() {}

// Прерывание А таймера 1
ISR(TIMER1_A) {  // пишем  в сериал
  Serial.println("timer1");
}

// Прерывание А таймера 2
ISR(TIMER2_A) {   // генерируем меандр 1 кгц, мигаем
  digitalWrite(13, !digitalRead(13));
  //Serial.println("timer2");
}
// Пример генерации двухканальных прерываний на таймере с РАВНЫМ периодом, но сдвинутых по фазе
// два потока прерываний с сдвигом 180 градусов (полная инверсия)

#include "GyverTimers.h"

void setup() {
  Serial.begin(9600);

  Serial.print("Real timer frequency is : ");        // Выведем реальную частоту, реальная может отличаться от заданой (ограничено разрешением таймера)
  Serial.println(Timer1.setFrequencyFloat(2.50));    // Частота прерываний - 2.5 гц , используй .setFrequency(...) для целых чисел
  delay(1000);
  Timer1.enableISR(CHANNEL_A, 0);     // Первый канал - А, начальная фаза - 0 градусов
  Timer1.enableISR(CHANNEL_B, 180);   // Второй канал - B, начальная фаза - 180 градусов
}

void loop() {}

// два прерывания на одном таймере
ISR(TIMER1_A) {
  Serial.println(" Channel A interrupt !");   // Прерывание А
}

ISR(TIMER1_B) {
  Serial.println(" Channel B interrupt !");   // Прерывание B
}
//Пример генерации меандра на таймере 2 , канале B (D3 на Arduino UNO) 
#include "GyverTimers.h"

void setup() {
  pinMode(3, OUTPUT);                           // настроить пин как выход

  // из-за особенности генерации меандра таймером
  // частоту нужно указывать в два раза больше нужной!
  Timer2.setFrequency(500 * 2);                 // настроить частоту таймера в Гц
  Timer2.outputEnable(CHANNEL_B, TOGGLE_PIN);   // в момент срабатывания таймера пин будет переключаться
}

void loop() {
}
// Пример генерации двухтактного меандра на таймере 2 (пины D3 и D11)
#include 

void setup() {
  pinMode(3, OUTPUT);                           // настроить пин как выход
  pinMode(11, OUTPUT);                          // настроить пин как выход

  // из-за особенности генерации меандра таймером
  // частоту нужно указывать в два раза больше нужной!
  Timer2.setFrequency(15000 * 2);               // настроить частоту в Гц и запустить таймер. Меандр на 15 кГц
  Timer2.outputEnable(CHANNEL_A, TOGGLE_PIN);   // в момент срабатывания таймера пин будет переключаться
  Timer2.outputEnable(CHANNEL_B, TOGGLE_PIN);   // в момент срабатывания таймера пин будет переключаться
  Timer2.outputState(CHANNEL_A, HIGH);          // задаём начальное состояние пина 11
  Timer2.outputState(CHANNEL_B, LOW);           // задаём начальное состояние пина 3
}

void loop() {
}