Прерывания по таймерам Arduino. GyverTimers v1.9

ОБНОВЛЕНИЯ


  • v1.7 — поправлена документация
  • v1.8 — исправлен баг с макс периодом
  • v1.9 — исправлен баг с возвращаемым 2х периодом

ТЕОРИЯ


На сайте есть отдельный более подробный урок по прерываниям таймера. Прерывание позволяет «отвлекаться» от выполнения программы, выполнить необходимый блок кода, а затем вернуться обратно в программу. Может быть использовано для опроса датчиков с высокой частотой, генерации сигналов, точного тайминга для переключений и проч. У микроконтроллера есть пара десятков прерываний по различным событиям, одно из них — прерывание по таймеру. Таймер является аппаратной единицей микроконтроллера, и его счёт идёт параллельно выполнению программы, т.е. не тратит процессорное время. Также таймер имеет аппаратные выходы, состоянием которых он может управлять самостоятельно без участия вычислительного ядра (именно так и генерируется ШИМ). Таймер 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 v1.9

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

  • Поддерживаются все три таймера на ATmega328 и шесть таймеров на ATmega2560;
  • Настройка периода (мкс) и частоты (Гц) прерываний:
    • 8 бит таймеры: 61 Гц – 1 МГц (16 384 мкс – 1 мкс);
    • 16 бит таймеры: 0.24 Гц – 1 МГц (4 200 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).

УСТАНОВКА


  • Библиотеку можно найти и установить через менеджер библиотек по названию GyverTimers в:
    • Arduino IDE (Инструменты/Управлять библиотеками)
    • Arduino IDE v2 (вкладка «Library Manager»)
    • PlatformIO (PIO Home, вкладка «Libraries»)
  • Про ручную установку читай здесь

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


Супер важный момент


При использовании .enableISR() обязательно нужно объявить обработчик прерывания ISR(TIMERn_A) {} соответствующего таймера и канала, иначе другие прерывания не будут работать!

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


Таймер-счётчик является аппаратной единицей МК и работает параллельно вычислительному ядру, т.е. никакие задержки и длинные вычисления не влияют на его работу. На 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 бит 61 Гц.. 1 МГц 16 384.. 1 мкс CHANNEL_A D6 PD6
CHANNEL_B D5 PD5
Timer1 16 бит 0.24 Гц.. 1 МГц 4 200 000.. 1 мкс CHANNEL_A D9 PB1
CHANNEL_B D10 PB2
Timer2 8 бит 61 Гц.. 1 МГц 16 384.. 1 мкс CHANNEL_A D11 PB3
CHANNEL_B D3 PD3

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


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

Максимальный период


В таблице выше приведены диапазоны для 16 МГц тактирования. Для другого системного клока максимальный период считается по формуле, где F_CPU - системная частота в Гц:
  • 8 бит таймеры: (1000000UL / F_CPU) * (1024 * 256)
  • 16 бит таймеры: (1000000UL / F_CPU) * (1024 * 65536)

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


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

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


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

Прерывания


  • enableISR(канал); - запустить прерывания на выбранном канале
    • Канал - CHANNEL_A, CHANNEL_B или CHANNEL_С (см. таблицу выше!)
  • disableISR(канал); - отключить прерывания на выбранном канале. Если ничего не указывать, будет выбран канал A
Библиотека даёт прямой доступ к прерыванию без "Ардуиновских" attachInterrupt, что позволяет сократить время вызова функции-обработчика прерывания. Прерывание с настроенной частотой будет обрабатываться в блоке вида ISR(канал) {}, пример:
ISR(TIMER1_A) {
// ваш код
}

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

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

ISR(TIMER0_A) {
// ваш код
}
Внимание! Если не объявить ISR(), все прерывания будут выключены и перестанут работать все завязанные на них функции (системный миллис, Сериал...).

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


  • 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
Важно: при генерации меандра реальная частота будет в два раза меньше заданной из-за особенности работы самого таймера. См. примеры с меандром.

Сдвиг фазы (с 1.6)


При помощи phaseShift(source, angle) можно сдвинуть прерывания или переключения пинов на выбранном канале source по фазе angle - угол сдвига в градусах от 0 до 360.
  • У 8-битных таймеров можно задать сдвиг только у второго канала (CHANNEL_B)
  • У 16-битных можно двигать все три канала

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


При помощи метода setDefault() можно сбросить настройки таймера на "ардуиновские" умолчания: частоту и режим работы.
Таймер описан как объект: Timer0, Timer1... Timer5. Обращение через точку, например Timer1.stop() . Методы:
setPeriod(период) - установка периода в микросекундах и запуск таймера. Возвращает реальный период (точность ограничена разрешением таймера).
setFrequency(частота) - установка частоты в Герцах и запуск таймера. Возвращает реальную частоту (точность ограничена разрешением таймера).
setFrequencyFloat(частота float) - установка частоты в Герцах и запуск таймера, разрешены десятичные дроби. Возвращает реальную частоту (точность ограничена разрешением таймера).
enableISR(источник) - включить прерывания, канал прерываний CHANNEL_A или CHANNEL_B (+ CHANNEL_C у Mega2560)
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
phaseShift(источник, фаза) - сдвинуть фазу канала на 0-360 градусов (у 8 бит таймеров двигается только канал B)

ПРИМЕРЫ


Остальные примеры смотри в папке examples библиотеки, также примеры можно открыть из Arduino IDE/Файл/Примеры

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

#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(20000);     // Устанавливаем период таймера 20000 мкс -> 50 гц
  Timer2.enableISR(CHANNEL_A);   // Или просто .enableISR(), запускаем прерывание на канале А таймера 2
  pinMode(13, OUTPUT);           // будем мигать
}

void loop() {}

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

// Прерывание А таймера 2
ISR(TIMER2_A) {   // генерируем меандр 25 гц, мигаем
  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 "GyverTimers.h"

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() {
}
// Пример генерации энкодерного сигнала на таймере 2 (пины D3 и D11)
#include "GyverTimers.h"

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);   // в момент срабатывания таймера пин будет переключаться

  // задаём фазовый сдвиг 0-360 (для всех каналов кроме A на 8 бит таймерах)
  Timer2.phaseShift(CHANNEL_B, 180);
}

void loop() {
}

ВИДЕО


ПОДДЕРЖАТЬ


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

5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

18 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх