Управление таймерами

В этом уроке мы познакомились с еще одним инструментом, позволяющим решать другую, часто встречающуюся на практике задачу - выполнять команды по расписанию. Этот инструмент - таймеры.

Теория:

Упрощенно можно представить себе таймер, как отдельно устройство, счетчик, в составе микроконтроллера, которое может считать от 0 до 65535 с различной частотой. Частота работы таймера определяется частотой работы микроконтроллера и предделителя. Частота - это единица измерения, показывающая, сколько раз в секунду происходит событие. Например, если светодиод мигает со скоростью 10 раз в секунду, то мы говорим, что частота миганий равна 10Гц (Герц, сокращ. Гц).

Микроконтроллер Atmega32U4, установленный на плате DaVinci имеет 4 таймера: Timer0, Timer1, Timer3 и Timer4. Причем Timer0 - 8-битный таймер/счетчик, то есть его счётный диапазон от 0 до 255. Timer1 и Timer3 - 16-битные таймеры/счетчики. Их счётный диапазон от 0 до 65535. А Timer4 - высокоскоростной 10-битный таймер/счетчик со счетным диапазоном от 0 до 1024.

Диапазон счета ограничен емкостью регистров в которых хранится текущее значение таймера. Как можно догадаться, 8-битный таймер хранит значение в 8-битном регистре, а 16-битный в 16-битном регистре. Максимальное число в двоичной форме, которое можно записать в один 8-битный регистр соответствует 28 = 256. А в 16-битный регистр можно записать число 216 = 65536.

Регистр - группа быстродействующих ячеек памяти, предназначенных для хранения данных в двоичном виде. Каждая ячейка регистра может хранить 1 бит информации - 0 или 1.

Для удобства работы с таймером мы будем использовать библиотеку TimerOne, которую вы можете скачать с нашего сайта, либо загрузить самую последнюю версию с сайта https://github.com/PaulStoffregen/TimerOne.

Библиотека работает с 16-битным таймером Timer1 и предоставляет ряд функций, для более удобного взаимодействия с ним. Некоторые из этих функций выходят за пределы изучаемой темы, поэтому они будут рассмотрены в последующих уроках.

Рассмотрим, как подключить библиотеку, инициализировать таймер и с его помощью управлять процессами привязанными ко времени.

Для подключения библиотеки (любой, а не только TimerOne), необходимо включить в начало программы строку с именем заголовочного файла: #include <TimerOne.h>.

При этом необходимо импортировать сам файл библиотеки в среду Arduino IDE. Для этого необходимо зайти в пункт меню Эскиз>Импорт библиотек>Добавить библиотеку (в том случае, если вы используете англоязычную версию Arduino IDE Sketch > Import Library .> Add Library).

Инициализация таймера осуществляется при помощи вызова функции Timer1.initialize(period), которая в качестве аргумента принимает значение периода между тактами в микросекундах. Если инициализировать таймер не указав период, то по умолчанию он будет равен 1000000 микросекундам (1 микросекунда = 0.000001сек). Период обязательно должен быть выражен целым числом, ни в коем случае не дробным.

Момент, когда таймер досчитает до числа, заданного периодом, называется переполнением таймера. Переполнение таймера и есть то событие, которое вызывает прерывание.

Timer1.setPeriod(period) - изменяет значение периода у инициализированного таймера и принимает в качестве аргумента новое значение периода в микросекундах. Функция полезна, если в процессе работы программы вы решили изменить период.

Timer1.attachInterrupt(void(*isr)(), long period=-1) - в качестве первого параметра передается функция обработчик прерывания. В качестве второго аргумента период, но его можно не указывать, если вы не собираетесь его (период) изменять.

Важно понимать, что изменяя период, вы меняете его для всего таймера. Следовательно будут затронуты все блоки кода работающие с таймером.

Внимание! Не стоит делать period слишком маленьким и не выполнять слишком много кода в обработчике прерывания. В противном случае может сложиться ситуация, когда контроллер все время будет работать в прерываниях и не будет успевать выполнять основной блок программы - программа окажется заперта в прерывании.

Timer1.detachInterrupt() - отключает прикрепленное прерывание.

Практика:

Перейдем к решению практической задачи.

Пусть синий светодиод мигает 10 раз в секунду в основном цикле. А желтый светодиод будет управляться таймером и мигать с частотой 1 раз секунду.

Соберем схему согласно рисунку.

После сборки схемы загружаем данный скетч:


//Подключаем библиотеку TimerOne
#include <TimerOne.h>

//Синий светодиод
#define LED_PIN1 9
//Желтый светодиод
#define LED_PIN2 10

void setup() {
  //Инициализация входов/выходов
  pinMode(LED_PIN1, OUTPUT);
  pinMode(LED_PIN2, OUTPUT);
  //Переполнение таймера в микросекундах
  Timer1.initialize(500000);
  Timer1.attachInterrupt(timerHandler);
}

void loop() {
  //Переключаем состояние светодиода на
  //противоположное с помощью оператора
  //логического отрицания «!»
  digitalWrite(LED_PIN1, !digitalRead(LED_PIN1));
  //Ждем 0.1 сек
  delay(100);
}

void timerHandler(void) {
  //Переключаем состояние светодиода на
  //противоположное с помощью оператора
  //логического отрицания «!»
  digitalWrite(LED_PIN2, !digitalRead(LED_PIN2));
}

 

Timer1.initialize(500000) - инициализирует таймер с периодом срабатывания 0.5 сек.

Timer1.attachInterrupt(timerHandler) - задает функцию обработчик прерывания.

Рассмотренный пример показывает, что таймер можно успешно использовать для решения задач строго привязанных ко времени. Причем, даже использование в основном цикле функции delay(), не мешает нормальной работе программы. Как и в случае с внешними прерываниями, программа представляет из себя два параллельно выполняющихся алгоритма. Превый выполняется в главном цикле, а второй в прерывании. При необходимости, можно даже расположить в функции timerHandler() код для управления обоими светодиодами и полностью освободить главный цикл.

Подобный подход позволяет упростить любую, даже сильно запутанную программу и заставить множество устройств работать вместе, не мешая друг другу, чего никак не удалось бы добиться без прерываний и таймеров.