РАБОТАЕМ ПО ТАЙМЕРУ В ARDUINO

Я думаю все знают классический алгоритм создания таймера на 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

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() – запустит таймер на новый круг с прежним периодом

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


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

  • start() – запускает (перезапускает) таймер с последним установленным временем
  • stop() – останавливает таймер
  • resume() – продолжает отсчёт таймера с момента остановки
  • reset() – сбрасывает таймер (отсчёт периода/таймаута начинается заново)
  • isEnabled() – возвращает true, если таймер работает (если он не stop() или не вышел таймаут)
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!");
}

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

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