FreeRTOS™ per AVR

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:

  1. .PORTB to LEDS
  2. PORTD bits 0 and 1 to RS
  3. 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:

  1. Assicurarsi  che  WinAVR  sia  correttamente installato e accessibile attraverso il vostro PATH.
  2. Aprire una sessione con “command.com” e posizionarsi in “Demo/AVR_ATMega323_WinAVR ”.
  3. Eseguire il makefile associato al progetto con il comando ‘make’. Al termine del comando viene generato l’intero progetto.
  4. Per forzare un rebuild occorre eseguire il commando ‘make clean’: in questo modo vengono cancellati tutti i file creati nel processo di make.
  5. 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.

 

4 Commenti

  1. Gaspare Santaera Gaspare Santaera 16 settembre 2017
  2. Luca Giuliodori Luca Giuliodori 16 settembre 2017
  3. Riccardo Ventrella Riccardo Ventrella 18 settembre 2017

Scrivi un commento