Introduzione al Micro Framework.NET

Di Raffaele Rialdi - Microsoft MVP

In questa pagina

Introduzione Introduzione
In cosa consiste il Micro Framework In cosa consiste il Micro Framework
Il cuore del sistema: la HAL e la PAL Il cuore del sistema: la HAL e la PAL
Il Common Language Runtime del Micro Framework Il Common Language Runtime del Micro Framework
La gestione della memoria La gestione della memoria
I Managed Drivers I Managed Drivers
La Base Class Library (BCL) La Base Class Library (BCL)
Conclusione Conclusione

Introduzione

Il software mi ha sempre affascinato perché senza ausilio di materia permette di creare infinite nuove applicazioni plasmate a proprio piacimento. Allo stesso tempo la sua impalpabilità è il suo più grosso limite e fonte di frustrazione che spesso condivido con amici che, come il sottoscritto, hanno fatto della tastiera il proprio mestiere.

Per chi ha vissuto l’inizio dell’epoca del PC come è capitato a me, l’hardware è un po’ meno misterioso e soprattutto a quel tempo una “out” in assembler diretta alla scheda video o sullo speaker dava un immediato senso di appagamento. Negli anni non ho abbandonato la voglia di contatto con l’hardware e più volte, per diletto e per mestiere, ho avuto l’occasione di scrivere del firmware per piccoli microcontrollori a 8 bit dove ad ogni istruzione il risultato è ben tangibile sull’oscilloscopio o su un display LCD.

Chi conosce entrambi i mondi non può non osservare un approccio radicalmente differente. Se in un PC il software si avvantaggia sempre di librerie e servizi come fossero i mattoni di un edificio (Framework.Net e Windows API sono un esempio), altrettanto non vale per le piccole piattaforme hardware dove quasi sempre si scrive in assembler, qualche volta in linguaggio C, ed il riuso del software è affidato il più delle volte ad un copia/incolla.

Il firmware diventa così un enorme monolite che ha il suo più grande difetto nel costringere lo sviluppatore a spendere tempo per i dettagli tecnologici invece di sviluppare funzionalità più sofisticate.

Sul mercato ci sono molte soluzioni che permettono uno sviluppo più comodo di quello a cui io, come altri sviluppatori, siamo abituati. Sono soluzioni che vanno da semplici compilatori in C o Basic a più sofisticati IDE grafici con tanto di debugger integrati e sistemi che, grazie ad hardware dedicato, permettono una emulazione della CPU nella scheda di produzione con tanto di backtrace, cioè vedere in debugging ciò che è successo andando indietro nel tempo. Io stesso ho un emulatore hardware con backtrace e confesso di usarlo molto di rado perché una volta che si ha familiarità sulla piattaforma hardware la sua utilità è molto relativa. Sento molto di più invece il problema della pulizia e della riusabilità del codice.

Il mondo embedded è costellato da una moltitudine di soluzioni. Microsoft, già da molto tempo nel settore, ha diverse pedine schierate. Windows XP Embedded è una versione modulare di Windows XP che lo sviluppatore può assemblare scegliendo solo i moduli e servizi indispensabili per ottenere maggiore velocità di boot ed un basso profilo di memoria. Volendo una piattaforma ancora più leggera si passa a Windows CE che, fra i più grossi pregi, ha quello di essere un sistema operativo real time con una ricca API Win32 che ricalca quasi completamente quella del fratello maggiore e di permettere, in certe configurazioni, l’installazione del Compact Framework con tutti i benefici che derivano dal codice managed.

Tra i piccoli microcontrollori a 8 bit con 1K RAM, per le cui risorse non esiste obiettivamente alternativa all’uso dell’assembler, e le mini mainboard con almeno 600K RAM e Windows CE già consolidate nel mercato embedded, esiste una fascia di mercato in cui Microsoft è voluta entrare con una proposta tecnologica realmente innovativa: parlo ovviamente del Micro Framework.

 

In cosa consiste il Micro Framework

Il Micro Framework è un’infrastruttura per piccoli microprocessori a basso costo e con poche risorse, anche privi di MMU (Memory Management Unit), i cui vantaggi principali consistono nel fornire una ricca API indipendente dall’hardware, un’efficiente controllo sul consumo di energia, un runtime che gestisce il boot e garantisce robustezza al codice, un ricco ambiente di sviluppo quale è Visual Studio che include le funzionalità di debugging e l’emulazione del device oltre, naturalmente, a molti altri vantaggi intrinseci del Framework.NET.

L’approfondimento del CLR e del Framework.NET va oltre lo scopo di questo articolo, ma ci sono una moltitudine di risorse in Internet che possono facilmente dare l’idea dei benefici in termini di produttività che questa infrastruttura offre allo sviluppatore. Nonostante questo, vale la pena ricordarne alcune caratteristiche che risultano particolarmente interessanti per il Micro Framework.

Per esempio, il type system del Framework permette di lavorare comodamente con tipi di dati da 8, 16, 32, 64 bit, float e double nonché stringhe, conservate internamente in formato UTF-8 per contenerne le dimensioni, classi, collection, array (limitati ad una sola dimensione), eventi e delegate, e blob binari gestiti con l’ausilio di una versione speciale di Resource Manager.

L’uso del Resource Manager risulta particolarmente comodo per recuperare Bitmap, tracce audio, stringhe localizzate in lingue diverse o le font da usare nel display grafico. Inoltre Visual Studio genera per noi il codice necessario per accedere in modo strongly-typed alle risorse, evitando brutte sorprese al runtime.

Un’altra caratteristica peculiare del Framework è la serializzazione che nella versione del Micro Framework permette di persistere in forma binaria particolarmente compatta un oggetto in memoria. La serializzazione viene eseguita grazie ad una versione ridotta di Reflection, una tecnologia che permette di ottenere informazioni sui metadati per ciascun oggetto in memoria, dalle classi ai metodi, dalle proprietà ai campi.

Per ridurre al massimo la dimensione dell’oggetto serializzato lo sviluppatore può dare preziosi suggerimenti al Micro Framework sotto forma di attributi che sono largamente usati dai linguaggi managed. Per esempio, è possibile indicare di compattare bit a bit delle informazioni riducendo drasticamente la lunghezza della serializzazione.

La serializzazione gioca un ruolo centrale perché consente ad un oggetto di essere persistito durante un reboot , trasmesso su una periferica come la porta seriale o, ancora, salvato su memoria E2PROM o Flash.

// Valvola è una classe utente
Valvola v1 = new Valvola(1);
// Serializzo lo stato della valvola
byte[] Blob = Reflection.Serialize(v1, typeof(Valvola));
// Trasmetto i dati della valvola via Seriale
port.Write(Blob, 0, Blob.Length);
// altre operazioni ...
// Deserializzo il nuovo stato della valvola
Valvola v = Reflection.Deserialize(Blob, typeof(Valvola)) as Valvola;

Per meglio comprendere come si incastrino tutti i tasselli di hardware, funzionalità del framework e applicazioni managed, guardiamo l’architettura del Micro Framework.

*

Per quanto concerne l’hardware, primo elemento dell’architettura, al suo esordio il Micro Framework gestisce la famiglia di microprocessori ARM7 e ARM9 a 32bit per un intervallo di velocità tra i 27 e i 100MHz con un minimo di 256K RAM e 512K Flash per la ROM. Questi sono numeri derivanti evidentemente dalla gioventù dell’infrastruttura e nonostante si parli di poter ottenere un footprint di memoria inferiore ed usare anche processori a 16 bit, già ora le caratteristiche sono molto appetibili per una vastissima gamma di progetti.

 

Il cuore del sistema: la HAL e la PAL

In un sistema embedded di piccole dimensioni la presenza di un sistema operativo potrebbe avere un impatto troppo importante sulle risorse. Per questo motivo il Micro Framework può farne a meno e, al contrario dei suoi fratelli maggiori (Compact Framework e Framework.NET) è in grado di prendere direttamente il controllo dell’hardware.

Il componente al livello più basso è la HAL (Hardware Abstraction Layer), è scritta in assembler nativo, e si occupa di gestire le operazioni più critiche con il microprocessore, come la gestione dell’I/O, gli IRQ e i timer nativi. È sempre la HAL a gestire anche la fase di boot che in un microcontrollore è critica perché le motivazioni del reboot possono essere di differente natura e quindi le inizializzazioni dovranno necessariamente essere contestuali alla motivazione del boot.

La HAL collabora con i driver nativi che permettono di astrarre le funzionalità delle periferiche hardware. A seconda del produttore hardware il Micro Framework porta con se una serie di driver come ad esempio la seriale asincrona e sincrona (SPI), display LCD, monitoraggio della batteria, porte I/O di varia natura, output PWM (usato ad esempio per pilotare motori passo-passo), memorie Flash ed E2prom e I2C™ bus. I driver nativi vengono tipicamente scritti dagli OEM che forniscono la versione di Micro Framework specifica per la loro architettura.

Sopra la HAL siede un altro componente fondamentale chiamato PAL (Platform Abstraction Layer) che insieme alla HAL provvede alla totale astrazione della piattaforma hardware. Il compito della PAL è di fornire l’interfacciamento per implementare nel modo più efficiente caratteristiche come le chiamate asincrone e i timer ad alto livello, e gestire le zone di memoria in cui sono mappate le funzionalità del microprocessore che possono cambiare a seconda dei modelli.

Al di fuori di questi componenti (HAL, PAL e driver a basso livello) il resto del Micro Framework è indipendente dall’hardware. Il porting del Micro Framework su un hardware differente si riduce perciò all’implementazione di entrambi i componenti o della sola PAL.

Un altro compito importante della HAL è quello di provvedere alla modalità di esecuzione del codice che avviene su singolo thread o tramite ISR (Interrupt Service Routine). In sostanza Micro Framework rinuncia allo Scheduler dei thread in luogo di un multitasking di tipo cooperativo, la cui scelta verrà analizzata più avanti, dove ciascun thread riceve una finestra temporale massima di 20ms.

Il Micro Framework può quindi fungere da pseudo sistema operativo ma può anche avvantaggiarsi di un vero sistema operativo qualora fosse presente. Questo è il caso, ad esempio, dell’emulatore che gira tutte le chiamate alle Win32 API del processo Windows che sta emulando il sistema embedded.

Un sistema operativo normalmente fa molto di più che da semplice ponte con l’hardware. Esso provvede infatti a funzionalità quali il processo, i thread e lo scheduler. Il Micro Framework non implementa queste funzionalità a livello kernel nella HAL e PAL ma astrae la loro logica ad un livello più alto, all’interno del Common Language Runtime.

 

Il Common Language Runtime del Micro Framework

Come nelle altre versioni del Framework .NET, il Common Language Runtime (CLR) ha il compito di fornire una infrastruttura base utilizzabile a prescindere dal linguaggio utilizzato. Nonostante nella prima versione del Micro Framework il linguaggio supportato sia solo C#, il Micro Framework è una implementazione totalmente rispettosa delle specifiche ECMA CLS (Common Language Specifications) ed infatti il risultato della compilazione di un applicativo è un assembly contenente codice IL (Intermediate Language).

Lo scopo del CLR è, fondamentalmente, di caricare gli assembly ed eseguire codice managed beneficiando quindi di protezione delle risorse, gestione della memoria, eccezioni in luogo degli errori, ed isolamento del codice in application domain.

L’esecuzione è un punto di grande attenzione visto che il codice generato dal compilatore è Intermediate Language. Nelle altre implementazioni di Framework.NET, alla prima esecuzione di una funzione, il JIT (Just in Time Compiler) ha il compito di compilare al volo il codice IL in nativo. Questa strategia è stata riconsiderata per il Micro Framework per ridurre il consumo di memoria dovuto alla doppia versione del codice (IL e nativo) ed al codice del JIT stesso.

L’alternativa scelta è stata quella di interpretare il codice IL. L’aspetto negativo di questa scelta è ovvio: minore velocità di esecuzione. È importate sottolineare che il codice interpretato non è C# ma IL. L’Intermediate Language è infatti un pseudo-assembler le cui specifiche CLI (Common Language Infrastructure) sono standard ECMA. Le istruzioni IL sono state create proprio allo scopo di essere tradotte nel modo più veloce possibile nei vari dialetti di assembler delle CPU fisiche. Questo processo è certamente oneroso ma, essendo l’IL in forma binaria, non viene eseguito parsing di sorgenti e risulta perciò molto più performante rispetto ad altri famosi linguaggi interpretati come le vecchie versioni di Basic o l’attuale Javascript.

Questa scelta ha portato anche a un numero di vantaggi quale la gestione dello scheduling dei thread con una granularità che può arrivare fino alla singola istruzione e la possibilità di assegnare un tempo massimo di esecuzione ad una porzione di codice, oltre il quale l’esecuzione viene interrotta con un’eccezione. Perciò, nonostante il multithreading sia di tipo cooperativo, al thread può essere assegnata una priorità ed anche essere interrotto, superando di fatto i maggiori svantaggi del multitasking cooperativo.

Micro Framework pone grande attenzione alle prestazioni e prevede un meccanismo di Interop che permette di scrivere una funzione direttamente in modalità nativa per quelle parti di applicazioni che dovessero essere particolarmente critiche per performance. Nella versione attuale di Micro Framework la chiamata al codice nativo viene fatta creando il driver di una periferica fittizia che potrà essere utilizzata dal codice C# ad alto livello. Sono stato recentemente informato dal team di sviluppo del Micro Framework che sono in fase di valutazione delle migliorie per il meccanismo di Interop per la prossima versione.

Ovviamente, pur risolvendo situazioni diversamente non affrontabili, l’uso di Interop complica notevolmente la portabilità del codice e può compromettere l’efficienza del CLR, motivo per cui questa soluzione deve essere utilizzata con parsimonia ed attenzione.

 

La gestione della memoria

Uno dei pregi delle versioni del Framework.NET è il Garbage Collector che permette di mappare la memoria a livello di oggetto. Non appena un oggetto diventa orfano dei suoi reference diretti e indiretti, il Garbage Collector è libero di riusare quella zona di memoria. Il compito del Garbage Collector è duplice: da una parte si occupa di rendere disponibile la memoria inutilizzata, dall’altra di spostare i blocchi di memoria quando si libera lo spazio in mezzo; questa procedura viene chiamata garbage collection e avviene sospendendo temporaneamente l’esecuzione dei thread e riaggiornando i reference ai blocchi di memoria spostati.

Il vantaggio principale della garbage collection è quello di evitare la frammentazione della memoria, fenomeno derivante dalla continua allocazione e disallocazione di blocchi di memoria di diversa misura che, alla lunga, porta a non avere più la disponibilità di blocchi contigui di memoria sufficientemente capienti. Quando si parla di CLR e della garbage collection, in molti si spaventano per il tempo di elaborazione necessario a svolgere questo processo. Nella versione del Micro Framework è stata implementata una versione molto leggera con un algoritmo di Mark+Sweep non incrementale in luogo del tradizionale sistema generazionale.

Il CLR prevede un meccanismo chiamato Weak Reference che permette al Garbage Collector di reclamare memoria sotto condizioni di Memory Pressure, ossia quando si arriva vicini all’esaurimento di memoria. In aggiunta a questo prezioso meccanismo, il Micro Framework aggiunge l’Extended Weak Reference che permette agli oggetti di essere persistiti e recuperati in memoria durante un reboot. Se consideriamo che il boot è molto sfruttato negli ambienti embedded, anche per assicurare la stabilità sul lungo termine, questo meccanismo si rivela assolutamente molto prezioso.

 

I Managed Drivers

È molto probabile che l’hardware sia utilizzato in modo differente da progetto a progetto sia per la scelta delle periferiche utilizzate che per la configurazione del processore. Per esempio, i pin della CPU possono assumere diverse configurazioni ed essere utilizzati in input, in output, in three-state, sollevare un interrupt o, ancora, diventare pin della SPI presente nella CPU.

Dal punto di vista applicativo un bottone rappresenta un dispositivo di input così come lo stream può astrarre una comunicazione di tipo RS485. Per mappare le funzionalità logiche all’hardware sottostante esistono i Managed Driver, che consistono in classi scritte in C# dallo sviluppatore.

Nel codice del Managed Driver vengono associate le funzionalità hardware come l’interrupt su un pin alle classi del Micro Framework come il bottone.

 

La Base Class Library (BCL)

Chi è abitutato alla Base Class Library del Framework .NET saprà che è estremamente ampia e ricca di funzionalità; non è quindi una sorpresa che il numero di classi disponibili nel Micro Framework sia molto ridotto ma considerate le risorse hardware su cui deve girare, è comunque considerevole.

Grazie alla struttura object oriented di questa piattaforma ed alla sua architettura, lo sviluppatore può estendere facilmente le classi esistenti o crearne di nuove che potranno essere facilmente riutilizzate anche su piattaforme hardware differenti.

Possiamo suddividere la class library in due blocchi: le classi specifiche del Micro Framework che fanno parte dei namespace Microsoft.SPOT e quelle compatibili con le altre versioni di Framework che si trovano nei namespace System.

*

Nel namespace Microsoft.SPOT.Hardware ci sono, ad esempio, le classi che astraggono e configurano le porte di I/O. Personalmente sono rimasto colpito dalla pulizia del codice nel configurare, ad esempio, una porta in ingresso con il filtro anti-glitch e la resistenza di pull-up:

InputPort port = new InputPort(Cpu.Pin.GPIO_Pin3, true, Port.ResistorMode.PullUp);

Sempre in quel namespace troviamo la classe Battery che ci dice lo stato di carica della batteria, il suo voltaggio e la temperatura. Oppure la classe Utility che indica quanto tempo è passato dall’ultimo boot.

Nel namespace Microsoft.SPOT.Cryptography ci sono le classi per la crittografia con algoritmi RSA e XTEA. Mentre in Microsoft.SPOT troviamo classi come Math per i calcoli matematici, Logging per gestire un log del sistema, Bitmap e Font per le risorse grafiche e molto altro ancora.

Di dimensione maggiore sono le numerose classi dei namespace nella gerarchia Microsoft.SPOT.Presentation che costituiscono un sottoinsieme molto ridotto di Windows Presentation Foundation, naturalmente senza il supporto del linguaggio dichiarativo XAML.

Questa versione ridotta di WPF è comunque molto ricca e consente di disegnare grazie al supporto di primitive grafiche come rettangoli, ellissi, linee e poligoni, immagini bmp e jpeg. C’è anche un ampio supporto per il testo, il suo allineamento, le font, la formattazione con alcune limitazioni sull’uso dei colori e il calcolo dello spazio occupato sullo schermo. Ed ancora meritano di essere citati una buona scelta di controlli come TextFlow (analogo della RichTextBox), Border, Canvas (un pannello), Image, ListBox, e StackPanel che permette un allineamento a stack per evitare di dover specificare le coordinate in cui posizionare i controlli. Grazie alle classi basi è inoltre possibile costruire controlli personalizzati particolarmente attrattivi nel mondo embedded, specie se combinati, ad esempio, ad un touch-screen.

Questo esempio mostra quanto sia semplice creare un nuovo controllo che rappresenta sullo schermo LCD grafico un LED che apparirà di colore grigio da spento e rosso da acceso.

public class Led : Control
{
bool _State;
Bitmap _LedOn, _LedOff;
public bool State
{
get { return _State; }
set { _State = value; Invalidate(); }
}

public Led()
{
this.Width = 20;
this.Height = 20;
_LedOn = Resources.GetBitmap(Resources.BitmapResources.LedOn);
_LedOff = Resources.GetBitmap(Resources.BitmapResources.LedOff);
}

public override void OnRender(DrawingContext dc)
{
if(State)
dc.DrawImage(_LedOn, 0, 0);
else
dc.DrawImage(_LedOff, 0, 0);
}
}

Per quanto concerne il sottoinsieme delle classi della Base Class Library del Framework, spicca per importanza la classe Socket di System.Net che apre il mondo del networking wired e wireless.

Il Patching

Aggiornare un device riprogrammando la memoria Flash è certamente un’operazione veloce che da tempo è possibile fare senza dover staccare fisicamente il processore dalla scheda (programmazione in-place). Ci sono però situazioni nelle quali questa opzione non è facilmente adottabile.

Il meccanismo di Patching consente al programmatore di aggiornare il codice da un sistema remoto. Poiché le modifiche applicate in questo modo avvengono durante l’operatività normale del microprocessore, vi sono delle limitazioni sulle modifiche che alterano la struttura dei metadati. Per esempio, non è possibile effettuare operazioni quali la cancellazione o l’aggiunta di classi, metodi, campi e attributi.

Al di là della correzione degli errori, il Patching è molto comodo per alterare dei parametri di sistema o variare l’implementazione di un metodo. Se penso ad un processo industriale dove viene impiegato un device dalle risorse limitate, è potrebbe sfruttare questo sistema per cambiare la modalità di lavoro di un processo indicando l’uso di materiali differenti dalla precedente lavorazione.

L’ambiente di sviluppo

Se mi immedesimo nella figura di sviluppatore di firmware di microcontrollori e penso di avere a disposizione un ambiente come quello di Visual Studio, questo diventa una delle motivazioni più forti ad utilizzare il Micro Framework.

Creare un nuovo progetto, scrivere la classe che mappa i pin sui pulsanti, disegnare in una form l’aspetto dell’interfaccia grafica usando primitive ad alto livello, testare sotto debugger l’applicazione nell’emulatore o sul device ed infine fare installare il firmware collegando la scheda alla porta USB: questo non è un sogno ma è il Micro Framework oggi.

*

 

Conclusione

Considerato che il prezzo di mercato di questa fascia di microprocessori comincia ad essere competitivo in confronto degli 8 e 16bit, il limite dei campi di applicazione del Micro Framework sono solo nella nostra fantasia. È facile pensare che uno tra i primi dispositivi che vedremo circolare sul mercato sarà SideShow per Windows Vista che Microsoft stessa indica come il più naturale candidato.

Pur essendo una tecnologia giovane, il cui rilascio pubblico è appena avvenuto, il Micro Framework è già alla versione 2.0. La tecnologia che lo ha preceduto era cononsciuta con il nome di SPOT (Smart Personal Object Technology), usata per qualche anno in vari dispositivi come orologi, set top boxes e robotica sperimentale. L’esperienza maturata in quesi settori rende il Micro Framework certamente utilizzabile in progetti reali. Naturalmente molto dipenderà dalla disponibilità di altro hardware rispetto a quello già disponibile oggi. Credo che nei prossimi mesi assisteremo a continue presentazioni da parte di molti produttori OEM i cui imminenti annunci sono già visibili su vari siti web.