Ардуино мультизадачность: выполняем несколько задач одновременно

Мультизадачность с Ардуино: несколько процессов одновременно

Этот материал описывает метод работы с Ардуино при котором создается иллюзия, что плата выполняет одновременно несколько задач.

Используя этот метод, Ардуино сможет одновременно делать ряд процессов:

  • переключение светодиода каждые 300 мс (миллисекунды);
  • прокрутка строки текста каждые 250 мс;
  • изменение настраиваемой гистограммы каждые 100 мс.

Вы сможете использовать данный материал как шаблон для своих собственных проектов.

Комплектующие

Для этого проекта требуется очень немного деталей:

  • 1x – микроконтроллер Arduino Uno R3
  • 1x – I2C ЖК-дисплей 16×2
  • 1x – USB-кабель arduino-to-PC
  • 4x – провода для перемычек с разъемами «папа-мама»

Все детали можно заказать в любом интернет-магазине.

Схема подключения

Изображение выше показывает как нам нужно всё соединить.

Код для Ардуино

Код вы найдете ниже. Вы можете скопировать его, либо скачать файл .ino и вставить его содержимое в новый эскиз arduino.

Вам также потребуется установить библиотеку I2C для вашего ЖК-дисплея. Можно посмотреть в наших библиотеках на сайте или использовать библиотеку скачанную с http://arduino-info.wikispaces.com/LCD-Blue-I2C#v1. Если у вас есть другой чип, а не тот, что отмечен на фото выше – вам нужно будет скачать другую библиотеку.

После того как вы найдете подходящую библиотеку, загрузите zip-файл и установите её, используя нашу инструкцию.

Теория

Пустой цикл loop()<> в коде arduino заставит ардуино «вращаться» (прим.ред. – цикличность) несколько тысяч раз в секунду. Давайте посмотрим, что произойдет, когда мы добавим эту задачу

Одна задача.

Следующий код будет отображать «Hello World!» на вашем последовательном мониторе один раз в секунду:

Каждому циклу loop()<> теперь нужно более 1 секунды для завершения из-за команды задержки delay(1000).

Предполагая, что arduino имеет 16-мегагерцовый кристалл, команда delay(1000) тратит 16 000 000 тактов. Другие задачи возможны, если мы устраним эту задержку.

Многозадачность.

Давайте перепишем вышеприведенный фрагмент кода:

“Привет мир!” выведется после чего цикл loop()<> возвращается к цикличности с полной скоростью до тех пор, пока флажок 1 = истина.

Поскольку loop()<> нечего делать, а процесс работает на полной скорости, добавим еще одну задачу.

Эта часть кода заставляет светодиод «переключаться», после чего loop()<> снова возвращается в процесс, пока флаг Flag2 = true (флажок 2 = ложь).

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

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

Многозадачность, скажем так, “коротких” задач, таких как мигание светодиода, довольно простое дело. Но что делать с многозадачностью при “длинных” задачах, такие как прокрутка текстового сообщения например? Это требуют другого подхода.

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

Это достигается за счет использования «статических» переменных* для отслеживания того, как далеко продвинулась задача. В данном случае это показывают функции «scrollMessage ()» и «bargraph()».

* Когда подпрограмма вызывается, все «переменные» забывают свои предыдущие значения, если они не были объявлены «статическими», и в этом случае предыдущее значение доступно при вызове следующей подпрограммы.

Планировщик заданий

Секрет установки флагов – создать цикл в 1 миллисекунду (мс) с использованием одного из таймеров Arduino Uno R3. Можно использовать Timer/Counter 2 (8 бит), который оставляет Timer/Counter 1 (16-бит) свободным для других задач.

1 мс достигается путем деления частоты 16 000 000 Гц на 128 для получения интервала времени 8 микросекунд. Если теперь считать эти импульсы 8 мкс и генерировать прерывание, когда счетчик достигнет 125, то 125 x 8 или 1 мс истечет**.

** Фактически мы загружаем 125-1 = 124 в «сравнительный регистр соответствия», потому что прерывание не происходит до следующего (125-го) тактового импульса.

Флаги устанавливаются путем помещения следующего кода в ISR (процедуру обслуживания прерываний):

Как только счетчик достигнет своего целевого значения, счетчик очищается и устанавливается флаг. Вышеприведенный код занимает очень мало времени для выполнения, так как команд очень мало.

Основной цикл loop()<> видит каждый флаг, который задает планировщик задач и выполняет задачу. Добавление дополнительных задач достаточно просто. Создайте дополнительный счетчик и флаг, а затем имитируйте приведенный выше код. На графике выше показана взаимосвязь между тремя задачами, которые выполняет ардуино в данном примере.

Вы можете заметить, что:

  • иногда loop()<> не имеет задач для выполнения;
  • иногда loop()<> выполняет только одну или две задачи;
  • каждая из задач вызывается разное количество раз;
  • гистограмма продвигается каждые 100 мс;
  • текст прокручивается каждые 250 мс;
  • светодиод переключается каждые 300 мс.

Ключевые моменты

Для успешной многозадачности:

  • Избегайте функций delay() или delayMicroseconds(). Вместо этого
  • создавайте циклы и
  • используйте планировщик задач,
  • а длительные задачи следует рассматривать как серию небольших «кусков»
  • со «статическими» переменными, отслеживающими прогресс.

На этом пока всё про мультизадачность в Ардуино. Хороших вам проектов.

Arduino.ru

Параллельное выполнение 2-3 задач

  • Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии

Доброго времени суток.

пришел наконец ардуино, побаловался с подлючением led, servo, motor.

хочу попробовать соорудить такую схемку: 2-3 датчика движения и включение света на лестничной клетке, в коридоре квартиры и предположим в ванной по датчикам расположенным в этих помещениях. интересен вопрос- возможно ли одновременное управление коммутацией света в двух(трех) местах, отработка предположим сразу по двум датчикам одновременно. именно одновременно. датчики ultrasonic sr04. Как все это соорудить на одном сонаре понятно, а вот на двух расположенных в разных местах. (если одновременно должны сработать именно два сонара в разных помещениях).помимо таймера можно еще что нибудь придумать?

в какую сторону искать?

пока нет сонаров сделал заготовку на кнопках.. вот такой код:

есть вопрос: миллис делал для LED1, поидее в viod loop шунтировать LED1 не надо, но если делаю без шунтирования, то нажимая КНОПКУ 1 надо ждать не отпуская кнопку время когда закончится миллис и только тогда диод включается и свое отрабатывает. не туда поместил код на включение LED1?

можно ли добавить в код миллис и для LED2? чтобы миллис был и для LED 1 и LED2.

конечно объявив соответствующие переменные..

уважаемый Максим помогите пожалуйсто с вопросом в этой ветке

пока нет сонаров сделал заготовку на кнопках.. вот такой код:

есть вопрос: миллис делал для LED1, поидее в viod loop шунтировать LED1 не надо, но если делаю без шунтирования, то нажимая КНОПКУ 1 надо ждать не отпуская кнопку время когда закончится миллис и только тогда диод включается и свое отрабатывает. не туда поместил код на включение LED1?

А что собственно нужно сделать то?

Задержку включения или выключения?

А может и то и другое?

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

2, а лучше три ультрасоника. один на лестничной клетке, второй в прихожей. как человек попадает в зону действия датчика- включается на 3 минуты свет. одновременная работа по двум(хотелось бы и по трем датчикам) ультрасоникам, так как может понадобиться включение света одновременно в двух местах.

не надо весь код. мне бы просто идею подсказать и указать на ошибку в моем коде

Данный вариант работать будет, но не долго. Через

20 дней так как previousMillis имеет тип long , произойдет ее переполнение и в ней начнут сохраняться отрицательные числа, в результате условия millis()>=previousMillis будет верно постоянно до тех пор пока не переполнится millis() последующие

20 дней. То есть данный вариант решения будет

20 дней работать и

20 дней свет не будет включаться. Если же изменить на тип unsigned long , то картина становится получше, но опять таки через

40 дней будет переполнение millis() и если это переполнение попадет на момент пока длится тот самый интервал, то свет не выключится до следующего срабатывания датчика.

Поэтому лучше применять такую конструкцию:

Ну а что бы попросту не тыркать digitalWrite’ы, то так:

а что за конструкция?

Тоже самое что и

Это позволяет выполняться тому что в фигурных скобках только один раз после выполнеия условия millis()-previousMillis1 >= interval , при этом не задействуя еще каких либо дополнительных переменных:

Всё понял, да этот момент я упустил.

Я пока только Учусь 🙂

огромное спасибо. правда я сам хотел дойти до всего это :). но все равно спасибо, (даже нашел опечатку в коде у уважаемого ivan_admin-строка 53:)) ). добавил третий LED, все работает изумительно, осталось дождаться посылку и баловаться с датчиками, хочу два включения света делать по сонарам, а третий по датчику звука. и про программирование- я так понял, все что выполняется в миллис, выполняется независимо от основного кода? миллис это и есть таймер МК? если миллис это таймер, то сколько может циклов миллис может быть в работе одновременно? 3. если циклов миллис в работе может быть три одновременно, то можно ли сделать код к примеру на 4-5 циклов миллис, но работать единовременно только три цикла миллис?

Это таймер(счётчик) как только включили МК он пошёл считать

А сколько раз ты его запоминаешь не имеет значения.

через 50 дней он переполнится и начнёт отсчёт с нуля,

так что об этом нужно позаботиться иначе после его обнуления счет будет гореть около 50 дней 🙂

Можно например просто перезагружать переодически МК

А насчёт опечаток, там много чего ещё можно переделать яж так для примера набросал.

Да и сам ещё только учусь.

спасибо. да и я тоже учусь. еще раз спасибо.

Здравствуйте. подскажите по праздному вопросу. вот к примеру экран TFT

судя по его пинам- все цифровые и аналоговые ножки заняты. Или я может что то не так увидел.

т.е если тфт присобачил то ничего больше не подсоеденишь. только вывод информации?

или есть какие нибудь другие тфт экраны которые подойдут под уно, но чтобы можно было что нибудь еще подвесить на цифровыеаналоговые выходы?

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

за основу взят вот этот скетч(конечно,писал не я,нашел в сети и скорректировал под себя).за основу он,так как он самый большой)

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

ну и вот с этого нужно слепить один скетч.

да,идею понял..буду разбираться. а то как-то немного разочаровался в Ардуино,все расхваливают,а по факту ничего кроме мигания светодиодамирелесервоприводами и прочей примитивщины оно не может..куча входоввыходов,а толку..вот яркий пример моя задача. чтоб ее реализовать,уже нужно как-то ухищраться,что-то выдумывать. безусловно понимаю что это от нехватки моих знаний,но всеравно впечатление какое-то такое=)

У вас нет библиотек от этого lcd?

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

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

Это надо распечатать и в рамочку повешать.

Это надо распечатать и в рамочку повешать.

эпитафия над воротами в кладбище разума “их наипали маркетолохи”.

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

Попробовал залить на ардуино про мини демо-код блинк. заливал через уно(есть usb to rs232 pl2303hx, но на нем нет пина ресет, а с кнопкой не охота было эксперементировать. ), все работает.

Есть вопросы – замерил ток потребляющий УНО ардуино- когда включены 2 реле, в работе 2 сонара, горит 2 LED- ток потребляет около 200 мА., а еще планирую прицепить еще один сонар и одно реле(и еще может, что подцеплю). т.е ток должен будет быть около 300 мА(LED понятно, что запитаю и запитано отдельно). опасаюсь что спалю все. а к мини и вообще боюсь подцеплять. Понятно, что можно через транзисторы, но охота подачей внешнего U на блок реле.Пробовал подавать питание внешнее и на сонары и на реле(GND и +5В.)–увы, не работает. сейчас буду погуглить поискать про внешнее питание, но может кто подскажет что нибудь.

Использую 2-канальный блок реле 5V Two 2 Chanel Relay Modul With optocoupler

в описании указано, что обмотка реле потребляет 15-20мА. т.е получается – 40мА -реле и 160 мА сонары получается, т.е по 80 мА на каждый сонар(сама ардуина почти нечего не ест). завтра попробую посмотреть сколько кушает одно реле, ну и потои один сонар( измеряю на разьме usb). про мини потянет такую нагрузку?

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

Прерывания и многозадачность в Arduino

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

Это могут быть прерывания по таймеру или внешние прерывания, уведомляющие о событиях, воздействующих на систему извне.

Прерывания и их источники

В процессе работы управляющий процессор выполняет определенные операции, а прерывание вызывает их остановку и в соответствие с кодом заставляет выполнить операции с более высоким приоритетом.

Проще говоря, прерывания – это набор приоритетов для тех или иных процессов, исполняемых контроллером.

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

Источником сигнала прерывания может стать таймер Arduino или процесс изменения состояния одного из контактов (пинов). Еще одним источником может стать какой-либо вход внешних прерываний: нужный сигнал появится при изменении его состояния.

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

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

Особенности прерывания по таймеру

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

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

Микроконтроллер Arduino Uno укомплектован тремя таймерами: из них timer0 предназначен для генерации прерываний с интервалом в одну миллисекунду. При этом будет постоянно обновляться счетчик, передающий информацию функции millis(). Вести точный подсчет таймеру позволяет определенная частота, получаемая из 16 МГц процессора.

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

Функция timer0 оперирует тактовым делителем на 64 и изменять это значение не стоит. Оно позволяет получить частоту прерывания, близкую к 1 кГц, оптимальную для целей большинства схемотехников. Если попытаться изменить данный параметр, то можно нарушить работу функции millis().

Регистры сравнения и их роль в генерации прерывания

Регистры сравнения выполняют функцию анализа хранимых данных с текущим состоянием счетчика прерывания. Например, регистр сравнения (OCR0A) эффективно применяется для прерывания в середине счета.

Программный код, пример которого приведен ниже, позволит генерировать функцию TIMER0_COMPA при прохождении счетчика 0xAF:

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

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

Какие внешние воздействия вызывают прерывание

Вызвать внешние прерывания могут определенные действия из внешней среды. Это может быть простым нажатием на кнопку или срабатыванием используемого датчика. При этом нет необходимости вести постоянный опрос вывода GPIO о происходящих изменениях.

Микроконтроллер Arduino может иметь несколько пинов, способных обрабатывать внешние прерывания. Так, на плате Arduino Uno их два, а на Arduino Mega 2560 – 6. Продемонстрировать их функционал можно с помощью кнопки сброса сервопривода. Для этого при написании кода в класс Sweeper необходимо добавит функцию reset(). Она способна установить нулевое положение и перетаскивать в нее сервопривод.

Соединить обработчик с внешним прерыванием поможет другая функция – attachInterrupt(). На приведенных в качестве примеров микроконтроллерах Interrupt0 реализована на втором контакте. Она сообщает микроконтроллеру о том, что на данном входе ожидается спад сигнала.

Достаточно нажать кнопку и сигнал действительно падает до минимального уровня, вызывая обработчик Reset.

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

Полный код программы с таймерами и внешними прерываниями:

Библиотеки прерываний

У схемотехников, благодаря Всемирной паутине есть доступ к широкому кругу библиотек, которые существенно облегчат работу с таймерами. Большинство из них предназначены для тех, кто применяет функцию millis(), но есть и такие, которые позволяют произвести желаемую настройку таймеров и сгенерировать прерывания. Оптимальным вариантом для этого являются библиотеки TimerThree и TimerOne, разработанные Paul Stoffregan.

Благодаря им, схемотехники получают широкий выбор возможностей, с помощью которых можно сконфигурировать прерывания с помощью таймера. Первая из этих библиотек не работает с Adruino Uno, но прекрасно зарекомендовала себя с платами Teensy, микроконтроллерами Adruino Mega2560 и Adruino Leonardo.

Недостатком Adruino Uno является наличие всего двух ходов, предназначенных для работы с внешними прерываниями. Если требуется большее количество подобных пинов, то отчаиваться не стоит, ведь этот микроконтроллер поддерживает pin-change – прерывания по изменению входа и работает это на всех восьми входах.

Их отличие от простых внешних прерываний – в сложности обработки, так как схемотехнику требуется отслеживать последнее из известных состояний пинов. Только в этом случае можно будет понять, какой из них вызвал прерывание.

Наиболее информативное и практичной библиотекой для прерываний по изменению входа является PinChangeInt.

Прерывания: основные правила работы

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

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

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

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

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

Если вызов функции Update() все же необходим, то предварительно необходимо будет проверить переменную состояния. Это позволит выяснить, необходима ли последующая обработка.

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

Какими функциями оперирует тот или иной таймер?

Для микроконтроллера Arduino Uno у каждого из трех таймеров свои операции.

Так Timer0 отвечает за ШИМ на пятом и шестом пине, функции millis(), micros(), delay().

Другой таймер – Timer1, используется с ШИМ на девятом и десятом пине, с библиотеками WaveHC и Servo.

Timer2 работает с ШИМ на 11 и 13 пинах, а также с Tone.

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

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

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

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

Как выполнять параллельные задачи (Threads) в программе для Arduino

В микропроцессорной технике параллельно выполняющиеся задачи называются тредами (Threads) – потоками. Это очень удобно, ведь часто бывает необходимо выполнять несколько операций одновременно. А можно ли заставить микроконтроллер Arduino выполнять сразу несколько задач, как настоящий процессор? Сейчас посмотрим.

  • Arduino UNO или иная совместимая плата;
  • 1 светодиод (вот из такого набора, например);
  • 1 пьезопищалка (вроде этой);
  • соединительные провода (рекомендую вот такой набор);
  • макетная плата (breadboard);
  • персональный компьютер со средой разработки Arduino IDE.

Инструкция по созданию параллельных потоков в программе для Arduino

1 Схема подключения для демонстрации потоков в работе с Arduino

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

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

Если вы подключаете светодиод к цифровому выводу, отличному от “13”, не забывайте о токоограничивающем резисторе примерно на 220 Ом.

Схема подключения к Arduino для демонстрации параллельных потоков

2 Управление светодиодом и пьезоизлучателемс помощью оператора delay()

Напишем вот такой скетч и загрузим его в Ардуино.

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

Дело в том, что обычным образом эту задачу не решить. Задачи выполняются микроконтроллером строго последовательно. Оператор delay() задерживает выполнение программы на указанный промежуток времени, и пока это время не истечёт, следующие команды программы не будут выполняться. Из-за этого мы не можем задать разную длительность выполнения для каждой задачи в цикле loop() программы. Поэтому нужно как-то сымитировать многозадачность.

3 Параллельные процессы без оператора “delay()”

Вариант, при котором Arduino будет выполнять задачи псевдо-параллельно, предложен разработчиками Ардуино. Суть метода в том, что при каждом повторении цикла loop() мы проверяем, настало ли время мигать светодиодом (выполнять фоновую задачу) или нет. И если настало, то инвертируем состояние светодиода. Это своеобразный вариант обхода оператора delay().

Существенным недостатком данного метода является то, что участок кода перед блоком управления светодиодом должен выполняться быстрее, чем интервал времени мигания светодиода “ledInterval”. В противном случае мигание будет происходить реже, чем нужно, и эффекта параллельного выполнения задач мы не получим. В частности, в нашем скетче длительность изменения звука сирены составляет 200+200+200+200 = 800 мсек, а интервал мигания светодиодом мы задали 200 мсек. Но светодиод будет мигать с периодом 800 мсек, что в 4 раза больше того, что мы задали.

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

В данном случае нужно было бы для блока управления звуком сирены также проверять, пришло время или нет, а не использовать delay(). Но это бы увеличило количество кода и ухудшило читаемость программы.

4 Использование библиотеки ArduinoThreadдля создания параллельных потоков

Чтобы решить поставленную задачу, воспользуемся замечательной библиотекой ArduinoThread, которая позволяет с лёгкостью создавать псевдо-параллельные процессы. Она работает похожим образом, но позволяет не писать код по проверке времени – нужно выполнять задачу в этом цикле или не нужно. Благодаря этому сокращается объём кода и улучшается читаемость скетча. Давайте проверим библиотеку в действии.

Библиотека ArduinoThread

Первым делом скачаем с официального сайта архив библиотеки и разархивируем его в директорию libraries/ среды разработки Arduino IDE. Затем переименуем папку ArduinoThread-master в ArduinoThread.

Схема подключений останется прежней. Изменится лишь код программы.

В программе мы создаём два потока – ledThread и soundThread, каждый выполняет свою операцию: один мигает светодиодом, второй управляет звуком сирены. В каждой итерации цикла для каждого потока проверяем, пришло ли время его выполнения или нет. Если пришло – он запускается на исполнение с помощью метода run(). Главное – не использовать оператор delay(). В коде даны более подробные пояснения.

Параллельное выполнение потоков на Arduino

Загрузим код в память Ардуино, запустим. Теперь всё работает в точности так, как надо!

Источники:

http://arduino.ru/forum/programmirovanie/parallelnoe-vypolnenie-2-3-zadach

http://voltiq.ru/wiki/interruptions-in-arduino/

http://soltau.ru/index.php/arduino/item/373-kak-vypolnyat-parallelnye-zadachi-threads-v-programme-dlya-arduino

http://robocraft.ru/forum/viewtopic.php?p=2524

Ссылка на основную публикацию