Arduino multitasking - část druhá

Při provádění projektů často existují chvíle, kdy chcete, aby procesor udělal ještě jednu věc. Ale jak ho k tomu přimět, když je zaneprázdněn jinými úkoly?

Orchestr muž

Man-orchestr, fotografie z doby kolem roku 1865, zdroj Wikimedia.

V této příručce budeme stavět na technikách, které jsme se naučili v části 1 .

Naučíme se, jak zkrotit přerušení stopek, aby vše fungovalo jako hodinky. Zjistíme také, jak používat externí přerušení k upozornění na externí události.

servo jako hodinky

Spojení

Všechny příklady v této příručce budou používat schéma připojení níže:

Schéma připojení

Co je to přerušení?

Přerušení je signál, který říká CPU, aby okamžitě zastavil vše a pokračoval v zpracování s vysokou prioritou. Jmenuje se Interrupt Handling.

Obsluha přerušení je další prázdná funkce. Pokud jeden napíšete a připojíte jej k přerušení, bude volán vždy, když je spuštěn signál přerušení. Když tuto funkci deaktivujete, procesor bude pokračovat v tom, co dělal dříve.

Odkud přicházejí přerušení?

Přerušení lze generovat z několika zdrojů:

  • Časová přerušení způsobená zátkami Arduino.
  • Externí přerušení způsobená změnou stavu jednoho z kolíků vnějšího přerušení.
  • Přerušení výměny kolíků způsobené změnou stavu kteréhokoli z kolíků ve skupině.

K čemu jsou užitečné?

Pomocí přerušení již nemusíte do kódu psát smyčky, abyste neustále kontrolovali stav přerušení s vysokou prioritou. Díky dlouhotrvajícím podprogramům se nemusíte starat o pomalé reakce nebo zapomínání na tlačítko.

Během přerušení procesor automaticky zastaví všechny operace a zavolá obsluhu přerušení. Musíte jen napsat kód, který bude reagovat na každé přerušení.

Přerušení času

Přerušení času

Nevolajte nám, zavoláme vám

V části 1 jsme se naučili, jak používat funkci millis () v prodlevě. Aby to fungovalo, museli jsme si to po každé smyčce připomenout, abychom zjistili, zda je třeba něco udělat. Volání této funkce více než každou milisekundu jen proto, abychom zjistili, že se čas nezměnil, bylo plýtvání. Bylo by lepší, kdyby k této kontrole došlo pouze každou milisekundu.

Časovače a časovače nám umožňují právě to. Můžeme nastavit stopky, aby nás přerušovaly jednou za milisekundu. Potom nás bude sám informovat, kdy bychom měli zkontrolovat čas!

Zátky Arduino

Existují 3 typy stopek Arduino Uno: Stopwatch0, Stopwatch1 a Stopwatch2. První je již nastaven na generování milisekundového přerušení, které aktualizuje počítadlo milisekund oznámené funkcí millis (). Použijeme také Stopwatch0.

Četnost a počítání

Časovače jsou jednoduché časovače, které běží na určité frekvenci odvozené od systémových hodin 16 MHz. Rozdělovač hodin můžete nastavit tak, aby měnil frekvenci a další různé režimy počítání. Můžete je také nastavit tak, aby vytvářely přerušení, když časovač dosáhne určitého čísla.

Stopky0 mají 8 bitů a mohou počítat od 0 do 255 a také generují přerušení pokaždé, když přeteknou. Ve výchozím nastavení používá dělič hodin 64, aby poskytoval přerušovací frekvenci 976,5625 Hz. Nebudeme měnit frekvenci Stopwatch0, protože by to změnilo funkci millis ()!

Srovnávací registry

Zátky Arduino mají řadu konfiguračních registrů. Lze je číst nebo zapisovat pomocí speciálních znaků specifikovaných v Arduino IDE.

Nastavíme registr komparátoru pro Stopky 0 (tento registr je známý jako OCR0A), který generuje další přerušení během počítání. Při každém zaškrtnutí je počítadlo časovače porovnáno se srovnávacím registrem, a když jsou stejné, je vygenerováno přerušení.

Následující kód vytvoří přerušení 'TIMER0_COMPA', kdykoli čítač hodnot překročí 0xAF.

 // Timer0 se již používá pro millis () - prostě někde vyrušíme
// uprostřed a zavoláme níže uvedenou funkci „Porovnat A“
OCR0A = 0xAF;
TIMSK0 | = _BV (OCIE0A);

Dále definujeme obslužný program přerušení pro vektor časového přerušení známý jako „TIMER0_COMPA_vect“. V tomto obslužném programu přerušení uděláme vše, co jsme udělali ve smyčce.

 // Přerušení se volá jednou za milisekundu,
SIGNÁL (TIMER0_COMPA_vect)
{
nepodepsaný dlouhý proud Millis = millis ();
sweeper1.Update (currentMillis);

// if (digitalRead (2) == HIGH)
{
sweeper2.Update (currentMillis);
led1.Update (currentMillis);
}

led2.Update (currentMillis);
led3.Update (currentMillis);
}

To nám ponechává zcela prázdnou smyčku.

 neplatná smyčka ()
{
}

Nyní můžete dělat cokoli ve své smyčce. Dokonce používejte funkci dealy () bez následků! To nezmění fungování blikačů a zametacích strojů .

Zdrojový kód:

Zde je veškerý kód, včetně blikačů a zametacích strojů :

 #include 
třída Flasher
{
// Proměnné členů třídy
// Jsou inicializovány při spuštění
int ledPin; // číslo kolíku LED
dlouhý OnTime; // milisekundy doby zapnutí
dlouhý čas OffTime; // milisekundy mimo provoz
// Udržují aktuální stav
int ledState; // ledState slouží k nastavení LED
nepodepsaný dlouhý předchozí Millis; // uloží poslední aktualizaci LED // Konstruktor - vytvoří Flasher
// a inicializuje členské proměnné a stav
veřejnost:
Blikač (int pin, long on, long off)
{
ledPin = špendlík;
pinMode (ledPin, OUTPUT);

OnTime = zapnuto;
OffTime = vypnuto;

ledState = LOW;
previousMillis = 0;
} void Update (nepodepsaný dlouhý currentMillis)
{
if ((ledState == HIGH) && (currentMillis - previousMillis> = OnTime))
{
ledState = LOW; // Vypněte to
previousMillis = currentMillis; // Pamatovat čas
digitalWrite (ledPin, ledState); // Aktualizace aktuální LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis> = OffTime))
{
ledState = HIGH; // zapnout
previousMillis = currentMillis; // Pamatovat čas
digitalWrite (ledPin, ledState); // Aktualizace aktuální LED
}
}
};

třídní zametač
{
Servo servo; // servo
int pos; // aktuální poloha serva
přírůstek int; // přírůstek pro pohyb pro každý interval
int updateInterval; // interval mezi aktualizacemi
nepodepsaný dlouhý lastUpdate; // poslední aktualizace pozice public:
Zametač (int interval)
{
updateInterval = interval;
přírůstek = 1;
}

void Attach (int pin)
{
servo.attach (pin);
}

void Detach ()
{
servo.detach ();
}

void Update (nepodepsaný dlouhý currentMillis)
{
if ((currentMillis - lastUpdate)> updateInterval) // čas aktualizace
{
lastUpdate = millis ();
pos + = přírůstek;
servo.write (pos);
if ((pos> = 180) || (pos <= 0)) // konec tažení
{
// opačný směr
přírůstek = -přírůstek;
}
}
}
};

LED1 blikač (11, 123, 400);
LED2 blikač (12, 350, 350);
LED3 blikač (13 200 222);
Zametací zametací stroj1 (25);
Zametací zametací stroj2 (35);

neplatné nastavení ()
{
zametač 1. připevněte (9);
2. zametač (10);

// Timer0 se již používá pro millis () - prostě někde vyrušíme
// uprostřed a zavoláme níže uvedenou funkci „Porovnat A“
OCR0A = 0xAF;
TIMSK0 | = _BV (OCIE0A);
}

// Přerušení se volá jednou za milisekundu, aby se aktualizovaly LED diody
// Sweeper2 s není aktualizován, pokud je stisknuto tlačítko na digitálním 2.
SIGNÁL (TIMER0_COMPA_vect)
{
nepodepsaný dlouhý proud Millis = millis ();
sweeper1.Update (currentMillis);

if (digitalRead (2) == HIGH)
{
sweeper2.Update (currentMillis);
led1.Update (currentMillis);
}

led2.Update (currentMillis);
led3.Update (currentMillis);
}

neplatná smyčka ()
{
}

Externí přerušení

Kdy nepoužívat opakování

Na rozdíl od časových přerušení jsou externí přerušení spouštěna vnějšími událostmi. Například po stisknutí tlačítka nebo po přijetí pulzu z rotačního kodéru. Nemusíte však stále kontrolovat, zda je třeba změnit GPIO piny, jako jste to udělali s časovými přerušeními.

Arduino UNO má 2 externí kolíky přerušení. V tomto příkladu připojíme tlačítko k jednomu z těchto kolíků a použijeme je k resetování našich zametacích strojů. Nejprve přidáme funkci „reset ()“ do třídy zametače. Tato funkce nastaví polohu na 0 a okamžitě tam umístí servo:

 reset prázdnoty ()
{
pos = 0;
servo.write (pos);
přírůstek = abs (přírůstek);
}

Poté přidáme volání AttachInterrupt (), abychom propojili externí přerušení s kódem obslužné rutiny.

V UNO je Interrupt 0 vázán na digitální pin 2. Přikazujeme mu, aby našel FALLING hranu signálu tohoto pin. Po stisknutí tlačítka signál „klesne“ z HIGH na LOW a je vyvolán obslužný program přerušení „Reset“.

 pinMode (2, INPUT_PULLUP);
attachInterrupt (0, Reset, PÁD);

A tady je "Reset" obslužné rutiny přerušení. Vyvolá funkce resetování zametače:

 void Reset ()
{
sweeper1.reset ();
sweeper2.reset ();
}

Od tohoto okamžiku, kdykoli stisknete tlačítko, serva zastaví své úkoly a okamžitě začnou hledat nulovou pozici.

Zdrojový kód:

Tady je kompletní náčrt se zarážkami a vnějšími zlomky:

 #include 

třída Flasher
{
// Proměnné členů třídy
// Jsou inicializovány při spuštění
int ledPin; // číslo kolíku LED
dlouhý OnTime; // milisekundy doby zapnutí
dlouhý čas OffTime; // milisekundy mimo provoz
// Udržují aktuální stav
volatile int ledState; // ledState slouží k nastavení LED
volatilní nepodepsaný dlouhý předchozí Millis; // uloží poslední aktualizaci LED
// Konstruktor - vytvoří Flasher
// a inicializuje členské proměnné a stav
veřejnost:
Blikač (int pin, long on, long off)
{
ledPin = špendlík;
pinMode (ledPin, OUTPUT);
OnTime = zapnuto;
OffTime = vypnuto;
ledState = LOW;
previousMillis = 0;
}
void Update (nepodepsaný dlouhý currentMillis)
{
if ((ledState == HIGH) && (currentMillis - previousMillis> = OnTime))
{
ledState = LOW; // Vypněte to
previousMillis = currentMillis; // Pamatovat čas
digitalWrite (ledPin, ledState); // Aktualizace aktuální LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis> = OffTime))
{
ledState = HIGH; // zapnout
previousMillis = currentMillis; // Pamatovat čas
digitalWrite (ledPin, ledState); // Aktualizace aktuální LED
}
}
};

třídní zametač
{
Servo servo; // servo
int updateInterval; // interval mezi aktualizacemi
volatilní int pos; // aktuální poloha serva
volatilní nepodepsaný dlouhý lastUpdate; // poslední aktualizace polohy
volatilní přírůstek int; // přírůstek pro pohyb pro každý interval
veřejnost:
Zametač (int interval)
{
updateInterval = interval;
přírůstek = 1;
}

void Attach (int pin)
{
servo.attach (pin);
void Detach ()
{
servo.detach ();
} void reset ()
{
pos = 0;
servo.write (pos);
přírůstek = abs (přírůstek);
}
void Update (nepodepsaný dlouhý currentMillis)
{
if ((currentMillis - lastUpdate)> updateInterval) // čas aktualizace
{
lastUpdate = currentMillis;
pos + = přírůstek;
servo.write (pos);
if ((pos> = 180) || (pos <= 0)) // konec tažení
{
// opačný směr
přírůstek = -přírůstek;
}
}
}
};
LED1 blikač (11, 123, 400);
LED2 blikač (12, 350, 350);
LED3 blikač (13 200 222);

Zametací zametací stroj1 (25);
Zametací zametací stroj2 (35);

neplatné nastavení ()
{
zametač 1. připevněte (9);
2. zametač (10);

// Timer0 se již používá pro millis () - prostě někde vyrušíme
// uprostřed a zavoláme níže uvedenou funkci „Porovnat A“
OCR0A = 0xAF;
TIMSK0 | = _BV (OCIE0A);

pinMode (2, INPUT_PULLUP); attachInterrupt (0, Reset, PÁD);
}

void Reset ()
{
sweeper1.reset ();
sweeper2.reset ();
}

// Přerušení se volá jednou za milisekundu,
SIGNÁL (TIMER0_COMPA_vect)
{
nepodepsaný dlouhý proud Millis = millis ();
sweeper1.Update (currentMillis); // if (digitalRead (2) == HIGH)
{
sweeper2.Update (currentMillis);
led1.Update (currentMillis);
}
led2.Update (currentMillis);
led3.Update (currentMillis);
}

neplatná smyčka ()
{
}

Knihovny

Více o stopkách

Časovače lze nakonfigurovat tak, aby fungovaly na různých frekvencích a v různých režimech. Kromě generování přerušení se také používají ke kontrole pinů PWM.

Tajemství Arduino PWM

Podvodník

Knihovny stopek

Na internetu je k dispozici řada knihoven „časovačů“ Arduino. Mnoho z nich sleduje funkci millis () a vyžaduje neustálou kontrolu, jak to bylo v části 1 . Existuje ale také několik, které vám umožní konfigurovat časovače a generovat přerušení.

Knihovny TimerOne a TimerThree

Přerušení výměny kolíků

Když 2 nestačí

Arduino UNO má pouze 2 externí kolíky přerušení. Ale co dělat, kdy potřebujete více? Naštěstí Arduino UNO podporuje přerušení „pin change“ na všech pinech.

Přerušení výměny kolíků jsou podobná externím přerušením. Jediný rozdíl je v tom, že u jednoho z nich došlo ke změně stavu na kterémkoli z 8 souvisejících pinů. Jejich zpracování je trochu složitější, protože musíte sledovat poslední známý stav všech 8 pinů, abyste zjistili, který z nich způsobil přerušení.

Knihovna PinChaneInt

 

Stopky a přerušení štítku

Přerušení jsou jako linky v pokladně supermarketu. Buďte opatrní a vyberte 10 nebo méně položek a vše bude probíhat hladce.

Pokud je vše důležité, pak není nic důležitého.

Obslužné rutiny přerušení by se měly používat pouze ke zpracování nejdůležitějších událostí souvisejících s časem. Pamatujte, že přerušení jsou v obslužném programu přerušení zakázána. Pokud se pokusíte udělat příliš mnoho na úrovni přerušení, snížíte tím odezvu na další přerušení.

Jedno přerušení po druhém.

V ISR jsou přerušení zakázána. To má dva velmi důležité důsledky:

  1. Práce prováděná v ISR by měla být krátká, aby nezmeškala žádné přerušení.
  2. Kód v ISR by neměl volat nic, co vyžaduje přerušení práce (např. Funkci delay () nebo cokoli, co používá sběrnici I2C). Pokud se to však stane, váš program selže.

Zpoždění dlouhého zpracování do smyčky

Pokud potřebujete podrobné zpracování v reakci na přerušení, použijte obslužný program přerušení k provedení pouze toho, co je nezbytné. Nastavením proměnné plovoucího stavu později označte, že je vyžadováno následné zpracování. Při volání funkce aktualizace ze smyčky zkontrolujte stav proměnné a zjistěte, zda je nutné další zpracování.

Před opětovnou konfigurací stopek zkontrolujte

Stopky jsou omezeným zdrojem. UNO má pouze 3 z nich a používají se k mnoha věcem. Pokud změníte nastavení stopek, některé další věci již nemusí fungovat. Například v Arduino UNO:

  • Stopoper0 - používá se ve funkcích millis (), micros (), delay () a v PWM na pinech 5 a 6
  • Stopwatch1 - použitý v slouží WaveHC knihovna a PWM na kolíky 9 a 10
  • Stopky2 - používají je Tone a PWM na pinech 11 a 13

Bezpečně si vyměňujte data

Musíme být opatrní při výměně dat mezi obsluhou přerušení a kódem ve smyčce, protože přerušení způsobí selhání jakékoli aktivity CPU, aby bylo možné pokračovat v práci.

Změna proměnných

Kompilátor se někdy pokusí optimalizovat váš kód na rychlost. Někdy tyto změny udrží kopii nejčastěji používaných proměnných v registru pro rychlý přístup. Problém je v tom, že některé z těchto proměnných jsou sdíleny mezi obsluhou přerušení a smyčkovým kódem. Jeden z nich možná používá starší verzi místo nejnovější. Označte proměnné jako měnící se, aby překladač věděl, že tyto potenciálně nebezpečné optimalizace neprovádí.

Ochrana větších proměnných

I když říkáme, že proměnná se mění , nestačí, pokud je větší než celé číslo (např. Řetězce, tabulky, struktury atd.). Větší proměnné vyžadují aktualizaci několika sad pokynů. Pokud během aktualizace dojde k přerušení, může dojít k poškození dat. Pokud máte větší proměnné nebo struktury, které jsou vyměňovány s obslužnými rutinami přerušení, měli byste při jejich aktualizaci vyloučit přerušení ze smyčky. (Přerušení jsou při zpracování přerušení ve výchozím nastavení zakázána).

<- část jedna část tři ->

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

zveme vás ke spolupráci!