Programmegszakítások RTOS környezetben
A fejezet tartalma:
A programmegszakítások kiszolgálása RTOS környezetben is ugyanúgy zajlik, s a megszakításokat kiszolgáló eljárásokban az RTOS API (alkalmazásprogramozói interfész) hívások is - néhány kivételtől eltekintve - ugyanúgy használhatók, mint a programszálak törzsében.
Amit megszakításokban nem használhatunk:
- Programmegszakításban nem használhatók a mutexek, a várakozások, a dinamikus tárterület lefoglalások.
- Szemaforoknál a wait() tagfüggvény nem hívható meg megszakításból, a release() tagfüggvény viszont meghívható.
- A programszálak jelzőbitjeit (Signals) kezelő függvények közül csak a signal_set() metódus hívható meg programmegszakításból, a signal_clr() és a Thread::signal_wait() pedig nem.
- A Queue üzenetsor objektumok put() és get() metódusai egyaránt meghívhatók megszakításból, de csak nulla várakozási idő megadásával. Arra ügyeljünk, hogy a get() metódusnál nem nulla az alapértelmezett érték, tehát a második paramétert feltétlenül meg kell adnunk! A put() metódusnál nincs ilyen probléma, ott eleve timeout = 0 az alapértelmezett érték.
- A levelesláda (Mail) esetén az alloc() és get() metódusnál kell ügyelnünk, hogy nulla várakozást adjunk meg. Ezek közül a get() metódusnál nem nulla az alapértelmezett érték, tehát ennek a második paraméterét is feltétlenül meg kell adnunk!
- Az RTOS Timer metódusai megszakításból nem hívhatók meg.
Mintapélda: ADC megszakítás kezelése
Programmegszakításokkal már a korábbi fejezetekben is találkoztunk: az Analóg perifériák fejezetben az analóg komparátornál, a Programmegszakítások fejezetben az InterruptIn objektumosztály kapcsán, az Időzítők/Számlálók fejezetben a Timeout, a Ticker, illetve a Wakeup objektumosztályok kapcsán. Ezúttal azonban azt mutatjuk meg, hogy az NVIC, illetve a periféria regiszterek programozásával hogyan kelthetünk és kezelhetünk olyan programmegszakítást, amelyre az mbed API nem nyújt kész megoldást.Az alábbi programban az ADC-vel végzünk méréseket, hardveres átlagolással (32 minta átlagolásával). A mérési csatornát kijelölő és a konverziót elindító függvényben azonban nem blokkoló módon várakozunk (ahogy az mbed API AnalogIn objektumosztályának read() és read_u16() függvényei csinálják). Ehelyett üzenetsorra (Queue) várunk, melynek üzeneteit az ADC megszakítást kiszolgáló függvény küldi el. Ehhez természetesen konfigurálnunk és engedélyeznünk kell az ADC megszakításokat, s egy kiszolgáló függvényt is kell rendelnünk hozzá. Egyúttal azt is megmutatjuk, hogy az ADC belső hőérzékelőjének kiválasztásával (az ADC 26-os csatornája) hogyan mérhetünk hőmérsékletet.
Az ADC kezeléséhez nem írtunk teljes inicializálást végző eljárást, csupán felülírjuk az mbed API AnalogIn konstruktor függvényének inicializálását (csak azokat a regisztereket írjuk felül, amelyeket módosítani akarunk). Ezért fontos, hogy ne felejtsük ki az AnalogIn objektumosztály példányosítását mindazon analóg bemenetekre, amelyeket használni akarunk, s amelyek fizikai kivezetéshez rendelhetők. A beépített hőmérő mikrovezérlő lábra nincs kivezetve, ezért ezt nem kell külön inicializálni.
Az ADC inicializálás felülírása az adc_read() függvényben található. A változtatások lényege:
- Az ADC0->SC3 regiszter módosításával 32 mintavételes hardver átlagolást írunk elő. Ennek részletei (AVGE bit = 1, AVGS bitek = 3) a Freescale KL25xx Reference Manual (KL25P80M48SF0RM.pdf) 28.3 alfejezetéből olvasható ki.
- Az ADC0->SC1[0] regiszter (a fent említett dokumentum jelölése szerint SC1A regiszter) beírása indítja a konverziót. Ebben kell megadnunk a kiolvasandó csatorna sorszámát, s ebben engedélyezzük hogy az ADC programmegszakítási kérelmet adjon ki a konverzió(k és az átlagolás) végén.
- A konverzió elindítása után nem blokkoló várakozást indítunk, hanem a queue néven példányosított üzenetsorra várunk (amely majd a megszakításból kap új adatot). Az üzenetsort itt nem mutatók, hanem közvetlenül adatok küldésére használjuk (lásd az RTOS_Queue fejezet mintapéldáját!). A kapott adat a beérkezett osEvent típusú üzenetstruktúra event.v eleméből vehető elő.
- Az adc_read() függvény paramétereként adhatjuk meg a mérendő ADC csatorna sorszámát. Az A0 (PTB0) bemenetre adott jel a 8-as számú csatornában, a belső hőmérő jele pedig a 26-es csatornában mérhető.
Az ADC programmegszakítások érvényesüléséhez az NVIC egységet is be kell állítani. Ezt csak a program elején egyszer kell elvégezni. Enenk részletei a CMSIS dokumentációban, illetve az mbed forráskód libraries/mbed/targets/cmsis szekciójában találhatók meg. További ötleteket nyújt az ADC0 Interrupt on kl25z című diszkusszió az mbed.org honlapján.
- Az ADC0_IRQn vektorba a megszakítási eljárás belépési címét be kell írni (NVIC_SetVector() függvény).
- Az ADC0_IRQn megszakítási vektort engedélyezni kell (NVIC_EnableIRQ() függvény).
Temp = 25 - (VTemp - V25)/m
ahol V25 a 25 ºC-on mért feszültség (kb. 716 mV), m pedig a meredekség (kb. 1620 µV/ºC)
Azt ne feledjük, hogy a belső hőmérő mindig a lapka hőmérsékletét méri, ami a mikrovezérlő pillanatnyi fogyasztásától függően több fokkal is magasabb lehet a környezeti hőmérsékletnél.
A programban mellékesen a vörös LED-et is villogtatjuk egy külön programszál segítségével, szimbolikusan ezzel is jelezve a többfeladatos környezetet.
Hardver követelmények:
- FRDM-KL25z kártya
- Analóg jel az A0 (PTB0) bemeneten
#include "mbed.h"
#include "rtos.h"
AnalogIn adc(A0);
DigitalOut led1(LED1);
typedef uint32_t message_t;
Queue <message_t, 4> queue;
void led1_thread(void const *args) {
while (true) {
led1 = !led1;
Thread::wait(1000);
}
}
//--- ADC Interrupt handler -----------------
extern "C" void ADC0_IRQHandler() {
NVIC_ClearPendingIRQ(ADC0_IRQn); //Clear ADC Interrupt Request Flag
uint16_t raw = ADC0->R[0];
queue.put((message_t*)raw); //Send result through a Queue
}
//--- Start conversion, wait for result -----
uint16_t adc_read(uint32_t ch) {
ADC0->SC3 = ADC_SC3_AVGE_MASK //Enable hardware averaging
| ADC_SC3_AVGS(3); //Average 32 samples
// start conversion
ADC0->SC1[0] = ADC_SC1_AIEN_MASK | ch; //Set channel, enable interrupt
osEvent evt = queue.get(); //Wait for a message
return (uint16_t)evt.value.v; //Return obtained value
}
int main() {
int32_t v25 = 716; //Voltage at 25C (in millivolts)
int32_t m = 1620; //Slope (in microvolts per degree)
NVIC_SetVector(ADC0_IRQn,(uint32_t)&ADC0_IRQHandler); //Attach ADC ISR
NVIC_EnableIRQ(ADC0_IRQn); //Enable ADC interrupts
Thread thread1(led1_thread);
while (true) {
uint16_t a1 = adc_read(8); //Measure voltage at A0 (PTB0)
uint16_t a2 = adc_read(26); //Internal temperature sensor
float v1 = a1*3.3f/65536; //Convert v1 to Volts
float v2 = a2*3300.0f/65536; //Convert v2 to millivolts
float temp = 25.0f-(v2-v25)*1000.0f/m; //Calculate temp in Celsius
printf("A0 = %6.3f V Temp = %5.2f C\n",v1,temp);
Thread::wait(2000);
}
}
A program futási eredménye az alábbi ábrán látható. Az A0 bemenetre itt
egy 2.5 V-os referencia feszültséget kapcsoltunk. A hőmérő adataiból
egy felprogramozás utáni lassú lehűlés képe rajzolódik ki.1. ábra: A 12_rtos_adcinterrupt program futási eredménye