Čeština / English
Login

Aplikace / Portace uC/OS-II na platformu FITKit

Autoři: Karel Koranda (); Vladimír Brůžek (); Tomáš Jílek ()

Abstrakt: Portace uC/OS-II na platformu FITKit

Update: 20101127

Více o uC/OS-II je možno nalézt na http://www.micrium.com/page/products/rtos/os-ii

ucosii.png

Obrázek 1.1:

1. uC/OS-II

uC/OS-II je jádro preemptivního real-time multitasking operačního systému založeného na prioritách probíhajících procesů. Je vytvořen společností Micros a je poskytován zdarma pro výukové účely. Je především určen pro vestavěné systémy. Protože se jedná o operační systém schopný běžet na různých mikroprocesorových platformách, je pro jeho zprovoznění nutné provést portaci pro konkrétní mikrokontroler.

2. Portace uC/OS-II

K portaci na konkrétní platformu slouží tzv. procesorově specifické zdrojové soubory (os_cpu.h, os_cpu_c.c a os_cpu_a.s). Ty je nutné upravit pro konkrétní mikrokontroler především z důvodu jejich rozdílné instrukční sady a složení registrů.

3. OS_CPU.H

Hlavičkový soubor os_cpu.h obsahuje procesorově a implementačně specifické konstanty (definované direktivami #define), makra a definice datových typů (typedef). Datové typy jsou definovány především z důvodu jejich různých délek na různých platformách.

typedef unsigned char  BOOLEAN;
typedef unsigned char  INT8U;                    /* Unsigned  8 bit quantity  */
typedef signed   char  INT8S;                    /* Signed    8 bit quantity  */
typedef unsigned int   INT16U;                   /* Unsigned 16 bit quantity  */
typedef signed   int   INT16S;                   /* Signed   16 bit quantity  */
typedef unsigned long  INT32U;                   /* Unsigned 32 bit quantity  */
typedef signed   long  INT32S;                   /* Signed   32 bit quantity  */
typedef float          FP32;                     /* Single precision floating point  */
typedef double         FP64;                     /* Double precision floating point  */

typedef unsigned int   OS_STK;                   /* Each stack entry is 16-bit wide  */
typedef unsigned int   OS_CPU_SR;                /* Define size of CPU status register (SR = 16 bits)  */

Dále tento soubor obsahuje další velice významnou definici následujících maker:

#define  OS_ENTER_CRITICAL()  (cpu_sr = OSCPUSaveSR())    /* Disable interrupts */
#define  OS_EXIT_CRITICAL()   (OSCPURestoreSR(cpu_sr))    /* Enable  interrupts */

Jedná se o makra umožňující zablokování a odblokování systému přerušení při vstupu do kritické sekce, neboť přerušení při výskytu v kritické sekci není žádoucí. Proto každý vstup do kritické sekce by měl být uvozen voláním OS_ENTER_CRITICAL() a ukončen OS_EXIT_CRITICAL().

Protože však pouhé zablokování a odblokování přerušení může způsobit jejich nežádoucí odblokování (před vstupem do kritické sekce byla přerušení zablokována, OS_EXIT_CRITICAL() však může provést jejich nechtěné odblokování), je vhodnějším řešením při volání OS_ENTER_CRITICAL() uložit obsah registru SR (Status Register), zablokovat přerušení a při opuštění kritické sekce obnovit stav přerušení na základě uloženého SR registru.

Hlavičkový soubor os_cpu.h obsahuje také neméně důležitou konstantu OS_STK_GROWTH, která určuje, jakým způsobem budou data ukládána na zásobník. V případě nastavení této konstanty na 0, bude zápis do paměti prováděn od nižších po vyšší adresy, pokud bude nastavena na 1, pak naopak. Pro potřeby MSP430 je použito #define OS_STK_GROWTH 1.

OS_TASK_SW() je makro vyvolávající přepnutí kontextu. Při přepnutí kontextu je potřeba uložit registry pozastaveného procesu a obnovení registrů procesu, který bude následně probíhat. Je důležité také vědět, že přepnutí kontextu se chová obdobně jako přerušení. Pokud tedy cílová architektura podporuje softwarová přerušení, je možno OS_TASK_SW() vyvolat tímto způsobem. V takovém případě se ve vektoru přerušení musí nacházet procedura OSCtxSw() definovaná v os_cpu_a.s. Pro platformy nepodporující softwarová přerušení a náš případ, je možné pouze definovat OS_TASK_SW() jako volání procedury OSCtxSw():

#define OS_TASK_SW()    OSCtxSw()

4. OS_CPU_A.S

OSTickISR(). Dále jsou v tomto souboru definovány těla funkcí OSCPUSaveSR()

a OSCPURestoreSR(), které jsou volány již vysvětlenými makry OS_ENTER_CRITICAL() a OS_EXIT_CRITICAL().

OSStartHighRdy() připraví ke spuštění úlohu s nejvyšší prioritou při startu OS. Tato funkce musí zavolat OSTaskSwHook() (probíhá de facto přepnutí kontextu), musí nastavit OSRunning na TRUE, identifikující běěh jádra, a samozřejmě přepnout na spouštěný proces načtením odpovídajícího zásobníku (registr R1 je na MSP430 Stack Pointer).

.global OSStartHighRdy
.type OSStartHighRdy,@function
OSStartHighRdy:
    CALL       #OSTaskSwHook
    MOV.B       #1, &OSRunning         ; kernel running

    MOV.W      r1, &OSISRStkPtr        ; save interrupt stack

    MOV.W      &OSTCBHighRdy, R13      ; load highest ready task stack
    MOV.W      @R13, r1

    POPALL

    RETI                               ; emulate return from interrupt

OSCtxSw() je funkcí umožňující přepnutí kontextu. Její funkce již byla vysvětlena spolu s OS_TASK_SW(). Při přepnutí kontextu je však potřeba uložit registr R2 (Status Register) a obecné registry R4 až R15 na zásobník, uložit současný Stack Pointer (registr R1) do tabulky TCB, zavolat OSTaskSwHook(), protože probíhá přepnutí kontextu, vybrat proces s vyšší prioritou a obnovit jeho registry uložené na jeho zásobníku.

.global OSCtxSw
.type OSCtxSw, @function
OSCtxSw:
    PUSH.W      r2

    PUSHALL

    MOV.W      &OSTCBCur, R13          ; OSTCBCur->OSTCBStkPtr = SP
    MOV.W      r1, 0(R13)

    CALL       #OSTaskSwHook

    MOV.B       &OSPrioHighRdy, R13    ; OSPrioCur = OSPrioHighRdy
    MOV.B       R13, &OSPrioCur

    MOV.W      &OSTCBHighRdy, R13      ; OSTCBCur  = OSTCBHighRdy
    MOV.W      R13, &OSTCBCur

    MOV.W      @R13, r1                ; SP        = OSTCBHighRdy->OSTCBStkPtr

    POPALL

    RETI                               ; Return from interrupt.

OSIntCtxSw() je funkcí způsobující přepnutí kontextu z obsluhy přerušení. Chová se obdobně jako OSCtxSw() s tím rozdílem, že neukládá současný kontext.

.global OSIntCtxSw
.type OSIntCtxSw, @function
OSIntCtxSw:
    CALL       #OSTaskSwHook

    MOV.B       &OSPrioHighRdy, R13    ; OSPrioCur = OSPrioHighRdy
    MOV.B       R13, &OSPrioCur

    MOV.W      &OSTCBHighRdy, R13      ; OSTCBCur  = OSTCBHighRdy
    MOV.W      R13, &OSTCBCur

    MOV.W      @R13, r1                ; SP        = OSTCBHighRdy->OSTCBStkPtr

    POPALL

    RETI                               ; return from interrupt

Funkce OSTickISR() je jednou z esenciálních součástí uC/OS-II, neboť je potřeba pro funkčnost časovačů a zpoždění v systému. Jedná se o obsluhu přerušení vyvolaném hardwarovým časovačem (Watchdog Timer).

Protože se jedná o obsluhu přerušení, je potřeba na začátku uložit všechny obecné registry R4 až R15 na zásobník, vyřadit časové přerušení. Pokud nedošlo k žádnému dalšímu přerušení, uložíme SP současné úlohy a načteme SP pro obsluhu tohoto přerušení. Dále je potřeba OS informovat o tom, že k přerušení došlo - k tomu slouží globální proměnná OSIntNesting. Následuje opětovné povolení časového a obecných přerušení. Poté je volána funkce OSTimeTick, což je obslužná rutina pro zpracování hodinového tiknutí. Po jejím průběhu je zapotřebí opět vyřadit obecná přerušení a zavolat OSIntExit(), která mimo jiné sníží OSIntNesting. Pokud se tato proměnná nsáledně rovná nule, všechna přerušení byla obsloužena a my můžeme obnovit SP původní úlohy. Nakonec proběhne obnova obecných registrů R4 až R15 a návrat z obsluhy přerušení.

.global vector_fff4
vector_fff4:                           ; wd timer ISR
    PUSHALL

    BIC.B       #0x01, &0              ; disable wd timer interrupt

    CMP.B       #0, &OSIntNesting      ; if (OSIntNesting == 0)
    JNE         WDT_ISR_1

    MOV.W      &OSTCBCur, R13          ;     save task stack
    MOV.W      r1, 0(R13)

    MOV.W      &OSISRStkPtr, r1        ;     load interrupt stack

WDT_ISR_1:
    INC.B       &OSIntNesting          ; increase OSIntNesting

    BIS.B       #0x01, &0              ; enable wd timer interrupt

    EINT                               ; enable general interrupt to allow for interrupt nesting

    CALL       #OSTimeTick             ; call ticks routine

    DINT                               ; IMPORTANT: disable general interrupt BEFORE calling OSIntExit()

    CALL       #OSIntExit

    CMP.B       #0, &OSIntNesting      ; if (OSIntNesting == 0)
    JNE         WDT_ISR_2

    MOV.W      &OSTCBHighRdy, R13      ;     restore task stack SP
    MOV.W      @R13, r1

WDT_ISR_2:
    POPALL

    RETI                               ; return from interrupt

Funkci nemáme pojmenovanou jako OSTickISR(), ale vector_fff4. To je z toho důvodu, že MSP430 má na adrese FFF4h uloženu právě obsluhu přerušení od Watchdog časovače.

Funkce OSCPUSaveSR() a OSCPURestoreSR() jsou již popsané metody pro uložení Status Registru a vyřazení/obnovení systému přerušení při vstupu do kritických sekcí. Předpokládáme, že přes R15 bude předáván výsledek funkce OSCPUSaveSR() a přijímán parametr funkce OSCPURestoreSR().

.global OSCPUSaveSR
.type OSCPUSaveSR, @function
OSCPUSaveSR:
    MOV.W       r2, R15
    DINT
    RET

.global OSCPURestoreSR
.type OSCPURestoreSR, @function
OSCPURestoreSR:
    MOV.W       R15, r2
    RET

5. OS_CPU_C.C

Soubor os_cpu_c.c obsahuje definice funkcí OSTaskStkInit(), OSTaskCreateHook(), OSTaskDelHook(), OSTaskSwHook(), OSTaskStatHook() a OSTimeTickHook(). Nicméně pro funkčnost uC/OS-II je nezbytně nutné implementovat pouze jednu z nich - OSTaskStkInit().

OSTaskStkInit() je funkcí, která inicializuje zásobník při vytváření úlohy a to tak, že simuluje chování "jako by právě nastalo přerušení" - na zásobníku je uložena adresa kódu úlohy, Status Registr a obecné registry R4 až R15. Předpokládáme, že přes R15 je předáván parametr úlohy.

OS_STK  *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt)
{
    INT16U  *top;

    opt    = opt;
    top    = (INT16U *)ptos;
    top--;

    *top-- = (INT16U)(task);                          /* Interrupt return pointer */
    *top-- = (INT16U)0x0008;                          /* Status register          */
    *top-- = (INT16U)(p_arg);                         /* Pass 'p_arg' through register R15 */
    *top-- = (INT16U)0x1414;
    *top-- = (INT16U)0x1313;
    *top-- = (INT16U)0x1212;
    *top-- = (INT16U)0x1111;
    *top-- = (INT16U)0x1010;
    *top-- = (INT16U)0x0909;
    *top-- = (INT16U)0x0808;
    *top-- = (INT16U)0x0707;
    *top-- = (INT16U)0x0606;
    *top-- = (INT16U)0x0505;
    *top   = (INT16U)0x0404;

     return ((OS_STK *)top);
}

Ostatní funkce definované v tomto souboru slouží pro potřeby uživatelů uC/OS-II rozšířit funkcionalitu OS, jejich těla tedy mohou zůstat prázdná. Pro naše potřeby je nebylo třeba více rozvíjet, přesto zde však popíšeme, kdy je jaká funkce volána.

OSTaskCreateHook() je volána kdykoli je vytvořena nová úloha. Je volána již po vytvoření PCB, avšak ještě před spuštěním plánovače úloh. Taktéž je ještě vyřazen systém přerušení. Jako parametr je této funkci předán ukazatel na PCB vytvářené úlohy. Tato funkce je generována pouze v případě, že je nastavena konstanta OS_CPU_HOOKS_EN na 1 v hlavičkovém souboru os_cfg.h.

OSTaskDelHook() je volána kdykoli je úloha odstraněna ze systému. Obdobně jako OSTaskCreateHook(), i tato funkce obdrží jako parametr ukazatel na PCB právě odstraňované úlohy. Tato funkce je generována pouze v případě, že je nastavena konstanta OS_CPU_HOOKS_EN na 1 v hlavičkovém souboru os_cfg.h.

OSTaskSwHook() je volána vždy, když dochází k přepnutí kontextu. Tato funkce má přístup ke globálním proměnným obsahujícím ukazatele na PCB aktuální úlohy a úlohy, na kterou se kontext přepíná. Je jisté, že při volání této funkce jsou vždy vyřazena přerušení. Tato funkce je generována pouze v případě, že je nastavena konstanta OS_CPU_HOOKS_EN na 1 v hlavičkovém souboru os_cfg.h.

OSTaskStatHook() je volána každou sekundu funkcí OSTaskStat() (statistická funkce), čímž lze rozšířit její funkcionalitu. Tato funkce je generována pouze v případě, že je nastavena konstanta OS_CPU_HOOKS_EN na 1 v hlavičkovém souboru os_cfg.h.

OSTimeTickHook() je volána funkcí OSTimeTick() při každém sytémovém tiknutí, resp. těsně před ním. Tato funkce je generována pouze v případě, že je nastavena konstanta OS_CPU_HOOKS_EN na 1 v hlavičkovém souboru os_cfg.h.

6. Statistická úloha

Statistická úloha je úloha OS, která umožňuje shromažďování statistik běhu operačního systému, resp. využití mikroprocesoru. Tato hodnota je uložena v proměnné OSCPUUsage, jednotkou jsou procenta.

Pokud je statistická úloha využita v systému, je potřeba, aby byla vytvořena voláním funkce OSStatInit() pouze v jedné a vždy první úloze OS. Tato úloha bývá označována jako "startovací úloha" a v jejím těle jsou kromě statistické úlohy a její samotné funkčnosti vytvořeny také další úlohy. Ukázka, jak je volána, bude nastíněna v popisu vytvořené ukázkové aplikace nad uC/OS-II.

Na tomto místě je nutné podotknout, že statistická úloha je generována pouze v případě, že je konstanta OS_TASK_STAT_EN nastavena na 1. Dále je nutné zmínit, že na mikrokontroleru MSP430F168, což odpovídá verzi FITKit 1.2, se nepodařilo statistickou úlohu zprovoznit. Protože však MSP430F168 obsahuje velice malou programovou paměť a není reálné zprovoznit nějakou výrazně užitečnou a rozsáhlou aplikaci nad uC/OS-II, tímto problémem se nebudeme dále zabývat.

7. Ukázková aplikace nad uC/OS-II

Protože se podařilo rozchodit uC/OS-II na platformách FITKit 1.2 i 2.0 (ač pro 1.2 s jistými omezeními), jsou pro jednotlivé verze FITKitu aplikace rozdílné. Kód aplikace se nachází v souboru app.c. Protože překladový systém QDevKit neumí na základě verze FITKitu vybrat soubory, jsou obě aplikace definovány v tomto souboru a direktivami #if je při překladu vybírána správná aplikace pro konkrétní platformu. To sice vede k méně přehlednému kódu, zachováme však funkčnost obou dvou verzí platformy FITKit.

8. Ukázka pro FITKit 1.2

Tuto aplikaci implementovali Ing. Jiří Novotňák a Ing. Tomáš Novotný v rámci projektu do předmětu ROS. Tuto aplikaci jsme se tedy rozhodli ponechat pro verzi FITKitu 1.2, neboť, jak jsme již zmínili, jiná, složitější aplikace vyvinout pro MSP430F168 nejde, pro ukázku funkčnosti uC/OS-II však postačuje.

Aplikace zpracovává dvě úlohy - jedna bliká zelenou diodou D5, druhá červenou. Obě diody blikají s různou frekvencí a časovými rozestupy.

/*
 * Uloha blikajici se zelenou LED diodou (D5)
 * Kazdych 100ms prepina stav diody
 */
void green_task(void *data) {
   P1OUT &= ~BIT0;

   for(;;) {
      OSTimeDlyHMSM(0,0,0,100);
      P1OUT ^= BIT0;
   }
}

/*
 * Uloha blikajici s cervenou LED diodou (D6)
 * Kazdou sekundu blikne na 100ms
 */
void red_task(void *data) {
   P5OUT |= BIT0;
   for(;;) {
      OSTimeDlyHMSM(0,0,0,900);
      P5OUT &= ~BIT7;
      OSTimeDlyHMSM(0,0,0,100);
      P5OUT |= BIT7;
   }
}

Pro inicializaci hardwaru (především clock system) je napsána funkce my_initialize_hardware(), která je inspirovaná kódem z knihovny libfitkit.

Funkce main() programu vypadá pro tuto aplikaci následovně:

int main() {

   // Inicializace HW
   my_initialize_hardware();

   // Smer pinu diod
   P1DIR |= BIT0; // D5
   P5DIR |= BIT7; // D6

   // Vypnuti diod
   P1OUT |= BIT0;
   P5OUT |= BIT7;

   // Inicializace OS a uloh
    OSInit();

    // Uloha blikani zelene diody
    OSTaskCreate(green_task, (void *)0, (void *)&TaskGreenLEDStk[TASK_STK_SIZE - 1], 0);
    // Uloha blikani cervene diody
    OSTaskCreate(red_task, (void *)0, (void *)&TaskRedLEDStk[TASK_STK_SIZE - 1], 1);

    // Spusteni watchdogu
    WDTCTL = WDTPW | WDTTMSEL | WDTCNTCL; // Timer mode, predeleni 32768
    IE1 |= WDTIE;

    // Povoleni general interrupt
    _EINT();

    // Start multitaskingu
    OSStart();

   return 0;
}

Z hlediska uC/OS-II jsou důležitá volání OSInit(), OSTaskCreate() a OSStart(). OSInit() provede inicializaci operačního systému a jeho základních úloh. Voláním OSTaskCreate() jsou do systému přidány úlohy blikání zelené a červené diody. OSStart() pak provede spuštění multitaskingu. Je důležité, aby před spuštěním multitaskingu byla povolena obecná přerušení a spuštěn Watchdog časovač! Pokud tomu tak nebude, pak uC/OS-II nebude fungovat správně.

9. Ukázka pro FITKit 2.0

Pro vytvoření ukázkové aplikace na FITKit 2.0 jsme se rozhodli využít knihovny libfitkit, což na FITKitu 1.2 nebylo možné z důvodu malé kapacity MSP430F168. Ukázková aplikace tedy bude schopna zasílat zprávy na terminál, reagovat na stisk určitých kláves, ovládat svícení diod D5 a D6 a ovládat výpis na displeji. Navíc, pokud je v souboru os_cfg.h nastavena konstanta OS_TASK_STAT_EN na 1, bude spuštěna statistická funkce a po stisku určité klávesy budou údaje o zatížení mikroprocesoru vypisovány na displej/terminál. Při vytváření aplikace jsme se částečně inspirovali u ukázkové aplikace nad FreeRTOS.

Stav, v jakém se aplikace nachází, budeme ukládat do globálních proměnných:

volatile char last_ch;
unsigned char c_pressed = 0;            // priznak stisku klavesy 'C'
unsigned char d_pressed = 0;            // priznak stisku klavesy 'D'
unsigned char load_pressed = 0;         // priznak stisku klavesy '*'
unsigned char previous_c_pressed = 0;   // priznak zpracovaneho stisku klavesy 'C'

unsigned char light_red = 1;            // priznak stisku klavesy 'A' - dioda D6
unsigned char light_green = 1;          // priznak stisku klavesy 'B' - dioda D5

První úlohou, kterou bude OS zpracovávat, bude práce s klávesnicí. Ta bude v případě stisku klávesy 'A' rozsvěcet/zhasínat červenou diodu D6 a odesílat o tom zprávu na terminál, při stisku klávesy 'B' rozsvěcet/zhasínat zelenou diodu D5 a odesílat o tom zprávu na terminál. Při stisku klávesy 'C' bude nastavován příznak jejího stisknutí a bude o tom odeslána zpráva na terminál - klávesa 'C' bude ovládat zapnutí/vypnutí displeje. Při stisku klávesy 'D' bude vybíráno, zda na displej bude vypisován údaj o rozsvícených/zhasnutých diodách nebo zda bude vypisována zátěž mikroprocesoru vypočítaná statistickou úlohou (pokud je generována). Při stisku klávesy '*' bude nastaven příznak generování zátěže v rámci zátěžové úlohy.

void keyboardTask(void *param)
{
  param = param;
  char ch;

  last_ch = 0;
  keyboard_init(); // inicializace klavesnice

  for (;;)
  {
    ch = key_decode(read_word_keyboard_4x4()); // dekodovani znaku z klavesnice
    if (ch != last_ch) {
      last_ch = ch;
      if (ch != 0) {
         switch (ch) {
           case 'A': // stisk klavesy 'A'
             set_led_d6(light_red); // nastaveni rozsviceni/zhasnuti diody
             if (light_red == 1)
             { // dioda byla rozsvicena
                term_send_str_crlf("'A': Turn on red light D6.");
             } else { // dioda byla zhasnuta
                term_send_str_crlf("'A': Turn off red light D6.");
             }
             // nastaveni pristiho stavu diody
             light_red = (light_red == 0 ? 1 : 0);
             break;
          case 'B': // stisk klavesy 'B'
             set_led_d5(light_green); // rozsviceni/zhasnuti diody
             if (light_green == 1)
             { // dioda je rozsvicena
                term_send_str_crlf("'B': Turn on green light D5.");
             } else { // dioda je zhasnuta
                term_send_str_crlf("'B': Turn off green light D5.");
             }
             // nastaveni pristiho stavu diody
             light_green = (light_green == 0 ? 1 : 0);
             break;
           case 'C': // stisk klavesy 'C'
             c_pressed = (c_pressed == 0 ? 1 : 0); // nastaveni priznaku
             if (c_pressed) { // stisknuta klavesa -> vypnuty displej
                term_send_str_crlf("'C': Display OFF");
             } else { // spusteny displej
                term_send_str_crlf("'C': Display ON");
             }
             break;
           case 'D':
             d_pressed = (d_pressed == 0 ? 1 : 0); // nastaveni priznaku
             if (d_pressed) { // stisknuta klavesa -> vypis statistik
                term_send_str_crlf("'D': CPU Usage Monitoring ON");
             } else { // vypnuty vypis statistik
                term_send_str_crlf("'D': CPU Usage Monitoring OFF");
             }
             break;
           case '*':
             load_pressed = (load_pressed == 0 ? 1 : 0); // nastaveni priznaku
             if (load_pressed) { // stisknuta klavesa -> zatezova uloha
                term_send_str_crlf("'*': Load task ON");
             } else { // vypnuta zatezova uloha
                term_send_str_crlf("'*': Load task OFF");
             }
             break;
           default:
             break;
         }
      }
   }
   OSTimeDlyHMSM(0,0,0,100); // zpozdeni 100ms
  }
}

Druhá úloha bude mít za úkol ovládání displeje na základě příznaků stisknutých kláves. Na základě stisku kláves/stavu diod bude na displej implicitně vypisovat, v jakém stavu se diody nachází (např. D5:OFF D6:ON). V případě, že je nastaven příznak po stisknutí klávesy 'C', displej je považován za vypnutý a nevypisuje se na něj nic. V případě, že je nastaven příznak klávesou 'D', a není displej vypnutý klávesou 'C', je na displej místo údajů o diodách vypisován stav zatížení mikroprocesoru (např. CPU 98%). V případě, že je nastaven příznak klávesy '*', je řízení displeje ponecháno na zátěžové úloze loadTask().

void displayTask(void *param)
{
   LCD_init(); // inicializace LCD

   for (;;)
   {

      if (d_pressed) { // nastaven priznak 'D'
#if OS_TASK_STAT_EN // je generovana statisticka uloha
        char result[20];
        sprintf (result, "CPU %d%c", OSCPUUsage, '\%'); // format vystupu stat.funkce
        if (!c_pressed && !load_pressed) { // pokud neni priznak 'C' a '*'
          if (previous_c_pressed) previous_c_pressed = 0;
          LCD_write_string(result); // vypis na displej
        }
        term_send_str_crlf(result); // odeslani dat na terminal
#else
        if (!c_pressed && !load_pressed) {
          if (previous_c_pressed) previous_c_pressed = 0;
          LCD_write_string("STATS DISABLED");
        }
        term_send_str_crlf("STATS DISABLED");
#endif
        OSTimeDlyHMSM(0,0,1,0);
      } else { // neni nastaven 'D'
        if (!c_pressed && !load_pressed) { // neni nastaven 'C' ani '*'
          if (previous_c_pressed) previous_c_pressed = 0;
          // vypis udaju o diodach
          if (light_red == 0 && light_green == 0) {
              LCD_write_string("D6:ON D5:ON");
            } else if (light_red == 1 && light_green == 0) {
              LCD_write_string("D6:OFF D5:ON");
            } else if (light_red == 1 && light_green == 1) {
              LCD_write_string("D6:OFF D5:OFF");
            } else {
              LCD_write_string("D6:ON D5:OFF");
            }
            OSTimeDlyHMSM(0,0,0,500); // zpozdeni 0.5s
        }
        if (load_pressed) // je nastaven priznak '*' zatezove ulohy
        { // tato uloha nebude nyni obsluhovat displej
          OSTimeDlyHMSM(0,0,0,500); // zpozdeni 0.5s
        }
      }
      if (c_pressed)
      { // je nastaven priznak vypnuteho displeje (klavesa 'C')
        if (previous_c_pressed == 0) // pokud jsme nastaveni priznaku identifikovali
        { // poprve
          LCD_write_string(" "); // cisteni displeje
          previous_c_pressed = 1; // priste uz cistit nebudeme, setrime
        }
        OSTimeDlyHMSM(0,0,0,500); // zpozdeni 500ms
      }
   }
}

Třetí úloha, zátěžová úloha loadTask(), je v aplikaci pro demonstraci funkčnosti statistické funkce. Tato úloha, pokud je nastaven příslušný příznak, generuje zátěž mikročipu rychlým obnovováním textu na displeji (to je činěno každých 50ms).

void loadTask(void *param)
{
  for (;;)
  {
    while (load_pressed) // dokud je nastaven priznak stisknute '*'
    {
      if (!c_pressed) { // pokud neni vypnuty displej
        if (previous_c_pressed) previous_c_pressed = 0; // pokud je treba, prehod stav displeje
#if OS_TASK_STAT_EN // je generovana statisticka funkce
        char result[32];
        sprintf(result, "!!!LOAD TASK!!! CPU %d%c", OSCPUUsage, '\%');
        LCD_write_string(result);
#else // neni generovana statisticka funkce
        LCD_write_string("!!!LOAD TASK!!! STATS DISABLED");
#endif
      }
      OSTimeDlyHMSM(0,0,0,50); // zpozdeni 50ms
    }
    OSTimeDlyHMSM(0,0,1,0); // zpozdeni 1s
  }
}

V aplikaci je ještě definována třetí úloha. Tato úloha je označena jako startTask() a je v ní, pokud je to vyžadováno, inicializována statistická funkce. V těle této funkce jsou také inicializovány ostatní úlohy. Samotná úloha jinak nic neprovádí.

Pozn.: Maximum úloh v aplikaci se nastavuje v souboru os_cfg.h konstantou OS_MAX_TASKS.

void startTask(void *param)
{
#if OS_TASK_STAT_EN
   OSStatInit(); // inicializace statisticke ulohy
#endif

  OSTaskCreate(keyboardTask, (void *)0, (void *)&TaskKeyStk[TASK_STK_SIZE - 1], 1);     // uloha zpracovani klavesnice
  OSTaskCreate(displayTask, (void *)0, (void *)&TaskDisplayStk[TASK_STK_SIZE - 1], 2);  // uloha zpracovani displeje
  OSTaskCreate(loadTask, (void *)0, (void *)&TaskLoadStk[TASK_STK_SIZE - 1], 3);        // zatezova uloha

  for (;;) {
    OSTimeDlyHMSM(0,0,1,0); // zpozdeni 1s
  }

}

Funkce main() celé aplikace pak vypadá takto:

int main() {

   // Inicializace HW
   initialize_hardware();

   // Inicializace OS a uloh
    OSInit();

    OSTaskCreate(startTask, (void *)0, (void*)&TaskStartStk[TASK_STK_SIZE - 1], 0);

    // Spusteni watchdogu
    WDTCTL = WDTPW | WDTTMSEL | WDTCNTCL; // Timer mode, predeleni 32768
    IE1 |= WDTIE;

    // Povoleni general interrupt
    _EINT();

    // Start multitaskingu
    OSStart();

   return 0;
}

Inicializace HW je ponechána v tomto případě na funkci initialize_hardware() z knihovny libfitkit. Na této funkci main() se ale od té předchozí pro FITKit 1.2 mnoho neliší - jediným podstatným rozdílem je vytvoření úloh OS až v rámci úlohy startTask().

10. Zprovoznění aplikace

Pro správný chod aplikace je nutné mít adresář se zdrojovými kódy umístěný v svn adresáři FITKitu, v /apps/demo_msp. Pro zprovoznění aplikace na FITKitu stačí přeložit aplikaci, naprogramovat MCU a FPGA a poté spustit terminálový program.

11. Poděkování

Tímto bychom chtěli poděkovat za pomoc a konzultace Ing. Jiřímu Novotňákovi a Ing. Tomáši Novotnému, kteří již dříve s uC/OS-II pracovali a poskytli nám k dispozici výsledky jejich práce, na které jsme mohli navázat a tím tento projekt rozvinout. Taktéž děkujeme Ing. Josefu Strnadelovi, Ph.D., za poskytnuté studijní materiály.

12. Použitá literatura

Labrosse J. Jean: uC/OS-II Real-Time Kernel

Texas Instruments: MSP430x1xx Family User's Guide

Texas Instruments: MSP430x2xx Family User's Guide

Zobrazeno: 3886x Naposledy: 2.6.2023 23:31:53