
FreeRTOS™ è uno small real-time kernel studiato e realizzato per architetture di bassa/media fascia basate su microcontrollore. In questo articolo, oltre a mettere in evidenza la distribuzione per AVR, verrà illustrato il porting su un dispositivo AVR con tutto quello che ne consegue.
Introduzione
Parlando di un sistema RTOS balzano subito alla mente le innumerevoli realizzazioni commerciali che certamente danno, almeno in apparenza, grandi garanzie da un punto di vista tecnico. Oggi, con l’avvio del progetto GCC, questo non è più sempre vero: esistono infatti prodotti “aperti” dalle prestazioni comparabili ai più noti prodotti a pagamento, anche se poi, per applicazioni commerciali, sono sottoposti ad una particolare licenza d’uso. Una volta scelto un microprocessore quali sono i punti che devono essere presi in considerazione per “portarlo” su FreeRTOS™? Lo scopo di questo articolo è cercare di dare una risposta in questo senso.
FREERTOS™
Il prodotto, FreeRTOS™ è un pacchetto che, pur essendo open, copre pienamente i requisiti prestazionali che un sistema commerciale richiede, primo tra tutti la possibilità di disporre di primitive (le APIs) che permettono la gestione di un sistema real time in maniera completa. Questo oggetto è poi portabile (esistono infatti porting per diverse architetture dal PIC della Microchip all’AVR, ATMega323) è open source ed è libero per usi personali non a scopo di lucro. Per utilizzi in ambito commerciale è sottoposto ad una licenza particolare allegata alla distribuzione. FreeRTOS™ è stato portato su diverse cross-factory, da CrossWorks a GCC. Il modo migliore di prendere dimestichezza di un RTOS è quello di utilizzarlo con una applicazione demo. Alcune demo dispongono di un simulatore; è il caso della distribuzione per Keil ARM7 che è corredata di un simulatore sotto windows. Esistono due porting disponibili per l’ambiente AVR supportati: uno usa la cross-factory della società svedese IAR, IAR Embedded Workbench e l’altra utilizza la distribuzione WinAVR (GCC). Brevemente qui di seguito viene proposta la soluzione basata su AVR (MegaAVR). MegaAVR – Win AVR. L’ambiente è pre-configurato per funzionare sul prototipo STK500 utilizzando un ATMEGA323. L’ambiente di cross-factory è basato sulla tecnologia GNU, WinAVR development tools. Mentre l’ambiente di debugger è AVR Studio. Questa distribuzione è free ma non rilasciata secondo la licenza open source. La frequenza di funzionamento è di 8 MHz, se poi viene utilizzato un ATMega32 la frequenza può essere incrementata fino a 16 MHz. Il porting di FreeRTOS™ può utilizzare anche il processore ATMega128. Sono disponibili 2KBytes di RAM e questo spazio risulta sufficiente per creare ed eseguire fino a 10 tasks, incluso l’idle task.
LA DISTRIBUZIONE SU AVR
La distribuzione AVR come WinAVR è localizzata in Demo/AVR_ATMega323_WinAVR. La distribuzione è corredata da una piccola applicazione (demo): questa utilizza dei tasks FreeRTOS™ che spediscono/ricevono caratteri su una linea seriale. Due Task gestiscono il flusso d’informazioni: uno spedisce e l’altro riceve. Tutti gli eventuali errori vengono notificati settando un’opportuna variabile. Da un punto di vista hardware, è necessario mettere in loopback la linea seriale (semplicemente connettendo i pins 2 e 3). Per controllare visivamente questo comportamento è possibile abilitare dei LEDs attraverso la porta parallela del PC. Prima di eseguire l’applicazione di demo è necessario fare il set-up del prototipo STK500 attraverso le seguenti connessioni:
- .PORTB to LEDS
- PORTD bits 0 and 1 to RS
- SPROG3 to ISP6PIN (correct link to program an AVR ATMega323
La costruzione dell’applicazione demo
Per costruire l’applicazione demo è necessario seguire i seguenti passi:
- Assicurarsi che WinAVR sia correttamente installato e accessibile attraverso il vostro PATH.
- Aprire una sessione con “command.com” e posizionarsi in “Demo/AVR_ATMega323_WinAVR ”.
- Eseguire il makefile associato al progetto con il comando ‘make’. Al termine del comando viene generato l’intero progetto.
- Per forzare un rebuild occorre eseguire il commando ‘make clean’: in questo modo vengono cancellati tutti i file creati nel processo di make.
- Esiste la possibilità di decidere il livello di ottimizzazione e di debug, sempre intervenendo nel makefile. Per default le opzioni di debug e ottimizzazione sono settate -O e –g rispettivamente.
Questo processo di building produrrà alla fine due files chiamati RTOSDemo.elf e RTOSDemo.hex. Il primo è utilizzato per l’esecuzione e il debugging dell’applicazione demo con il tool Atmel AVR Studio, l’altro è utilizzato per fare il burning del programma in memoria.
L’estensione .elf individua un file che contiene informazioni di debug, cioè con i simboli. Invece, l’estensione .hex indica un file con nessuna informazione di debug, ma solo una rappresentazione esadecimale o binaria, a seconda della cross-factory in uso, del programma per consentirne il caricamento in memoria.
Informazioni per la configurazione dell’RTOS
Le informazioni di configurazione di questa distribuzione sono contenute nel file
Demo/AVR_ATMega323_WinAVR/FreeRTOSConfig.h
I parametri definiti sono configurabili dall’utente e pienamente modificabili, come per esempio configTICK_RATE_HZ utilizzato per fissare la frequenza di funzionamento del tick di FreeRTOS™. Il valore definito a 1000Hz ha solo uno scopo didattico per mostrarne il funzionamento, in applicazioni reali questo valore è molto più basso, di solito 50Hz. Per usare un microcontrollore diverso dall’AVR ATMega323 basta intervenire nel makefile cambiando la definizione di MCU:
MCU = “altro dispositivo AVR”
Inoltre sono necessari i seguenti passi:
- Fissare la frequenza di clock in
Demo/AVR_ATMega323_WinAVR/FreeRTOSC onfig.h - Assicurarsi che la definizione di “configTOTAL_HEAP_SIZE” rispecchi la reale disponibilità della RAM
- Il system tick ISR è prodotto da un compare match timer. La gestione dei timers potrebbe non essere identica con quella dell’AVR che si intende utilizzare. A questo scopo sarà necessario controllare la funzione prvSetupTimerInterrupt() in Source/portable/GCC/ATMega323/port.c per apportarvi le necessarie modifiche.
Il file FreeRTOSConfig.h, contiene altri parametri di configurazione, quali:
- Esiste la possibilità di escludere funzioni di FreeRTOS™ (API) dall’applicazione, semplicemente mettendo a 1 o 0 la relativa API di FreeRTOS™. Il risultato di questa operazione è un risparmio di spazio in RAM e in ROM dall’uso delle API non richieste. Queste macro hanno la forma INCLUDE_FunctionName, ad esempio: INCLUDE_vTaskPrioritySet 0
- configUSE_PREEMPTION, utilizzato per utilizzare la pre-emption ( se posto a 1) o per usare la coroutine (se messo a 0).
- configUSE_IDLE_HOOK, utilizzato per permettere agganciare un user idle (se posto a 1), altrimenti, se messo a 0, per omettere un idle hook.
- configUSE_TICK_HOOK, utilizzato per avere un tick hook (se posto a 1). Un Tick hook permette ad ogni system tick di avere una user routine agganciata al system tick.
- configMINIMAL_STACK_SIZE. Identifica la disponibilità di stack per l’idle task.
- configMAX_TASK_NAME_LEN. Questo parametro identifica la lunghezza massima permessa del nome di un task. La lunghezza è individuata dal numero di caratteri incluso il carattere di terminazione della stringa (NULL byte).
- configUSE_CO_ROUTINES. Se questo parametro viene posto a 1 allora viene manifestata l’intenzione di includere la funzionalità di co-routine nell’applicazione, altrimenti è da porre a 0. Oltre a mettere a 1 questo parametro occorre includere nel makefile il file routine.c.
Inoltre, nella distribuzione viene incluso il file Source/Portable/MemMang/heap_1.c nel makefile per permettere la funzionalità di memory allocation richiesta dal real time kernel.
PORTING SU AVR
Quali sono le informazioni che bisogna conoscere per fare il porting di un sistema operativo, piccolo per quanto sia, su un nuovo microprocessore? Le problematiche da approfondire sono diverse. In linea generale occorre fissare le caratteristiche che sono dipendenti dall’hardware, quali:
- Tick di sistema:
Per scandire le operazioni di un kernel real time occorre definire un tick di sistema.
- Integrità delle operazioni:
Per preservare l’integrità delle operazioni è necessario predisporre alcune routine come l’abilitazione e la disabilitazioni delle interruzioni per gestire l’ingresso e l’uscita dalle operazioni critiche.
- Contesto:
Quali sono le informazioni che sono necessarie per passare da un processo all’altro e come devono essere preservate?
- ISR e RTOS:
Come deve essere gestita una ISR all’interno di un RTOS.
- Creazione di un nuovo task:
Quali accorgimenti occorre seguire se viene creato un nuovo task nel sistema?
- Funzioni generiche di gestione:
È necessario disporre di funzioni di inizializzazione dell’AVR in particolari situazioni?
TICK DI SISTEMA
In un sistema sono presente diversi clock. Oltre al clock di una CPU, che scandisce la velocità di elaborazione delle informazioni, ne esistono almeno altri due tipi: un clock per ricavare il tempo e la data di un sistema (time-of-day o RTC) e un clock in tempo reale (System Ticks) la cui funzione è quella di scandire le operazioni svolte dalle unità elementari di elaborazioni (tasks). Un time-of-day è banalmente indicato come un cronometro che, settato ad un valore predefinito, insieme ad un evento asincrono di solito interno al componente tiene traccia del tempo (come un orologio). Da questo RTC si ricavano le informazioni dell’ora e della data. A differenza di un clock time-of-day, un clock in tempo reale non conta impulsi da un valore noto, ma genera un evento asincrono ogni “quanto” di tempo (di solito ogni 50Hz) per scandire le operazioni che il kernel real time (FreeRTOS™) dovrà eseguire: dalla schedulazione alle operazioni di delay da associare ad ogni task. Un’altra differenza sostanziale tra un RTC e un clock real time è che quest’ultimo non contiene nessun contatore e non accumula interruzioni, semmai è lo strato software che predispone e gestisce un contatore per i suoi svariati usi. Come gestire un clock real time utilizzando un componente AVR? Il listato 1 mostra un esempio.
static void prvSetupTimerInterrupt( void ) { unsigned portLONG ulCompareMatch; unsigned portCHAR ucHighByte, ucLowByte; /* Using 16bit timer 1 to generate the tick. Correct fuses must be selected for the configCPU_CLOCK_HZ clock. */ ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ; /* We only have 16 bits so have to scale to get our required tick rate. */ ulCompareMatch /= portCLOCK_PRESCALER; /* Adjust for correct value. */ ulCompareMatch -= ( unsigned portLONG ) 1; /* Setup compare matc value for compare match A. Interrupts are disabled before this is called so we need not worry here. */ ucLowByte = ( unsigned portCHAR ) ( ulCompareMatch & ( unsigned portLONG ) 0xff ); ulCompareMatch >>= 8; ucHighByte = ( unsigned portCHAR ) ( ulCompareMatch & ( unsigned portLONG ) 0xff ); OCR1AH = ucHighByte; OCR1AL = ucLowByte; /* Setup clock source and compare match behaviour. */ ucLowByte = portCLEAR_COUNTER_ON_MATCH | portPRESCALE_64; TCCR1B = ucLowByte; /* Enable the interrupt - this is okay as interrupt are currently Globally disabled. */ ucLowByte = TIMSK; ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE; TIMSK = ucLowByte; }
Listato 1 |
Nel listato viene utilizzato il Timer 1, un registro a 16 bit, per generare un interrupt di questo tipo. La gestione di questo timer avviene con il meccanismo noto come “compare match event” ed è necessario definire alcune costanti adibite alla gestione del timer (listato 2).
/* Hardware constants for timer 1. */ #define portCLEAR_COUNTER_ON_MATCH ( ( unsigned portCHAR ) 0x08 ) #define portPRESCALE_64 ( ( unsigned portCHAR ) 0x03 ) #define portCLOCK_PRESCALER ( ( unsigned portLONG ) 64 ) #define portCOMPARE_MATCH_A_INTERRUPT_ENABLE( ( unsigned portCHAR ) 0x10 )
Listato 2 |
INTEGRITÀ DELLE OPERAZIONI
Come evitare le corse critiche? Per evitare le corse critiche è necessario impiegare un particolare meccanismo di mutua esclusione. Esistono vari accorgimenti per soddisfare questo requisito e uno di questi è il meccanismo di mutua esclusione con attesa attiva. Rientrano in questa categoria i seguenti accorgimenti:
- Disabilitazione delle interruzioni
- Variabili di lock
- Alternanza stretta
- Soluzione di Paterson
- Istruzioni del tipo TSL (test and set lock)
appena questo entri nella propria zona critica, per poi riabilitarle all’uscita. Nell’implementazione di FreeRTOS™ viene usato proprio questo accorgimento. Per implementare questi meccanismi vengono generalmente predisposte alcune primitive che possono essere costruite come macro o come funzioni. Nel caso dell’esempio può essere decisa la scrittura di due macro per consentire le abilitazioni e disabilitazioni delle interruzioni, come:
#define portDISABLE_INTERRUPTS() asm volatile ( “cli” :: );
#define portENABLE_INTERRUPTS() asm volatile ( “sei” :: );
a questo riguardo si sfrutta il linguaggio assembler in quanto risulta più efficiente per questa particolare operazione. Queste due macro sono utilizzate sotto interrupts all’interno della gestione del timer. Può capitare che per garantire l’atomicità di alcune operazioni si debba ricorre a particolari meccanismi. Se ad esempio si intendono gestire particolari code di task con la garanzia di non essere interrotti, si possono impiegare due primitive: Enter Critical Section e Exit Critical Section. Nel listato 3 è riportata l’implementazione di queste operazioni.
#define portENTER_CRITICAL() asm volatile ( “in __tmp_reg__, __SREG__” :: ); \ asm volatile ( “cli” :: ); \ asm volatile ( “push __tmp_reg__” :: ) #define portEXIT_CRITICAL() asm volatile ( “pop __tmp_reg__” :: ); \ asm volatile ( “out __SREG__, __tmp_reg__” :: )
Listato 3 |
CONTESTO IN AVR
Le informazioni che devono essere preservate, nel passaggio tra due task vengono identificate con il termine di “contesto”. Il contesto su AVR viene individuato dall’insieme dei seguenti registri:
- Il set dei registri della famiglia AVR consistono di 32 registri. Il compilatore GCC assume il registro r1 a zero.
- Il registro di stato. Questo registro è importante perchè riflette le istruzioni svolte dal microprocessore, le eventuali segnalazioni di riporto, overflow, …(SREG).
- Program Counter. È un registro importante che determina il flusso del programma: una volta riesumato, un task deve proseguire con le istruzioni immediatamente prima della sua sospensione (PC).
- I due registri di stack pointer (SPL, SPH).
Queste informazioni consentono all’AVR di fissare il contesto. Nel passaggio da un processo all’altro viene utilizzata una procedura che permette di gestire il cambio del contesto: il cambio dei registri non può essere fatto in un linguaggio ad alto livello come il C e la sua codifica, com’è ovvio, dipende da macchina a macchina.
GESTIONE DI UNA ISR
La ISR necessaria è quella che deve gestire il system tick. Ci sono due possibili gestioni del tick di sistema: per avere una politica di schedulazione di tipo cooperative allora verrà usato un contatore di ticks di sistema, mentre, se per usare l’altra politica prevista da FreeRTOS™, oltre all’incremento del tick di sistema dovrà essere gestita anche una rischedulazione.
CONCLUSIONE
Abbiamo così visto quali sono le considerazioni da tenere presente per fare un porting verso AVR. Gli stessi accorgimenti, in linea di principio, devono essere seguiti se si intende adattare FreeRTOS™ ad altri tipi di microprocessori. Sul sito ufficiale di FreeRTOS™ sono disponibili tutte le informazioni tecniche per configurare correttamente la propria applicazione e tutta la documentazione per utilizzare il sistema.

Finalmente un articolo che spiega come iniziare ad usare RTOS.
Sarebbe interessante analizzare nel dettaglio un esempio con FreeRTOS. Comunque articolo molto interessante!
Non posso spoilerare, ma soltanto dire: stay tuned!
L’argomento FreeRTOS e’ sicuramente uno dei piu’ affascinanti, almeno per me.
Tempo fa avevo iniziato il porting della scheda della board TRKEA128, perche’ mi interessavano
utilizzi del CAN in thread diversi. Nel recente passato esistevano anche dei bei libri sul”argomento, orientati ai microprocessori. Ora purtroppo diventano piu’ difficili da trovare, poiche’ la FreeRTOS ha deciso di pubblicare il manuale in pdf da una delle ultime versioni in poi:
http://www.freertos.org/Documentation/RTOS_book.html
Gia’ per il PIC32 ho fatto molta fatica a trovarlo.
Peccato, il libro in formato cartaceo ha sempre il suo bel fascino.