This is an old revision of the document!
Î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()
void setup() { // Cod ce rulează la încărcarea programului sau alimentarea plăcuței } void loop() { // Cod ce rulează continuu, similar unei bucle while (true) }
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.
Î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.
Pentru a fi considerat de timp real, un sistem de operare trebuie să îndeplinească mai multe caracteristici, precum:
FreeRTOS reprezintă un kernel (doar baza unui sistem de operare, deoarece sistemele încorporate nu necesită toate caracteristicile unui OS generalizat), cu ajutorul căruia aplicațiile de timp real pot respecta limite de timp impuse. De asemenea, poate fi utilizat conceptul de multitasking, unde schimbarea contextului de execuție pe un procesor cu un singur nucleu conduce la execuția cvasi-paralelă a mai multor secțiuni de cod din aplicație. Firul de execuție care are acces la resursele hardware este ales de către algoritmul de planificare, prin
asignarea de priorități și urmărirea perioadelor de timp în care un fir de execuție este suspendat.
FreeRTOS este open-source, ceea ce înseamnă că nu este necesară achiziționarea acestuia (după cum sugerează și numele) pentru utilizare în aplicații comerciale. Totuși, există posibilitate achiziționării de garanții suplimentare din partea dezvoltatorului Real Time Engineers Ltd.,
dacă situația din proiect o cere. Ca medii de dezvoltare compatibile, pot fi amintite familiile de microcontrolere ARM Cortex, Atmel SAM, Cypress PSoC, Microchip PIC etc.
Compatibilitatea cu unii dintre membrii familiei Arduino (Uno, Nano, Leonardo, Mega) vine tocmai din utilizarea microprocesoarelor amintite ulterior. În plus, kernel-ul a fost portat sub formă de bibliotecă, instalarea realizându-se din Library Manager. Trebuie specificat că toate funcțiile FreeRTOS pot fi utilizate pe Arduino, iar dintre acestea se pot aminti:
Toate caracteristicile menționate anterior sunt utilizate cu ajutorul API-urilor specializate. Un Application Programming Interface (API) reprezintă un grup de funcții ce pot fi apelate, fiecare având un rezultat diferit. De exemplu, crearea semafoarelor și mutexurilor, ștergerea și 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/.
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. Pentru a utiliza task-uri, trebuie inclusă și biblioteca task.h, alături de FreeRTOS.h.
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:
Dacă valoarea întoarsă de funcție este pdPASS, atunci task-ul a fost creat cu succes.
Exemplu:
// 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() { TaskHandle_t xHandle; /* Create the task. */ if( xTaskCreate( vTaskCode, /* Pointer to the function that implements the task. */ "Demo task", /* Text name given to the task. */ STACK_SIZE, /* The size of the stack that should be created for the task. This is defined in words, not bytes. */ (void*) &xParameter,/* A reference to xParameters is used as the task parameter. This is cast to a void * to prevent compiler warnings. */ TASK_PRIORITY, /* The priority to assign to the newly created task. */ &xHandle /* The handle to the task being created will be placed in xHandle. */ ) != pdPASS ) { /* The task could not be created as there was insufficient heap memory remaining. If heap_1.c, heap_2.c or heap_4.c are included in the project then this situation can be trapped using the vApplicationMallocFailedHook() callback (or ‘hook’) function, and the amount of FreeRTOS heap memory that remains unallocated can be queried using the xPortGetFreeHeapSize() API function.*/ } else { /* The task was created successfully. The handle can now be used in other API functions, for example to change the priority of the task.*/ vTaskPrioritySet( xHandle, 2 ); } } void vTaskCode( void * pvParameters ) { xStruct *pxParameters; /* Cast the void * parameter back to the required type. */ pxParameters = ( xStruct * ) pvParameters; /* The parameter can now be accessed as expected. */ if( pxParameters->cStructMember1 != 1 ) { /* Etc. */ } /* Enter an infinite loop to perform the task processing. */ for( ;; ) { /* Task code goes here. */ } }