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:

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 ADC programmegszakítások kiszolgálása az ADC0_IRQHandler() függványben történik. Itt törölnünk kell az ADC-hez tartozó megszakításjelző bitet, majd elküldjük a kapott eredményt (az ADC0->R[0] kiolvasott tartalmát) a put() metódus segítségével a queue néven példányosított üzenetsorba. Az üzenetsor itt egy négyelemű FIFO tárként működik.

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.
A belső hőmérő jelének feldolgozását a Freescale KL25xx Reference Manual (KL25P80M48SF0RM.pdf) 28.4.8 alfejezete ismerteti:

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:
1. lista: A 11_rtos_queue/main.cpp program listája
#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