Таймер на ардуино с обратным отсчетом

Я думаю все знают классический алгоритм создания таймера на millis() – счётчике аптайма:

Классический таймер на millis()

// Данный код выполняет действия периодически за указанный период // Нам нужно задать период таймера В МИЛЛИСЕКУНДАХ // дней*(24 часов в сутках)*(60 минут в часе)*(60 секунд в минуте)*(1000 миллисекунд в секунде) // (long) обязательно для больших чисел, иначе не посчитает // можно посчитать на калькуляторе, но какбэ ардуино и есть калькулятор, пусть считает...  unsigned long period_time = (long)5*24*60*60*1000;  // переменная таймера, максимально большой целочисленный тип (он же uint32_t) unsigned long my_timer;  void setup() {   my_timer = millis();   // "сбросить" таймер }  void loop() {   if (millis() - my_timer >= period_time) {     my_timer = millis();   // "сбросить" таймер     // дейтвия, которые хотим выполнить один раз за период   } }

Несколько таймеров

/*    Делаем "параллельное" выполнение нескольких задач    с разным периодом выполнения */ #define PERIOD_1 100    // период первой задачи #define PERIOD_2 2000   // период второй задачи #define PERIOD_3 666    // ... unsigned long timer_1, timer_2, timer_3; void setup() { } void loop() {   if (millis() - timer_1 >= PERIOD_1) {    // условие таймера     timer_1 = millis();                   // сброс таймера          // выполняем блок №1 каждые PERIOD_1 миллисекунд   }   if (millis() - timer_2 >= PERIOD_2) {     timer_2 = millis();          // выполняем блок №2 каждые PERIOD_2 миллисекунд   }   if (millis() - timer_3 >= PERIOD_3) {     timer_3 = millis();          // выполняем блок №3 каждые PERIOD_3 миллисекунд   } }

Данный алгоритм позволяет спокойно переходить через переполнение millis() без потери периода, но имеет один большой минус – время считается с момента последнего вызова таймера, и при наличии задержек в коде таймер будет накапливать погрешность, отклоняясь от “ритма”. Недавно я придумал более точный алгоритм таймера на миллис, который соблюдает свой период даже после пропуска хода!

Улучшенный таймер

#define PERIOD 500 uint32_t timer = 0; void loop() {   if (millis() - timer >= PERIOD) {     // ваше действие     do {       timer += PERIOD;       if (timer < PERIOD) break;  // переполнение uint32_t     } while (timer < millis() - PERIOD); // защита от пропуска шага   } }

Данный таймер имеет механику классического таймера с хранением переменной таймера, а его период всегда кратен PERIOD и не сбивается. Эту конструкцию можно упростить до

#define PERIOD 500 uint32_t timer = 0; void loop() {   if (millis() - timer >= PERIOD) {     // ваше действие     timer += PERIOD;   } }

В этом случае алгоритм получается короче, кратность периодов сохраняется, но теряется защита от пропуска вызова и переполнения millis().

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

В этой библиотеке реализован полноценный таймер на счётчике аптайма, позволяющий даже “приостановить” счёт (не приостанавливая сам аптайм).

Примечание: таких таймеров можно создать сколько душе угодно (пока хватит памяти), что позволяет создавать сложные программы с кучей подзадач, но для функционирования данного таймера нужен “чистый” loop с минимальным количеством задержек, или вообще без них. Всё таки таймер “опрашивается” в ручном режиме. Таймер, который не боится задержек, делается на прерывании таймера, смотрите вот эту библиотеку.

БИБЛИОТЕКА GYVERTIMER

GyverTimer v3.2

GTimer – полноценный таймер на базе системных millis() / micros(), обеспечивающий удобную мультизадачность и работу с временем, используя всего одно родное прерывание таймера (Timer 0)

  • Миллисекундный и микросекундный таймер
  • Два режима работы:
    • Режим интервала: таймер “срабатывает” каждый заданный интервал времени
    • Режим таймаута: таймер “срабатывает” один раз по истечении времени (до следующего перезапуска)
  • Служебные функции:
    • Старт
    • Стоп
    • Сброс
    • Продолжить

Поддерживаемые платформы: все Arduino (используются стандартные Wiring-функции)

ДОКУМЕНТАЦИЯ

Документация

Конструктор

Класс GTimer позволяет работать как с миллисекундным, так и с микросекундным таймером. В общем виде пример выглядит так:

GTimer myTimer(type, period);

Где type это MS (мс, миллисекундный таймер) или US (мкс, микросекундный), period – период в мс или мкс соответственно.

Настройки по умолчанию

  • При создании таймера можно ничего не указывать: GTimer myTimer; , тогда таймер будет сконфигурирован как миллисекундный и не запустится
  • Если указать только тип таймера (MS/US) GTimer myTimer(MS); , таймер настроится на выбранный режим (мс/мкс) и не запустится
  • Если указать тип таймера и интервал GTimer myTimer(US, 5000); , таймер настроится на выбранный режим (мс/мкс) и запустится в режиме интервала

Режимы работы

Таймер может работать в режиме интервалов и в режиме таймаута:

  • Интервалы. Запуск – метод setInterval(время) с указанием времени. В режиме интервалов таймер срабатывает (метод isReady() возвращает true) каждый раз при достижении указанного периода и автоматически перезапускается. Удобно для периодических действий
  • Таймаут. Запуск – метод setTimeout(время) с указанием времени. В режиме таймаута таймер срабатывает (метод isReady() возвращает true) только один раз при достижении указанного периода и автоматически отключается. Для повторного запуска нужно вызвать .setTimeout() с указанием периода, или просто .start() – запустит таймер на новый круг с прежним периодом

Важный момент: сравнение времени происходит в методе isReady(), без его вызова таймер ничего не считает! Нужно вызывать isReady() всегда, когда требуется счёт времени.

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

Для управления состоянием таймера есть следующие методы:

  • start() – запускает (перезапускает) таймер с последним установленным временем
  • stop() – останавливает таймер
  • resume() – продолжает отсчёт таймера с момента остановки
  • reset() – сбрасывает таймер (отсчёт периода/таймаута начинается заново)
  • isEnabled() – возвращает true, если таймер работает (если он не stop() или не вышел таймаут)

Список функций и методов библиотеки из файла .h

GTimer(timerType type, uint32_t interval);  // объявление таймера с указанием типа и интервала (таймер не запущен, если не указывать) void setInterval(uint32_t interval);    // установка интервала работы таймера (также запустит и сбросит таймер) - режим интервала void setTimeout(uint32_t timeout);      // установка таймаута работы таймера (также запустит и сбросит таймер) - режим таймаута boolean isReady();                      // возвращает true, когда пришло время boolean isEnabled();                    // вернуть состояние таймера (остановлен/запущен) void reset();                           // сброс таймера на установленный период работы void start();                           // запустить/перезапустить (со сбросом счёта) void stop();                            // остановить таймер (без сброса счёта) void resume();                          // продолжить (без сброса счёта)

ПРИМЕРЫ

Демка

// пример работы в режиме интервалов  #include "GyverTimer.h"   // подключаем библиотеку  GTimer myTimer(MS);  // создать миллисекундный таймер (ms) (по умолч. в режиме интервала) //GTimer myTimer(MS, 1000);  // можно сразу указать период (по умолч. в режиме интервала) //GTimer myTimer(US, 5000);  // или микросекундный (us), на 5000 мкс (по умолч. в режиме интервала)  // без указания периода таймер автоматически не запустится!  void setup() {   Serial.begin(9600);   myTimer.setInterval(500);   // запуск в режиме интервала 500 мс      // myTimer.stop(); // "остановить" таймер   // myTimer.start(); // запустить (перезапустить) таймер   // myTimer.reset(); // сбросить период   // myTimer.resume(); // продолжить работу после stop }  void loop() {   if (myTimer.isReady()) Serial.println("Timer!");  // 2 раза в секунду }

Режим таймаута

// Пример работы в режиме таймаута  #include "GyverTimer.h"   // подключаем библиотеку  GTimer myTimer(MS);    // создать миллисекундный таймер //GTimer myTimer(US);    // US - микросекундный  void setup() {   Serial.begin(9600);   myTimer.setTimeout(3000);   // настроить таймаут 3 сек   Serial.println("Start"); }  void loop() {   // выведет Timeout 3 sec! через 3 секунды после вызова setTimeout(3000)   if (myTimer.isReady()) Serial.println("Timeout 3 sec!");      // после срабатывания остановит счёт   // можно перезапустить при помощи setTimeout(время) или start() } 

Несколько таймеров

// пример параллельной работы нескольких таймеров  #include "GyverTimer.h"   // подключаем библиотеку  // создаём таймеры в миллисекундах GTimer myTimer1(MS, 500); GTimer myTimer2(MS, 600); GTimer myTimer3(MS, 1000);  void setup() {   Serial.begin(9600); }  void loop() {   if (myTimer1.isReady())     Serial.println("Timer 1!");    if (myTimer2.isReady())     Serial.println("Timer 2!");    if (myTimer3.isReady())     Serial.println("Timer 3!"); } 

Пример с кнопкой

// пример с кнопкой, кнопка сбрасывает таймер  #include "GyverButton.h"   // библиотека кнопки GButton btn(3);           // кнопка на D3  #include "GyverTimer.h"   // библиотека таймера GTimer myTimer(MS);       // создать таймер (по умолч. в режиме интервала)  void setup() {   Serial.begin(9600);   myTimer.setTimeout(1000); // запуск в режиме таймаута 1 секунда }  void loop() {     btn.tick();   // опрос кнопки   // при нажатии кнопки задаём таймаут 1 секунду      if (btn.isClick()) myTimer.setTimeout(1000);    // через секунду после последнего нажатия выведет Timeout!   if (myTimer.isReady()) Serial.println("Timeout!"); }

Кнопка останавливает и запускает таймер

// пример с кнопкой, которая приостанавливает таймер  #include "GyverButton.h"  // библиотека кнопки GButton btn(3);           // кнопка на D3  #include "GyverTimer.h"   // библиотека таймера GTimer myTimer(MS);       // создать таймер (по умолч. в режиме интервала)  void setup() {   Serial.begin(9600);   myTimer.setInterval(5000); // запуск в режиме таймаута 5 секунд }  void loop() {   btn.tick();   // опрос кнопки      // при нажатии кнопки останавливаем и продолжаем таймер   if (btn.isClick()) {     static bool flag = false;     flag = !flag;     if (flag) myTimer.stop();     else myTimer.resume();   }    // оповещение о таймауте   if (myTimer.isReady()) Serial.println("Timeout!"); }

УСТАНОВКА БИБЛИОТЕКИ

Если вы не знаете, как установить библиотеку – читайте отдельный урок по работе с библиотеками!

БАГИ И ОШИБКИ

Если вы нашли баг или ошибку в исходнике или примерах, или у вас есть идеи по доработке библиотеки – пишите пожалуйста на почту alex@alexgyver.ru. В комментарии на страницах я заглядываю очень редко, на форум – ещё реже.

ОСТАЛЬНЫЕ БИБЛИОТЕКИ

У меня есть ещё очень много всего интересного! Смотрите полный список библиотек вот здесь.

Простой таймер на Arduino

Автор: Mike(admin) от 27-05-2014, 07:45

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

Устройство может быть запитано как от батарейки 9 В, так и от подходящего сетевого адаптера для Arduino. Работа схемы очень проста!

При нажатии кнопки RESET на плате Arduino таймер начнет обратный отсчет 60 секунд. При достижении нулевой секунды пьезозуммер (BZ1) начнет издавать звук, и на дисплее высветится сообщение «TIMER ALERT!». По желанию к четвертому выводу платы можно подключить реле. Например, если вы хотите установить электромагнитное реле для включения/выключения любой внешней электрической нагрузки, просто используйте этот выход для управления электромагнитной реле с помощью подходящей для этих целей схемы драйвера на транзисторной основе.

Код:

      //Arduino Self-Timer      //T.K.Hareendran      //www.electroschematics.com      #include       LiquidCrystal lcd(7,8,9,10,11,12);      int runTimer = 1;      int runFor = 60; // time in seconds      int buzzerPin = 13;      int relayPin=4;      int data = 0;             void setup() {      pinMode(buzzerPin, OUTPUT);      pinMode(relayPin,OUTPUT);      lcd.begin(16, 2);      }             void loop() {      if(runTimer == 1){      digitalWrite(relayPin,LOW); // реле отключено при счете таймера от 60 до 0      /* *измените на HIGH, если хотите, чтобы оно было включено во время счета */      lcd.clear();      lcd.print("TIMER=");      //Start timer      timer();      } else {      digitalWrite(relayPin,HIGH); // реле включается по срабатыванию таймера      /* *измените на LOW, если хотите, чтобы оно выключалось по срабатыванию таймера */      }      runTimer = 0;      lcd.noDisplay();      delay(250);      for(int duration = 0; duration < 100; duration ++){      digitalWrite(buzzerPin, HIGH);      delayMicroseconds(500);      digitalWrite(buzzerPin, LOW);      delayMicroseconds(500);      }      lcd.display();      delay(250);      }             void timer() {      for(int timer = runFor;timer > 0; --timer){      if(timer >= 10) {      lcd.setCursor(6,0);      } else {      lcd.setCursor(6,0);      lcd.print("0");      lcd.setCursor(7,0);      }      lcd.print(timer);      lcd.print(" SECOND!");      delay(1000);      }      lcd.setCursor(0,0);      lcd.clear();      lcd.print(" TIMER ALERT!");      }  

Перевод В© digitrode.ru

<Источник>

Версия для печати &nbsp&nbsp&nbspБлагодарим Вас за интерес к информационному проекту digitrode.ru. &nbsp&nbsp&nbspЕсли Вы хотите, чтобы интересные и полезные материалы выходили чаще, и было меньше рекламы, &nbsp&nbsp&nbspВы можее поддержать наш проект, пожертвовав любую сумму на его развитие. —> Вернуться 48772—> 7 В 

Категория: Микроконтроллеры и микропроцессоры, Статьи

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь. Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Комментарии:

Оставить комментарийЧасть II (продолжение, см. начало в Части I) Пришло время сосредоточиться на программной части. Мне пришло в голову вполне тривиальное деление по функциям:

  1. поддержка отсчета времени
  2. индикация
  3. сигнализация
  4. опрос и чтение кнопок

1.Ура! С поддержкой времени нам повезло: в некотором смысле, поддерживать отсчет времени Arduino умеет самостоятельно. В его ядре определена процедура прерывания по таймеру 0, которая прозрачно для нашего скетча инкрементирует счетчик «тиков». Пользовательская программа в любой момент может получить значение этого счетчика в виде количества миллисекунд с момента запуска через вызов millis(). Остается только зафиксировать в переменной начальное значение, а потом ожидать конечное, которое вычисляется путем прибавления задержки к зафиксированному начальному. Правда, есть маленькая неприятность: как и любой другой счетчик, он подвержен переполнению. На наше счастье, это случится через 49 дней после старта скетча, поэтому попробуем пока этим пренебречь 😉 2. Алгоритм индикации можно с небольшими изменениями позаимствовать из моей статьи про Shield с семисегментным индикатором на шине I2C. 3. Включать и выключать пищалку надо записью HIGH или LOW в соответствующий порт. Так что вся премудрость сводится к вызову digitalWrite. 4. Чуть больше внимания надо уделить кнопкам. Во-первых, оставлять висеть в воздухе цифровые входы нежелательно, иначе мы получим непредсказуемые результаты. Обычно, в таких случаях вывод притягивают к Vcc, чтобы в разомкнутом состоянии поддерживать на входе МК логическую единицу. Во-вторых, надо бороться с дребезгом. На схеме в первой части нет никаких подтягивающих резисторов. Не верьте своим глазам, на самом деле они есть! Просто внутри ATmega. Ну зачем ставить внешние, если можно использовать готовые внутренние? Вот так по datasheet-у выглядит логическая схема универсального Pin-а ATmega: Каждый пин может быть и выходом, и входом, переключаться между этими функциями динамически, уметь возвращаться в исходное состояние по сбросу… Именно поэтому так много смущающих неподготовленные умы логических элементов. Но нас интересует один-единственный резистор, обведенный красненьким овальчиком: его номинал составляет 20К, а чтобы подключить его к Vcc, надо открыть полевой транзистор. Программно это делается так: Функцию digitalWrite надо вызывать строго после перевода пина режим INPUT, в противном случае у нее будет другой смысл. Теперь — самое сложное — надо спроектировать интерфейс с человеком. Всем известно, что инструкции не читают, предпочитая полагаться на интуицию и законы подобия. Даже от сверхпедантичного пользователя, вдумчиво штудирующего руководства по эксплуатации нового телевизора или холодильника, нельзя ожидать аналогичного подхода к таймеру с двумя кнопками и четырьмя цифрами — чего там можно напридумывать-то?! Действительно, придумывать особо нечего. Но чтобы описать и запрограммировать это самое ничего, воспользуемся концепцией машины состояний. Для тех, кто хочет подробно разобраться, рекомендую статью в русской википедии. В двух словах: считается, что система может находиться в одном из нескольких состояний. Переход в другое состояние осуществляется по наступлению некоего события (нажали или отпустили кнопку, достигнуто время срабатывания таймера, и т.д). Таким образом, определив все состояния системы и все события, надо написать маленькие фрагменты алгоритма: как реагировать на такой-то сигнал в таком-то состоянии. После обработки события можно изменить состояние системы или оставить ее в текущем. Вот как это будет выглядеть на практике, в нашем случае: 1. Исходное — после включения питания. Таймер бесконечно долго ждет, когда ему определят время срабатывания. В этом состоянии дисплей мигает целиком. 2. Выбор времени — нажимая кнопки, пользователь устанавливает желаемое время до срабатывания, в минутах. Чтобы войти в это состояние, достаточно нажать одну из кнопок. Одна кнопка увеличивает время, вторая — уменьшает. И поскольку клавиши Enter не предусмотрено, будем считать, что вы закончили ввод, если кнопки не трогали 5 секунд подряд и установлено время, отличное от нуля. Пока происходит установка времени, дисплей по-прежнему мигает целиком, отображая текущее установленное число минут. 3. Отсчет времени. В этом состоянии дисплей не мигает, за исключением точек по центру — раз в секунду. Естественно, отображается время, оставшееся до срабатывания — чтобы пользователь всегда знал, сколько еще осталось ждать. Для простоты, кнопки в этом режиме — игнорируем. 4. Сигнализация. Включается пьезоизлучатель — желательно прерывисто, так его лучше слышно (чтобы человеческое ухо не адаптировалось к раздражителю). Дисплей вновь мигает (как в исходном состоянии), но при этом показывает время, на которое был установлен (как бы напоминая «а ставили-то меня вот на сколько минут, вы не забыли?»). По любому нажатию на кнопку снова попадаем в исходное состояние (всего-то надо выключить пьезоизлучатель) — круг замкнулся, таймер снова готов к использованию. Перейдем к рассмотрению скетча. Основной цикл будет выглядеть так:

 void loop() {  indicate();  update_time();  update_alarm();  update_buttons(); } 

Чтобы обеспечить стабильное отображение дисплея, каждая процедура должна работать фиксированное количество времени ( в таком простом устройстве даже не потребовалось изощряться — все именно так ). indicate() — поочередно отображает все четыре цифры на дисплее (включая точки), показывая нам каждую по 1 мс (таким образом, в сумме выполняясь не менее 4 мс). update_time() — отвечает за правильные параметры отображения дисплея ( обновляет высвечиваемую величину, управляет миганием ) и дополнительно следит, когда систему надо переводить в состояние «Сигнализация». update_alarm() — управляет пьезоизлучателем, в том числе его прерывистой трелью. update_buttons() — опрашивает кнопки и, в зависимости от состояния системы, предпринимает соответствующие действия. Состояние системы хранится в глобальной переменной state, имеющий характерный для C тип enum: На самом деле, в state хранится целое число от до 3, но применение enum позволяет безо всяких дополнительных #define придать этим значениям смысл, помогая в конечном итоге разбираться с текстом программы. Ведь куда проще смотрится if (state != E_STARTED), чем if (state!=2). По мере необходимости, все функции в скетче сверяются со значением state: например, update_alarm() не будет пытаться включать пьезоизлучатель, если состояние state не равно E_ALARMED, а update_time() переведет систему в состояние E_ALARMED только из состояния E_STARTED и только по истечению установленного времени. Кстати, я забыл упомянуть по борьбу с дребезгом. Так вот — при такой идеологии построения скетча, этой проблемы — нет. Ведь в чем суть дребезга? В момент нажатия кнопки случается переходной процесс, когда контакт еще не до конца нажат. В итоге происходит серия последовательных разрывов-замыканий с высокой скоростью (дребезг). Чтобы программа не реагировала на быстрые смены состояний кнопки, вводятся задержки (иногда их называют умным словом «гистерезис«). Однако, в моем случае на каждое однократное чтение кнопок в процедуре update_buttons() приходится один вызов indicate(), который и обеспечивает ту самую необходимую задержку. Периодически по тексту можно встретить в операторах if такую конструкцию: if (millis() % 1000UL > 500) или (millis() % 1000UL > 500) ? HIGH:LOW Это — простой способ определить фазу в мигании или звуковом сигнале. Берем текущее время в миллисекундах, выделяем остаток от деления на 1000 и получаем таким образом число от 0 до 999 — это число миллисекунд в текущем времени. Далее сравниваем с границей ( 500 ) и решаем — включить или выключить наше устройство. Увеличивая или уменьшая остаток от деления, можно ускорить или замедлить мигание, а изменяя соотношение с границей — скважность, т.е. преобладание одного состояния над другим. Вот полный текст скетча для скачивания: cook_timer_1.0.zip. Кстати, в нем есть небольшая ошибка в процедуре установки времени, попробуйте ее отыскать в качестве тренировки (или развлечения 😉 В последней части я добавлю схему внешнего питания, спаяю и упакую таймер в корпус, а затем буду бороться со злыми духами аппаратной части (не путать с багами в скетче ;). Так что, окончание обязательно следует.

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

const int ledPin = 2;  void setup() {     pinMode(ledPin, OUTPUT); }  void loop() {     digitalWrite(ledPin, HIGH);     delay(500);     digitalWrite(ledPin, LOW);     delay(500);  }

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

А что, если теперь мы захотим мигать одновременно двумя светодиодами с разным периодом? Допустим, первый светодиод должен включаться и выключаться каждую секунду, а второй каждые 300 миллисекунд.

Чтобы решить эту задачу, нам придется отказаться от функции delay. Это может быть тяжелым ударом для каждого новичка, но в серьезных программах delay никто не использует 🙂 Если мы научимся обходить эту функцию, мы автоматически научимся одновременному выполнению задач. Вперед!

Таймеры, секундомеры и таймаут

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

Если вы внимательно посмотрите на этот алгоритм, то поймете, что он полностью отражает действия живого человека. Часто, вместо секундомера мы используем внутренний биологический таймер. У нас есть чувство времени, которое особенно развито у музыкантов.

Итак, алгоритм мы имеем. Как его переложить на программу для Ардуино? Нам потребуется электронный аналог секундомера — таймер. Благо он имеется во всех контроллерах, включая и Ардуино. Таймер — это устройство, которое умеет точно отсчитывать время. Запускается и обнуляется он каждый раз во время подачи питания на контроллер. А чтобы узнать, сколько времени «натикало» на таймере с момента запуска, мы будем использовать функцию millis:

int time = millis();

эта функция не имеет аргументов; она опрашивает таймер и возвращает количество миллисекунд, которые прошли с момента запуска Ардуино.

Теперь у нас есть всё, чтобы записать программу для Ардуино.

const int ledPin = 2;  unsigned long next_time; // время очередного переключения первого светодиода int timeout = 500; // половина периода мигания int led_state = 0; // начальное состояние светодиода - выключен  void setup() {     pinMode(ledPin, OUTPUT);      digitalWrite(ledPin, led_state); // гасим светодиод     next_time = millis() + timeout; // вычисляем время следующего переключения }  void loop() {     int now_time = millis(); // текущее время     if( now_time >= next_time ){ // если текущее время превысило намеченное время, то         next_time = now_time + timeout; // вычисляем время следующего переключения         led_state = !led_state; // меняем состояние на противоположное         digitalWrite(ledPin, led_state); // зажигаем или гасим светодиод     } }

Загружаем программу на Ардуино. Светодиод будет мигать точь в точь, как в первой программе. Но теперь без delay! Переходим к следующем шагу.

Одновременное выполнение действий с разным периодом

У нас есть программа, которая совершает действие каждые 500 миллисекунд. Добавим в неё второе действие, но уже с другим периодом — 150 мс.

const int ledPin_1 = 2; const int ledPin_2 = 3;  unsigned long next_time_1; // время очередного переключения первого светодиода unsigned long next_time_2; // ... второго светодиода int timeout_1 = 500; // половина периода мигания первого светодиода int timeout_2 = 150; // ... второго светодиода int led_state_1 = 0; // начальное состояние первого светодиода - выключен int led_state_2 = 0; // ... второго светодиода  void setup() {     pinMode(ledPin_1, OUTPUT);     pinMode(ledPin_2, OUTPUT);      digitalWrite(ledPin_1, led_state_1); // гасим первый светодиод     digitalWrite(ledPin_2, led_state_2); // гасим второй светодиод      next_time_1 = millis() + timeout_1; // вычисляем время следующего переключения первого светодиода     next_time_2 = millis() + timeout_2; // ... второго светодиода }  void loop() {     int now_time = millis(); // текущее время     if( now_time >= next_time_1 ){ // если текущее время превысило намеченное время, то         next_time_1 = now_time + timeout_1; // вычисляем время следующего переключения         led_state_1 = !led_state_1; // меняем состояние на противоположное         digitalWrite(ledPin_1, led_state_1); // зажигаем или гасим светодиод     }      now_time = millis();     if( now_time >= next_time_2 ){         next_time_2 = now_time + timeout_2;         led_state_2 = !led_state_2;         digitalWrite(ledPin_2, led_state_2);     } }

Загружаем программу, включаем питание. Теперь светодиоды мигают с разным периодом!

Заключение

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

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

image

Прерывания по таймеру позволяют выполнять задачу с очень определенными временными интервалами независимо от того, что еще происходит в вашем коде. В этом руководстве я объясню, как настроить и выполнить прерывание в Clear Timer в режиме сравнения совпадений или в режиме CTC. Перейдите прямо к шагу 2, если вы ищете образец кода.

Обычно, когда вы пишете набросок Arduino, Arduino выполняет все команды, инкапсулированные в функции loop () {}, в том порядке, в котором они написаны, однако сложно определить время событий в loop (). Некоторые команды выполняются дольше других, некоторые зависят от условных операторов (if, while …), а некоторые библиотечные функции Arduino (например, digitalWrite или analogRead) состоят из множества команд. Прерывания таймера Arduino позволяют на мгновение приостановить нормальную последовательность событий, происходящих в функции loop (), с точно установленными интервалами, пока вы выполняете отдельный набор команд. Как только эти команды будут выполнены, Arduino снова обнаружит, где это было в цикле ().

Прерывания полезны для:

Есть несколько способов сделать прерывания, сейчас я сосредоточусь на типе, который я считаю наиболее полезным / гибким, называемом Очистить таймер в режиме сравнения совпадений или режим CTC. Кроме того, в этом руководстве я напишу конкретно о таймерах для Arduino Uno (и любого другого Arduino с ATMEL 328/168 … Lilypad, Duemilanove, Diecimila, Nano …). Основные идеи, представленные здесь, применимы и к Mega и более старым платам, но настройка немного отличается, и таблица ниже относится к ATMEL 328/168.

Расходные материалы:

Шаг 1: Прескалеры и регистр сравнения совпадений

image

Uno имеет три таймера, которые называются timer0, timer1 и timer2. Каждый из таймеров имеет счетчик, который увеличивается на каждый тик часов таймера. Прерывания таймера CTC срабатывают, когда счетчик достигает заданного значения, сохраненного в регистре сравнения сравнения. Как только счетчик таймера достигнет этого значения, он очистится (обнулится) на следующем такте часов таймера, затем он продолжит отсчет до значения сравнения сравнения снова. Выбрав значение сравнения и установив скорость, с которой таймер увеличивает счетчик, вы можете контролировать частоту прерываний таймера. Первый параметр, который я рассмотрю, — это скорость, с которой таймер увеличивает счетчик. Часы Arduino работают с частотой 16 МГц, это самая высокая скорость, с которой таймеры могут увеличивать свои счетчики. На частоте 16 МГц каждый такт счетчика представляет 1/16 000 000 секунд (~ 63 нс), поэтому счетчику потребуется 10/16 000 000 секунд, чтобы достичь значения 9 (счетчики имеют индекс 0), и 100/16 000 000 секунд, чтобы достичь значения из 99 Во многих ситуациях вы обнаружите, что установка скорости счетчика на 16 МГц слишком быстрая. Timer0 и timer2 — это 8-битные таймеры, что означает, что они могут хранить максимальное значение счетчика 255. Timer1 — это 16-битный таймер, что означает, что он может хранить максимальное значение счетчика 65535. Как только счетчик достигнет своего максимума, он вернется к нулю. (это называется переполнением). Это означает, что на частоте 16 МГц, даже если мы установим регистр сравнения сравнения на максимальное значение счетчика, прерывания будут происходить каждые 256/16 000 000 секунд (~ 16us) для 8-битных счетчиков и каждые 65 536/16 000 000 (~ 4 мс) секунд для 16-битный счетчик. Очевидно, что это не очень полезно, если вы хотите прерывать только раз в секунду. Вместо этого вы можете контролировать скорость приращения счетчика таймера, используя то, что называется прескалером. Прескейлер определяет скорость вашего таймера в соответствии со следующим уравнением: (скорость таймера (Гц)) = (тактовая частота Arduino (16 МГц)) / предварительный масштаб Таким образом, 1 прескалер увеличит счетчик на 16 МГц, 8 прескалер увеличит его на 2 МГц, 64 прескалер = 250 кГц и так далее. Как указано в таблицах выше, прескалер может быть равен 1, 8, 64, 256 и 1024. (Я объясню значение CS12, CS11 и CS10 на следующем шаге.) Теперь вы можете рассчитать частоту прерываний с помощью следующего уравнения: частота прерывания (Гц) = (тактовая частота Arduino 16 000 000 Гц) / (прескалер * (сравнить регистр совпадений + 1)) +1 там, потому что регистр сопоставления совпадений равен нулю переставив вышеприведенное уравнение, вы можете найти значение регистра сравнения совпадений, которое даст желаемую частоту прерываний: регистр сравнения сравнения = 16 000 000 Гц / (прескалер * желаемая частота прерывания) — 1 помните, что когда вы используете таймеры 0 и 2, это число должно быть меньше 256 и меньше 65536 для timer1 так что если вы хотите прерывание каждую секунду (частота 1 Гц): сравнить регистр совпадений = 16 000 000 / (прескалер * 1) -1 с прескалером 1024 вы получаете: сравнить регистр совпадений = 16 000 000 / (1024 * 1) -1 = 15,624 так как 256 <15,624 <65,536, вы должны использовать timer1 для этого прерывания. </p>

Шаг 2: Структурирование прерываний по таймеру

image

Код установки таймера выполняется внутри функции setup () {} в эскизе Arduino. Код, используемый для настройки прерываний по таймеру, немного утомителен, но на самом деле это не так сложно. Я просто копирую один и тот же основной кусок кода, меняю прескалер и сравниваю регистр совпадений, чтобы установить правильную частоту прерываний. Основная структура установки прерывания выглядит следующим образом:

// прерывания по таймеру // Аманда Гассаи // июнь 2012 г. //http://www.instructables.com/id/Arduino-Timer-Interrupts/ / * * Эта программа является свободным программным обеспечением; вы можете распространять его и / или изменять * в соответствии с условиями Стандартной общественной лицензии GNU, опубликованной * Free Software Foundation; либо версия 3 лицензии, либо * (по вашему выбору) любая более поздняя версия. * * / // настройка таймера для timer0, timer1 и timer2. // Для arduino uno или любой платы с ATMEL 328/168 .. diecimila, duemilanove, lilypad, nano, mini … // этот код активирует все три прерывания таймера arduino. // timer0 будет прерываться с частотой 2 кГц // timer1 будет прерываться с частотой 1 Гц // timer2 будет прерываться с частотой 8 кГц // переменные хранения boolean toggle0 = 0; логическое значение toggle1 = 0; логическое значение toggle2 = 0; void setup () = (1 << OCIE0A); // устанавливаем прерывание timer1 на частоте 1 Гц TCCR1A = 0; // устанавливаем весь регистр TCCR1A на 0 TCCR1B = 0; // то же самое для TCCR1B TCNT1 = 0; // инициализируем значение счетчика равным 0 // устанавливаем регистр сравнения сравнения для приращений 1 Гц OCR1A = 15624; // = (16 * 10 ^ 6) / (1 * 1024) — 1 (должно быть <65536) // включить режим CTC TCCR1B // завершить настройку ISR (TIMER0_COMPA_vect) {// прерывание по таймеру 0 при 2 кГц переключает вывод 8 // генерирует пульсовую волну с частотой 2 кГц / 2 = 1 кГц (требуется два цикла для высокого уровня полного переключения и затем низкого уровня) if (toggle0) {digitalWrite (8, HIGH); toggle0 = 0; } else {digitalWrite (8, LOW); toggle0 = 1; }} ISR (TIMER1_COMPA_vect) {// прерывание по таймеру 1 1 Гц переключает вывод 13 (светодиод) // генерирует пульсовую волну с частотой 1 Гц / 2 = 0,5 кГц (требуется два цикла для достижения высокого уровня полной волны, а затем низкого уровня) if (toggle1) { digitalWrite (13, HIGH); toggle1 = 0; } else {digitalWrite (13, LOW); toggle1 = 1; }} ISR (TIMER2_COMPA_vect) {// прерывание по таймеру 1 8 кГц переключает вывод 9 // генерирует пульсовую волну с частотой 8 кГц / 2 = 4 кГц (занимает два цикла для полной волны — высокий уровень переключения и низкий уровень) if (toggle2) {digitalWrite (9, ВЫСОКО); toggle2 = 0; } else {digitalWrite (9, LOW); toggle2 = 1; }} void loop () {// здесь делают другие вещи} Изображения выше показывают выходные данные этих прерываний таймера. На рис. 1 показана прямоугольная волна, колеблющаяся между 0 и 5 В при частоте 1 кГц (прерывание по таймеру 0), на рис. 2 показан светодиод, подключенный к контакту 13, который включается на одну секунду, а затем выключается на одну секунду (прерывание по таймеру1), на рис. 3 — колебание пульсовой волны. между 0 и 5 В на частоте 4 кГц (прерывание по таймеру 2). <h2>Шаг 3: Пример 1: велосипедный спидометр

image

В этом примере я сделал велосипедный спидометр с приводом от Arduino. Он работает, прикрепляя магнит к колесу и измеряя количество времени, которое требуется, чтобы пройти с помощью магнитного переключателя, установленного на раме, — время одного полного вращения колеса. Я установил таймер 1 на прерывание каждые мс (частота 1 кГц) для измерения магнитного переключателя. Если магнит проходит мимо переключателя, сигнал от переключателя высокий, а переменная «время» обнуляется. Если магнит не находится рядом с переключателем, «время» увеличивается на 1. Таким образом, «время» фактически является измерением количества времени в миллисекундах, которое прошло с момента, когда магнит последний раз прошел магнитный переключатель. Эта информация используется позже в коде для расчета оборотов и миль / ч мотоцикла. Вот фрагмент кода, который устанавливает таймер 1 для прерываний 1 кГц cli (); // остановка прерываний // устанавливаем прерывание timer1 на 1 кГц TCCR1A = 0; // установить весь регистр TCCR1A в 0 TCCR1B = 0; // то же самое для TCCR1B TCNT1 = 0; // установить значение счетчика в 0 // установить счетчик таймера с шагом 1 кГц OCR1A = 1999; // = (16 * 10 ^ 6) / (1000 * 8) — 1 // пришлось использовать 16 битный таймер1 для этого bc 1999> 255, но мог переключаться на таймеры 0 или 2 с большим прескалером // включить режим CTC TCCR1B | = (1 << WGM12); // Установить бит CS11 для 8 прескалеров TCCR1B | = (1 << CS11); // включить прерывание сравнения таймера TIMSK1 | = (1 << OCIE1A); sei (); // разрешить прерывания Вот полный код, если вы хотите посмотреть: <p>

Шаг 4: Пример 2: Последовательная связь

image

Этот проект представляет собой кнопочную панель с подсветкой 4×4. Проект подключается к моему компьютеру через usb, он отправляет информацию о кнопках на компьютер и получает информацию о том, как загораются светодиоды. Вот видео: Для этого проекта я использовал прерывания timer2, чтобы периодически проверять наличие поступающих последовательных данных, читать их и сохранять в матрице ledData . Если вы посмотрите на код, то увидите, что основной цикл скетча — это то, что действительно отвечает за использование информации в ledData для подсветки правильных светодиодов и проверки состояния кнопок (функция, называемая «shift» ( ) «). Процедура прерывания настолько коротка, насколько это возможно — просто проверяйте входящие байты и сохраняйте их соответствующим образом. Вот настройка для timer2: cli (); // остановка прерываний // устанавливаем прерывание timer2 каждые 128us TCCR2A = 0; // установить весь регистр TCCR2A на 0 TCCR2B = 0; // то же самое для TCCR2B TCNT2 = 0; // установить значение счетчика в 0 // устанавливаем регистр сравнения сравнения с шагом 7,8 кГц OCR2A = 255; // = (16 * 10 ^ 6) / (7812,5 * 8) — 1 (должно быть <256) // включить режим CTC TCCR2A | = (1 << WGM21); // Установить бит CS21 для 8 прескалеров TCCR2B | = (1 << CS21); // включить прерывание сравнения таймера TIMSK2 | = (1 << OCIE2A); sei (); // разрешить прерывания Вот полный эскиз Arduino: <p> скачайте патч MaxMSP ниже (он также будет работать в Max Runtime).

Шаг 5: Пример 3: ЦАП

image

В этом проекте я использовал прерывание по таймеру для вывода синусоидальной волны определенной частоты из Arduino. Я припаял простой 8-битный ЦАП R2R к цифровым контактам 0-7. Этот ЦАП был построен из резисторов 10 кОм и 20 кОм, расположенных в многоуровневом делителе напряжения. Я буду публиковать больше о конструкции ЦАП в другом учебном пособии, а пока я включил фото выше. Я подключил выход ЦАП к осциллографу. Если вам нужна помощь в понимании того, как использовать / читать осциллограф, ознакомьтесь с этим руководством. Я загрузил следующий код на Arduino:

Оцените статью
Рейтинг автора
5
Материал подготовил
Илья Коршунов
Наш эксперт
Написано статей
134
А как считаете Вы?
Напишите в комментариях, что вы думаете – согласны
ли со статьей или есть что добавить?
Добавить комментарий