This shows you the differences between two versions of the page.
patr:laboratoare:07 [2022/01/09 15:40] alexandru.ionita99 [Task-uri FreeRTOS] |
patr:laboratoare:07 [2022/02/14 15:16] (current) alexandru.ionita99 [Task-uri FreeRTOS] |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Laboratorul 07 - Programare în timp-real pe Arduino ===== | ===== Laboratorul 07 - Programare în timp-real pe Arduino ===== | ||
- | ===== Fire de execuție în Ardunio ===== | + | ==== Fire de execuție în Ardunio ==== |
Întrucât microcontroller-ele prezente pe Arduino sunt single-chip și single core, un singur fir de execuție poate rula simultan. Mai mult, Arduino este construit în jurul unui singur fir de execuție principal, ce rulează în interiorul funcției **void loop()** | Întrucât microcontroller-ele prezente pe Arduino sunt single-chip și single core, un singur fir de execuție poate rula simultan. Mai mult, Arduino este construit în jurul unui singur fir de execuție principal, ce rulează în interiorul funcției **void loop()** | ||
Line 18: | Line 18: | ||
Pentru a putea diviza programul în mai multe fire de execuție și a avea o rulare cvasi-paralelă a acestora, utilizăm biblioteca FreeRTOS, pe care am instalat-o în cadrul laboratorului trecut. | Pentru a putea diviza programul în mai multe fire de execuție și a avea o rulare cvasi-paralelă a acestora, utilizăm biblioteca FreeRTOS, pe care am instalat-o în cadrul laboratorului trecut. | ||
- | ===== Ce este FreeRTOS? ===== | + | ==== Ce este FreeRTOS? ==== |
În general, un sistem de operare controlează procesele și resursele hardware ale unui calculator, definind regulile care permit unui program să acceseze servicii și să interacționeze cu sistemul de calcul. \\ | În general, un sistem de operare controlează procesele și resursele hardware ale unui calculator, definind regulile care permit unui program să acceseze servicii și să interacționeze cu sistemul de calcul. \\ | ||
Line 45: | Line 45: | ||
modificarea acestora se realizează cu ajutorul Semaphore API. Toate aceste funcții sunt definite și prezentate detaliat în documentația FreeRTOS, disponibilă pe site-ul https://www.freertos.org/. | modificarea acestora se realizează cu ajutorul Semaphore API. Toate aceste funcții sunt definite și prezentate detaliat în documentația FreeRTOS, disponibilă pe site-ul https://www.freertos.org/. | ||
+ | ==== FreeRTOS Scheduler ==== | ||
- | ===== Task-uri FreeRTOS ===== | + | Tot mecanismul de funcționare FreeRTOS are la bază un scheduler, care gestionează task-urile și alte elemente real-time și realizează preempțiunea, atunci când este cazul. Funcția de pornire a scheduler-ului este **vTaskStartScheduler()** și, în mod normal, este apleată la sfârșitul funcției **void setup()**, pentru a începe gestionarea elementelor de programare real-time. |
- | Crearea, ștergerea și gestionarea task-urilor se realizează cu ajutorul **Task and Scheduler API**. | + | ==== Task-uri FreeRTOS ==== |
+ | Crearea, ștergerea și gestionarea task-urilor se realizează cu ajutorul **Task and Scheduler API**. Pentru a utiliza task-uri, trebuie inclusă și biblioteca //task.h//, alături de //FreeRTOS.h//. | ||
+ | |||
+ | |||
+ | <note warning>Utilizarea FreeRTOS face ca funcția clasică **void loop()** să nu mai aibă nicio utilitate, întrucât toată logica programului se desfășoară în task-uri separate. Astfel, funcția va fi doar definită, pentru a nu primi erori la compilare, dar va rămâne goală</note> | ||
+ | |||
+ | === Crearea unui Task === | ||
+ | |||
+ | Pentru a crea un nou task, utilizăm funcția **BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, unsigned short usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask )**, care are următorii parametri: | ||
+ | * **pvTaskCode** - pointer către funcția ce va rula la execuția task-ului | ||
+ | * **pcName** - un nume pentru task, necesar la debugging | ||
+ | * **usStackDepth** - dimensiune stivei de memorie ce este alocată pentru task, sub formă de număr de cuvinte ( un cuvânt de memorie = 4 bytes, pentru Arduino Uno/Mega/Nano etc) | ||
+ | * ***pvParameters** - parametru ce va fi transmis funcției specificate la **pvTaskCode** | ||
+ | * **uxPriority** - prioritatea task-ului. 0 este valoarea minimă și specifică prioritatea cea mai mică | ||
+ | * ***pxCreatedTask** - opțional, specificarea unui hadler pentru task | ||
+ | |||
+ | Dacă valoarea întoarsă de funcție este **pdPASS**, atunci task-ul a fost creat cu succes. | ||
+ | |||
+ | Exemplu: | ||
+ | <code c> | ||
+ | |||
+ | // Definim structura xStruct, cu 2 elemente | ||
+ | typedef struct A_STRUCT | ||
+ | { | ||
+ | char cStructMember1; | ||
+ | char cStructMember2; | ||
+ | } xStruct; | ||
+ | |||
+ | // Definim o variabilă de tipul xStruct | ||
+ | xStruct xParameter = { 1, 2 }; | ||
+ | |||
+ | |||
+ | /* Define a function that creates a task. This could be called either before or after the | ||
+ | scheduler has been started. */ | ||
+ | void setup() | ||
+ | { | ||
+ | |||
+ | ... | ||
+ | |||
+ | // Definim un element de tipul TaskHandle, pentru a referenția viitorul task | ||
+ | TaskHandle_t xHandle; | ||
+ | |||
+ | // Creăm task-ul | ||
+ | if( xTaskCreate( | ||
+ | vTaskCode, //Pointer către funcția ce va rula la pornirea task-ului | ||
+ | "Demo task", // Un nume pentru task | ||
+ | 200, // Sunt alocate 200 cuvinte de memorie din stivă pentru acest task | ||
+ | (void*) &xParameter, // Cast la (void*) pentru parametrul ce va fi transmis funcției | ||
+ | 1, // Task-ul are setată prioritatea | ||
+ | &xHandle // Handle pentru task-ul creat | ||
+ | ) != pdPASS ) | ||
+ | { | ||
+ | // Task-ul nu a putut fi creat. Nu există suficient spațiu în stack. | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | // Task-ul a fost creat cu succes | ||
+ | |||
+ | // Schimbăm prioritatea task-ului, utilizând handle-ul xHandle | ||
+ | vTaskPrioritySet( xHandle, 2 ); | ||
+ | } | ||
+ | |||
+ | // Pornim scheduler-ul FreeRTOS. Fără acest pas, task-ul nu ar rula. | ||
+ | vTaskStartScheduler(); | ||
+ | |||
+ | } | ||
+ | |||
+ | // Funcția ce va fi rulată la execuția task-ului | ||
+ | void vTaskCode( void * pvParameters ) | ||
+ | { | ||
+ | xStruct *pxParameters; | ||
+ | |||
+ | // Cast al parametrului înapoi la tipul necesar | ||
+ | pxParameters = ( xStruct * ) pvParameters; | ||
+ | |||
+ | // Lucru cu parametrul | ||
+ | if( pxParameters->cStructMember1 != 1 ) | ||
+ | { | ||
+ | /* Etc. */ | ||
+ | } | ||
+ | |||
+ | // Buclă infinită | ||
+ | for( ;; ) | ||
+ | { | ||
+ | ... | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Definim funcția void loop(), dar rămâne goală, pentru că utilizăm task-uri | ||
+ | void loop(){} | ||
+ | |||
+ | </code> | ||
+ | |||
+ | |||
+ | === vTaskDelay() === | ||
+ | |||
+ | O funcție foarte importantă din Task API este **void vTaskDelay( TickType_t xTicksToDelay );**. Aceasta înlocuiește clasica funcție **delay()** din Arduino. Funcția plasează **task-ul din care a fost apelată** în starea **BLOCAT** pentru o perioadă de X ticks, unde X este oferit ca parametru, timp în care alte task-uri, de exemplu cele cu o prioritate mai mică, pot accesa procesorul. | ||
+ | |||
+ | Pentru a transforma timpul din milisecunde în ticks, utilizăm funcția **pdMS_TO_TICKS()**. | ||
+ | |||
+ | <note warning>Utilizarea funcției **delay()** nu este recomandată împreună cu FreeRTOS, deoarece conduce la **suspendarea activității procesorului**, adică a tutoror task-urilor care rulează. Dacă în cazul programării clasice, acest aspect nu era o problemă (exista un singur task), în cazul FreeRTOS pot apărea efecte neașteptate.</note> | ||
+ | |||
+ | |||
+ | |||
+ | **Exemplu:** | ||
+ | <code c> | ||
+ | void vAnotherTask( void * pvParameters ) | ||
+ | { | ||
+ | for( ;; ) | ||
+ | { | ||
+ | ... | ||
+ | // Intră în starea BLOCAT pentru 20 ticks | ||
+ | vTaskDelay( 20 ); | ||
+ | |||
+ | // Intră în starea blocat pentru 20 ms | ||
+ | vTaskDelay( pdMS_TO_TICKS( 20 ) ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | |||
+ | === Alte funcții utile === | ||
+ | |||
+ | * **vTaskDelete( TaskHandle_t pxTask ) ** - Șterge task-ul aferent hande-ului oferit ca parametru. Este eliberată memoria alocată automat la momentul creării task-ului | ||
+ | * **xTaskGetCurrentTaskHandle() ** - Este întors handle-ul task-ului care se află **în execuție** la acel moment | ||
+ | * **xTaskGetHandle( const char *pcNameToQuery ) ** - Este întors handle-ul task-ului cu numele oferit ca parametru | ||
+ | * **pcTaskGetName( TaskHandle_t xTaskToQuery ) ** - Întoarce numele task-ului referit prin handle | ||
+ | * **uxTaskPriorityGet( TaskHandle_t pxTask ) ** - Întoarce prioritatea task-ului referit prin hadle, la acel moment | ||
+ | * **vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority ) ** - Setează o nouă prioritate pentru un task | ||
+ | * **vTaskSuspend( TaskHandle_t pxTaskToSuspend ) ** - Pune task-ul în starea **Suspendat** | ||
+ | * **vTaskResume( TaskHandle_t pxTaskToResume) ** - Pune task-ul în starea **Running**. Funcționează doar pentru task-uri **suspendate** | ||
+ | * **taskYIELD() ** - Se apelează dintr-un task **Running**, care iși oferă partiția de timp către alte task-uri **de aceeași prioritate** |