Arduino la sfida embedded per il Software Architect
L'Arduino Uno, è un micro controller tutto italiano, sviluppato in open source e che sta avendo un grande successo.
Una parte del suo successo è dovuto al fatto che costa meno di trenta euro, e per essere così economico la versione base ha un chip ATmega328 con soltanto 2 KB di RAM, 1KB di EEPROM e 32KB di memoria flash per il codice. Come implementare comportamenti complessi con risorse così scarse? L'ingegneria del software ci può aiutare?... Vediamolo assieme, in una serie di articoli che avranno come obiettivo l'ingegnerizzazione spinta del sistema di sviluppo Arduino.
Per prima cosa, va considerato che un micro controller è un dispositivo embedded, da cui derivano queste caratteristiche:
- Il dispositivo spesso deve poter lavorare in un ambiente Real Time.
- Il dispositivo deve essere tollerante agli errori software catastrofici. Questo implica che il codice deve essere solido. Il modo migliore per avere un codice solido è averlo semplice
- Arduino normalmente si programma in C++
Analizziamo le conseguenze di queste caratteristiche:
Il dispositivo spesso deve poter lavorare in un ambiente Real Time
La prima conseguenza pratica, è che non è possibile servirsi di un garbage collector: difatti il garbage collector impone pause non predicibili a priori per gestire l'allocazione alla memoria. Questa limiazione è molto forte, ma è rafforzata anche dal fatto che con una memoria molto ridotta l'overhead di un garbage collector sarebbe difficilmente tollerabile (parliamo di 2-8Kb di RAM a seconda del modello di Arduino).
Tolleranza agli errori
A nessuno piacerebbe che il proprio ABS vada in segmentation fault durante una frenata! Questi problemi esistono, e sono stati oggetto di clamorosi ritiri da parte delle aziende costruttrici (la Toyouta Prius ha sofferto di un problema simile mesi fa).
Al'aumento delle condizioni al contorno è impossibile prevedere tutti i casi possibili. Una strutturazione analitica è preferibile. Per esempio il costrutto try..catch del C++ serve proprio per isolare le zone critico e riportare il sistema in uno stato noto in modo più dichiarativo possibile.
Approccio funzionale
Negli ultimi anni ha avuto un revival l'approccio operato da linguaggi funzionali, ma non come il Lisp bensì come Erlang e varianti simili (perlatro noi non ci consideriamo fan del lisp). Le ragioni sono le seguenti:
- I linguaggi funzionali costringono a non avere stato esplicito e solitamente scoraggiano i side effect. Ne consegue che:
- Il codice è più chiaro, e putroppo fin troppo compatto.
- E' possibile implementare un paradigma concorrete più semplicemente, poiché l'approccio "share-nothing" consente di architettare intorno alle funzioni un sistema a servizi state-less, assai facili da parallelizzare
- Il compilatore può ottimizzare di più. In Erlang le variabili possono essere "assegnate" una sola volta, e quindi è possibile unire all'analisi statica delle diramazioni decisionali la "trasformazione" della variabili assegnate in costanti, con conseguente ottimizzazione del codice.
Purtroppo Erlang ha bisogno di assai più risorse del C++, e quindi al momento non esiste una ErlangVM che possa girare sull'Arduino Mega o sull'Arduino Uno. Esiste tuttavia una implementazione sperimentale di Occam per Arduino, che vi suggeriamo di esplorare.
Hands on
Prima di tutto, è indispensabile un approccio test-driven. Per iniziare, potete scaricarvi emulino, un emulatore del chip ATMega che vi consentirà di far girare i programmi in modo emulato.
Può essere un utile strumento per testare algoritmi complessi. Purtroppo al momento non siamo riusciti ad usarla in congiunzione con la libreria ArduinoTestSuite inclusa con l'Arduino IDE
Sul Playground (lo wiki) di Arduino vi è un ottima raccolta di librerie: inizieremo da queste per arricchire e potenziare il nostro codice.
Le librerie di infrastruttura che suggeriamo sono
- #include <Flash.h> Consente di memorizzare nella memoria Flash le variabili, risparmando i preziosi 2Kb di RAM.
- #include <TimedAction.h> Implementa una semplicissima classe per agganciare eventi da eseguire in particolari intervalli di tempo
Poiché implementare un RTOS (sistema operativo real time) è fuori dai nostri scopi, le librerie sopra ci consentono di risparmiare memoria RAM e di organizzare i nostri programmi in modo dichiarativo. Per esempio: [cpp]const int pingSampleSpeedMs=60;
TimedAction heartBeatTimedAction = TimedAction(20,heartBeat); TimedAction pinSensorAction= TimedAction(pingSampleSpeedMs,pingAction);
unsigned long startTime,endTime; void loop() { startTime= millis(); heartBeatTimedAction.check(); pinSensorAction.check(); endTime=millis() - startTime; if(endTime>pingSampleSpeedMs/5){ Serial.println("Slowdown Roundtrip:"); Serial.print(endTime); Serial.println(); // Azione da implementare in caso di rallentamenti } }[/cpp] Il codice sopra dichiara che vi saranno due azioni da eseguire in due istanti diversi del tempo. I controllo però sarà guidato dal main loop, che potrà decidere se saltare alcuni eventi se si verificheranno dei rallentamenti (slowdown). Le azioni saranno implementate da due funzion (non riportate) chiamate heartBeat e ping Questa strutturazione ha ancora dei problemi perché il sistema non è preemptive ma semi-cooperativo: se una funzione va in loop infinito, il sistema si blocca, ma è già un modo molto pulito di strutturare il codice, e ci consente di impilare molte TimedAction isolate tra loro.
Comment by Keny on 2011-03-16 17:34:52
Volevo segnalare l’iniziativa di un sito di elettronica di creare un concorso dove si può vincere la scheda arduino e tanti altri premi it.emcelettronica.com/marzuino-non-solo-arduino-ma-anche-oscilloscopio-digitale-multimetro-e