Как сделать таймер на ардуино

Добавил пользователь Евгений Кузнецов
Обновлено: 05.09.2024

Работает на Atmega8, Atmega168, Atmega328, Atmega2560

Upd Декабрь 2019 добавлена поддершка Лянарды.

Много таймеров из одного

Основная задача, стоящая перед горе-программистом AVR это не строгий математический анализ каких-нить бестолковых данных, не быстрое преобразование Фурье и даже не унылое мигание светлодиодиком. Основная задача, занимающая 80% программы - измерение временных интервалов. Чаще всего требуется узнать, скока времени прошло перед/после какого-нить события, чтобы не пропустить и вовремя откликнуться на него. Кто-то (90% новичков) жизни своей не мыслят без функции delay(), кто-то постоянно теребонькает несчастную millis(), зачастую, делая это неправильно и со священным ужасом ожидая страшного переполнения. Сложность в том, что временных интервалов по ходу программы требуется много, часто и, как правило, разных. Например, время конверсии даччика DS18B20 при максимальном 12-битовом разрешении составляет 750 миллисекунд, и раньше, чем вычислится температура опрашивать снова его бессмысленно. Для других даччиков время опроса будет совершенно другое, и временной интервал для него, соотвецтвенно - тоже. На экран информацию чаще 1 раза в 50 миллисекунд выводить тоже неприлично, только лишняя трата ресурсов, нервов и самого экранчика, значит опять надо где-то хранить время, когда вывод осуществлялся последний раз, чтоб не обращаться к нему чаще положенного. Везде, куда не посмотришь - время, время и время.

Кто-то, не мудрствуя лукаво, использует для своих целей delay() и не парится, даже не понимая, что процессор во время выполнения этой функции просто висит в холостом цикле, не делая ничего полезного, и не способен исполнять что-то иное (на самом деле способен, канеш, но для новичков это тайна). В некоторых задачах, будь то простейший Blink или управление светлодиодной лентой это не критично, но если delay() используется в программе управления газовой горелкой, тут впору эвакуировать из дома родных и близких (и кота) на время эксплуатации такой горелки.

Более продвинутые открыли для себя функцию millis() и поглядывают/поплевывают на делейщиков с высоты своего воображаемого Олимпа как исполненные благодати Свидетели Переполнения. И пестрят их программы чуть более чем полностью конструкциями вида

но последние, обычно, к исходу 49-х суток взрываются вместе с переполнившимся millis-om() и угрозы больше не представляют.

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

millis() в этом отношении гораздо гуманнее. Он похож на раба, которому хозяин вручил наручные часы, и строго настрого наказал следить за временем, если не хочется получить дубиной по башке, как в прошлый раз. И вот человечек, выполняя свою нехитрую работу, всё время пялится на часы, чтоб узнать не прошло ли достаточно времени, чтоб включить/выключить что то важное. Иначе, не уследишь/забудешь и улетишь вабнимку с газовым котлом Марс заселять вместе с родными и близкими (и котом), а жалка. Такое можно, канеш, потерпеть, если временной интервал один, но как только их становится несколько, да еще и друг от друга зависящих, выполнение задания превращается в атнюдь не тривиальную задачу (мошт, лучше дубиной, а?).

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

В основе всего этого священнодействия лежит самодостаточный класс TCounterDown, который, как ясно из названия является счетчиком вниз, до нуля. Класс самодостаточный, его можно применять самостоятельно для подсчета внешних событий. Каких? Да каких угодно, например, сколько раз человек нажал на кнопку, допустим, звонка. Если больше 10, то выйти и спросить, чо звонишь-то? видишь дома нет никто? или активировать механическую руку в боксерской перчатке, которая автоматически колотит слишком настойчивого посетителя по темечку (да, это я Том и Джерри насмотрелса за время сынова децтва).

У класса есть конструктор, который принимает начальное значение счетчика событий и адрес функции, которая вызовется когда это количество событий произойдёт.

тип pvfCallback - это указатель на функцию ничего не принимающую и ничего не отдающую

ее задача только в том, чтобы просигнализировать основной программе, что требуемое количество событий случилось (прозвонить будильником).

еще, из главных, у класса есть функция Tick() которая уменьшает заданный в конструкторе счетчик событий, и если он достиг 0, т.е требуемое кол-во событий случилось - то она и вызывает пользовательскую функцию CallbackFunc. После этого, внутренний счетчик вновь заполняется первоначальным значением автоматически и подсчёт событий начинается сначала. Для удобства программирования Tick() завернут в перегруженный оператор --, чтоб при наступлении события вызывать его както так

у класса есть еще несколько вспомогательных функций, которые могут пригодиться. Если нужно изменить счетчик событий, например, считать не 10 а 20 нажатий, есть функция

счетчик можно остановить, запустить снова с того же места или сбросить счетчик на первоначальное значение

кроме того, функцией isActive() можно проверить, что счетчик не остановлен, а функцией isEmpty() проверить, что назначена функция обратного вызова.
Полностью публичный интерфейс класса выглядит так

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

Поэтому следующий класс для рассмотрения - TTimerList, которыйхранит в себе список программных счетчиков TCounterDown, которые считают число миллисекунд. Каждое срабатывание аппаратного таймера, TTimerList пробегает по списку сохраненных счетчиков и выполняет декремент каждого. При достижении 0 счетчик сам вызовет функцию обратного вызова (уведомление, будильник), и отсчет пойдет сначала. Интерфейс TTimerList прост как 2 копейки, у него есть функции как для всего списка (остановить/запустить), так и для каждого отдельного таймера.

основную идею я корявенько донес, а комментарии в тексте подскажут что там к чему (надеюсь)

Использование: скопировать все файлы в свою директорию. В своем .ino файле написать

и всё. Можно добавлять, удалять, останавливать и вновь запускать нужные счётчики.

рабочий Blink без delay(), millis() и loop() выглядит так

а если вдруг нам надо, чтобы светодиод мигал неравномерно, например 200 мс горел, а 4800 мс не горел, то просто на лету меняем интервалы отсчета.

пробуем, спрашиваем, не надоедаем.

Я НЕ ЗАНИМАЮСЬ встраиванием таймеров в ваш код, не надо присылать мне свои простыни для "анализа" как туда прикрутить это. Алгоритм работы ВАШЕЙ программы известен только ВАМ, я в нем разбираца не буду. Сможете прикрутить туда таймеры, буду рад, не сможете - значит еще не пришло время, пользуйтесь другими решениями. И материть меня за это по электропочте не надо, мы с котом расстраиваемся, плачем и сильнапьём.

Данная библиотека представляет собой набор функций для настройки аппаратного 16-битного таймера Timer1 в ATMega168/328. В микроконтроллере доступно 3 аппаратных таймера, которые могут быть настроены различными способами для получения различных функциональных возможностей. Начало разработки данной библиотеки было вызвано необходимостью быстро и легко установить период или частоту ШИМ сигнала, но позже она разраслась, включив в себя обработку прерываний по переполнению таймера и другие функции. Она может быть легко расширена или портирована для работы с другими таймерами.

Точность таймера зависит от тактовой частоты процессора. Тактовая частота таймера Timer1 определяется установкой предварительного делителя частоты. Этот делитель может быть установлен в значения 1, 8, 64, 256 или 1024.

для тактовой частоты 16 МГц
ДелительДлительность одного отсчета, мксМаксимальный период, мс
10,06258,192
80,565,536
644524,288
256162097,152
1024648388,608

  • Максимальный период = (Делитель / Частота) × 2 17
  • Длительность одного отсчета = (Делитель / Частота)

Для установки просто распакуйте и поместите файлы в каталог Arduino/hardware/libraries/Timer1/ .

Timer3

Обратите внимание, что библиотека Timer1 может использоваться на Arduino Mega, но она не поддерживает все три выходных вывода OCR1A , OCR1B и OCR1C . Поддерживаются только A и B . OCR1A подключен к выводу 11 на Mega, а OCR1B – к выводу 12. С помощью одного из трех вызовов, которые задают вывод, значение 1 задаст вывод 11 на Mega, а 2 – задаст вывод 12. Библиотека Timer3 была протестирована только на Mega.

Библиотеку для таймера Timer3 можно здесь (TimerThree.zip)

Для установки просто распакуйте и поместите файлы в каталог Arduino/hardware/libraries/Timer3/ .

Методы библиотек TimerOne и TimerThree

Настройка

void initialize(long microseconds=1000000); Вы должны вызвать этот метод первым, перед использованием любых других методов библиотеки. При желании можно задать период таймера (в микросекундах), по умолчанию период устанавливается равным 1 секунде. Обратите внимание, что это нарушает работу analogWrite() на цифровых выводах 9 и 10 на Arduino. void setPeriod(long microseconds); Устанавливает период в микросекундах. Минимальный период и максимальная частота, поддерживаемые данной библиотекой, равны 1 микросекунде и 1 МГц, соответственно. Максимальный период равен 8388480 микросекунд, или примерно 8,3 секунды. Обратите внимание, что установка периода изменит частоту срабатывания прикрепленного прерывания и частоту, и коэффициент заполнения на обоих ШИМ выходах.

Управление запуском

void start(); Запускает таймер, начиная новый период. void stop(); Останавливает таймер. void restart(); Перезапускает таймер, обнуляя счетчик и начиная новый период.

Управление выходным ШИМ сигналом

void pwm(char pin, int duty, long microseconds=-1); Генерирует ШИМ сигнал на заданном выводе pin . Выходными выводами таймера Timer1 являются выводы PORTB 1 и 2, поэтому вы должны выбрать один из них, всё остальное игнорируется. На Arduino это цифровые выводы 9 и 10, эти псевдонимы также работают. Выходными выводами таймера Timer3 являются выводы PORTE , соответствующие выводам 2, 3 и 5 на Arduino Mega. Коэффициент заполнения duty задается, как 10-битное значение в диапазоне от 0 до 1023 (0 соответствует постоянному логическому нулю на выходе, а 1023 – постоянной логической единице). Обратите внимание, что при необходимости в этой функции можно установить и период, добавив значение в микросекундах в качестве последнего аргумента. void setPwmDuty(char pin, int duty); Быстрый способ для настройки коэффициента заполнения ШИМ сигнала, если вы уже настроили его, вызвав ранее метод pwm() . Этот метод позволяет избежать лишних действий по включению режима ШИМ для вывода, изменению состояния регистра, управляющего направлением движения данных, проверки необязательного значения периода и прочих действий, которые являются обязательными при вызове pwm() . void disablePwm(char pin); Выключает ШИМ на заданном выводе, после чего вы можете использовать этот вывод для чего-либо другого.

Прерывания

Остальные

Пример 1

В примере ШИМ сигнал с коэффициентом заполнения 50% подается на вывод 9, а прикрепленный обработчик прерывания переключает состояние цифрового вывода 10 каждые полсекунды.

Модифицированные библиотеки от Paul Stoffregen

Также доступны отдельно поддерживаемые и обновляемые копии TimerOne и TimerThree, которые отличается поддержкой большего количества оборудования и оптимизацией для получения более эффективного кода.

ПлатаШИМ выводы TimerOneШИМ выводы TimerThree
Teensy 3.13, 425, 32
Teensy 3.03, 4
Teensy 2.04, 14, 159
Teensy++ 2.025, 26, 2714, 15, 16
Arduino Uno9, 10
Arduino Leonardo9, 10, 115
Arduino Mega11, 12, 132, 3, 5
Wiring-S4, 5
Sanguino12, 13

Методы модифицированных библиотек аналогичны описанным выше, но добавлен еще один метод управления запуском таймера:

void resume(); Возобновляет работу остановленного таймера. Новый период не начинается.

Прежде, чем программировать таймеры, поскольку мне удобно использовать библиотечные функции Arduino, то мне хотелось бы иметь возможность узнать, в каком режиме работает таймер-счётчик 0.

Поэтому прежде всего я решил написать скетч, который общался бы с Arduino IDE через последовательный порт и выполнял бы функции терминала, для того, чтобы я мог в Arduino IDE открыть окно монитора, ввести команду и получить в ответ дамп регистров. В результате получилась библиотека под названием Simple Dumping Monitor, используя которую я получил следующую о состоянии таймера-счётчика 0 микроконтроллера ATmega328p Arduino:

Итак, как следует из вышеприведённого дампа, таймер-счётчик 0 тактируется через предделитель /64 , то есть с частотой 250 кГц, в режиме быстрого ШИМ с переполнением каждые 256 тактов (976.5625 Гц), выводы ШИМ не задействованы, прерывания по совпадению не используются ни A, ни B, но вызывается прерывание по переполнению. То есть с частотой 976 Гц вызывается прерывание системного таймера Arduino.

Arduino UNO имеет три таймера: Timer0, Timer1 и Timer2.

Timer0 уже настроен для генерации миллисекундных прерываний, обновляя счетчик миллисекунд, передаваемый в millis ().

В регистрах сравнения хранятся данные, которые постоянно сравниваются с состоянием таймера/счетчика. Установим регистр сравнения (OCR0A) для генерации другого прерывания где-то в середине этого счета. Приведенный ниже код будет генерировать прерывание TIMER0_COMPA всякий раз, когда значение счетчика проходит 0xAF.

// Timer0 уже используется millis() - мы создаем прерывание где-то // в середине и вызываем ниже функцию "Compare A" OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A);

// Timer0 уже используется millis() - мы создаем прерывание где-то

// в середине и вызываем ниже функцию "Compare A"

Затем определим обработчик прерывания для вектора прерывания по таймеру, называемому TIMER0_COMPA_vect.

Читайте также: