View Categories

Прерывания таймера

Урок написан для AVR Arduino (UNO, Nano, Mega)

У МК есть много разных прерываний. Мы уже рассмотрели внешние прерывания, которые удобно настраиваются средствами фреймворка Arduino - attachInterrupt(). Есть и другие прерывания, например прерывания таймера, которые не входят в фреймворк. МК можно настроить на запуск прерывания через заданное время, этим занимается аппаратный таймер.

Таймер #

Таймер-счётчик - отдельный блок внутри МК, который занимается очень простой задачей: считает "тики" тактового генератора и, в зависимости от режима работы и настроек, может дёргать ногами и давать сигнал процессору, например в виде прерывания. Таймер работает и считает импульсы независимо от процессора, т.е. работает параллельно выполнению программы. В Ардуино таймеры активно используются для стандартных задач и библиотек:

  • Генерация ШИМ через analogWrite() - здесь у каждого таймера есть 2 канала ШИМ. Например у Nano - 3 таймера и соответственно 6 ШИМ-пинов
  • Таймер 0 по умолчанию занят счётом реального времени - на нём работают millis(), micros() и delay()
  • Таймер 1 используется стандартной библиотекой Servo для генерации сигналов на приводы параллельно выполнению программы
  • Таймер 2 используется в работе функции tone(), чтобы выдавать стабильный звуковой сигнал

Если перенастроить соответствующий таймер под свои нужды - может потеряться его "стандартная" функция в рамках фреймворка

Ардуино-фреймворк не предоставляет инструментов для настройки прерывания по таймерам, но это можно сделать вручную или при помощи библиотек.

Таблица таймеров 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)

GyverTimers #

Библиотека GyverTimers позволяет генерировать прерывания по таймеру с заданной частотой на выбранном канале таймера или на нескольких каналах сразу со сдвигом по фазе: прерывания будут происходить с одинаковой частотой, но "сдвинуты" друг относительно друга. Также можно задать действие для вывода таймера: включить/выключить/переключить: таймер будет управлять выбранным пином независимо от вычислительного ядра МК, таким образом можно генерировать меандр (квадратный сигнал), причём как однотактный, так и двух- и трёхтактный с настраиваемым сдвигом по фазе. Для Arduino Nano/UNO/Pro Mini доступно три таймера: Timer0, Timer1, Timer2. Для Arduino MEGA - пять: Timer0, Timer1, Timer2, Timer3, Timer4, Timer5.

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

  • 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

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

Примеры #

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

#include <GyverTimers.h>

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

  // Подключить прерывание таймера, с этого момента прерывания начнут вызываться
  Timer0.enableISR();               // Подключить стандартное прерывание, канал А, без сдига фаз
  Timer2.enableISR(CHANNEL_B); // Подключить прерывание таймера 2, канал B
  Timer1.enableISR(CHANNEL_A);  // Подключить прерывание канала А
  Timer1.enableISR(CHANNEL_B); // Подключить второе прерывание таймера 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() {}
Меандр
//Пример генерации меандра на таймере 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
// Пример генерации двухтактного меандра на таймере 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() {
}
Прерывания
// Пример простой генерации прерываний аппаратным таймером
#include <GyverTimers.h>

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

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

void loop() {}

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

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

#include <GyverTimers.h>

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

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

void loop() {}

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

ISR(TIMER1_B) {
  Serial.println(" Channel B interrupt !");   // Прерывание B
}

Полезные страницы #

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

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