Як я можу створити кілька запущених потоків?


60

Чи є спосіб, коли я можу кілька частин програми працювати разом, не роблячи кілька речей в одному блоці коду?

Один потік очікує зовнішнього пристрою, а також блимає світлодіод в інший потік.


3
Вам, мабуть, слід спочатку запитати себе, чи справді вам потрібні нитки. Таймери можуть бути вже добре для ваших потреб, і вони підтримуються на Ардуїно.
jfpoilpret

1
Ви також можете перевірити Uzebox. Це два чіпові консолі відеоігри домашнього виробництва. Тож поки це не зовсім Ардуїно, вся система побудована на перериваннях. Таким чином, аудіо, відео, елементи керування і т. Д. Перериваються, тоді як головна програма не має турбуватися ні про що. Може бути хорошим посиланням.
cbmeeks

Відповіді:


50

Немає багатопроцесорної, багатопотокової підтримки на Arduino. З деяким програмним забезпеченням ви можете зробити щось наближене до декількох потоків.

Ви хочете подивитися на Protothreads :

Protothreads - це надзвичайно легкі безликі потоки, розроблені для систем з обмеженою пам'яттю, таких як невеликі вбудовані системи або бездротові сенсорні вузли мережі. Protothreads забезпечують лінійне виконання коду для керованих подіями систем, реалізованих у C. Protothreads можна використовувати з базовою операційною системою або без неї для блокування обробників подій. Protothreads забезпечують послідовний потік управління без складних станкових машин або повної багатопотокової різьблення.

Звичайно, є Arduino приклад тут з прикладами коду . Це питання також може бути корисним.

ArduinoThread теж хороший.


Зауважте, що Arduino DUE має виняток із цього, з кількома петлями управління: arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi

18

Ардуїно, що базується на AVR, не підтримує (апаратне) нанизування, я не знайомий з Arduino на базі ARM. Одним із способів цього обмеження є використання переривань, особливо тимчасових переривань. Ви можете запрограмувати таймер, щоб перервати основну процедуру кожні стільки мікросекунд, щоб запустити певну іншу процедуру.

http://arduino.cc/en/Reference/Interrupts


15

На Uno можна виконати багатопроменеву сторону програмного забезпечення. Нитка апаратного рівня не підтримується.

Для досягнення багатопотокової редакції знадобиться реалізація базового планувальника та ведення процесу чи списку завдань для відстеження різних завдань, які потрібно виконати.

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

//Pseudocode
void loop()
{

for(i=o; i<n; i++) 
run(tasklist[i] for timelimit):

}

Тут tasklistможе бути масив покажчиків функцій.

tasklist [] = {function1, function2, function3, ...}

З кожною функцією форми:

int function1(long time_available)
{
   top:
   //Do short task
   if (run_time<time_available)
   goto top;
}

Кожна функція може виконувати окреме завдання, наприклад, function1виконувати маніпуляції зі світлодіодом та function2виконувати обчислення поплавця. Відповідальність за кожне завдання (функцію) буде дотримуватися відведеного для нього часу.

Сподіваємось, цього має бути достатньо для початку роботи.


2
Я не впевнений, що хотів би говорити про "потоки" при використанні непередбачуваного планувальника. До речі, такий планувальник уже існує як бібліотека arduino
jfpoilpret

5
@jfpoilpret - Кооперативне багатопотокове читання - реальна річ.
Вонор Коннор

Так, ви праві! Моя помилка; це було так давно, як я не стикався з кооперативними багатопотоковими темпами, що, на мою думку, багатопотокове читання повинно бути превентивним.
jfpoilpret

9

Відповідно до опису ваших вимог:

  • одна нитка, що чекає зовнішнього пристрою
  • одна нитка блимає світлодіодом

Здається, ви могли використати один переривник Arduino для першої "нитки" (я б швидше назвав це "завдання" насправді).

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

Однак важливим моментом, який слід пам’ятати про переривання, є те, що викликана функція повинна бути якомога швидшою (як правило, не повинно бути delay()виклику чи будь-якого іншого API, від якого залежатиме delay()).

Якщо у вас є тривале завдання активації при запуску зовнішньої події, ви потенційно можете використовувати спільний планувальник і додати до нього нове завдання зі своєї функції переривання.

Другим важливим моментом щодо перерв є те, що їх кількість обмежена (наприклад, лише 2 щодо ООН). Отже, якщо ви почнете мати більше зовнішніх подій, вам потрібно буде реалізувати якесь мультиплексування всіх входів в один, і ваша функція переривання визначає, який саме мультиплексований inut був фактичним тригером.


6

Просте рішення - використовувати Scheduler . Є кілька реалізацій. Тут коротко описаний той, який доступний для плат AVR та SAM. В основному один виклик почне завдання; "ескіз всередині ескізу".

#include <Scheduler.h>
....
void setup()
{
  ...
  Scheduler.start(taskSetup, taskLoop);
}

Scheduler.start () додасть нове завдання, яке запустить один раз програму taskSetup, а потім повторно викличе taskLoop так само, як працює ескіз Arduino. У завдання є свій стек. Розмір стека - необов'язковий параметр. Розмір стека за замовчуванням - 128 байт.

Для дозволу переключення контексту завдання потрібно викликати прибутковість () або затримати () . Існує також макрос підтримки для очікування стану.

await(Serial.available());

Макрос є синтаксичним цукром для наступного:

while (!(Serial.available())) yield();

Очікування також можна використовувати для синхронізації завдань. Нижче наведено приклад фрагмента:

volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
  await(taskEvent);
  switch (taskEvent) {
  case 1: 
  ...
  }
  taskEvent = 0;
}
...
void loop()
{
  ...
  signal(1);
}

Детальнішу інформацію див. У прикладах . Є приклади від декількох світлодіодних миготливих кнопок відключення та простої оболонки з неблокуючим командним рядком зчитування. Шаблони та простори імен можуть бути використані для структури та зменшення вихідного коду. Нижче ескіз показує, як використовувати функції шаблону для мультиварки. Для стека достатньо 64 байтів.

#include <Scheduler.h>

template<int pin> void setupBlink()
{
  pinMode(pin, OUTPUT);
}

template<int pin, unsigned int ms> void loopBlink()
{
  digitalWrite(pin, HIGH);
  delay(ms);
  digitalWrite(pin, LOW);
  delay(ms);
}

void setup()
{
  Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
  Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
  Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}

void loop()
{
  yield();
}

Існує також орієнтир, який дає деяке уявлення про ефективність, тобто час для початку завдання, переключення контексту тощо.

Нарешті, є кілька класів підтримки для синхронізації та завдань на рівні завдань; Черга та Семафор .


3

З попереднього засідання цього форуму наступне питання / відповідь було перенесено до Електротехніки. Він має зразок ардуїно-коду, щоб блимати світлодіод, використовуючи перерив таймера під час використання основного циклу для здійснення послідовного вводу-виводу.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

Репост:

Перерви - це звичайний спосіб зробити справи, поки щось інше відбувається. У наведеному нижче прикладі світлодіод блимає без використання delay(). Щоразу, коли Timer1пожежа isrBlinker()викликається, функція обслуговування переривання (ISR) викликається. Він вмикає / вимикає світлодіод.

Щоб показати, що можуть відбуватися одночасно інші речі, loop()кілька разів записує foo / bar на послідовний порт незалежно від індикатора, що блимає.

#include "TimerOne.h"

int led = 13;

void isrBlinker()
{
  static bool on = false;
  digitalWrite( led, on ? HIGH : LOW );
  on = !on;
}

void setup() {                
  Serial.begin(9600);
  Serial.flush();
  Serial.println("Serial initialized");

  pinMode(led, OUTPUT);

  // initialize the ISR blinker
  Timer1.initialize(1000000);
  Timer1.attachInterrupt( isrBlinker );
}

void loop() {
  Serial.println("foo");
  delay(1000);
  Serial.println("bar");
  delay(1000);
}

Це дуже проста демонстрація. ІР можуть бути набагато складнішими і можуть бути спровоковані таймерами та зовнішніми подіями (штифтами). Багато спільних бібліотек реалізовані за допомогою ISR.


3

Я також прийшов до цієї теми під час реалізації матричного світлодіодного дисплея.

Одним словом, ви можете побудувати планувальник опитування за допомогою функції millis () та переривання таймера в Arduino.

Пропоную наступні статті від Білла графа:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview


2

Ви також можете спробувати мою бібліотеку ThreadHandler

https://bitbucket.org/adamb3_14/threadhandler/src/master/

Він використовує перериваючий планувальник, щоб дозволити комутацію контексту без ретрансляції на вихід () або затримку ().

Я створив бібліотеку, тому що мені були потрібні три нитки, і мені були потрібні два, щоб вони працювали в точний час, незалежно від того, що робили інші. Перший потік обробляв послідовне спілкування. Другий запускав фільтр Кальмана, використовуючи множення матричної поплавкової матриці з бібліотекою Ейґена. І третя - це швидка петля керування струмом управління, яка повинна була мати можливість переривати обчислення матриці.

Як це працює

Кожна циклічна нитка має пріоритет і період. Якщо потік, що має більш високий пріоритет, ніж поточний виконуючий потік, досягне свого наступного часу виконання, планувальник призупинить поточний потік і переключиться на більш високий пріоритет. Після завершення потоку з високим пріоритетом планувальник повертається до попереднього потоку.

Правила планування

Схема планування бібліотеки ThreadHandler така:

  1. Найперший пріоритет.
  2. Якщо пріоритет однаковий, спочатку виконується нитка із самим раннім терміном.
  3. Якщо два потоки мають однаковий термін, то перший створений потік буде виконаний першим.
  4. Нитка може бути перервана лише нитками з більш високим пріоритетом.
  5. Після виконання потоку він заблокує виконання для всіх потоків з нижчим пріоритетом, поки функція запуску не повернеться.
  6. Функція циклу має пріоритет -128 порівняно з потоками ThreadHandler.

Як користуватись

Нитки можна створити за допомогою успадкування c ++

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

Або через createThread та лямбда-функцію

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //code to run
    });

Об'єкти нитки автоматично підключаються до ThreadHandler, коли вони створені.

Щоб розпочати виконання створених об'єктів потоку, виклик:

ThreadHandler::getInstance()->enableThreadExecution();

1

А ось ще одна багатопрофільна бібліотека для багатозадачності мікропроцесорів - PQRST: Черга з пріоритетами для виконання простих завдань.

У цій моделі потік реалізований як підклас а Task, який запланований на деякий майбутній час (і, можливо, перенесений через рівні проміжки часу, якщо, як це прийнято, LoopTaskзамість нього підкласи ). run()Метод об'єкта викликається , коли завдання стає з - за. run()Метод робить деякі належну роботу, а потім повертається (це кооперативне біт); як правило, він підтримує деякий стан машини для управління своїми діями на послідовних викликах (тривіальний приклад - це light_on_p_змінна в наведеному нижче прикладі). Це вимагає невеликого переосмислення того, як ви впорядковуєте свій код, але виявився дуже гнучким та надійним при досить інтенсивному використанні.

Це агресивно щодо одиниць часу, тому він так само щасливий, що працює в одиницях, millis()як micros()і будь-який інший галочок.

Ось програма "блимати", реалізована за допомогою цієї бібліотеки. Це показує лише одне виконання завдань: інші завдання, як правило, створюються і починаються всередині setup().

#include "pqrst.h"

class BlinkTask : public LoopTask {
private:
    int my_pin_;
    bool light_on_p_;
public:
    BlinkTask(int pin, ms_t cadence);
    void run(ms_t) override;
};

BlinkTask::BlinkTask(int pin, ms_t cadence)
    : LoopTask(cadence),
      my_pin_(pin),
      light_on_p_(false)
{
    // empty
}
void BlinkTask::run(ms_t t)
{
    // toggle the LED state every time we are called
    light_on_p_ = !light_on_p_;
    digitalWrite(my_pin_, light_on_p_);
}

// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    flasher.start(2000);  // start after 2000ms (=2s)
}

void loop()
{
    Queue.run_ready(millis());
}

Це завдання "запуск до завершення", правда?
Едгар Бонет

@EdgarBonet Я не впевнений, що ти маєш на увазі. Після run()виклику методу він не переривається, тому він несе відповідальність закінчити розумно оперативно. Типово, однак, вона виконує свою роботу, а потім перенесе себе (можливо, автоматично, у разі підкласу LoopTask) на деякий майбутній час. Загальна модель полягає в тому, щоб завдання підтримувати деяку внутрішню машину стану (тривіальним прикладом є light_on_p_стан вище), щоб вона поводилася належним чином, коли наступна.
Норман Грей

Так, так, це завдання з запуском до завершення (RtC): жодне завдання не може виконуватися до того, як поточне завершить його виконання, повернувшись із run(). Це контрастує з кооперативними потоками, які можуть дати процесор, наприклад, шляхом викликом yield()або delay(). Або попереджувальні теми, які можна запланувати в будь-який час. Я вважаю, що відмінність важлива, оскільки я бачив, що багато людей, які приходять сюди, шукаючи теми, роблять це, тому що вони вважають за краще писати код блокування, а не державні машини. Блокування реальних потоків, які дають процесор, добре. Блокування завдань RtC не є.
Едгар Бонет

@EdgarBonet Це корисна відмінність, так. Я б розглядав і цей стиль, і нитки в стилі урожайності, як просто різні стилі кооперативної нитки, на відміну від попереджувальних потоків, але це правда, що для їх кодування потрібен інший підхід. Цікаво було б побачити продумане і поглиблене порівняння різних підходів, згаданих тут; одна приємна бібліотека, не згадана вище, - це прототипи . Я знаходжу речі, щоб критикувати в обох, але також і хвалити. Я (звичайно) віддаю перевагу своєму підходу, тому що він здається найбільш явним і не потребує додаткових пачок.
Норман Грей

(Корекція: protothreads було згадано, в відповідь @ sachleen в )
Norman Gray
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.