Хабрахабр

Таймеры и многозадачность на Ардуино

image

Поводом для написания этой статьи послужили лекции Олега Артамонова @olartamonov для студентов МИРЭА в рамках IoT Академии Samsung, а точнее, высказывание Олега, цитата (2-я лекция, 1:13:08):
Сегодня мы поговорим о такой актуальной теме, как таймеры и организация многозадачности на Arduino.

«Есть, например, задачи на которых можно сломать большинство ардуинщиков, особенно начинающих, попросите их помигать пятью разными светодиодами с разной частотой и периодом и так, чтобы ещё период можно было индивидуально для каждого светодиода изменять...»

Судя по высказываниям Олега, у него весьма превратное представление об Arduino вообще и об «ардуинщиках» в частности. Мигание пятью светодиодами в означенных им режимах это абсолютно тривиальная задача для Arduino, а для Arduino Mega Server это вообще не задача, а сущее недоразумение — его штатными средствами организуется многозадачность, которая легко управляет сотнями различных сущностей (светодиодов, сервоприводов, шаговых моторов и т. д.) в реальном времени.

Давайте вместе разберёмся как организовать многозадачность на Arduino, а заодно поможем студентам МИРЭА избавится от навязанных им стереотипов восприятия по отношению к социо-культурному и технологическому феномену нашего времени под названием Arduino.

Лекции Олега Артамонова

Нужно отдать должное, сами лекции Олега хороши — в них даётся много полезной и хорошо структурированной информации о микроконтроллерах и я бы рекомендовал всем заинтересованным в этом вопросе с ними ознакомиться. Единственным недостатком этих лекций мне показался неприкрытый техно-снобизм в отношении Arduino, которая выступает в них в роли «мальчика для битья».

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

На Arduino можно делать потрясающие многозадачные системы в которых в (псевдо, естественно) многозадачном режиме одновременно работают десятки и сотни сущностей (светодиодов, датчиков, актуаторов, сервоприводов, шаговых моторов, беспроводных и проводных интерфейсов и т. д.).

Вот проект Зимнего сада («Умной теплицы») в котором в реальном времени в многозадачном режиме работают следующие сущности: Не будем далеко ходить за примерами.

image

Пользователь имеет дело только с «базой», работа nRF24 партнёра полностью прозрачна для него. Топология распределённого nRF24 контроллера с огромном числом подключённого и работающего в реальном времени оборудования. И, да, это Arduino.

На «базе»:

— 7 сервоприводов
— 9 шаговых моторов
— 6 реле
— 3 датчика влажности почвы
— 2 датчика освещённости
— Датчик уровня воды
— Датчик влажности и температуры воздуха

На nRF24 удалённой части:

— 12 датчиков влажности почвы
— 12 реле
— 3 шаговых мотора
— 2 датчика освещённости
— Датчик уровня воды

Кроме этого, в реальном времени функционирует собственно сама nRF24 связь между двумя распределёнными частями системы и Ethernet интерфейс сервера и серверный движок, обеспечивающий веб-интерфейс пользователя системы.

Что очевидным образом никак не согласуется с высказыванием о том, что «на Arduino невозможна настоящая многозадачность и мигать даже пятью светодиодами на ней проблематично». Итого, в реальном времени, в многозадачном режиме на 8-битной Меге функционирует как минимум 60 сущностей (и это не считая множества сервисов самой операционной системы AMS, с ними число сущностей приблизится к сотне).

Пара слов в защиту Arduino

(Хотя очевидно, что Arduino как социо-культурный и технологический феномен с многомиллионной армией поклонников и многими тысячами потрясающих проектов в защите не нуждается.)

И пользователю нет абсолютно никакой разницы, что «крутится» внутри его маленького кусочка кремния — «чистая» Arduino, RTOS, RIOT OS, AMS или какая-то другая математическая абстракция представления и управления железными ресурсами контроллера. Я много раз говорил и ещё раз повторю, что Arduino в своей софтверной составляющей это, по сути, просто один из возможных уровней абстракции (как и любой другой) со своими достоинствами и недостатками.

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

Как это работает?

Сама по себе многозадачность на микроконтроллерах может быть организована разными способами, в данном случае речь пойдёт о самом простом — процессы по очереди получают управление и добровольно отдают его после использования своего кванта времени. Этот способ, конечно, не лишён очевидных недостатков, но, как говорится, практика — критерий истины и он прекрасно зарекомендовал себя в реальных условиях: он используется как в стандартных дистрибутивах Arduino Mega Server, так и во множестве проектов на AMS Pro. И эти системы работают в режиме 24/7 и имеют подтверждённые аптаймы во многие месяцы беспроблемной работы.

image

Обратите внимание на два последних индикатора «CPU» — при этом даже на 8-битной Меге загрузка процессора ровна нулю (то есть система полностью свободна). Это индикация около сотни сущностей распределённой nRF24 системы, управляемых независимо друг от друга в реальном времени.

Немного о таймерах

Для организации управления сложными системами недостаточно просто передавать по очереди управление между процессами и наряду с автоматической передачей управления в AMS используются различные виды таймеров: циклические, циклические с заданным количеством повторений (пакетные), одиночные, рандомные, смешанные и т. д. Всё это организовано нативными средствами Arduino и не использует прерывания или прямое программирование таймеров микроконтроллера (но прерывания, конечно же, использоваться системой «по их прямому назначению»).

Не начнутся. Что опять же вступает в прямое противоречие с высказыванием «На 3 светодиода железных таймеров хватит, с дальше у ардуинщиков начнутся проблемы». И, при желании, мы можем наделать себе ещё сколько угодно новых и сколь угодно экзотических. Нам доступны любые типы таймеров в любом количестве.

Основной кейс

Основной кейс при данном типе организации многозадачности — это создавать так называемый «неблокирующий» код, то есть код, который не использует функцию delay(), которая просто приостанавливает выполнение программы на заданное время.

Реальное время

Описываемый способ реализации многозадачности можно охарактеризовать как «soft-realtime», типовое время задержки в системе составляет 10 мс (но пиковые задержки могут быть значительно больше и не нормируются). Это накладывает известные ограничения на спектр применения данного решения, но для большинства «бытовых» задач (и не только) он прекрасно подходит, см. пример выше.

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

Это общее теоретическое описание работы многозадачности в Arduino вообще и в AMS в частности, теперь перейдём к рассмотрению практических примеров.

Циклические таймеры

image

Это таймеры (в терминологии AMS «cycles»), которые включаются через определённые, заранее заданные промежутки времени и используются для активации циклических процессов. Рассмотрим реализацию самых простых циклических таймеров.

Вообще, таймеры программно лучше оформлять в виде объектов, но в стандартной поставке Arduino Mega Server эти таймеры реализованы в виде функций, поэтому, для начала, рассмотрим их в этой ипостаси.

Если нужно использовать другой интервал срабатывания, то просто используем нужную переменную вместо cycle1s. Использовать циклические таймеры очень просто: достаточно поместить код, который нужно периодически выполнять, между скобками оператора if. Различных циклов можно сделать сколько угодно — система даже на 8-битной Меге без проблем потянет обслуживание буквально сотен таких таймеров (только, естественно, нужно не забывать чтобы вызываемый код не был блокирующим).

if (cycle1s)

Теперь организация работы таймеров. Определение управляющих переменных в главном файле:

// Cycles
bool cycle1s = false;
bool cycle5s = false;
bool cycle20s = false;
bool cycle30s = false;
bool cycle1m = false;
bool cycle3m = false;
bool cycle5m = false;

Набор интервалов может быть расширен любыми нужными значениями от десятков миллисекунд до суток и более.

Модуль «Timers»:

/* Module Timers part of Arduino Mega Server project
*/ // Cycles
unsigned long timeSec;
unsigned long timer1s;
unsigned long timer5s;
unsigned long timer20s;
unsigned long timer30s;
unsigned long timer1m;
unsigned long timer3m;
unsigned long timer5m; void timersInit() { unsigned long uptimeSec = millis() / 1000; timeSec = uptimeSec; timer1s = uptimeSec; timer5s = uptimeSec; timer20s = uptimeSec; timer30s = uptimeSec; timer1m = uptimeSec; timer3m = uptimeSec; timer5m = uptimeSec;
} void timersWorks() { timeSec = millis() / 1000; if (timeSec - timer1s >= 1) { timer1s = timeSec; cycle1s = true; if (timeSec - timer5s >= 5) {timer5s = timeSec; cycle5s = true;} if (timeSec - timer20s >= 20) {timer20s = timeSec; cycle20s = true;} if (timeSec - timer30s >= 30) {timer30s = timeSec; cycle30s = true;} if (timeSec - timer1m >= 60) {timer1m = timeSec; cycle1m = true;} if (timeSec - timer3m >= 180) {timer3m = timeSec; cycle3m = true;} if (timeSec - timer5m >= 300) {timer5m = timeSec; cycle5m = true;} }
} void eraseCycles() { cycle1s = false; cycle5s = false; cycle20s = false; cycle30s = false; cycle1m = false; cycle3m = false; cycle5m = false;
}

И далее код подключения этих таймеров в основном цикле. В начале главного цикла проверяется не сработал ли какой-либо из таймеров, а в конце все поднятые флаги таймеров сбрасываются. Любой системный код находится между этими двумя функциями и, что наиболее важно, любой из таймеров можно использовать многократно в любых частях кода системы.

void loop() { timersWorks(); // Код системных процессов eraseCycles();
}

Циклические таймеры в виде объектной библиотеки

Теперь рассмотрим организацию тех же таймеров, но в более правильном объектном виде, оформленном в готовую библиотеку. Назовём её myCycle.

Заголовочный файл в котором представлены объявления класса, методов и некоторых предопределённых констант:

/* myCycle Library part of Arduino Mega Server project
*/ #ifndef _MYCYCLE_H
#define _MYCYCLE_H #define MS_500 500
#define MS_01S 1000
#define MS_02S 2000
#define MS_05S 5000
#define MS_10S 10000
#define MS_13S 13000
#define MS_17S 17000
#define MS_20S 20000
#define MS_30S 30000
#define MS_01M 60000
#define MS_03M 180000
#define MS_05M 300000
#define MS_01H 3600000
#define MS_06H 21600000
#define MS_12H 43200000
#define MS_01D 86400000 class myCycle { private: bool _go; bool _active; unsigned long _start; unsigned long _period; public: myCycle(unsigned long per, bool act); void reInit(unsigned long per, bool act); void reStart(); bool check(); bool go(); void clear(); // active bool active(); void setActive(bool act); // period unsigned long period(); void setPeriod(unsigned long per);
}; // class myCycle #endif // _MYCYCLE_H

И файл реализации в котором находится код библиотеки:

/* myCycle Library part of Arduino Mega Server project
*/ #include "myCycle.h"
#include <Arduino.h> myCycle::myCycle(unsigned long per, bool act) { _go = false; _active = act; _period = per; _start = millis();
} // Methods void myCycle::reInit(unsigned long per, bool act) { _go = false; _active = act; _period = per; _start = millis();
} void myCycle::reStart() { _start = millis();
} bool myCycle::check() { if (millis() - _start >= _period) { _start = millis(); if (_active) { _go = true; } } return _go;
} bool myCycle::go() { return _go;
} void myCycle::clear() { _go = false;
} // Active bool myCycle::active() { return _active;
} void myCycle::setActive(bool act) { _active = act;
} // Period unsigned long myCycle::period() { return _period;
} void myCycle::setPeriod(unsigned long per) { _period = per;
}

Использование этого варианта тоже просто и имеет некоторые преимущества перед «функциональным» вариантом: тут можно очень легко объявлять таймеры с нужными интервалами и не нужно заранее создавать множество таймеров «на всякий случай».

В главном файле:

Подключаем библиотеку:

#include "myCycle.h"

Создаём объекты:

// Cycles
myCycle cycle500(MS_500, true);
myCycle cycle2s(MS_02S, true);
myCycle cycle5s(MS_05S, true);

Добавляем функции обслуживания работы таймеров:

void timersWorks() { cycle500.check(); cycle2s.check(); cycle5s.check();
} void eraseCycles() { cycle500.clear(); cycle2s.clear(); cycle5s.clear();
}

В главном цикле используем объявленные таймеры в любом нужном месте кода:

void loop() { timersWorks(); // Код системных процессов if (cycle5s.go()) { Serial.println(F("cycle5s!")); } eraseCycles();
}

Библиотека имеет несколько более широкий функционал, чем код, приведённый в первом варианте. Например, она содержит методы активации/дезактивации таймеров, установки и получения текущего значения, рестарта и реинициализации таймеров, что ещё больше расширяет возможности использования их в практических задачах.

Другие виды таймеров на Arduino

image

Если эта тема будет интересна, то можно будет написать отдельную статью об этом. Чтобы не загромождать статью, я не буду здесь приводить код и разбирать работу всех возможных типов таймеров — все они строятся по одним и тем же принципам. Здесь я только дам общее описание таймеров, которые используются в AMS и прекрасно себя зарекомендовали на практике.

Циклические с заданным количеством повторений (пакетные)

Это таймеры, которые срабатывают заранее определённое количество раз. Например, вам нужно делать 3 попытки отправки сообщения по беспроводному каналу nRF24. Таймер активируется только 3 раза и соответственное количество раз делаются попытки отправки сообщений.

Тут же возможны различные расширения функциональности типа активации/дезактивации таймера в зависимости от определённых условий и т. п.

Одиночные

Это различные вариации на тему «автозагрузки», когда какое-либо действие выполняется через определённый интервал времени после старта контроллера или какого-либо события.

Рандомные

Иногда требуется не строгая периодичность выполнения каких-то действий, а наоборот некий разброс срабатываний возле некоторого среднего значения, для этих целей применяются так называемые рандомные таймеры.

Если одна система будет посылать свои сообщения другой, согласуясь со срабатыванием обычного таймера, например, раз в 20 секунд, то эти сообщения будут приходить на вторую систему в строго определённой «фазе», которая может попадать в «проблемный» интервал работы цикла второго контроллера, в результате его работа может быть нестабильной. Например, у вас есть два распределённых контроллера, которые связаны друг с другом по беспроводному каналу. Если в этом случае использовать рандомный таймер, то он «размажет» по времени моменты прихода сообщений от первого контроллера и проблемы удастся избежать.

И вы можете ознакомиться с их реализацией — стандартный дистрибутив Arduino Mega Server содержит код такого таймера. Это только абстрактный пример для понимания того, что собой представляют рандомные таймеры.

Смешанные

Тут полная свобода действий, можно как угодно комбинировать работу различных типов таймеров, используя запуск одних таймеров от других и их встроенную логику типа активации/дезактивации по условиям, изменение периода срабатывания «на лету» и т. п.

Нет практически никаких ограничений на количество таймеров и логику работы — их может быть сотни, даже на 8-битном контроллере.

Межпроцессное взаимодействие

Межпроцессное взаимодействие, семафоры, почтовые ящики и прочие атрибуты многозадачных систем организуются на Arduino тоже без каких-либо проблем — их можно организовать любым удобным для вас способом — начиная от передачи параметров через статические переменные и заканчивая упаковкой логики в любые классы и объекты, тут нет абсолютно никаких ограничений и проблем.

Подробное описание межпроцессного взаимодействия выходит за рамки этой статьи, но вы можете скачать себе дистрибутив Arduino Mega Server для любой платформы и самостоятельно ознакомиться с его устройством и работой.

Заключение

В общем, можно сказать, что средствами Arduino можно создать любые виды таймеров, запускать любое их количество и использовать любое их сочетание, другими словами, Arduino может удовлетворить любой каприз по таймерному (псевдо) многозадачному управлению сложными микроконтроллерными системами.

И это не должно быть секретом для студентов МИРЭА, как будущих инженеров микропроцессорных систем, ведь эти принципы можно применять на любой платформе.

S.
Недавно вышла новая, 0. P. Код таймеров, описанный в этой статье, взят из этих дистрибутивов. 17 версия Arduino Mega Server для Arduino Mega, Due, 101 и M0, которую вы можете скачать на официальной странице загрузки.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»