Урок написан для 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
}
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками
