Рейтинг@Mail.ru
Arduino и Threads
Arduino и Threads

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

13 comments Arduino
Print Friendly, PDF & Email

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

Нам понадобится:

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

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

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

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

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

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

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

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

const int soundPin = 3; /* объявляем переменную с номером пина, 
                           на который подключён пьезоэлемент */
const int ledPin = 13;  // объявляем переменную с номером пина светодиода
                     
void setup() {
    pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход.
    pinMode(ledPin, OUTPUT);   // объявляем пин 13 как выход.
}

void loop() {
    // Управление звуком:
    tone(soundPin, 700); // издаём звук на частоте 700 Гц
    delay(200);
    tone(soundPin, 500); // на частоте 500 Гц
    delay(200);
    tone(soundPin, 300); // на частоте 300 Гц
    delay(200);
    tone(soundPin, 200); // на частоте 200 Гц
    delay(200);
    
    // Управление светодиодом:
    digitalWrite(ledPin, HIGH); // зажигаем
    delay(200);
    digitalWrite(ledPin, LOW); // гасим
    delay(200);
}

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

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

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

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

const int soundPin = 3;  // переменная с номером пина пьезоэлемента
const int ledPin = 13;  // переменная с номером пина светодиода
const long ledInterval = 200; // интервал мигания светодиодом, мсек.

int ledState = LOW;  // начальное состояние светодиода
unsigned long previousMillis = 0;  // храним время предыдущего срабатывания светодиода

void setup() {
    pinMode(soundPin, OUTPUT); // задаём пин 3 как выход.
    pinMode(ledPin, OUTPUT);   // задаём пин 13 как выход.
}

void loop() {
    // Управление звуком:
    tone(soundPin, 700); 
    delay(200);
    tone(soundPin, 500); 
    delay(200);
    tone(soundPin, 300);
    delay(200);
    tone(soundPin, 200);
    delay(200);
     
    // Мигание светодиодом:
    // время с момента включения Arduino, мсек:
    unsigned long currentMillis = millis(); 
    // Если время мигать пришло,
    if (currentMillis - previousMillis >= ledInterval) {
        previousMillis = currentMillis;  // то запоминаем текущее время
        if (ledState == LOW) {  // и инвертируем состояние светодиода
              ledState = HIGH;
        } else {
              ledState = LOW;
        }
        digitalWrite(ledPin, ledState); // переключаем состояние светодиода
    }
}

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

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

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

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

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

Библиотека ArduinoThread
Библиотека ArduinoThread

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

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

#include <Thread.h>  // подключение библиотеки ArduinoThread
const int soundPin = 3;  // переменная с номером пина пьезоэлемента
const int ledPin = 13;  // переменная с номером пина светодиода

Thread ledThread = Thread(); // создаём поток управления светодиодом
Thread soundThread = Thread(); // создаём поток управления сиреной

void setup() {
    pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход.
    pinMode(ledPin, OUTPUT);   // объявляем пин 13 как выход.

    ledThread.onRun(ledBlink);  // назначаем потоку задачу
    ledThread.setInterval(1000); // задаём интервал срабатывания, мсек
    
    soundThread.onRun(sound);     // назначаем потоку задачу
    soundThread.setInterval(20); // задаём интервал срабатывания, мсек
}

void loop() {
    // Проверим, пришло ли время переключиться светодиоду:
    if (ledThread.shouldRun())
        ledThread.run(); // запускаем поток
    
    // Проверим, пришло ли время сменить тональность сирены:
    if (soundThread.shouldRun())
        soundThread.run(); // запускаем поток
}

// Поток светодиода:
void ledBlink() { 
    static bool ledStatus = false;    // состояние светодиода Вкл/Выкл
    ledStatus = !ledStatus;           // инвертируем состояние
    digitalWrite(ledPin, ledStatus);  // включаем/выключаем светодиод
}

 // Поток сирены:
void sound() { 
    static int ton = 100;  // тональность звука, Гц
    tone(soundPin, ton);  // включаем сирену на "ton" Гц
    if (ton <= 500) {  // до частоты 500 Гц 
        ton += 100;  // увеличиваем тональность сирены
    }
    else {  // по достижении 500 Гц
        ton = 100;  // сбрасываем тональность до 100 Гц
    }
}

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

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

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

Last modified onПятница, 01 Ноябрь 2019 19:29 Read 59658 times

Поделиться

Print Friendly, PDF & Email

13 comments

  • Кирилл
    Кирилл Пятница, 13 Сентябрь 2019 14:49 Ссылка на комментарий

    Вау,спс бро.Ты решил одну из самых сложных(как для меня) задач.
    Press F to pay respect

  • aave1
    aave1 Суббота, 14 Сентябрь 2019 20:11 Ссылка на комментарий

    Рад помочь!

  • Максим
    Максим Пятница, 01 Ноябрь 2019 10:10 Ссылка на комментарий

    Есть возможность в этой библиотеке менять значение времени срабатывания для потока?
    Пример:
    Хочу, чтобы при определенных условиях поток срабатывал через минуту, а если эти условия не выполняются, поток выполняется раз в 10 минут. Как изменить время срабатывания? Пробовал задать время через переменную, но при ее изменении поток срабатывает с изначальным интервалом.

  • aave1
    aave1 Пятница, 01 Ноябрь 2019 19:24 Ссылка на комментарий

    Максим, сделайте два потока: один с интервалом минута, а другой - 10 минут. У каждого потока есть свойство "enabled", через которое вы можете активировать или деактивировать любой поток в любое время.
    Например:
    if (что-то) {
    myThread.enabled = true;
    }
    else {
    myThread.enabled = false;
    }
    Возможно стоит подумать в этом направлении.

  • Леонид Кукленко
    Леонид Кукленко Воскреснье, 26 Январь 2020 03:12 Ссылка на комментарий

    Здравствуйте. Скопировал ваш пример - сам по себе он работает хорошо. Заменил рабочие части обоих новых loop на свои (по отдельности они работают отлично) и программа выводит в монитор порта только информацию из участка setup. Есть ли какие-то ограничения на использование каких либо функций в новых loop (delay используется один раз)? И если кто-то сталкивался, подскажите, пожалуйста, как это решить.

  • aave1
    aave1 Воскреснье, 26 Январь 2020 16:17 Ссылка на комментарий

    Леонид, возможно в вашем скетче есть какой-то участок кода, из которого программа никогда не выходит. Без самого кода трудно сказать точнее. Можете разместить здесь хотя бы псевдокод участка, который не срабатывает?

  • Леонид Кукленко
    Леонид Кукленко Воскреснье, 26 Январь 2020 17:55 Ссылка на комментарий

    Здравствуйте, aave1. Вот один из void к примеру, Обычный вольтметр, в цикле идет усреднение за период времени. Остальные части как в примере
    int Uphre = A0;
    float Uanalog = 0;
    float Udigital[Kolichestvo_izmereniy];
    float Uavgdigital = 0;
    int i = 0;

    void Dannie()
    {
    for(i=0; i

  • Леонид Кукленко
    Леонид Кукленко Воскреснье, 26 Январь 2020 17:57 Ссылка на комментарий

    Kolichestvo_izmereniy; i++){
    Uanalog = analogRead(Uphre);
    Udigital[i] = Uanalog*5.0/1024.0;
    Uavgdigital += Udigital[i];
    delay(Vremya_izmereniya_min*60000/Kolichestvo_izmereniy);
    }
    Serial.println(Uavgdigital/Kolichestvo_izmereniy);
    Uavgdigital = 0;
    }

  • Леонид Кукленко
    Леонид Кукленко Воскреснье, 26 Январь 2020 23:32 Ссылка на комментарий

    Извините за беспокойство, ошибка была в разделе setup, действительно цикл мешал, спасибо за наводку

  • aave1
    aave1 Понедельник, 27 Январь 2020 18:03 Ссылка на комментарий

    Рад, что вы разобрались! Я ещё не успел вникнуть в вашу проблему.

  • Сергей
    Сергей Воскреснье, 05 Июль 2020 04:39 Ссылка на комментарий

    понятия потока тут недопустимо, даже таймеры грузят МК, это вам не СТМ32, у которого конвейер.

  • aave1
    aave1 Понедельник, 06 Июль 2020 19:43 Ссылка на комментарий

    Сергей, конечно, это не поток в классическом понимании, но тем не менее. Даже библиотека называетсяThreads.

  • Олег
    Олег Среда, 14 Октябрь 2020 07:31 Ссылка на комментарий

    Друзья, с отцом сварили ворота для двора дома, в электронике соображаю хорошо а вот с программированием все плохо, подскажите мне скетч, который можно использовать в моём случае
    1.есть ворота и один двигатель переменного тока на 220 вольт.
    2.есть релюхи для реверса двигателя.
    3.есть симисторы и оптотранзисторы для управления симисторами
    4. есть ардуино UNO
    5. есть 4 датчика холла

    нужен скетч с такой раскладкой... одна кнопка при нажатии которой, ардуино на какой то пин даёт команду подать напряжение на реле после чего начинает шумировать с другого пина симистор плавно до ста процентов, примерно пройдя путь 80-90% стоит еще один татчик который начинает замедлять ход мотора от 100 до скажем 10 % и в конце еще датчик который говорит о том что нужно выключить мотор. (отключить питание от реле)
    и в обратном порядке тоже самое, другое реле но те же датчики только в обратном направлении.
    Сразу скажу с таймером у нас не получилось, дело в том что в разное время суток, разное напряжение в сети, и это мучение.... то на 20% не закрывает то на 10% заходи кто хочет (((((( помогите ребята знающие . 0le9@list.ru

Leave a comment

  1. Arduino это...
  2. Arduino это...
  3. Arduino это...
Отличный способ начать знакомство с электроникой, микроконтроллерами и программированием!
Замечательное средство для создания собственных электронных устройств, которые пригодятся в быту или для развлечения!
Уникальный конструктор, для которого разработаны десятки совместимых датчиков и модулей!
next
prev