Первое, с чем сталкивается осваивающий Arduino новичок, это неприятное свойство функции delay() — блокирование выполнения программы. Множество примеров в интернете используют эту функцию, но практическое применение как-то намекает, что лучше без неё обойтись.

Как и положено начинающему, я изобрёл велосипед сделал свою реализацию неблокирующей задержки. Задача стояла так:

  • Обеспечить псевдомногозадачность, чтобы разные события происходили в своё время, со своими интервалами и не блокировали друг-друга.
  • Было удобно этим пользоваться.
  • Можно было оформить как библиотеку и легко включать в другие проекты без копипастов.

Подсмотрев, что большинство ардуинских библиотек сделаны с применением ООП, я тоже решил не выделываться и написал класс SmartDelay, который можно получить с гитхаба как zip для добавления в Arduino IDE или сделать git clone в ~/Arduino/libraries/

В результате получилось вот такое.

#include <SmartDelay.h>

SmartDelay foo(1000000UL); // в микросекундах

void loop () {
  if (foo.Now()) {
    // Код здесь выполняется каждый интервал в микросекундах, указанный в конструкторе выше.
  }
  //Прочий код
}

Метод Now() возвращает true, если интервал прошёл. В этом случае отсчёт начинается снова на тот же интервал. То есть, Now() каждый раз «перезаряжается» автоматически.

Классическое мигание светодиодом можно сразу усложнить до мигания двумя. Например, лампочки подключены к ножкам 12 и 11, должны мигать с интервалом в 1с и 777мс соответственно.

#include <SmartDelay.h>

SmartDelay led12(1000000UL); 
SmartDelay led11(777000UL);

setup () {
  pinMode(12,OUTPUT);
  pinMode(11,OUTPUT);
}

byte led12state=0;
byte led11state=0;

void loop () {
  if (led12.Now()) {
      digitalWrite(12,led12state);
      led12state=!led12state;
  }
  if (led11.Now()) {
      digitalWrite(11,led11state);
      led11state=!led11state;
  }
}

В цикле можно выполнять ещё что-то, мигание светодиодов не будет блокировать выполнение этого кода.

Понятно, что это не полная замена delay(), который останавливает по��ок на заданное время, надо писать программу всегда как МКА (механизм конечных автоматов). То есть, хранить состояние и в зависимости от него переходить к нужному месту кода.

Старый вариант:

...
action1();
delay(1000);
action2();
delay(500);
action3();
...

Новый вариант:

byte state=0;
SmartDelay d();
...
switch (state) {
case 0: 
  action1(); 
  d.Set(1000000UL);
  state=1;
  break;
case 1:
  if (d.Now()) {
    action2();
    d.Set(500000UL);
    state=2;
  }
  break;
case 2:
  if (d.Now()) {
    action3();
    d.Stop();
    state=0;
  }
  break;
}
...

Метод Set(интервал) устанавливает новый интервал и возвращает старый. Просто посмотреть на интервал можно методом Get();

Stop() останавливает обработку и Now() всегда возвращает false.

Start() возобновляет работу и Now() начинает работать как обычно.

Если надо притормозить подсчёт времени, но не останавливать совсем, то есть метод Wait(). Например, если мигает светодиод 12, а при нажатии кнопки не мигает, достаточно добавить вот такой код в loop() в примере с двумя диодами выше:

...
if (digitalRead(9)) led12.Wait();
...

Так, при высоком уровне сигнала на 9 ноге диод на 12 мигать не будет и продолжит, когда там появится 0.

Когда по такому «таймеру» отрисовывается экран, например, и параллельно обрабатываются кнопки, то бывает нужно перерисовать экран или часть сразу после нажатия на кнопку, а не ждать окончания интервала. Для этого служит метод Reset(), после которого следующий вызов Now() вернёт true. Например:

SmartDelay display(1000000UL);

void loop() {
  if (btClick()) display.Reset(); // ткнул в кнопку, надо отрисовать экранчик.
  if (display.Now()) screenRedraw(); // отрисовка экранчика.
}

Из багов я вижу только, что не учитывается переполнение счётчика микросекунд, а в остальном да, надо почистить код. Мне не нравится, как сделан Reset(), пока думаю.

Объектный подход мне понравился, позволяет спрятать весь код в библиотеку, в которую можно потом уже никогда не заглядывать. Теперь эта маленькая библиотечка живёт во всем моих проектах :)

Проект на GitHub
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Имело смысл это делать?
41.95%Полезно.125
25.5%Хорошая попытка.76
3.69%Фигня.11
10.74%Бессмысленный говнокод.32
1.01%Я сделал лучше!3
11.07%Всё придумано до нас!33
6.04%Что такое delay()?18
Проголосовали 298 пользователей. Воздержались 94 пользователя.