domingo, 23 de marzo de 2014

Sistema Operativos en tiempo real Vs Multiprocesos en Arduino.

Sistema operativo en tiempo real

Un sistema operativo en tiempo real ( RTOS ) es un sistema operativo (SO) destinado a atender las solicitudes de aplicaciones en tiempo real. Debe ser capaz de procesar los datos de bucle y por lo general sin los retrasos o cuellos de botella. Los requisitos para el tratamiento del tiempo (incluida cualquier demora OS ) se miden en décimas de segundo o ms (o mas corto, sólo que deja de ser trabajable).

No es un secreto que el Arduino tiene pocos recursos del sistema y humildemente considero una plataforma Marca aunque no Optima, pero así es la electrónica. La pregunta que todos nos hacemos (sobre todo estudiantes que se inician en este mundo sin entender la diferencia) ¿Es posible ejecutar RTOS en Arduino? Afortunadamente, sí! Hay algo de RTOS (básico pero se puede jugar con ello) que puede funcionar  con el Arduino. Hemos utilizado algunos de ellos FreeRTOS, ODAs y nuestra elección es ChibiOS (básico para los que se inician).

ChibiOS / RT está diseñada para aplicaciones profundamente arraigadas en tiempo real, donde la eficiencia y la ejecución de código compacto son requisitos importantes. Este RTOS se caracteriza por su alta portabilidad, tamaño compacto (recordemos que ya tenemos consumido en ARDUINO unos 2 a 3k) y, sobre todo, por su arquitectura optimizada para la conmutación de contexto de procesos extremadamente eficiente. En pocas palabra convive con el Bootloader del Arduino.

¿Qué más tenemos cuando uso RTOS?
Todos los ejemplos que usted ve cuando trabajamos con el Arduino es un programa lineal, a veces se le indica con el nombre de procedimentales. La parte básica del programa se ponen a la función Loop y se ejecuta desde su inicio del Setup hasta el final del cierre de Loop; este Loop es simplemente un Goto Inicio con un retraso al final del bucle cuando nuestro Arduino no hace nada (es un control de bootloader del Arduino).

El típico problema de programas de tareas similares, por ejemplo: como hacemos para leer algunos sensores una vez por segundo.
¿Qué debemos hacer si tenemos que leer el sensor de luz cada diez milisegundos y lee el sensor de temperatura una vez por cinco segundos?. o en resumen que hacemos con procesos Coincidentes en tiempo? Podemos escribir este programa en bucle estándar del Arduino utilizando la función millis. Este programa funciona, pero el código de bucles será muy larga con muchas condiciones. Programas como éste no es fácil de leer, muy modificados en toda la linealidad del programa y pueden contener múltiples errores difíciles de depurar y lo imposible: la escalabilidad (elementos que un diseñador profesional debe considerar) .

Cuando usamos RTOS podemos crear procesos separados denominados hilos. Cada uno de los procesos se parece a la función loop independiente (estimado lector, es como multiples loop uno seguidos de otro pero con un control). Así que podemos crear un programa con mucho bucles cada uno de ellos ejecutará su tarea independiente. En nuestro ejemplo, el primer proceso leerá sensor de luz "proceso rápido" el segundo proceso leerá sensores de temperatura con gran retraso. Y el resultado será enviado al Arduino .

Configuración RTOS y crear bocetos
¿Como instalar o corre el RTOS con el Arduino y que tan complejo es? El ChibiOS es una simple biblioteca que debe copiarse a la carpeta de biblioteca Arduinos. Y el programa es simple script Arduino creado en Arduino IDE, es decir una simple llamada #include "ChibiOS_AVR.h".

Creamos el primer boceto y ejemplo.

Pagina de ChibiOS:
Conseguimos los ChibiOS de página http://code.google.com/p/rtoslibs/ . Esta página contiene información sobre otros RTOS y enlace para ChibiOS pagina oficial .

Puede descargar también el archivo de la página web Arduino:
http://ksduino.org/downloads/ChibiOS20130208.zip

El ejemplo ChibiOS y KSduino
En este ejemplo vamos a crear los procesos:

Proceso 1 para el envío de parámetros a KSduino o Arduino
Proceso 2 para calcular la temperatura DS18B20
Proceso 3 respuestas lectura KSduino o Arduino

Para sincronizar las tareas Arduino utilizaremos el concepto de semáforo, por lo que en un momento de tiempo sólo una tarea del Arduino puede correr, si lector esto quiere decir que dentro de un proceso yo puedo controlar los demás procesos ya sea inhabilitaros o enviadole una clave o mensaje encolado.

EJEMPLO 1:

// Includes #include "SPI.h" #include "EthernetUdp.h" #include "OneWire.h" // one wire library to connect ds18b20 #include "ksDS18B20.h" // subclass to read ds18b20 values #include "KSduino.h" // KSduino Library #include "ChibiOS_AVR.h" // ChibiOS library // DS18B20 address uint8_t devAddr1[] = { 0x28, 0x16, 0x05, 0xB2, 0x03, 0x00, 0x00, 0x00 }; // Define OneWire, DS18B20 & Timer classes OneWire ds(7); // OneWire on pin 7 ksDS18B20 ds18b20_1 (&ds, devAddr1); // KSduino User definition ----------------------------------------- // This Arduino ID and Password // Change this values to yours unsigned int deviceID = 0000; // set your Device ID unsigned int devicePwd = 1111; // set your Device Password // This Arduino MAC, IP,DNS, Gateway and port // Change this values to yours byte mac[] = { 0x74,0x69,0x69,0x2D,0x00,0x00 }; byte ip[] = { 192, 168, 1, 222 }; //byte dns[] = { 192, 168, 1, 1 }; //byte gateway [] = { 192, 168, 1, 1 }; //byte subnet [] = { 255, 255, 255, 0 }; unsigned int port = 40000 + deviceID; // KSduino Server definition ---------------------------------------- // Server address & port byte serverIp[] = { 178,63,53,233 }; // Global IP address unsigned int serverPort = 9930; // Port number // ---------------------------------------------------------------- // Define KSduino class KSduino ksd (deviceID, devicePwd, serverIp, serverPort); // Define some KSduino parameters with KSduinoParameter class KSduinoParameter d1 (&ksd, "d1", 0.5, 15*60); KSduinoParameter f1 (&ksd, "f1", 0.5, 15*60); // Semaphore to trigger context switch Semaphore sem; // Idle time counter volatile uint32_t count = 0; //------------------------------------------------------------------ void setup() { ksd.begin(mac, ip, port); // start KSduino // ksd.begin(mac, ip, dns, gateway, port); // ksd.begin(mac, ip, dns, gateway, subnet, port); // Setup ChibiOS chBegin(mainThread); } //------------------------------------------------------------------------------ // thread 1 - this task send KSduino parameters // 160 byte stack for KSduino task, beyond task switch and interrupt needs static WORKING_AREA (waThread1, 160); static msg_t Thread1(void *arg) { #define WAIT_FOR_ANSWER 2 for (;;) { // Create D1 test value int d1_generator = random(1,1000); byte d1_value = d1_generator < 10 ? 1 : 0; // will high ones per second // Send values to KSduino if something changed if (d1.check (d1_value) || ds18b20_1.getTemp () && f1.check(ds18b20_1.celsius)) { chSemWait(&sem); // only one KSduino task should run at this moment ksd.beginPacket (); d1.addParameter (d1_value); f1.addParameter( ds18b20_1.celsius); ksd.endPacket (WAIT_FOR_ANSWER); chSemSignal(&sem); } chThdSleepMilliseconds(10); } return 0; } //------------------------------------------------------------------------------ // thread 2 - calqulate DS18b20 thermometer every second // 64 byte stack beyond task switch and interrupt needs static WORKING_AREA (waThread2, 64); static msg_t Thread2(void *arg) { for (;;) { ds18b20_1.readTermometer(); chThdSleepMilliseconds(1000); } return 0; } //------------------------------------------------------------------------------ // thread 3 - KSduino update // 160 byte stack for KSduino task, beyond task switch and interrupt needs static WORKING_AREA(waThread3, 160); static msg_t Thread3 (void *arg) { for (;;) { chSemWait(&sem); // only one KSduino task should run at this moment ksd.update (); chSemSignal(&sem); chThdSleepMilliseconds(5); } return 0; } //------------------------------------------------------------------------------ void mainThread() { // initialize semaphore chSemInit(&sem, 1); // start blink thread chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL); // start print thread chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 1, Thread2, NULL); // start ksduino send thread chThdCreateStatic(waThread3, sizeof(waThread3), NORMALPRIO + 1, Thread3, NULL); // Main thread (iadle thread) loop for (;;) { // must insure increment is atomic // in case of context switch for print noInterrupts(); count++; interrupts(); } } //------------------------------------------------------------------ void loop() { // not used }

EJEMPLO 2:

// scope test to determine ChibiOS context switch time
#include <ChibiOS.h>

const uint8_t LED_PIN = 3;
// semaphore to trigger thread2
Semaphore sem;
//------------------------------------------------------------------------------
//Proceso de Led Apagado 1.
static WORKING_AREA(waThread1, 64);

static msg_t Thread1(void *arg) {
  while (TRUE) {
    chSemWait(&sem);
    digitalWrite(LED_PIN, LOW);
  }
  return 0;
}
//------------------------------------------------------------------------------
// thread 2 - cause context switch to thread 1 with semaphore
static WORKING_AREA(waThread2, 64);

static msg_t Thread2(void *arg) {
  while (TRUE) {
    // toggle LED to get time for digitalWrite
    digitalWrite(LED_PIN, HIGH);    
    digitalWrite(LED_PIN, LOW);
    digitalWrite(LED_PIN, HIGH);
    // signal to switch to thread1 to get context switch time
    chSemSignal(&sem);
    // wait 10 ms
    chThdSleepMilliseconds(10);
  }
  return 0;
}
//------------------------------------------------------------------------------
void setup() {
  pinMode(LED_PIN, OUTPUT);
  halInit();
  chSysInit();
  chSemInit(&sem, 0);
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 2, Thread1, NULL);
  chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 1, Thread2, NULL);  
  while(TRUE){}
}
void loop() {
  // never called
}