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

Мультизадачность на Arduino

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

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

 

Отказываемся от delay ()

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

Во время выполнения delay () вы не можете опрашивать входы, обрабатывать данные и менять состояние выходов. Эта функция связывает процессор на все 100%.

Давайте вспомним «Hello, world» и вновь помигаем светодиодом на плате Arduino, используя delay (). Я использую Arduino Mega 2560 и также, как и на Arduino UNO, встроенный в плату светодиод подключен к 13 пину. Простенький код включает светодиод на 1 секунду (1000 мс), затем выключает его на этот же временной интервал.

Показать/скрыть код

Напомню, что функция setup () выполняется единожды при подаче напряжения питания на Arduino, или же после нажатия кнопки reset на плате. Затем функция loop () и, соответственно, и все что в ней находится, выполняется бесконечно, снова и снова, пока на микроконтроллер подано напряжение питания. Поэтому процессор не может выполнить ничего, кроме как мигать светодиодом — все время тратится на выполнение функции delay.

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

Показать/скрыть код

Для подключения множества различных сенсоров, индикаторов и исполнительных устройств к Arduino Mega2560 я использую MEGA Sensor Shield v1.2 от DFROBOT. На этой плате к каждому пину Arduino, использующемуся в качестве входа или выхода, добавлены выводы питания и земли, что очень удобно (многие датчики являются активными и требуют внешнего питания) и позволяет подключать внешние устройства, используя трехвыводной разъем. Все светодиоды продублированы на этом шилде.

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

DFROBOT MEGA Sensor Shield v1.2

Подключим сервопривод к 9 пину, загрузим скетч в Arduino и посмотрим что из этого выйдет.

Мы видим, что мигание светодиода чередуется с полным циклом вращения сервы — мигает светодиод, сервопривод делает оборот с 0 до 180 градусов, затем обратно, только после этого вновь мигает светодиод. То есть действия происходят последовательно. Но нам хотелось бы, чтобы это происходило параллельно и светодиод выполнял свой цикл мигания вне зависимости от местоположения сервы.

Каким же образом мы можем управлять временными интервалами не используя функцию delay ()?

 

Используем millis ()

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

Вместе с Arduino IDE в качестве простого примера идет скетч под названием BlinkWithoutDelay

Пример скетча BlinkWithoutDelay

Соберем простенькую схемку, как показано ниже.

Светодиод, подключенный к Arduino

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

Для расчета номинала токоограничивающего резистора воспользуемся законом Ома и параметрами имеющегося светодиода. У меня под рукой оказались 45º красные матовые светодиоды NationStar BT-204SED диаметром 5 мм. Прямое напряжение для них составляет 2.0 В и номинальный рабочий ток 20 мА. Считаем, что высокий уровень напряжения на выходе нашей платы составляет 5 В. Чтобы задать необходимый ток в 20 мА, производим простейшие вычисления сопротивления:

    \[R = \frac{U_R}{I_R} = \frac{5 B - 2 B}{0.02 А} = 150 \Omega \]

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

Показать/скрыть код

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

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

Вместо использования delay () для задания времени мигания, в этом примере запоминается текущее состояние светодиода и момент, когда оно в последний раз изменялось. На каждой итерации цикла, используется функция millis () для определения времени, когда необходимо вновь изменить состояние светодиода.

Давайте рассмотрим более интересный пример, где время когда светодиод включен отличается от времени, когда он не светится.

Показать/скрыть код

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

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

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

Подключаем второй светодиод к Arduino

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

Показать/скрыть код

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

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

Конечно, существует! Есть методы программирования, которые и проще и эффективнее.

 

Используем классы

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

Реализуем тот же самый код методом объектно-ориентированного программирования (ООП).

Язык программирования Arduino — Wiring, является разновидностью C++, который поддерживает объектно-ориентированное программирование. Использовав объектно-ориентированные свойства языка, мы можем собрать вместе все переменные состояния и функциональность для мигающего светодиода в класс C++.
Это совсем не сложно сделать. Весь код уже есть. Нам просто нужно переупаковать его в класс.

Начинаем с объявления класса Flasher:

Показать/скрыть код

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

Показать/скрыть код

Наконец, мы возьмем наш цикл и преобразуем его в функцию-член класса, называемую Update (). Она иденична оригинальному void loop () — изменено только название.

Показать/скрыть код

Повторно скомпоновав существующий код в класс Flasher, мы инкапсулировали все переменные (состояние) и функциональность (автомат) для мигания светодиодом.

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

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

Ниже приведен полный код скетча, реализующий независимое мигание трех светодиодов.

Показать/скрыть код

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

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

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

Добавим на нашу макетную плату пару сервомоторов, подключив их к пинам 9 и 10 и третий светодиод на пин 11.

Две сервы и три светодиода на Arduino

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

Также добавим функции Attach () и Detach () сопоставления сервопривода с конкретным выводом микроконтроллера.

Показать/скрыть код

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

Каждый экземпляр  Flasher требует две строки кода:

  • Одну для объявления экземпляра
  • Одну для вызова обновления в цикле

Каждый экземпляр Sweeper требует только три строки кода:

  • Одну для объявления экземпляра
  • Одну для сопоставления с выводом при установке
  • Одну для вызова обновления в цикле

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

Показать/скрыть код

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

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

multitasking_scheme04

В коде, представленном ниже проверяется состояние кнопки на каждом шаге цикла. led1 и sweeper2 не обновляются, если кнопка нажата.

Показать/скрыть код

Теперь у нас три светодиода мигают в своих собственных режимах, 2 сервы вращаются независимо друг от друга. При нажатии кнопки, sweeper2 и led1 останавливаются до той поры, пока мы удерживаем кнопку нажатой.

Вот как все это выглядит у меня, что называется, «вживую».

Так как у нас нет никаких задержек в цикле, наша схема реагирует на нажатие кнопки практически мгновенно.

У нас получилось 5 независимо выполняющихся задач, управляемых при помощи простого пользовательского интерфейса (кнопка). Мы смогли избавиться от задержки, фактически блокирующей работу процессора. И наш эффективный объектно-ориентированный код оставляет много места для расширения функциональности!

 
Как вы оцениваете эту публикацию? 1 звезда2 звезды3 звезды4 звезды5 звезд (149 голосов, средняя оценка: 4.87 из 5)
Loading ... Loading ...

Вы можете пропустить чтение записи и оставить комментарий. Размещение ссылок запрещено.

151 комментарий к записи “Многозадачность на Arduino”

  1. Алексей пишет:

    Подскажите как решить проблему. На мини про 328 сделан стробоскоп, светодиод 2мс горит, 8мс не горит. При использовании delay () все ок, строб работает как часы, но естественно нет возможности делать что-то еще. А с вашим строб выдает паузу которую видно на глаз.

    • Вся проблема в реализации функции digitalWrite (). Вот здесь я об этом писал robotosha.ru/arduino/digi...ing-arduino.html

    • Попробуйте вот этот код:

  2. АлексейМ пишет:

    подскажите как подобно реализовать код, IR дальномер прикреплённый к серво, что бы получать с него данные к примеру на позиции 180,90,0, 2 серво работает отталкиваясь от полученных данных с IR

    • Можно сделать примерно так:

  3. Александер пишет:

    Добрый вечер! Подскажите пожалуйста, как реализировать код без delay (), если я нажал кнопку, то светодиод должен загорется и через 30 секунд потухнуть, пока я снова не нажал на кнопку?

    • Можно сделать так:

  4. Александер пишет:

    Большое Вам спасибо за вашу помощь и за очень интересную страницу!

  5. Дмитрий пишет:

    Здравствуйте!

    Подскажите пожалуйста, как реализовать вращение сервоприводом от 0 до 180 градусов без delay при условии, когда на аналоговом входе будет превышен некоторый порог? (в виде примера).

    • Давайте уточним. Вам нужно, чтобы при превышении некоторого порога серва производила вращение от 0 до 180 градусов даже если напряжение опять упало ниже порога или что-то нужно делать в этом случае? То есть, если порог превышен, мы перестаем отслеживать этот аналоговый вход и вращаем сервой от 0 до 180 и после того как вращение произведено, опять начинаем отслеживать вход. Или нужна какая-то более сложная логика работы?

      • Дмитрий пишет:

        Добрый день!

        Прошу прощения за неточное формулирование.

        Необходимо следующее.

        Если на аналоговом входе не превышен некоторый порог, то сервопривод должен поворачиваться от 0 до 180 градусов до остановки. При превышении некоторого порога на этом же аналоговом входе, сервопривод должен начать обратное вращение от 180 до 0 градусов до остановки. Каким параметром можно регулировать скорость вращения сервопривода, не применяя delay.

        • Дмитрий, воспользуйтесь кодом к комментарию вопроса АлексеяМ, приведенного чуть выше. Вместо числа 50 в 59-й строке

          Sweeper sweeper1 (50);

          установите требуемую задержку в миллисекундах (время через которое будет изменяться положение сервопривода, фактически определяет скорость вращения сервопривода). Также для отслеживания порогового уровня threshold (которое необходимо предварительно задать) на аналоговом входе (например, с номером 3) нужно изменить функцию Update класса Sweeper:

          • Дмитрий пишет:

            Андрей спасибо большое за разъяснение! Всё понятно!

  6. BiHiTRiLL пишет:

    Андрей, подскажите пожалуйста как изменить класс чтобы в него можно было передавать задержку до включения? Или каким либо другим образом указывать не только время когда светодиод включен и когда погашен, но и время до первого включения. Хочу реализовать световые эффекты на матрице светодиодов. Спасибо большое заранее!

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

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

  7. BiHiTRiLL пишет:

    Андрей спасибо! Пробую подстроить под свои нужды, но не получается. Либо я что-то не понимаю, либо какая-то ошибка.

    Например:

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

    но она не работает. Первая и вторая группы включаются и выключаются синхронно.

    если задержка 0, то вообще получается бред:

    в этом случае одинаково мигают все три группы.

    Эмпирическим путём нашел как последовательно включать три группы:

    Но логики работы, хоть убейте, не вижу.

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

    • Накосячил я чуток :-) Забыл после стартовой паузы зажечь светодиод. Нужно код в строке 40 заменить на:

      Должно заработать нормально и с первым вашим вариантом.

  8. BiHiTRiLL пишет:

    Спасибо большое! Всё отлично работает! Ща буду адаптировать для своего проекта.

  9. Петр_К пишет:

    Андрей, подскажите, пожалуйста...

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

    Если правильно понимаю, это как в вопросе у Дмитрия, только там возврат в 0, а мне надо постоянное колебание.

    Заранее спасибо.

    • Петр, у Дмитрия точно такая же задача — постоянное вращение от 0 до 180 и обратно. Воспользуйтесь его кодом. С порогами только разберитесь :-) — а то вы хотите при превышении порога вращаться и влево и вправо, получается некоторая неопределенность. Или я что-то не так понял.

      • Петр_К пишет:

        серво движется из 0 в 180 и обратно и т.д. (0-180-0-180-0-180 до бесконечности)

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

        • Пример: серва вращается направо, напряжение превысило некоторый порог, мы меняем направление вращения, но напряжение на входе не падает. Что делать в этом случае? Опять менять направление вращения? Если напряжение будет опять превышать уровень, то мы увидим серву, которая дергается на месте, постоянно меняя направление вращения. Лучше сделать два условия: первое — если напряжение ВЫШЕ некоторого уровня вращаемся в одну сторону, если напряжение НИЖЕ некоторого уровня вращаемся в другую сторону. По сути это будут два состояния в зависимости от которых будет меняться направление вращения.

          • Петр_К пишет:

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

  10. Петр, если предположить, что пороговое напряжение при изменении направления вращения обязательно падает ниже порога threshold и падает быстрее, чем наша программа обновляет положение сервы, то можно метод Update переписать следующим образом:

  11. Петр_К пишет:

    Спасибо.

  12. Николай пишет:

    Здравствуйте.

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

    Когда значения несколько миллсекунд-не страшно, а мне надо, чтоб,например диод работал 3 секунды и был выключен 15 секунд. А тут сначала идет пауза в 15 сек, и только потом срабатывает.

    Спасибо заранее.

    • Николай, здравствуйте

      Нужно внести две правки в код:

      1. Изменить в конструкторе класса Flasher значение переменной ledState на HIGH:

      2. В условии, которое выполняется при первом запуске нужно перевести светодиод в это состояние:

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

      стартовая пауза — 0 мс, время свечения — 3 c, в выключенном состоянии — 15 с

  13. denn пишет:

    Андрей, подскажите, пожалуйста

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

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

    подставил в свои вводные

    первые 10 минут вроде синхронно работает, потом потихоньку начинает разъезжаться.

    led1 отстает

    пробовал вместо >= поставить ==

    не помогает.

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

    как победить?

    • В текущем коде погрешность определения времени ± 1 мс. Поэтому, постепенно происходит накопление ошибки. Можно увеличить точность, используя вместо функции millis () функцию micros (), которая является счетчиком микросекунд. Рассинхронизация сохранится, но сократится в сотни раз. Для этого, в коде нужно заменить millis () на micros () и создавать экземпляры класса используя микросекунды:

      Нужно помнить, что счетчики millis () и micros () имеют тип unsigned long (целые значения от 0 до 2^32-1) и поэтому периодически происходит их переполнение (обнуление счетчика). Для millis () этот период составляет 50 дней, для micros () — около 70 минут.

      Для микроконтроллеров, работающих на частоте 16 МГц разрешение функции micros () составляет 4 мкс, для 8 МГц микроконтроллеров — 8 мкс. Это означает, что результат, возвращаемый функцией micros () всегда будет кратен 4 (8).

      В программе дополнительно нужно будет отследить момент обнуления счетчика.

      • denn пишет:

        а не подскажете как отследить переполнение?

        я про переполнение читал, вот только как построить код пока не понял.

        • Приведу пример метода Update с использованием функции millis, в котором отслеживается переполнение счетчика. Для micros — делается аналогично.

  14. Дмитрий пишет:

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

    Помогите, пожалуйста.

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

      • denn пишет:

        так быстро отвечаете, класс!

      • Дмитрий пишет:

        Спасибо огромное! Я еще сделал вставку в строке 53, чтобы серва не жужжала во время паузы.

        Еще раз спасибо!

        • Полезное вы дополнение сделали.

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

          • Дмитрий пишет:

            Спасибо за совет с кодом. Пытаюсь дальше связать свой скетч.

          • Дмитрий пишет:

            Здравствуйте. Обращаюсь к вам еще раз. Задача: Нужно, чтобы серва работала по вашему коду с паузой, а параллельно с ней должен включаться светодиод при достижении определенной температуры (датчик DHT 22).

            Когда связываю два кода и светодиод и серва ведут себя как попало. Пытался вписать код для светодиода в ваш Flasher — ничего не получилось. Скажите, пожалуйста, что нужно сделать?

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

    • Дмитрий пишет:

      Здравствуйте. Спасибо за ответ, но я так уже пробовал. Светодиод работает от датчика температуры, а серва постоянно жужжит и крутится со скоростью 180 градусов за 20 секунд, на паузу не реагирует.

  16. Дмитрий, что-то где-то у вас не так. У меня нет датчика температуры — я его заменил на переменный резистор. Светодиод включается при достижении заданного значения, ниже этого значения — выключается. Серва вращается с паузой независимо от светодиода. Полный вариант кода:

  17. Видео с результатом работы этого скетча:

  18. Схема соединений:

    Пример скетча BlinkWithoutDelay

    • Дмитрий пишет:

      Здравствуйте. С потенциометром и датчиком дождя все работает. Подключенный датчик температуры сам по себе ничем не мешает. Проблема возникает (серва просто крутится) при добавлении строчки в void loop

      Может быть проблема с переменной float. Все дело в том, что нужно включение и выключение в диапазоне температур 38,2-38,8.

      Может быть есть какое-то решение данной проблемы.

      Подключаю считывание данный датчика к порту D3.

      • Дмитрий, проблема, по всей видимости, в библиотеке DHT. Там много использований функции delay, во время выполнения которой микроконтроллер перестает обрабатывать другие события (я вначале статьи про это писал). Нужно править библиотеку. Я датчик DHT11 себе купил, пока жду поступления. Как придет, попробую разобраться с проблемой и вам помочь.

        • Дмитрий пишет:

          Андрей, действительно в библиотеке есть четыре delay. Пользуюсь этой библиотекой

          • Дмитрий пишет:

            Нашел вот эту библиотеку

            Все заработало.

            Спасибо большое за помощь и за направление моих мыслей в нужную сторону!

  19. Дмитрий, это хорошо, что заработало. Рад помочь.

  20. Геннадий пишет:

    Здравствуйте Андрей. Очень понравилась ваша статья. Загрузил скетч с тремя светодиодами. Всё работает отлично. Но не смог добавить в код возможность одному из светодиодов моргать двумя вспышками. Думал так получится:

    но, не работает. Не подскажите как можно реализовать?

  21. Геннадий, ваша задумка реализуется чуть сложнее :-) . Для дважды мигающего светодиода нам нужно отслеживать уже четыре события: длительность первой и второй вспышек и промежутки времени между ними. Функция Update в классе Flasher отслеживала только одну вспышку и паузу после нее, поэтому, можно модифицировать класс Flasher, добавив в него обработку еще двух событий. Для этого реализуем новый класс DoubleFlasher. Пример скетча с двумя различными вспышками одного светодиода:

  22. Дмитрий пишет:

    Здравствуйте Андрей .Очень понравилась ваша структура кода,если вас не затруднит подскажите пожалуйста как можно реализовать управление сервоприводом двумя кнопками в шаблоне вашего кода. Т.е пока нажата одна кнопка сервопривод двигается в одно направление,кнопку отпустили сервопривод остановился и инверсия.С delay это делается просто, а вот с millis «танцую с бубном» и пока не особо получается.

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

      Управление серво 2-мя кнопками

    • Реализуем новый класс SweeperKeys для управления серво при помощи двух кнопок:

      • Дмитрий пишет:

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

  23. Дмитрий пишет:

    Доброй ночи, Андрей.

    Помогите решить следующую задачу:

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

    Спасибо!

    • Дмитрий, здравствуйте! Для решения этой задачи нам нужно отслеживать два состояния: один для режима мигания и второй для режима паузы. Если мы находимся в режиме мигания, то тут еще два состояния: когда светодиод горит и не горит. В режиме паузы нам нужно отслеживать нажатие кнопки и после каждого нажатия изменять ее состояние (нажатие кнопки включает светодиод, повторное нажатие — выключает его). С кнопкой есть проблема, связанная с, так называемым, дребезгом контактов. Здесь я боролся с дребезгом, используя программный способ. Кнопка подключена ко второму цифровому пину. Также я здесь отслеживаю переполнение счетчика миллисекунд (я не проверяю условие переполнения счетчика для кнопки, но при желании, по аналогии, вы самостоятельно сможете добавить пару строк кода). Мой вариант скетча:

      • Дмитрий пишет:

        Добрый день, Андрей.

        Спасибо за ответ.

        Как вы считаете, если использовать библиотеку TimerOne — которая будет управлять таймером включения/выключения светодиода раз в 30 минут, и в loop писать остальной код, это упростит или усложнит работу процессора, по сравнению с тем вариантом, который вы предложили?

        Спасибо!

        • Использование аппаратного прерывания по таймеру (как в TimerOne) будет более предпочтительно. Нужно учесть только, что функция millis использует Timer0 (подробнее). Если вы начнете использовать timer0, изменяя период его прерываний, у вас millis перестанет работать как надо.

          • Дмитрий пишет:

            Андрей, спасибо за ответ.

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

            Правки связаны со следующим. Есть подключаемая библиотека, которая пиликает тональными сигналами разной длины. Эти сигналы я описываю в основной программе. При этом эти сигналы должны пропиликать каждые 30 минут. Тональники выходят на 6 пин и пока пиликают они горит светодиод на 13 пине.

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

            Спасибо!

  24. Дмитрий пишет:

    Андрей, уже сам придумал ))))

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

  25. Как вы предлагаете — еще лучше))

    • Дмитрий пишет:

      Андрей,как показала практика не совсем лучше (((

      Связано с тем, что максимальный интервал срабатывания таймера при использовании библиотеки Timer0 составляет 8388480 мкс (около 8.4 с).

      Время пиликанья тональником у меня, намного больше, соответственно Ваш код лучше!

      Спасибо!

  26. Рашит пишет:

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

    заранее спасибо!

    • Рашит, здравствуйте. Алгоритм должен выглядеть примерно следующим образом:

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

      2. Нужно завести две константы: время в течении которого мы считаем, что кнопка нажата долго pressed_long

      3. Определяем, что кнопка нажата и запоминаем этот момент времени pressed_moment

      4. Определяем, что кнопку отпустили, считываем текущее время и вычисляем сколько времени она удерживалась, используя pressed_moment

      5. Если время удержания < pressed_long, то переключаемся на следующий режим мигания, иначе - переходим в режим, когда светодиоды не горят

      6. Реализуем текущий режим и ожидаем следующего нажатия клавиши. Если клавиша нажата — опять переходим к п.3

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

      • Рашит пишет:

  27. Рашит пишет:

    это кусок обработки кнопки

  28. Рашит пишет:

    кнопка не реагирует(

    • Рашит, я в конце статьи про Подключение кнопки к Arduino добавил еще один пример, в котором отслеживается продолжительность нажатия кнопки. При кратковременном нажатии — происходит циклическое переключение рабочих режимов, при длительном — режим сбрасывается в 0. Используя этот пример, вам останется только написать проверку того, какой режим выбран и, в зависимости от этого, по-разному мигать светодиодами. В режиме = 0 выключаете все светодиоды. Думаю, это вам поможет в решении вашей задачи.

  29. Игорь пишет:

    Добрый день!

    Очень нужный код!

    А как менять интервалы уже созданному led? Мне нужно что бы отображать частой мигания уровни температуры: ниже порога светодиод не мигает, превышение на два градуса мигает редко, на три часто, на 5 градусов просто горит! И соответственно наоборот, если температура снизится то будет все реже, пока не перейдет порог и тогда вообще гасить светодиод.

    Видимо надо какой то метод добавить и его вызывать?

    • Да, Игорь, вы правы. Можно создать новый метод ChangeTimeParameters для изменения переменных OnTime и OffTime для объекта класса Flasher, а затем вызывать этот метод (что-то типа led1.ChangeTimeParameters (300, 2000)), в качестве параметров передавая новые значения.

  30. Руслан пишет:

    Здравствуйте.Очень понравилось, как вы объясняете. Сам начал заниматься программированием недавно, поэтому требуется Ваша помощь.

    Задача состоит вот в чём. Необходимо управлять скоростью сервопривода, который вращается от 0 до 180 градусов, в бесконечном цикле. Требуется регулировать его скорость в реальном времени с помощью переменного резистора, т.е. поворачиваю ручку переменного резистора и сервопривод тут же замедляется или ускоряется.

    Как я понял из вашего кода необходимо изменять параметр

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

  31. Руслан, здравствуйте. Да, изменять временной параметр для сервоприводов можно точно также как и для светодиодов — написав аналогичную функцию ChangeTimeParameter для изменения временного параметра внутри класса Sweeper. Считывать значение с переменного резистора, можно внутри loop, а затем уже, проведя анализ полученного значения, вызывать метод ChangeTimeParameter для изменения параметра:

  32. АлексейМ пишет:

    Здравствуйте, возможно задам глупый вопрос, а как мне вывести результаты с ультразвукового дальномера в Монитор порта, за ранее Спасибо.

    • Алексей, а какой именно у вас датчик? Приведенный ниже скетч считывает значения (0-300) с ультразвукового датчика URM37 v3.2 (работает на дистанции до 3 метров) и передает их в Serial-порт.

      Подключение

      5V (Arduino) -> Pin 1 VCC (URM V3.2)

      GND (Arduino) -> Pin 2 GND (URM V3.2)

      Pin 19 (RX1) (Arduino MEGA) -> Pin 9 (URM V3.2)

      Pin 18 (TX1) (Arduino MEGA) -> Pin 8 (URM V3.2)