Pull to refresh

Замена delay() для неблокирующих задержек в Arduino IDE

Reading time 3 min
Views 55K
Первое, с чем сталкивается осваивающий 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
Only registered users can participate in poll. Log in, please.
Имело смысл это делать?
42.03% Полезно. 124
25.42% Хорошая попытка. 75
3.73% Фигня. 11
10.85% Бессмысленный говнокод. 32
1.02% Я сделал лучше! 3
11.19% Всё придумано до нас! 33
5.76% Что такое delay()? 17
295 users voted. 94 users abstained.
Tags:
Hubs:
+26
Comments 48
Comments Comments 48

Articles