leOS - un semplice OS per schedulare piccoli task

E' vero, ci sono tanti RTOS in circolazione che funzionano molto meglio del leOS... ma a che prezzo? Avete dato un'occhiata alla complessità del codice necessario per fare un semplice blink, ad esempio con freeRTOS?

Posto il codice di UNOBlink:

////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////    main.c
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////

#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include <avr/io.h>

/* Scheduler include files. */
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <semphr.h>

/* serial interface include file. */
#include <lib_serial.h>

/* LCD (Freetronics 16x2) interface include file. */
#include <hd44780.h>


/*-----------------------------------------------------------*/
/* Create a handle for the serial port. */
xComPortHandle xSerialPort;

static void TaskBlinkRedLED(void *pvParameters); // Main Arduino Mega 2560, Freetronics EtherMega (Red) LED Blink

static void TaskBlinkGreenLED(void *pvParameters); // Main Arduino Uno 328p (Green) LED Blink
/*-----------------------------------------------------------*/

/* Main program loop */
int16_t main(void) __attribute__((OS_main));

int16_t main(void)
{

    // turn on the serial port for debugging or for other USART reasons.
	xSerialPort = xSerialPortInitMinimal( 115200, portSERIAL_BUFFER, portSERIAL_BUFFER); //  serial port: WantedBaud, TxQueueLength, RxQueueLength (8n1)

	avrSerialPrint_P(PSTR("\r\n\n\nHello World!\r\n")); // Ok, so we're alive...

    xTaskCreate(
		TaskBlinkRedLED
		,  (const signed portCHAR *)"RedLED" // Main Arduino Mega 2560, Freetronics EtherMega (Red) LED Blink
		,  256				// Tested 9 free @ 208
		,  NULL
		,  3
		,  NULL ); // */

    xTaskCreate(
		TaskBlinkGreenLED
		,  (const signed portCHAR *)"GreenLED" // Main Arduino Uno 328p (Green) LED Blink
		,  256				// Tested 9 free @ 208
		,  NULL
		,  3
		,  NULL ); // */


	avrSerialPrintf_P(PSTR("\r\n\nFree Heap Size: %u\r\n"),xPortGetFreeHeapSize() ); // needs heap_1 or heap_2 for this function to succeed.

	vTaskStartScheduler();

	avrSerialPrint_P(PSTR("\r\n\n\nGoodbye... no space for idle task!\r\n")); // Doh, so we're dead...

}

/*-----------------------------------------------------------*/


static void TaskBlinkRedLED(void *pvParameters) // Main Red LED Flash
{
    (void) pvParameters;;
    portTickType xLastWakeTime;
	/* The xLastWakeTime variable needs to be initialised with the current tick
	count.  Note that this is the only time we access this variable.  From this
	point on xLastWakeTime is managed automatically by the vTaskDelayUntil()
	API function. */
	xLastWakeTime = xTaskGetTickCount();

	int8_t i;
	uint8_t j;

	lcd_Init();

	DDRB |= _BV(DDB7);

    while(1)
    {

    	PORTB |=  _BV(PORTB7);       // main (red IO_B7) LED on. EtherMega LED on
		vTaskDelayUntil( &xLastWakeTime, ( 10 / portTICK_RATE_MS ) );

		lcd_Locate (0, 0);
		lcd_Printf_P(PSTR("HighWater @ %u"), uxTaskGetStackHighWaterMark(NULL));
		lcd_Cursor (1);

#if _USE_FUEL
		lcd_Locate (1, 0);
		lcd_PutFuel (--i, 0);
		if (i < 0) i = 6;
#endif

#if _USE_BAR
		lcd_Locate (1, 2);
		lcd_PutBar (j++, 14, 2);
#endif

		PORTB &= ~_BV(PORTB7);       // main (red IO_B7) LED off. EtherMega LED off
		vTaskDelayUntil( &xLastWakeTime, ( 40 / portTICK_RATE_MS ) );

//		xSerialPrintf_P(PSTR("RedLED HighWater @ %u\r\n"), uxTaskGetStackHighWaterMark(NULL));
    }

}

/*-----------------------------------------------------------*/
static void TaskBlinkGreenLED(void *pvParameters) // Main Green LED Flash
{
    (void) pvParameters;;
    portTickType xLastWakeTime;
	/* The xLastWakeTime variable needs to be initialised with the current tick
	count.  Note that this is the only time we access this variable.  From this
	point on xLastWakeTime is managed automatically by the vTaskDelayUntil()
	API function. */
	xLastWakeTime = xTaskGetTickCount();

	DDRB |= _BV(DDB5);

    while(1)
    {
    	PORTB |=  _BV(PORTB5);       // main (red PB5) LED on. Arduino LED on
		vTaskDelayUntil( &xLastWakeTime, ( 100 / portTICK_RATE_MS ) );

		PORTB &= ~_BV(PORTB5);       // main (red PB5) LED off. Arduino LED off
		vTaskDelayUntil( &xLastWakeTime, ( 400 / portTICK_RATE_MS ) );

		xSerialPrintf_P(PSTR("GreenLED HighWater @ %u\r\n"), uxTaskGetStackHighWaterMark(NULL));
    }
}
/*-----------------------------------------------------------*/

Questo è il codice di leOS_BlinkWithoutMillis:

//include the OS
#include "leOS.h"

leOS myOS; //create a new istance of the class leOS

//variables to control the LEDs
byte led1Status = 0;
const byte LED1 = 13;

//program setup
void setup() {
    myOS.begin(); //initialize the scheduler
    pinMode(LED1, OUTPUT);
    
    //add the tasks at the scheduler
    myOS.addTask(flashLed1, 1000);
}


//main loop
void loop() {
    //empty
}


//flashing task
void flashLed1() {
    led1Status^=1;
    digitalWrite(LED1, led1Status);
}

leOS è limitato ma è semplicissimo da usare.

son d'accordo che non è facilissimo da implementare..anche se per la maggior parte dei casi basta un po' di copia e incolla..

dipende dalle tue necessità: se hai bisogno di avere tempi precisi con funzioni che usano interrupt,puoi o usare leOS + wire non bloccante(xò se tu volessi anche usare la seriale?),o RTOS..
altrimenti usi il looper,è semplice da usare,non ha problemi con interrupt,ma non garantisce tempi brevi..

In tanti anni che programmo ho imparato una cosa, che cioè il risultato ottimale è quello che ha il miglior rapporto prezzo/prestazioni.
Fare un codice semplicistico comporta un costo in termini di impegno di progettazione veramente minimo ma, per contro, hai prestazioni anch'esse minime. L'altra faccia della medaglia è un codice esageratamente ricercato, con un costo di progettazione molto elevato e prestazioni anch'esse al top. Alla fine, il risultato finale è identico: ognuno dei due programmi eseguirà il compito assegnato.

Vale la pena usare freeRTOS o FemtoOS per modificare un paio di porte in background? Non credo proprio. Vale la pena usare il leOS per gestire un multitasking preemptivo? Non credo proprio. Come dici giustamente tu, ognuno deve scegliere gli strumenti pensando al miglior rapporto per il proprio progetto.

ragazzi scusate la mia assenza, dopo diversi test ecco i primi risultati:

ho utilizzato le librerie:

  1. leos
  2. asincI2Ctest2_asinc (non bloccante)

devo dire che questa accoppiata funziona egregiamente.

La semplicità della realizzazzione dei task di leo, e l'utilizzo asincrono della wire di lesto
possono dare nuovi spunti a chi vuole realizzare sitemi anche complessi.

In questi giorni ho avuto modo di studiare e testare anche i sistemi RTOS (chibios e freertos), questi ultimi nonostante le loro caratteristiche importanti che si basano sui sistemi operativi real time, non risultano essere semplici nell'utilizzo, inoltre come accennato anche da leo ad esempio per scrivere un banale blick_led bisogna scrivere ben 4 volte il numero di righi di codice rispetto all'esempio di leos.

Certo ancora manca da implementare semafori, code ed altre logiche... ecc ecc
A mio avviso già questo è un passo avanti
ed il lavoro di leos non ha nulla da invidiare ai sistemi RTOS

questo è il mio umile giudizio

dasty:
Certo ancora manca da implementare semafori, code ed altre logiche... ecc ecc
A mio avviso già questo è un passo avanti
ed il lavoro di leos non ha nulla da invidiare ai sistemi RTOS

questo è il mio umile giudizio

:* Grasssie

PS:
sto usando il leOS su un Attiny85, è un progettino a cui sto lavorando da 2 giorni a questa parte (ecco perché sono un po' latitante, oltre agli impegni di lavoro): 3 task che fermo e riavvio, con intervalli differenti, abbinati alla gestione di un segnale PWM, a 2 letture analogiche e sleep/risveglio. E funziona tutto perfettamente. Siccome i 3 task dovevano girare "intrecciati" tra di loro e con il resto del codice, l'uso del leOS mi ha semplificato non poco la stesura del codice.

Certo ancora manca da implementare semafori, code ed altre logiche... ecc ecc

Leo, correggimi se sbaglio ma penso che leOS difficilmente otterrà questo tipo di servizi, perché lo porterebbero alla fine ad essere molto simile ad un "vero" rtos, togliendogli la semplicità e la "snellezza" che lo contraddistinguono ora.

Per essere un po' più costruttivo, dico che la comunicazione inter-thread è facilmente ottenibile con l'uso di variabili globali e la keyword "volatile". Non che ciò sostituisca mutex, code e quant'altro, ma credo che sia sufficiente per l'ambito di utilizzo cui leOS si riferisce.

(disclaimer: finora ho fatto pochi test per confermare la mia precedente affermazione, anche se sono stati tutti positivi).

i semafori non sarebbero molto complessi da fare: quando voglio testarlo o cambiare il valore,disabilito temporaneamente gli interrupt(ovviamente da ripristinare dopo)..al posto di usare il wait che blocca la funzione corrente,fare un wait a cui passare la funzione contenente la sezione critica..

tuxduino:

Certo ancora manca da implementare semafori, code ed altre logiche... ecc ecc

Leo, correggimi se sbaglio ma penso che leOS difficilmente otterrà questo tipo di servizi, perché lo porterebbero alla fine ad essere molto simile ad un "vero" rtos, togliendogli la semplicità e la "snellezza" che lo contraddistinguono ora.

E' esatto. Non è nei progetti del leOS portarlo ai livelli di un vero RTOS. Sia per mancanza di conoscenze da parte mia in materia sia anche perché di RTOS pronti e funzionanti ce ne sono diversi e sarebbe reinventare l'acqua calda. Il leOS è bello perché è semplice da usare e da implementare, supporta un sacco di microcontrollori e permette di sgravare tanti compiti noiosi la cui implementazione nel loop() principale crea non pochi grattacapi.

Senza dimenticare il fatto che in caso di necessità il sorgente è del tutto affrontabile (rispetto ad un RTOS completo).

Ciao Leo72,
Volevo chiederti una cosa il tuo leOS per funzionare si basa su, un timer interno ma se tu ti basassi su un timer hw esterno di precisione non risolveresti tutti i problemi di incompatibilita con altre liberie?

come vorresti farlo(lo so di non essere leo :slight_smile: ) programmi un timer esterno per cambiarti lo stato di un pin dell'arduino ogni millisecondo(o ogni 10..),e intercetti l'interrupt del cambio stato del pin?

cmq,la comodità è che non richiede componenti esterne..

Madwriter:
Ciao Leo72,
Volevo chiederti una cosa il tuo leOS per funzionare si basa su, un timer interno ma se tu ti basassi su un timer hw esterno di precisione non risolveresti tutti i problemi di incompatibilita con altre liberie?

Sicuramente si può fare, e ci sono altri sistemi, uno è quello detto da m_ri. Però così fai tutto senza componenti esterni. Inoltre considera che il leOS gira anche sull'Attiny85: se su quel micro, che ha solo 5 pin utili, gliene levi 1, lo depauperi del 20% delle sue capacità di I/O :wink:
Inoltre il leOS risulta incompatibile con alcune librerie per via dell'uso del timer 2, ma usando un timer esterno potrebbe risultare incompatibile con tanti shield, visto che dovresti togliere un pin a quelli usabili. Insomma, è un cane che si morde la coda :stuck_out_tongue_closed_eyes:

m_ri:
come vorresti farlo(lo so di non essere leo :slight_smile: ) programmi un timer esterno per cambiarti lo stato di un pin dell'arduino ogni millisecondo(o ogni 10..),e intercetti l'interrupt del cambio stato del pin?

cmq,la comodità è che non richiede componenti esterne..

ma per una componente esterna di 20 cent puoi tranquillamente usare i timer interni per le librerie più standard,si pensavo ad una cosa del genere ma non so se è realizzabile.

Vi aggiorno sugli... aggiornamenti del leOS :sweat_smile:

Con la versione 1.0.0 ho introdotto un nuovo metodo per sapere lo stato di un task.

myOS.taskIsRunning(nomeFunzione)

Esso restituisce PAUSED, SCHEDULED o ONETIME. La cosa è utile se ad esempio un task modifica lo stato di un altro task, indipendentemente dal codice dell'utente.

Con la versione 1.0.1 ho invece sistemato il problema dell'overflow del contatore a 32 bit. Ciò significa che dopo 49,7 giorni non si verificano situazioni strane. Si può così evitare di utilizzare i contatori a 64 bit, che comportano un aggravio di centinaia di byte di flash occupata in più, cosa utile soprattutto sui piccoli micro con poca memoria.

L'ultima versione della lib è scaricabile come sempre dal mio sito (link in calce alla mia firma).

Ciao, se posso permettermi esprimo un dubbio sul nome della funzione.

Se tale funzione mi servisse per sapere se un task è "running" o no, la chiamaerei isTaskRunning(). Ma poiché non ritorna un booleano ma una tra diverse possibili costanti simboliche, secondo me è meglio chiamarla getTaskState() (o status).

my 2 cents :slight_smile:

Uhm... detta così ha una sua logica :wink:

EDIT:
la versione 1.0.1a, da poco online, ha il metodo rinominato in getTaskStatus.

per la serie,facciamo i rompic*****ni, una teorica funzione taskIsRunning dovrebbe (quasi) sempre restituire false,dato che non c'è esecuzione parallela.. :grin:

(tanto leo non sai dove abito..)

m_ri:
per la serie,facciamo i rompic*****ni, una teorica funzione taskIsRunning dovrebbe (quasi) sempre restituire false,dato che non c'è esecuzione parallela.. :grin:

Bast.... :stuck_out_tongue_closed_eyes: :stuck_out_tongue_closed_eyes:

(tanto leo non sai dove abito..)

Sei fortunato perché non me lo ricordo più :stuck_out_tongue:

m_ri:
per la serie,facciamo i rompic*****ni, una teorica funzione taskIsRunning dovrebbe (quasi) sempre restituire false,dato che non c'è esecuzione parallela.. :grin:

(tanto leo non sai dove abito..)

Ti prendo sul serio: un task è running quando il suo stato ha quel valore. Stai confondendo lo stato di un task con il fatto che in dato istante stia occupando la CPU.

tuxduino:

m_ri:
per la serie,facciamo i rompic*****ni, una teorica funzione taskIsRunning dovrebbe (quasi) sempre restituire false,dato che non c'è esecuzione parallela.. :grin:

(tanto leo non sai dove abito..)

Ti prendo sul serio: un task è running quando il suo stato ha quel valore. Stai confondendo lo stato di un task con il fatto che in dato istante stia occupando la CPU.

ti contraddisco: quando si parla di stati di schedulatori,tra i tanti stati c'è ready(pronto per l'esecuzione,ma NON in esecuzione) e running(in esecuzione in quel preciso momento)..sono due stati diversi..i pcb(process control block,o i tcb) vengono poi spostati da uno all'altro a seconda delle politiche usate..

prima di contraddire leo,penso 2 volte a quello che scrivo.. :wink:
altrimenti qualcuno mi sputt.....bbe per l'eternità,ti pare? ]:smiley: