РАБОТАЕМ ПО ТАЙМЕРУ В 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

Компактная альтернатива конструкции таймера с millis() / micros(), обеспечивающая удобную мультизадачность на Arduino

  • Вся работа с таймером заменяется одной функцией
  • Миллисекундный и микросекундный таймер
  • Автоматический и ручной режим работы
  • Новый улучшенный алгоритм счёта времени!

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

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


GTimer_ms();                          // объявление таймера (МИЛЛИСЕКУНДНЫЙ)
GTimer_ms(uint16_t interval);         // объявление таймера с указанием интервала
GTimer_us();                          // объявление таймера (МИКРОСЕКУНДНЫЙ)
GTimer_us(uint16_t interval);         // объявление таймера с указанием интервала
void setInterval(uint16_t interval);  // установка интервала работы таймера
void setMode(mode mode);              // установка типа работы: AUTO или MANUAL (MANUAL нужно вручную сбрасывать reset)
boolean isReady();                    // возвращает true, когда пришло время. Сбрасывается в false сам (AUTO) или вручную (MANUAL)
void reset();                         // ручной сброс таймера на установленный интервал
void stop();                          // остановить таймер
void start();                         // продолжить счёт

ПРИМЕРЫ


#include "GyverTimer.h"
GTimer_ms myTimer;               // создать таймер

void setup() {
  Serial.begin(9600);
  myTimer.setInterval(500);   // настроить интервал
}

void loop() {
  if (myTimer.isReady()) Serial.println("Timer!");
}
/*
 Пример работы в ручном режиме (отложенный пуск)
*/

#include "GyverTimer.h"
GTimer_ms myTimer;                // создать таймер

void setup() {
  Serial.begin(9600);
  myTimer.setInterval(5000);   // настроить интервал 5 сек
  myTimer.setMode(MANUAL);     // ручной режим
  Serial.println("Start");
  Serial.println("Wait 5 sec");
}

void loop() {
  if (myTimer.isReady()) Serial.println("Timer!");
}
#include "GyverTimer.h"

// создать таймер, в скобках период в миллисекундах
GTimer_ms myTimer1(500);
GTimer_ms myTimer2(600);
GTimer_ms myTimer3(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!");
}
// пример с таймером на micros

#include "GyverTimer.h"
GTimer_us myTimer(1500);    // создать таймер 1500 мкс = 1,5 миллисекунды

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (myTimer.isReady()) Serial.println("Timer!");
}

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

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