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

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

Таймер 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() {
}