Многозадачность в Arduino с помощью функции millis()


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

Внешний вид проекта для реализации многозадачности в Arduino

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

Что такое многозадачность

Многозадачность означает выполнение более одной задачи/программы в одно и то же время. В настоящее время практически все операционные системы поддерживают многозадачность. К примеру, ваш персональный компьютер может в одно и то же время принимать электронную почту, открывать сайты в веб-браузере, выполнять какие-нибудь расчеты и т.п. Пользователю кажется что все эти процессы выполняются одновременно, однако у операционной системы на этот счет несколько иной взгляд. Давайте рассмотрим более подробно как выглядит многозадачность с точки зрения операционной системы.

Как выглядит многозадачность с точки зрения операционной системы

Как показано на представленном рисунке, микропроцессор делит общее время на 3 равные части и в каждой части времени выполняет отдельную задачу. Таким образом реализуется концепция многозадачности в большинстве современных компьютерных систем. Аналогичный подход мы применим и в нашем проекте для Arduino, однако распределение времени на разные задачи будет немного отличаться. Поскольку плата Arduino работает на значительно меньшей частоте чем современные персональные компьютеры и объем оперативной памяти у нее значительно меньше, то и время, выделяемое на каждую задачу, будет различным.

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

Почему стоит воздержаться от использования функции delay() в Arduino

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

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

Рассмотрим, к примеру, две кнопки. Допустим, нам нужно переключать состояние двух светодиодов с помощью этих двух кнопок. Например, при нажатии первой кнопки первый светодиод должен включаться на 2 секунды, а при нажатии второй кнопки второй светодиод должен включаться на 4 секунды. Если в данном случае мы будем использовать функцию delay(), то при нажатии первой кнопки программа остановится на 2 секунды и даже если в это время нажать вторую кнопку, то программа просто не сможет обработать нажатие этой кнопки.

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

Почему стоит использовать функцию millis()

Для преодоления проблем, описанных в предыдущем пункте данной статьи, вызванных использованием функции delay(), можно использовать функцию millis(). Использование данной функции позволит более гибко использовать вычислительные возможности платы Arduino, не прибегая к полному останову программы как в случае использования функции delay(). millis() – это функция, которая возвращает количество миллисекунд, прошедших с момента начала выполнения платой Arduino текущей программы. Никакого останова программы во время использования этой функции не происходит. Счетчик этой функции будет переполняться (то есть сбрасываться в ноль) примерно через 50 дней функционирования программы.

Также как и в случае с функцией delayMicroseconds(), в Arduino имеется аналогичная функция и для функции millis() – это функция micros(). Она работает так же как и millis(), но счет ведет в микросекундах. Ее счетчик переполняется примерно через 70 минут функционирования программы.

Чтобы использовать функцию millis() для организации временных задержек в программе вам необходимо сохранять в переменной момент времени когда начало выполняться какое либо действие и затем непрерывно проверять сколько времени истекло с этого момента времени. Для решения стоящей перед нами задачи вначале сохраним текущее время в соответствующей переменной:

Но кроме этой переменной нам будут необходимы еще две переменные чтобы определить что заданный промежуток времени закончился. Мы сохраняем текущее значение времени в переменной currentMillis, но кроме этого нам необходимо знать когда начался заданный временный промежуток и какой временной промежуток (задержка) нам необходим. Для хранения этих значений объявим еще две переменные: previousMillis и period. В переменной period будет храниться значение требуемого (заданного) временного интервала, а в переменной previosMillis – последнее время, когда произошло заданное событие.

Для упрощения понимания этого способа организации временных задержек рассмотрим простой пример с миганием светодиода. Значение переменной period = 1000 будет обозначать, что светодиод будет мигать с задержкой 1 секунда (1000 миллисекунд).

В рассмотренной программе выражение <if (currentMillis - previousMillis >= period)> проверяет прошел ли заданный промежуток времени (1000 мс). Если 1000 мс прошли, светодиод мигнет и затем снова возвратится в исходное состояние. И так будет продолжаться снова и снова пока выполняется программа. Таким образом, мы смогли организовать временную задержку в программе с помощью использования функции millis() вместо использования функции delay(). При этом не происходит останова выполнения программы.

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

Для реализации этой задачи мы будем использовать прерывания. Внешние прерывания в плате Arduino Uno можно задействовать на ее контактах 2 и 3. Более подробно об использовании прерываний в платах Arduino вы можете прочитать в данной статье.

Необходимые компоненты

  1. Плата Arduino Uno (купить на AliExpress).
  2. Три светодиода (любого цвета) (купить на AliExpress).
  3. Резисторы 470 Ом и 10 кОм (купить на AliExpress).
  4. Макетная плата.
  5. Соединительные провода.

Схема проекта

Схема проекта с демонстрацией многозадачности в Arduino представлена на следующем рисунке.

Схема проекта с демонстрацией многозадачности в Arduino

Объяснение программы для Arduino

Полный текст программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.

Программирование мультизадачности для платы Arduino будет основано на использовано функции millis() – подробно эти вопросы рассмотрены ранее в статье. Рекомендуем потренироваться с представленным выше в статье примером с мигающим светодиодом чтобы лучше понять принцип использования функции millis() в программе для организации задержек.

В этой программе мы будем использовать механизм прерываний – при нажатии кнопки в схеме будет срабатывать прерывание.

В начале программы необходимо объявить используемые контакты.

Также объявим две переменные для хранения состояний светодиодов.

Как уже рассматривалось ранее в статье, для организации временных задержек с помощью функции millis() мы будем использовать дополнительные переменные для хранения момента времени начала события и требуемой продолжительности временного интервала. Первый светодиод будет мигать с использованием задержки в 1 секунду, а второй светодиод – с использованием задержки в 200 мс.

Также мы будем использовать еще одну функцию millis() для формирования задержки, необходимой для устранения эффекта дребезга контактов (debounce delay) – чтобы предотвратить “множественные нажатия” при одиночном срабатывании кнопки. Принцип организации этой задержки будет аналогичен рассмотренному подходу. Для формирования этой задержки также заведем еще две переменные.

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

Далее зададим режимы работы используемых контактов – на ввод или вывод данных.

После этого запишем правила обработки прерывания и определим контакт, с которого будет обрабатываться прерывание. При возникновении прерывания будет вызываться функция pushButton_ISR (функция обработки прерывания). Контакт, на котором будет обрабатываться прерывание, будет передаваться в функцию attachInterrupt с помощью функции digitalPinToInterrupt(pin_number) – можно конечно и напрямую просто указывать контакт, но использование функции digitalPinToInterrupt(pin_number) – в данном случае это более рекомендуемый подход.

Функция обработки прерывания в нашем случае будет очень простой – в ней нам будет необходимо всего лишь изменять флаг buttonPushed. Стоит отметить, что функция обработки прерывания должна быть по возможности как можно более короткой.

Бесконечный цикл в нашей программе (Loop) начнется с сохранения значения функции millis() в переменной currentMillis.

В рассматриваемом нами проекте многозадачности для Arduino будет три функции: мигание первого светодиода с частотой в 1 секунду, мигание второго светодиода с частотой 200 мс и, если нажата, кнопка, то включение/выключение третьего светодиода (toggleLed). Для каждой из этих задач будет своя часть кода в программе.

Первая задача – это переключение состояния первого светодиода каждую секунду. Эта секунда будет измеряться с помощью функции millis() – этот подход мы уже подробно рассмотрели ранее в статье.

Аналогичный подход мы будем использовать для переключения состояния второго светодиода каждые 200 мс.

И, наконец, для выполнения третьей задачи будет проверяться состояние флага buttonPushed и после организации задержки в 20 мс, необходимой для устранения эффекта дребезга контактов кнопки (debounce delay) будет переключаться состояние светодиода в зависимости от нажатия кнопки. Нажатия кнопки будут обрабатываться с помощью механизма прерывания.

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

Тестирование проекта многозадачности

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

Исходный код программы (скетча)

Видео, демонстрирующее работу проекта многозадачности для Arduino

(1 голосов, оценка: 5,00 из 5)
Загрузка...
13 880 просмотров

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

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