Багатозадачність на мікроконтролерах PIC


17

Багатозадачність важлива в ці дні. Цікаво, як ми можемо досягти цього в мікроконтролерах та вбудованому програмуванні. Я розробляю систему, що базується на мікроконтролері PIC. Я розробив його прошивку в MplabX IDE за допомогою C, а потім розробив додаток для цього в Visual Studio за допомогою C #.

Оскільки я звик використовувати теми в програмуванні C # на робочому столі для реалізації паралельних завдань, чи є такий спосіб зробити в коді мікроконтролера? MplabX IDE надає, pthreads.hале це лише заглушка без реалізації. Я знаю, що існує підтримка FreeRTOS, але використання цього робить ваш код складнішим. Деякі форуми говорять, що переривання також можна використовувати як багатозадачні завдання, але я не думаю, що переривання еквівалентні потокам.

Я розробляю систему, яка надсилає деякі дані до UART, і в той же час їй потрібно надсилати дані на веб-сайт через (дротовий) Ethernet. Користувач може керувати результатами через веб-сайт, але вихід вмикається / вимикається із затримкою на 2-3 секунди. Отже, це проблема, з якою я стикаюся. Чи є рішення для багатозадачних мікроконтролерів?


Нитки можна використовувати лише на процесорах, на яких працює ОС, оскільки потоки є частиною процесу, а процеси використовуються лише в ОС.
TicTacToe

@Zola так, ти маєш рацію. Але що робити у випадку контролерів?
Літак

2
Можливий дублікат RTOS для вбудованих систем
Roger Rowland

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

2
Ну, як я вже говорив, я надсилаю та отримую дані на uart і одночасно надсилаю та отримую дані в Ethernet. Крім цього, мені також потрібно зберігати дані на SD-картці разом із часом, так що так, DS1307 RTC задіяний і EEPROM також. До цього часу у мене є лише 1 UART, але через кілька днів я можу надсилати та отримувати дані з 3 модулів UART. Веб-сайт також отримає дані від 5 різних систем, встановлених у віддаленому місці. Це все повинно бути паралельним, але правильно не його паралельно, а із затримкою на кілька секунд. !
Літак

Відповіді:


20

Існує два основних типи багатозадачних операційних систем: попереджувальна та спільна. Обидва дозволяють визначити кілька завдань у системі, різниця полягає в тому, як працює комутація завдань. Звичайно, з одним ядерним процесором фактично виконується лише одне завдання.

Обидва типи багатозадачних ОС вимагають окремого стека для кожного завдання. Отже, це означає дві речі: по-перше, процесор дозволяє розміщувати стеки в будь-якому місці оперативної пам’яті, а отже, має вказівки переміщувати покажчик стека (SP) навколо - тобто немає апаратного стеку спеціального призначення, як це є в нижньому кінці PIC's. Це виключає серії PIC10, 12 та 16.

Можна майже повністю написати ОС на C, але перемикач завдань, де SP отримує рух, повинен бути в зборі. У різні часи я писав перемикачі завдань для PIC24, PIC32, 8051 та 80x86. Усі кишки відрізняються залежно від архітектури процесора.

Друга вимога - достатня кількість оперативної пам’яті для забезпечення декількох стеків. Зазвичай хотілося б принаймні пару сотень байт для стека; але навіть у всього 128 байт на завдання, для восьми стеків буде потрібно 1 К байт оперативної пам’яті - вам не потрібно виділяти один і той же розмір стека для кожного завдання. Пам'ятайте, що вам потрібен достатній стек для обробки поточного завдання та будь-яких викликів його вкладених підпрограм, а також стек місця для переривання виклику, оскільки ви ніколи не знаєте, коли це відбудеться.

Існують досить прості методи, щоб визначити, скільки стеку ви використовуєте для кожного завдання; наприклад, ви можете ініціалізувати всі стеки до певного значення, скажімо, 0x55, та запустити систему на деякий час, а потім зупинитись та вивчити пам'ять.

Ви не кажете, який тип PIC ви хочете використовувати. Більшість PIC24 та PIC32 мають багато місця для роботи багатозадачної ОС; PIC18 (єдиний 8-розрядний PIC, що має стеки в оперативній пам'яті) має максимальний розмір оперативної пам'яті 4K. Так що це досить іффі.

При спільній багатозадачності (простіший з двох) переключення завдань робиться лише тоді, коли завдання "відмовиться" від управління назад до ОС. Це відбувається, коли завдання потрібно викликати операційну систему ОС для виконання певної функції, якої вона буде чекати, наприклад запит вводу / виводу або виклик таймера. Це полегшує ОС для перемикання стеків, оскільки не потрібно зберігати всі регістри та інформацію про стан, SP можна просто переключити на інше завдання (якщо немає інших завдань, готових до запуску, бездіяльний стек - це дано контроль). Якщо в поточному завданні не потрібно здійснювати виклик ОС, але він працює протягом певного часу, йому потрібно відмовитися від керування, щоб добровільно реагувати на систему.

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

Windows 3.1 і новіші версії були кооперативними операційними системами, тому почасти їхня ефективність була не такою великою.

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

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

Як в коментарі зазначається supercat, однією з переваг спільної багатозадачності є легше обмінюватися ресурсами (наприклад, апаратне забезпечення, наприклад, багатоканальний АЦП або програмне забезпечення, наприклад, модифікація пов'язаного списку). Іноді дві задачі хочуть отримати доступ до одного і того ж ресурсу одночасно. За допомогою попереднього планування операційна система може перемикати завдання посередині однієї задачі за допомогою ресурсу. Таким чином, блокування необхідне для запобігання надходженню та доступу до того ж ресурсу до іншого завдання. За допомогою багатозадачності спільної роботи це не обов'язково, оскільки завдання контролює, коли він поверне себе до ОС.


3
Перевагою кооперативного багатозадачності є те, що в більшості випадків не потрібно використовувати блоки для координації доступу до ресурсів. Досить забезпечити, щоб завдання завжди залишали ресурси у доступному стані, коли вони відмовляються від контролю. Попереджувальне багатозадачність набагато складніше, якщо завдання може бути вимкнено, поки він містить блокування ресурсу, необхідного для іншого завдання. У деяких випадках друге завдання може заблокуватись довше, ніж це було б у системі співпраці, оскільки завдання, що тримає замок, присвячувало б ...
supercat

1
... повні ресурси для завершення дії, яка (в системі попередження) потребувала б блокування, тим самим зробивши охоронюваний об'єкт доступним для другого завдання.
supercat

1
Хоча кооперативні багатозадаччі вимагають дисципліни, гарантування того, що вимоги щодо термінів будуть дотримані, іноді може бути простішим у рамках кооперативного багатозадачності, ніж під попереднім. Оскільки дуже мало заблоків потрібно буде провести через перемикач завдань, система з переключенням завдань із п’ятьма завданнями, де завдання не повинні перевищувати 10 мс, не поступаючись, поєднується з невеликою логікою, яка говорить "Якщо завдання X терміново потрібно запустити, запустити його далі ", забезпечить, що завдання X ніколи не доведеться чекати більше 10 мс, як тільки він подає сигнал до того, як він запуститься. На противагу цьому, якщо завдання вимагає блокування, яке завдання X ...
supercat

1
... знадобиться, але вимикається вигідним комутатором, перш ніж випускати його, X може не зробити нічого корисного, поки планувальник процесора не обійдеться для виконання першого завдання. Якщо планувальник не включає логіку для розпізнавання та обробки інверсії пріоритетів, це може зайняти деякий час, перш ніж вона обійдеться, щоб перше завдання дозволило закінчити свій бізнес і звільнити замок. Такі проблеми не є нерозв'язними, але для їх вирішення потрібна велика складність, якої можна було б уникнути в системі кооперативів. Кооперативні системи чудово працюють за винятком однієї ґотчі: ...
supercat

3
вам не потрібно декілька стеків у кооперативі, якщо ви кодуєте в продовженнях. По суті, ваш код розділений за функціями, void foo(void* context)що логіка контролера (ядро) витягує одну вказівну та функціональну пару вказівника черги і викликає її по черзі. Ця функція використовує контекст для зберігання своїх змінних і подібних, а потім може додати подання продовження в чергу. Ці функції повинні швидко повернутися, щоб інші завдання поставили свій момент у процесорі. Це метод, заснований на подіях, що вимагає лише одного стека.
щурячий вирод

16

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

  • Класична основна петля для голосування. Ваша основна функція має деякий час (1), який виконує завдання 1, а потім виконує завдання 2 ...
  • Основний цикл + прапорці ISR: у вас є ISR, який виконує критичну за часом функцію, а потім попереджає головний цикл за допомогою змінної прапорця, що завдання потребує обслуговування. Можливо, ISR поміщає нового символу в круговий буфер, а потім повідомляє головному циклу обробляти дані, коли він готовий до цього.
  • Весь ISR: Значна частина логіки тут виконується з ISR. На сучасному контролері типу ARM, який має кілька рівнів пріоритетності. Це може забезпечити потужну "потокоподібну" схему, але також може бути заплутаною для налагодження, тому вона має бути зарезервована лише для критичних обмежень у часі.
  • RTOS: Ядро RTOS (полегшене за допомогою ISR таймера) дозволяє перемикатися між декількома потоками виконання. Ви згадали FreeRTOS.

Я б радив вам використовувати найпростіші з перерахованих вище схем, які працюватимуть для вашої заявки. З того, що ви описуєте, я мав би основний цикл, що генерує пакети та розміщував їх у кругових буферах. Потім майте драйвер на основі UART ISR, який спрацьовує кожного разу, коли попередній байт відправляється, поки не буде надісланий буфер, а потім чекає більше вмісту буфера. Аналогічний підхід для Ethernet.


3
Це дуже корисна відповідь, оскільки вона стосується корінь проблеми (як зробити багатозадачність на невеликій вбудованій системі, а не на потоки як рішення). Абзац про те, як це можна застосувати до оригінального питання, був би чудовим, можливо, включаючи плюси та мінуси кожного для сценарію.
Девід

8

Як і в будь-якому одноядерному процесорі, робити багатозадачність справжнього програмного забезпечення неможливо. Тому ви повинні подбати про перехід між декількома завданнями в одну сторону. Різні RTOS дбають про це. У них є планувальник і на основі системної галочки вони переключатимуться між різними завданнями, щоб надати вам можливість багатозадачності.

Концепції, що беруть участь у цьому (збереження та відновлення контексту), є досить складними, тому робити це вручну, ймовірно, буде складно і робить ваш код складнішим, і тому, що ви ніколи цього не робили, у ньому будуть помилки. Моя порада тут буде використовувати тестований RTOS так само, як FreeRTOS.

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

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

Єдиний реальний спосіб домогтися одночасного виконання декількох завдань на одноядерному MCU - це використання DMA та периферійних пристроїв, оскільки вони працюють незалежно від ядра (DMA і MCU діляться однією шиною, тому вони працюють трохи повільніше, коли обидва активні). Тож поки DMA перетасовує байти до UART, ваше ядро ​​вільно може надсилати інформацію до Ethernet.


2
дякую, DMA звучить цікаво. Я обов'язково шукаю це!
Літак

Не всі серії PIC мають DMA.
Метт Янг

1
Я використовую PIC32;)
Літак

6

В інших відповідях уже описані найбільш використовувані варіанти (основний цикл, ISR, RTOS). Ось ще один варіант компромісу: Protothreads . Це в основному дуже легка вкладка для ниток, яка використовує основний цикл і деякі макроси C, щоб "імітувати" RTOS. Звичайно, це не повна ОС, але для "простих" потоків це може бути корисно.


звідки я можу завантажити його вихідний код для Windows? Я думаю, що його доступно лише для Linux.!
Літак

@CZAbhinav Це має бути незалежно від ОС, і ви можете отримати останню завантаження тут .
erebos

Я зараз у Windows і використовую MplabX, я не вважаю його корисним тут. Все одно дякую!
Літак

Ніколи не чув про прототони, це звучить як цікава техніка.
Арсенал

@CZAbhinav Про що ти говориш? Це код C і не має нічого спільного з вашою операційною системою.
Метт Янг

3

Моя основна конструкція для мінімально розрізаного часу RTOS не змінилася сильно за декілька мікросімей. Це в основному перерва таймера, керуючи державною машиною. Програма обслуговування переривання - це ядро ​​ОС, тоді як оператор перемикання в основному циклі - це завдання користувача. Драйвери пристроїв - це службові програми переривань для переривань вводу / виводу.

Основна структура така:

unsigned char tick;

void interrupt HANDLER(void) {
    device_driver_A();
    device_driver_B();
    if(T0IF)
    {
        TMR0 = TICK_1MS;
        T0IF = 0;   // reset timer interrupt
        tick ++;
    }
}

void main(void)
{
    init();

    while (1) {
        // periodic tasks:
        if (tick % 10 == 0) { // roughly every 10 ms
            task_A();
            task_B();    
        }
        if (tick % 55 == 0) { // roughly every 55 ms
            task_C();
            task_D();    
        }

        // tasks that need to run every loop:
        task_E();
        task_F();
    }
}

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

Ви можете побачити приклад такого стилю архітектури в моєму програмному забезпеченні передавача RC (так, я фактично використовую його для польоту літаків RC, тому це дещо важливо для безпеки, щоб запобігти мені розбиття літаків і потенційно вбивство людей): https://github.com / slebetman / pic-txmod . В основному це 3 завдання - 2 завдання в режимі реального часу, реалізовані як драйвери драйверів пристроїв (див. Матеріали ppmio) та 1 фонове завдання, що реалізує логіку змішування. Так що в основному він схожий на ваш веб-сервер тим, що він має 2 потоки вводу / виводу.


1
Я б не називав це «кооперативним багатозадачністю», оскільки це насправді не відрізняється від будь-якої іншої програми мікроконтролерів, яка має робити кілька речей.
whatsisname

2

Хоча я розумію, що питання спеціально задає питання про використання вбудованого RTOS, мені здається, що більш широке питання, яке мені задають, - це "як досягти багатозадачності на вбудованій платформі".

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

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

Сира ілюстрація:

for(;;)
{
    main_lcd_ui_tick();
    networking_tick();
}


...

// In your LCD UI module:
void main_lcd_ui_tick(void)
{
    check_for_key_presses();
    update_lcd();
}

...

// In your networking module:
void networking_tick(void)
{
    //'Tick' the TCP/IP library. In this example, I'm periodically
    //calling the main function for Keil's TCP/IP library.
    main_TcpNet();
}

Однопоточна структура програмування, подібна до цієї, коли ви періодично викликаєте основні функції машинного стану з основного циклу планувальника, є повсюдним у вбудованому програмуванні, і саме тому я б настійно закликав ОП спочатку бути знайомим і комфортним з ним, перш ніж занурюватися безпосередньо в використання Завдання / потоки RTOS.

Я працюю над типом вбудованого пристрою, який має апаратний ЖК-інтерфейс, внутрішній веб-сервер, клієнт електронної пошти, клієнт DDNS, VOIP та багато інших функцій. Хоча ми використовуємо RTOS (Keil RTX), кількість використаних окремих потоків (завдань) дуже мала, і більшість «багатозадачності» досягається, як описано вище.

Наведіть кілька прикладів бібліотек, які демонструють цю концепцію:

  1. Мережева бібліотека Кіля. Весь стек TCP / IP може запускатися однопотоково; Ви періодично викликаєте main_TcpNet (), який ітераціює стек TCP / IP та будь-який інший варіант мереж, який ви склали з бібліотеки (наприклад, веб-сервер). Дивіться http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm . Справді, в деяких ситуаціях (можливо, поза рамками цієї відповіді) ви дійдете до того моменту, коли це стає корисним або необхідним для використання потоків (особливо, якщо використовується блокування розеток BSD). (Далі зауважте: новий V5 MDK-ARM фактично породжує виділену нитку Ethernet - але я просто намагаюся надати ілюстрацію.)

  2. Бібліотека VOIP Linphone. Сама бібліотека лінійних телефонів є однопоточною. Ви викликаєте iterate()функцію через достатній інтервал. Див. Http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate () . (Трохи поганий приклад, тому що я використовував це на вбудованій платформі Linux і бібліотеках залежностей лінфонів, безсумнівно, породив нитки, але знову ж таки, щоб проілюструвати точку.)

Повертаючись до конкретної проблеми, окресленої ОП, проблемою здається той факт, що UART-зв'язок повинен відбуватися одночасно з деякою мережею (передача пакетів через TCP / IP). Я не знаю, яку мережеву бібліотеку ви фактично використовуєте, але я припускаю, що в ній є основна функція, яку потрібно часто викликати. Вам потрібно буде написати свій код, який стосується передачі / прийому даних UART, щоб бути структурованим аналогічно, як державна машина, яку можна повторювати періодичними викликами до основної функції.


2
Дякую за це приємне пояснення, я використовую бібліотеку TCP / IP, що надається мікрочіпом, і це дуже величезний складний код. Мені якось вдалося розбити його на частини і зробити його корисним відповідно до моїх вимог. Я обов'язково спробую один із ваших підходів.
Літак

Розважайтеся :) Використання RTOS безумовно полегшує життя у багатьох ситуаціях. На мій погляд, використання потоку (завдання) значно спрощує програмування в одному сенсі, оскільки ви можете уникнути необхідності розбивати завдання на машині стану. Натомість ви просто записуєте код завдання так, як це було б у своїх програмах C #, при цьому ваш код завдання створюється так, ніби це єдине, що існує. Важливо вивчити обидва підходи, і, як ви робите більше вбудованого програмування, ви починаєте відчувати, який підхід найкращий у кожній ситуації.
Сторінка Тревору

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