giovedì 9 giugno 2016

RealTime Operating Systems RTOS per Arduino

Una delle cose belle di Arduino e' che, pur nella sua semplicita', esegue in modo ripetibile e prevedibile un compito...ma in fondo e' uno solo. Se il sistema e' intrappolato, per esempio in un loop infinito, esistono sistemi basati sulla gestione degli interrupt per venire incontro a condizioni non prevedbili, come per esempio la pressione di un tasto da parte dell'utente

Un sistema decisamente piu' evoluto per gestire processi concorrenti su un processore da risorse cosi' ridotte come l'AVR ATMega328 e' quello di usare i sistemi operativi realtime, in breve RTOS (sistema operativo inteso come kernel ed API, non come interfaccia utente)

Con questi software si puo' avere la gestione di thread concorrenti con gestione della priorita' dei task, la gestione di semafori Mutex., binari o counting semaphore per sincronizzare i processi. Una caratteristica fondamentale dei sistemi RTOS e' quello di eseguire un task in un arco temporale (minimo e massimo) ben definito; se dopo il tempo massimo il task non ha finito il compito deve lasciare spazio agli altri task concorrenti. Questa caratteristica e' importante per sistemi robotizzati: per esempio se un automa si muove e contemporaneamente acquisisce dati, i sensori di urto devono avere la priorita' sull'acquisizione e non ci deve essere ritardo tra quando viene lanciato l'allarme di prossimita' e la conseguente azione. Su Linux (sempre che non monti un kernel modificato per essere realtime..la Nasa all'inizio degli anni 2000 aveva creato Flight Linux) puo' succedere che il sistema sia occupata in un task lungo e non rilasci in tempo le risorse per evitare l'urto del robot

Per Arduino esistono sostanzialmente FreeRTOS e ChibiOS

Come documentazione Chibios e' decisamente meglio documentato , FreeRTOS ha un libro a pagamento mentre nel file .zip con il sistema operativo  c'e' una cartella con gli esempi ma non relativi ad ATMega.

Un caso pratico in cui si puo' capire il vantaggio di un sistema RTOS della classica programmazione Arduino e' quello in cui si gestisce un flusso seriale ed in maniera indipendente si devono gestire anche l'acquisizione dei sensori


FreeRTOS
Per installare le libreriei di FreeRTOS nella IDE di Arduino e' sufficiente andare nel menu Sketch/Includi libreria/Gestione Librerie e cercare ed installare FreeRTOS. Stranamente non vengono installati degli esempi




ChibiOS
Per installare ChibiOS in Arduino IDE si parte scaricando le librerie da questo link.(esiste un sistema di sviluppo completo costituito da ChibiOS Studio ma e' possibile compilare per ATMega ma solo per ARM STM32) e si pone la sottodirectory /libraries/ChibiOS_AVR presente nel file zip dentro la directory libraries della IDE di Arduino (sotto Windows si trova nella propria C:\Documents and Settings\xxxxx\Documenti\Arduino\libraries\ChibiOS_AVR mentre in Linux e' semplicemente in libraries)

ChibiOS Studio

Dal punto di vista di scrittura del codice ci sono diversi similitudini. Prendendo due esempi semplici (riportati piu' in basso). Nella parte di setup vengono dichiarati i task che fanno riferimenti a due funzioni con l'indicazione della priorita'; all'interno della funzione del task sono riportate le azioni di ciascun thread FreeRTOS usa il termine task che corrisponde al thread di Chibios)

Esempio FreeRTOS. 
Due Task in cui uno lampeggia un led e l'altro legge i dati da una porta analogica e lo spedisce sulla seriale
---------------------------
#include <Arduino_FreeRTOS.h>
#include <croutine.h>
#include <event_groups.h>
#include <FreeRTOSConfig.h>
#include <FreeRTOSVariant.h>
#include <list.h>
#include <mpu_wrappers.h>
#include <portable.h>
#include <portmacro.h>
#include <projdefs.h>
#include <queue.h>
#include <semphr.h>
#include <StackMacros.h>
#include <task.h>
#include <timers.h>

#include <Arduino_FreeRTOS.h>

// define two tasks for Blink & AnalogRead
void TaskBlink( void *pvParameters );
void TaskAnalogRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {

  // Now set up two tasks to run independently.
  xTaskCreate(
    TaskBlink
    ,  (const portCHAR *)"Blink"   // A name just for humans
    ,  128  // Stack size
    ,  NULL
    ,  2  // priority
    ,  NULL );

  xTaskCreate(
    TaskAnalogRead
    ,  (const portCHAR *) "AnalogRead"
    ,  128 // This stack size can be checked & adjusted by reading Highwater
    ,  NULL
    ,  1  // priority
    ,  NULL );

  // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskBlink(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);

  for (;;) // A Task shall never return or exit.
  {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
  }
}

void TaskAnalogRead(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);

  for (;;)
  {
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // print out the value you read:
    Serial.println(sensorValue);
    vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  }
}
---------------------------

Esempio ChibiOs 
Due Task che fanno lampeggiare il led con l'uso di un semaforo per scambiarsi i dati tra i due thread
----------------------------
// Example to demonstrate thread definition, semaphores, and thread sleep.
#include <ChibiOS_AVR.h>

// The LED is attached to pin 13 on Arduino.
const uint8_t LED_PIN = 13;

// Declare a semaphore with an inital counter value of zero.
SEMAPHORE_DECL(sem, 0);
//------------------------------------------------------------------------------
// Thread 1, turn the LED off when signalled by thread 2.

// 64 byte stack beyond task switch and interrupt needs
static THD_WORKING_AREA(waThread1, 64);

static THD_FUNCTION(Thread1, arg) {

  while (!chThdShouldTerminateX()) {
    // Wait for signal from thread 2.
    chSemWait(&sem);

    // Turn LED off.
    digitalWrite(LED_PIN, LOW);
  }
}
//------------------------------------------------------------------------------
// Thread 2, turn the LED on and signal thread 1 to turn the LED off.

// 64 byte stack beyond task switch and interrupt needs
static THD_WORKING_AREA(waThread2, 64);

static THD_FUNCTION(Thread2, arg) {
  pinMode(LED_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED_PIN, HIGH);

    // Sleep for 200 milliseconds.
    chThdSleepMilliseconds(200);

    // Signal thread 1 to turn LED off.
    chSemSignal(&sem);

    // Sleep for 200 milliseconds.
    chThdSleepMilliseconds(200);
  }
}
//------------------------------------------------------------------------------
void setup() {

  chBegin(chSetup);
  // chBegin never returns, main thread continues with mainThread()
  while(1) {
  }
}
//------------------------------------------------------------------------------
// main thread runs at NORMALPRIO
void chSetup() {

  // start blink thread
  chThdCreateStatic(waThread1, sizeof(waThread1),
    NORMALPRIO + 2, Thread1, NULL);

  chThdCreateStatic(waThread2, sizeof(waThread2),
    NORMALPRIO + 1, Thread2, NULL);

}
//------------------------------------------------------------------------------
void loop() {
  // not used
}