Ako súčasne vykonávať viacero úloh alebo procesov (Multitasking) s Arduino
Povedzme, že máte projekt Arduino, v ktorom chcete vykonávať niekoľko akcií súčasne:
Napríklad – čítať údaje z používateľského vstupu, rozblikať niektoré diódy, monitorovať potenciometer atď. Takže v podstate chcete robiť s Arduinom nejaký multitasking.

A to je miesto, kde sa veci trochu skomplikujú, najmä ak ste už zvyknutí vytvárať nové vlákna vždy, keď potrebujete spustiť nový paralelný program. Skutočné multithreading na Arduine nie je možné robiť.

Ale mám pre vás dobrú správu:
S Arduinom môžete stále vykonávať multitasking. Všetko, čo potrebujete, je trochu pochopenia, aby veci fungovali hladko! Predtým, ako vám vysvetlím, ako robiť multitasking s Arduinom, pozrime sa, prečo nemôžete robiť „štandardné“ paralelné programovanie.

Rozdiel medzi počítačom, ako je laptop alebo webový server, a elektronickou doskou s mikrokontrolérom (Arduino) :

S Arduino sú ale veci úplne iné.
„Mozgom“ dosky Arduino je mikrokontrolér (ATmega328 pre Arduino Uno). Mikrokontrolér má iba jedno jadro a je schopný naraz vykonávať iba jednu inštrukciu. Takže, ak sa rozhodnete urobiť pauzu vo funkcii, potom sa celý váš program zasekne a čaká. Vždy, keď niečo napíšete, musíte myslieť na to, aký to bude mať dopad na celý kód.
Ako robiť multitasking s Arduino ?
Ak budete robiť malé akcie a robiť ich veľmi rýchlo, jednu po druhej, získate pocit multitaskingu. To je princíp multitaskingu s Arduino. Ukážme si to na optickej ilúzii.

Ak si vezmete jeden modrý papier a jeden červený papier a veľmi rýchlo ich striedate pred vašimi očami (aspoň 10-krát za sekundu), uvidíte fialovú farbu. Pretože zmena medzi farbami prebieha tak rýchlo, získate ilúziu, že všetky farby sú zmiešané do inej farby. To je zhruba to, čo je multitasking s Arduino, ale s oveľa vyššou frekvenciou.
Začnime multitasking
Ak chcete vykonávať multitasking s Arduino, postupujte podľa týchto tipov:
- Udržujte čas vykonania všetkých vašich funkcií veľmi krátky. Nehovorím, že vaše funkcie by mali mať maximálne (x) riadkov kódu. Hovorím, že by ste mali sledovať čas vykonania a uistiť sa, že je dosť krátky.
- Nepoužívajte funkciu delay(). Táto funkcia úplne zablokuje váš program. Ako uvidíme nižšie na príklade kódu, existujú aj iné spôsoby, ako dosiahnuť rovnaké správanie ako pri funkcii delay().
- Ak váš program počúva vstup používateľa, napríklad textovú správu cez sériovú komunikáciu, potom to znamená, že nemáte kontrolu nad tým, kedy sa táto udalosť stane, pretože je z externého zdroja. Najjednoduchší spôsob, ako získať vstup od používateľa, je počkať naň a potom pokračovať v vykonávaní programu, keď získate údaje.
- Na dlhšie procesy použite niečo ako stavový automat. Povedzme, že máte proces, ktorý skutočne vyžaduje veľa rôznych akcií a určitý čas na čakanie medzi dvoma akciami. V takom prípade by ste mali tento proces rozdeliť na niekoľko malých funkcií (pozri prvý bod vyššie) a vytvoriť stavový automat vo svojom hlavnom programe, aby ste ich v prípade potreby mohli volať jeden po druhom. To vám umožní vypočítať aj akúkoľvek inú časť programu medzi 2 krokmi procesu.
Príklad kódu :
Schéma
Zapojenie, ktoré budeme ovládať

Čo všetko tu mame
- 4
diódy pripojené k 4 digitálnym pinom (ako výstup). Odpory sú 220 ohmov.
- 1 tlačidlo pripojené k 1 digitálnemu kolíku (ako vstup). Rezistor je 10 kOhm.
- 1 potenciometer pripojený k 1 analógovému kolíku (ako vstup).
- A ešte (nezobrazuje sa) jedna sériová komunikácia cez USB kábel s počítačom.
Čo chceme robiť:
- Každú sekundu blikne
1.
- Prečítať si vstup používateľa cez seriál port (hodnota potenciometru) (číslo medzi 0 a 255) a zapísať údaje do
číslo 2.
- Zapnúť
3, ak je stlačené tlačidlo.
- Zapnúť
4, ak je hodnota potenciometra väčšia ako 512.
- Zobraziť hodnotu potenciometra cez seriál port každé 2 sekundy.
A ako ste uhádli, urobíme všetko súčasne.
Jednoducho rozdelíme kód na malé kúsky kódu, ktoré sa veľmi rýchlo vykonávajú. Jeden malý kúsok kódu pre jednu úlohu.
#define LED_1_PIN 9
#define LED_2_PIN 10
#define LED_3_PIN 11
#define LED_4_PIN 12
#define POTENTIOMETER_PIN A0
#define BUTTON_PIN 5
unsigned long previousTimeLed1 = millis();
long timeIntervalLed1 = 1000;
int ledState1 = LOW;
unsigned long previousTimeSerialPrintPotentiometer = millis();
long timeIntervalSerialPrint = 2000;
void setup() {
// Tento kód sa spusti len raz :
Serial.begin(9600);
pinMode(LED_1_PIN, OUTPUT);
pinMode(LED_2_PIN, OUTPUT);
pinMode(LED_3_PIN, OUTPUT);
pinMode(LED_4_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
}
void loop() {
// vložte sem svoj hlavný kód, aby sa spustil opakovane:
unsigned long currentTime = millis();
// úloha 1
if(currentTime - previousTimeLed1 > timeIntervalLed1) {
previousTimeLed1 = currentTime;
if (ledState1 == HIGH) {
ledState1 = LOW;
}
else {
ledState1 = HIGH;
}
digitalWrite(LED_1_PIN, ledState1);
}
// úloha 2
if (Serial.available()) {
int userInput = Serial.parseInt();
if (userInput >= 0 && userInput < 256) {
analogWrite(LED_2_PIN, userInput);
}
}
// úloha 3
if (digitalRead(BUTTON_PIN) == HIGH) {
digitalWrite(LED_3_PIN, HIGH);
}
else {
digitalWrite(LED_3_PIN, LOW);
}
// úloha 4
int potentiometerValue = analogRead(POTENTIOMETER_PIN);
if (potentiometerValue > 512) {
digitalWrite(LED_4_PIN, HIGH);
}
else {
digitalWrite(LED_4_PIN, LOW);
}
// úloha 5
if (currentTime - previousTimeSerialPrintPotentiometer > timeIntervalSerialPrint) {
previousTimeSerialPrintPotentiometer = currentTime;
Serial.print("Value : ");
Serial.println(potentiometerValue);
}
}
Rozoberme si kód krok za krokom, aby ste pochopili, o čom to je
Nastavenie :
#define LED_1_PIN 9
#define LED_2_PIN 10
#define LED_3_PIN 11
#define LED_4_PIN 12
#define POTENTIOMETER_PIN A0
#define BUTTON_PIN 5
unsigned long previousTimeLed1 = millis();
long timeIntervalLed1 = 1000;
int ledState1 = LOW;
unsigned long previousTimeSerialPrintPotentiometer = millis();
long timeIntervalSerialPrint = 2000;
Len pre prehľadnosť som použil niektoré #define na použitie mien namiesto čísel pre všetky hardvérové piny. Tiež som deklaroval niektoré premenné na sledovanie času.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(LED_1_PIN, OUTPUT);
pinMode(LED_2_PIN, OUTPUT);
pinMode(LED_3_PIN, OUTPUT);
pinMode(LED_4_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
}
Ako viete, funkcia setup() sa na Arduine volá najskôr. Tu iba inicializujeme sériovú komunikáciu a nastavíme správny režim pre digitálne kolíky (analógové kolíky nevyžadujú nastavenie, pretože sú automaticky nastavené ako vstupné kolíky).
void loop() {
// vložte sem svoj hlavný kód, aby sa spustil opakovane:
unsigned long currentTime = millis();
A… skočíme priamo do funkcie loop()! Táto funkcia sa bude volať znova a znova, kým bude trvať váš program. Prvá vec, ktorú tu urobíme, je získať aktuálny čas pomocou millis(). Toto je veľmi dôležité. Pri väčšine čiastkových úloh programu použijeme určité techniky sledovania času na spustenie akcie, čím sa vyhneme použitiu funkcie delay().
Úloha 1: Každú sekundu zablikajte 1
// úloha 1
if(currentTime - previousTimeLed1 > timeIntervalLed1) {
previousTimeLed1 = currentTime;
if (ledState1 == HIGH) {
ledState1 = LOW;
}
else {
ledState1 = HIGH;
}
digitalWrite(LED_1_PIN, ledState1);
}
Tu vypočítame trvanie medzi aktuálnym časom a posledným spustením 1. Ak je trvanie dlhšie ako interval, ktorý sme predtým nastavili (tu 1 sekunda), môžeme skutočne vykonať akciu! Všimnite si, že keď vstúpime do bloku if, nastavíme predchádzajúci čas ako aktuálny.
Týmto spôsobom hovoríme programu: „nevracaj sa sem, kým neuplynie ďalší časový interval“. Používanie tejto štruktúry kódu je v Arduine celkom bežné. Ak to nepoznáte, nájdite si čas na napísanie kódu a vyskúšajte niekoľko príkladov sami. Keď pochopíte, ako to funguje, budete ho používať všade vo svojich programoch Arduino!
Úloha 2: Prečítajte si vstup používateľa zo sériového čísla (číslo medzi 0 a 255) a zapíšte údaje do 2
// úloha 2
if (Serial.available()) {
int userInput = Serial.parseInt();
if (userInput >= 0 && userInput < 256) {
analogWrite(LED_2_PIN, userInput);
}
}
Musíme počúvať seriál, aby sme mohli získať vstup od používateľa. Namiesto blokovania jednoducho zavoláme metódu Serial.available() zakaždým, keď sme vo funkcii main loop(). Keďže všetky ostatné bloky kódu sú dosť malé a rýchle, môžeme očakávať, že Serial bude monitorovaný pomerne často. Týmto spôsobom máme istotu, že nám pri vykonávaní akejkoľvek inej akcie na strane neuniknú žiadne údaje.
Úloha 3: Zapnite 3, ak je stlačené tlačidlo
// úloha 3
if (digitalRead(BUTTON_PIN) == HIGH) {
digitalWrite(LED_3_PIN, HIGH);
}
else {
digitalWrite(LED_3_PIN, LOW);
}
Len monitorujeme tlačidlo zakaždým, keď spustíme funkciu loop() (Všimnite si, že tento kód by sa dal vylepšiť funkciou debounce).
Úloha 4: Zapnite 4, ak je hodnota potenciometra väčšia ako 512
// úloha 4
int potentiometerValue = analogRead(POTENTIOMETER_PIN);
if (potentiometerValue > 512) {
digitalWrite(LED_4_PIN, HIGH);
}
else {
digitalWrite(LED_4_PIN, LOW);
}
Rovnako ako pri tlačidle, iba načítame hodnotu a aktualizujeme v závislosti od tejto hodnoty. Všimnite si, že pre potenciometer používame analogRead() a získaná hodnota je medzi 0 a 1023 (analógový prevodník Arduino má rozlíšenie 10 bitov a 2^10 = 1024).
Úloha 5: Vytlačte hodnotu potenciometra cez sériové číslo každé 2 sekundy
// úloha 5
if (currentTime - previousTimeSerialPrintPotentiometer > timeIntervalSerialPrint) {
previousTimeSerialPrintPotentiometer = currentTime;
Serial.print("Value : ");
Serial.println(potentiometerValue);
}
Rovnako ako pri úlohe 1 vypočítame časový rozdiel (trvanie), aby sme skontrolovali, či môžeme akciu vykonať alebo nie. Toto je pekná alternatíva k funkcii delay(). A tu namiesto spustenia len odošleme hodnotu so sériovou komunikáciou. Zdá sa, že celý kód beží veľmi rýchlo a ak skutočne zostavíte tento obvod a spustíte tento program, budete mať skutočný pocit multithreadingu.
Keď už viete, ako vytvoriť jeden malý blok kódu, aby bežal veľmi rýchlo (tento kód môžete vložiť aj do funkcie), všetko, čo musíte urobiť, je zopakovať túto štruktúru pre každý krok zložitého procesu, ktorý chcete urobiť.
Niektoré ďalšie spôsoby multitaskingu
Funkcie prerušenia
Niektoré piny Arduino podporujú hardvérové prerušenie. V podstate vytvoríte funkciu, ktorá sa spustí tlačidlom alebo iným ovládačom na hardvérovom kolíku. Keď sa spustí prerušenie, program sa preruší a vaša funkcia sa vykoná. Po dokončení funkcie program pokračuje tam, kde bol. Vaša funkcia by samozrejme mala byť veľmi rýchla, takže nezastaví hlavné vykonávacie „vlákno“ príliš dlho.
To môže byť skvelé na vykonanie nejakého kódu v závislosti od niektorých externých vstupov a uistite sa, že vám neunikne žiadny vstup, ak je frekvencia prichádzajúceho signálu veľmi vysoká. Ako príklad si pozrite úlohu č.3 vyššie uvedeného príkladu kódu. Tlačidlo sledujeme veľmi rýchlym potiahnutím jeho stavu. Tu by sme mohli použiť aj prerušenia na spustenie pri každom stlačení tlačidla.
Prerušenia Arduino sú ako upozornenia push. Môže sa to stať kedykoľvek. Ak sa rozhodnete nepoužívať prerušenia, budete musieť manuálne skontrolovať (vytiahnuť) vstup, aby ste zistili, či môžete spustiť akciu. Rozdiel je len v tom, že ak s Arduino „potiahnete“ o upozornenie a upozornenie sa stratí, neuvidíte ho. Nie je to však nevyhnutne zlá vec: ak nepotrebujete byť extra precízni, keď bol spustený špendlík, môžete jednoducho vytiahnuť stav, kedykoľvek budete chcieť. V tomto prípade máte úplnú kontrolu nad kontrolou vstupu.
Protothreads
Je čistá knižnica v jazyku C. Vyžaduje si to viac vedomostí a je zložitejšie na ovládanie pre začiatočníkov a programátorov strednej úrovne. S Protothreads môžete tiež „falšovať“ multithreading pre systémy riadené udalosťami, takže je to celkom užitočné pre zložitejšie programy Arduino. Upozorňujeme však, že používanie Protothreads nie je absolútne povinné. Môžete úplne napísať kompletný multitaskový program so základnými tipmi, ktoré som vám dal skôr v tomto príspevku.
Môžete sa cítiť ohromení všetkými vecami, ktoré čítate o multithreadingu a o tom, ako by to malo byť „zložité na zvládnutie“. Ale v skutočnosti, dodržiavaním niekoľkých celkom jednoduchých pravidiel, môžete ísť s Arduinom dosť ďaleko.
Ohmov zákon: Základ elektrických obvodov
Pridaj komentár