Прерывания по таймеру
Мы с вами уже много раз рассматривали конструкцию таймера на millis()
, который позволяет наладить логику работы кода по таймерам. Минусом этого способа является необходимость постоянно опрашивать конструкцию таймера, чтобы проверять, не сработал ли он. Соответственно код в главном цикле должен быть "прозрачным", то есть не содержать задержек, долгих замкнутых циклов и просто блокирующих кусков. Если для таймеров с длинным периодом (минута, 5 секунд) это не так критично, то для выполнения действий с высокой строго заданной частотой любая маленькая задержка в главном цикле может стать большой проблемой! Выходом из ситуации может стать прерывание по таймеру. Вспомните урок про аппаратные прерывания: прерывание позволяет "выйти" из любого выполняемого на данный момент участка кода в основном цикле, выполнить нужный блок кода, который находится внутри прерывания, и вернуться туда, откуда вышли, и продолжить выполнение. Таким образом это практически параллельное выполнение задач. В этом уроке мы научимся делать это по аппаратному таймеру. Для чего использовать прерывания по таймеру?
- Генерация сигналов
- Измерение времени
- Параллельное выполнение задач
- Выполнение задачи через строго заданный период времени
- И многое другое
Таймеры
Прерывания генерируются отдельным аппаратным таймером, который находится в микроконтроллере где-то рядом с вычислительным ядром. Аппаратный таймер, он же счётчик, занимается очень простой задачей: считает "тики" тактового генератора (который задаёт частоту работы всей системы) и, в зависимости от режима работы, может дёргать ногами или давать сигнал на микроконтроллер при определённых значениях счётчика. Таким образом "разрешение" работы таймера - один тик (такт) задающего генератора, при 16 МГц это 0.0625 микросекунды. Второй важный момент для понимания: таймер-счётчик работает и считает импульсы параллельно вычислительному ядру. Именно поэтому генерация ШИМ сигнала даже на высокой частоте абсолютно не влияет на выполнение кода - оно всё происходит параллельно. В ардуино нано (atmega328) у нас три таких таймера, и каждый может активировать независимое прерывание по своему периоду. Что касается счета времени: функции millis()
и micros()
как раз таки работают на прерывании таймера 0. Если перенастроить таймер 0 - у нас пропадёт корректный счёт времени (и, возможно, ШИМ на пинах 5 и 6). Некоторые библиотеки также используют прерывания таймеров, например Servo использует первый, а встроенная функция tone()
- второй. Также мы обсуждали в уроках что таймеры занимаются генерацией ШИМ сигнала на своих пинах, и при перенастройке таймера ШИМ может отключиться, начать работать в другом режиме или изменить частоту. В отличие от генерации ШИМ сигнала и аппаратных прерываний, управление прерываниями по таймерам не реализовано разработчиками Ардуино в ядре и стандартных библиотеках, поэтому работать с прерываниями будем при помощи сторонних библиотек. Можно работать с таймером напрямую, как описано в даташите, но это не входит в данный курс уроков. Для первого и второго таймеров можно найти старые библиотеки, называются timerOne и timerTwo. У меня есть своя библиотека - GyverTimers, которая позволяет гибко настроить все таймеры на atmega328 (Arduino UNO/Nano) и atmega2560 (Arduino Mega). Скачать библиотеку можно по прямой ссылке, также про нее есть отдельная страница у меня на сайте, с описанием, документацией и примерами. Рассмотрим основные инструменты библиотеки.
Библиотека GyverTimers
Библиотека 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(канал, фаза);
- запустить прерывания на выбранном канале с выбранным сдвигом фазы. Если ничего не указывать, будет выбран канал 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
- Канал:
Важно: при генерации меандра реальная частота будет в два раза меньше заданной из-за особенности работы самого таймера. См. примеры с меандром.
Сдвиг фазы (с 1.6)
При помощи phaseShift(source, angle)
можно сдвинуть прерывания или переключения пинов на выбранном канале source
по фазе angle
- угол сдвига в градусах от 0 до 360.
- У 8-битных таймеров можно задать сдвиг только у второго канала (
CHANNEL_B
) - У 16-битных можно двигать все три канала
Настройка по умолчанию
При помощи метода setDefault()
можно сбросить настройки таймера на "Ардуиновские" умолчания: частоту и режим работы.
Примеры
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])