SVILUPPO DI SOFTWARE PER SISTEMI EMBEDDED

1. Introduzione

Normalmente viene chiamato "embedded software" il software creato su misura per essere eseguito su un particolare sistema embedded, ovvero sistemi "dedicati", in opposizione a quelli general-purpose, che svolgono un'unica funzione ben definita, in relazione alla quale la loro particolare architettura è costruita e dimensionata. Spesso è richiesto un funzionamento in "tempo reale (real time)" (ovvero tale da garantire che una certa cosa verrà fatta entro un certo tempo. Quanto tempo effettivamente non importa, perché "tempo reale" non è sinonimo di veloce, bensì di deterministico, ossia prevedibile e garantibile a priori).

Per soddisfare alle esigenze normalmente molto stringenti dei sistemi embedded (tempo reale, basso costo, semplicità, velocità, basso consumo,...), il software embedded ha un legame intimo con l'hardware del sistema, e la sua realizzazione non può prescindere da una approfondita conoscenza dell' architettura, ad esempio della struttura dei registri e degli interrupt.

Le principali caratteristiche che lo allontanano da quello tradizionale per macchine general-purpose sono:

Per quanto riguarda il requisito di elaborazione in tempo reale vale la pena di precisare che il corretto funzionamento del sistema non deve dipendere soltanto dall'esattezza logica del risultato, ma anche dal momento nel quale il risultato stesso viene prodotto. Una distinzione più sottile, e più controversa, é quella tra tempo reale di tipo "hard" e "soft". In linea di massima i sistemi "hard" richiedono un rispetto rigido dei vincoli di precisione temporale, in quanto mancare una scadenza significherebbe invalidare il funzionamento dell'intero sistema; quelli "soft" si limitano ad un rispetto statistico dei vincoli che, se forzati, portano ad una degradazione dell'applicazione che può però essere tollerata in funzione del suo costo per l'utilizzatore.

2. Pianificazione ad "alto livello"

Quando si affronta lo sviluppo di un nuovo progetto di sistema embedded, occorre innanzi tutto pianificare il lavoro prendendo decisioni "ad alto livello", innanzi tutto quali funzioni realizzare via software e quali mediante hardware.

Questo processo di "pianificazione" è normalmente frutto dell'esperienza dei progettisti e non viene sempre affrontato in forma "strutturata". Tuttavia, esistono anche procedure formalizzate da seguire come riferimento.

In particolare, la norma DoD-STD 2167A , "Defense System Software Development", definisce una serie di passaggi nel ciclo di sviluppo del software che mirano a garantirne la Qualità.

Figura 1. Stadi dello sviluppo software secondo la norma DoD-STD 2167A .

Una volta definite le specifiche del sistema, si suddivide la realizzazione tra hardware e software, con la definizione delle relative interfacce tra i due ambiti. Tipicamente lo sviluppo hardware e software procede parallelamente.

Per quanto riguarda il software, la fase di analisi dei requisiti (Software Requirements Analysis) riguarda il processo di specificazione dei compiti e delle caratteristiche dello stesso; segue quella di design, preliminare e poi dettagliata con rappresentazioni grafiche o linguaggi di programmazione (Preliminary and Detailed Design), e l' implementazione di quanto progettato che comprende scrittura del codice per ognuna della unità e fase di testing individuale (Coding and Unit Testing). Lo stadio finale tratta l' integrazione ed il testing al livello di software completo (CSC Integration and Testing).

La progettazione software preliminare definisce la struttura gerarchica dei componenti software che sono indipendenti tra loro, generalmente rappresentata graficamente. In questa fase sono utilizzati dei tool di Computer Aided Software Engineering (CASE), che servono ad accelerare il lavoro perché aiutano ad accorgersi di incongruenze nella logica e nella struttura del software già prima della scrittura del codice.

In questa fase si utilizza spesso un linguaggio ad alto livello (High- Level Language), che potrà poi essere modificato per soddisfare le specifiche volute. Il tipo di strumento CASE da utilizzare viene normalmente scelto in base al punto di vista supportato, che può essere object-oriented (data-oriented), process- oriented (functional o structured), e behavior-oriented (temporal o dynamic).

Esistono tool che ne prevedono uno solo, ed altri che invece li contemplano tutti.

Nella progettazione a basso livello sono aggiunte le considerazioni sulla temporizzazione dell'architettura "target", cioè del sistema embedded (con il termine "host" si intende invece il sistema di sviluppo, cioè la macchina che utilizziamo per eseguire la cross-compilazione ed altre operazioni). Inoltre, il Detailed Design divide le componenti in unità più piccole, che contengono ancora dati e procedure, delle quali si può dare una rappresentazione grafica, o tramite fusione di linguaggio di programmazione e pseudocodice.

3. Sviluppo del software (dettagliato)

In generale, lo sviluppo del software per sistemi embedded richiede l'uso di un insieme di strumenti: editor, assembler, compilatori, debuggers, simulatori, emulatori e programmatori di memorie Flash/OTP. Inoltre, si possono seguire approcci differenti in relazione alle competenze e ai mezzi effettivamente disponibili.

Il flusso di sviluppo del software è illustrato nella Figura 2

Figura 2

In buona sostanza, le operazioni fondamentali sono le seguenti:

  1. scrittura del codice (normalmente su un PC che funge da "host", che contiene un processore diverso da quello su cui il software dovrà girare);
  2. traduzione del codice in linguaggio macchina
  3. debugging del software (con l'aiuto di opportuni strumenti di simulazione ed emulazione. La prima operazione è essenzialmente di tipo "logico" e può essere condotta anche a prescindere dalle caratteristiche del sistema fisico reale , in particolare dalle caratteristiche dei suoi componenti, la seconda, invece, prova a tenere conto di queste ultime nel modo più "realistico" possibile)
  4. programmazione delle memorie Non Volatili del microcontrollore
realizzazione di un prototipo dell'intero sistema

3.1 Scrittura del codice (linguaggi di programmazione)

In dipendenza delle competenze disponibili e/o delle esigenze del progetto, il codice può essere scritto in linguaggio macchina (Assembler) o in linguaggio evoluto (tipicamente C). In ogni caso si usa un sistema integrato di sviluppo software (Integrated Development Environment IDE) e il risultato è memorizzato in un file di testo ASCII.

Per programmare in Assembler è necessario conoscere il set di istruzioni del processore, ed è utile conoscere almeno a grandi linee anche la sua architettura, il che complica il progetto e richiede competenze più evolute e specializzate. D'altra parte, questo approccio consente di ottimizzare il funzionamento del microcontrollore e consente di realizzare codici più compatti e veloci, ma anche specifici del processore utilizzato e, dunque, non facilmente trasferibili ("portabili") su altre macchine.

Al contrario, il codice può essere sviluppato in linguaggio più evoluto, che a sua volta richiede la disponibilità di un compilatore adeguato. In questo caso il software sviluppato è più generale (anche se occorre prestare attenzione ad eventuali estensioni del linguaggio specifiche per i vari processori) ed risulta dunque portabile su altre macchine. Tuttavia, il risultato è necessariamente non ottimale (codice più complesso e di esecuzione più lenta). La penalizzazione in questo senso rispetto ad un buon programma scritto in assembler, è valutabile tra il 20% e il 40%.

Il linguaggio C, con il suo discendente C++, è il linguaggio ad alto livello più utilizzato tra i programmatori embedded sostanzialmente perché rappresenta un linguaggio relativamente a "basso-livello" pur nell' ambito di quelli ad alto livello. In particolare, il linguaggio C offre ai programmatori la straordinaria possibilità di permettere un controllo diretto sull'hardware senza sacrificare i benefici comuni dei linguaggi di alto livello. IL C++ è un' estensione orientata agli oggetti del C.

Un altro linguaggio adatto per i sistemi embedded è l'Ada, che è object-oriented ed è però utilizzato in pratica solo dalle industrie militari ed aerospaziali.

3.2 Traduzione del codice

Il codice sviluppato deve poi essere tradotto in istruzioni che il microcontrollore possa effettivamnete eseguire, ovvero in set di bit chiamati "op codes" che il controllore della cpu possa decodificare ed eseguire. Poiché, per facilitarne la lettura, gli op-code sono normalmente rappresentati in forma esadecimale, la forma "leggibile" del codice "eseguibile" è normalmente chiamata "hex-file".

La traduzione del software scritto in Assembler, C (o, più raramente in altro linguaggio evoluto) in hex-code richiede rispettivamente l'uso di un assemblatore ("Assembler") o di un compilatore ("Compiler").

In aggiunta, poiché il software è sempre più sviluppato in moduli diversi (da diverse persone, sfruttando parti scritte in precedenza e disponibili in librerie,...) sono praticamente indispensabili anche "linkers", ovvero strumenti per collegare pezzi diversi del software assicurando la coerenza logica e la corretta allocazione di memoria e "librarians", ovvero di strumenti che consentono di memorizzare, maneggiare, organizzare e rendere ri-utilizzabili per uso futuro parti di codice sviluppato.

Il processo di conversione del codice che rappresenta il software embedded in un'immagine binaria eseguibile coinvolge tre fasi distinte: per prima cosa, ogni file sorgente deve essere compilato o assemblato in un file oggetto; poi tutti i file oggetto risultanti saranno legati per ottenere un unico file, detto programma relocatable, e infine gli indirizzi in memoria fisica devono essere assegnati ai relativi offset nel programma con un procedimento detto relocation. Finalmente, come risultato si ottiene un file che contiene un'immagine binaria eseguibile che è pronta per girare sul sistema embedded.

I passaggi fondamentali sono schematizzati nella Figura 3.

Figura 3. Dalla compilazione al file eseguibile.

Nella figura sopra, i rettangoli con i bordi arrotondati mostrano i tool di sviluppo. E' importante osservare che queste fasi sono realizzate da software che gira su una macchina general-purpose, ovvero sul componente host. La divisione dei compiti è rappresentata in Figura 4.

Fig. 4. Divisione dei compiti tra host e target.

Il lavoro del compilatore è di tradurre i programmi scritti in linguaggio leggibile dall'uomo in una equivalente serie di codice per un processore particolare.

I principali parametri che servono per valutare la bontà del codice prodotto dal compilatore sono la ridotta lunghezza (prioritaria nelle applicazioni a microcontrollore) e la velocità di esecuzione (primo requisito per le applicazioni su DSP).

Un limite che si incontra con l'utilizzo del linguaggio C è che l' efficienza dei compilatori in commercio è scarsa: perciò, la qualità del codice binario del file oggetto che essi producono risulta spesso inappropriata.

Confronti fatti utilizzando dei benchmark hanno mostrato che il codice compilato ha una velocità di esecuzione molto più bassa di quella del codice scritto a mano direttamente in assembler e poi assemblato. Perciò, in molte applicazioni di interesse, è indispensabile scrivere il codice in assembler. In questo modo si ha la possibilità di controllare completamente il processore ed il resto dell'hardware, ma al prezzo di uno sforzo e di costi notevolmente maggiori e di una minore portabilità.

Gli assemblatori appartengono alla classe dei compilatori, con la differenza di operare una traduzione semplice uno-a-uno da una riga di codice in linguaggio leggibile all'uomo all'equivalente riga di codice oggetto. Ogni processore tuttavia ha il proprio linguaggio macchina, quindi si deve scegliere un compilatore che sia capace di produrre programmi per quel processore target specifico. Ogni cross-compilatore supporta una serie di host e di target, le cui scelte sono indipendenti l' una dall' altra, ed ha una specifica configurazione per ciascuna delle combinazioni.

La struttura del file oggetto è generalmente definita da un formato standard come il Common Object File Format (COFF) o l'Extended Linker Format (ELF). Se si utilizza più di un compilatore (per esempio perché si scrivono le parti di codice in linguaggi diversi) bisogna accertarsi che tutti possano produrre dei file oggetto nello stesso formato. Esistono anche compilatori che producono del codice oggetto in un formato riservato.

La maggior parte dei file oggetto inizia con un'intestazione che descrive le sezioni che seguono. Queste sezioni contengono ciascuna uno o più blocchi di codice e dati che sono stati originati e suddivisi dal compilatore. Per esempio, tutti i blocchi di codice sono raggruppati in una sezione chiamata text, le variabili globali inizializzate (con il loro valore iniziale) in una sezione detta data, e quelle globali non inizializzate in una sezione detta bss.

Spesso c'è una tavola simbolica da qualche parte nell'object file che contiene i nomi e le locazioni di tutte le variabili e le funzioni del file sorgente, che può essere in parte incompleta perché non tutte le variabili e le funzioni sono sempre definite nello stesso file. Sarà il linker a risolvere i riferimenti incompleti.

L' uscita del linker sarà un file oggetto che contiene tutto il codice ed i dati derivati dal file oggetto in input. Si ottiene fondendo le sezioni text, data, bss del file di input ed è nello stesso formato di quest'ultimo. In questo modo, finito il processo di linking, tutto il codice ricevuto in ingresso sarà nella sezione text, mentre tutte le variabili inizializzate e non saranno nelle sezioni data e bss.

Il linker nel frattempo è anche attento a scovare dei riferimenti irrisolti: ad esempio, se un file oggetto contiene un riferimento ad una variabile che è dichiarata in un altro dei file oggetto, la cercherà in tutti i file oggetto. Se ancora non l' ha scovata, cercherà poi all' interno delle librerie standard e, in caso positivo questa volta, introdurrà nel file oggetto il codice e la sezione di dati associati.

Concluso il lavoro del linking, il programma è ora completo eccetto per il fatto che non gli sono stati assegnati indirizzi in memoria. Serve l' ulteriore azione di trasformazione del programma relocatable realizzato in un' immagine binaria eseguibile, svolta dal locator in collaborazione con il programmatore. Questi deve dare in input al locator delle informazioni sulla memoria del target, in modo che poi il locator stesso possa assegnare gli indirizzi in memoria fisica a ciascuna sezione di codice e di dati. Il file prodotto in output è finalmente un' immagine binaria che può essere caricata sulla memoria target riprogrammabile.

3.3 Verifica del codice

Una volta che il codice sorgente è stato assemblato/compilato e "linkato, è necessario verificarne la correttezza individuando e correggendo eventuali errori (con una operazione correntemente denominata de-bugging). In seguito è necessario programmare correttamente le memorie Non Volatili del processore.

Naturalmente, queste operazioni richiedono l'uso di una serie di strumenti automatici di supporto. A questo proposito, va considerato che la scelta degli strumenti per l'analisi e per il design è critica, per cui è essenziale operare con oculatezza, anche al costo di grosse quantità di risorse. In effetti, utilizzando dei tool adatti alle particolari esigenze si può ridurre di molto il tempo dello sviluppo seguente: ad esempio possono permettere una operazione di debugging molto più semplice e veloce.

In generale, si chiama "debugger" un programma che gira su PC (host) e deve poter funzionare in stretta simbiosi con l' "emulatore" utilizzato per valicare il codice (per questo motivo, tutti i fabbricanti di emulatori offrono anche un loro specifico debugger).

Il debugger consente di scaricare il codice nella memoria dell'emulatore e di controllare il funzionamento di quest'ultimo dal PC host. In particolare, i debugger rendono generalmente possibile esaminare e modificareil contenuto dei registri e delle memorie del microcontrollore, arrestare l'esecuzione del programma in punti definiti, eseguire il programma un'istruzione per volta, esaminare la storia (traccia) dell'esecuzione del programma.

A questi fini, si usano una serie di strumenti,di nome non sempre univoco, tra cui in particolare i seguenti.

Simulazione

Un primo modo per verificare il corretto funzionamento del software è quello di "simulare" il funzionamento del microcontrollore sul PC host. Naturalmente questa verifica dipende in modo assoluto dalla bontà del modello del processore. Inoltre, in nessun caso il PC (più potente del processore presente nel microcontrollore, ma anche molto più generale) non riesce a simulare il funzionamento del microcontrollore funzionante in tempo reale. Infine, per simulare l'intero sistema (microcontrollore, sensori, convertitori,...) è necessario simulare anche i componenti essenziali che comunicheranno con il micorcontrollore, il che pone un altro problema di modelli.

In buona sostanza, come sempre, la simulazione è piuttosto lontana dalla realtà e la sua affidabilità dipende dalla bontà dei modelli che utilizza.

Perciò, da sola non è rapprenda uno strumento sufficiente per la verifica di un progetto. Piuttosto può essere utilizzata insieme agli altri strumenti descritti in seguito, soprattutto per verificare la correttezza di operazioni svolta interamente all'interno del microcontrollore (quindi senza necessità di simulare altri componenti e la comunicazione con questi), come per esempio l'esecuzione di funzioni matematiche.

Emulazione

Gli emulatori (come vedremo ce ne sono di diversi tipi) rappresentano i più potenti strumenti di debugging e, almeno in linea di principio, è costituito da un strumento hardware in grado di comportarsi esattamente come il microcontrollore target e di essere "inserito" all'interno della scheda del sistema embedded in modo da funzionare "insieme" al resto dell'hardware del sistema vero. Naturalmente, gli emulatori hanno anche capacità di controllo remoto per eseguire le operazioni necessarie in fase di debugging (controllo dello stato delle memorie, interruzione dell'esecuzione del programma,...).

Mediante l'emulatore si può verificare il funzionamento in tempo reale del microprocessore. A questo scopo, l'emulatore può supportare alcune importanti funzionalità di debugging come l' hardware breakpoint e il monitoraggio in tempo-reale, oltre a tutte quelle dei debug monitor. Anche con un debug monitor si possono piazzare dei breakpoint nel programma; tuttavia si tratta di software breakpoint, che riguardano solo il fetch delle istruzioni, cioè l' equivalente del comando "stoppa l'esecuzione prima che il fetch delle istruzioni sia eseguito". Un emulatore invece supporta anche degli hardware breakpoint, che permettono di bloccare l' esecuzione in risposta ad una grossa varietà di eventi, che comprendono non solo il fetch delle istruzioni, ma anche la letture e scrittura in memoria o in I/O e gli interrupt.

La funzionalità di monitoraggio in tempo reale è realizzata tipicamente incorporando nell'emulatore un grosso blocco di RAM che sono dedicate a registrare informazioni su ogni ciclo di esecuzione effettuato dal processore. Questo permette di verificare con precisione in quale ordine si sono succeduti gli avvenimenti.

Tutti gli emulatori hanno tre componenti essenziali (realizzate in vario modo in diversi prodotti), ovvero:

  1. logica di controllo (comprensiva del controllo della memoria)
  2. dispositivo di emulazione del microcontrollore

pin adapter, ovvero un connettore che è in grado di riprodurre lo stesso pin-out del microprocessore target (in modo da poter inserire l'emulatore sulla scheda del sistema embedded al posto del microcontrollore target)

Questo tipo di emulatori è anche chiamato In-Circuit Emulator (ICE).

La maggior parte degli emulatori offrono diverse alternative per quanto riguarda i punti b e c, in modo da consentire di emulare esattamente il dispositivo di interesse.

Nel caso in cui avvenga senza sacrificare alcune potenzialità e/o pin del microcontrollore target, l'emulazione si dice "trasparente".

Esistono anche altri tipi di emulazione che utilizzano parte delle risorse del microcontrollore target ai fini di controllo. In questi casi il sistema di debugging prende più propriamente il nome di starter kit.

Emulazione delle memorie . Per quanto riguarda il punto a, una considerazione di tipo fondamentale è che i microcontrollori utilizzano anche memorie che non possono venir programmate (ROM), altre che lo possono essere una volta sola (OTP) oppure poche volte (che non è opportuno ridurre a seguito di operazioni eseguite in fase di debugging). Perciò, l'emulatore deve consentire di rimpiazzare tutte queste memorie con RAM esterne nelle quali il programma e i dati possano essere collocati e cambiati come occorre.

Bond-out emulation chip o PLD. Poiché, come appena detto, l'emulatore fa uso di memorie RAM esterne, che non sono parte del microcontrollore target, è chiaro che è necessario disporre di un dispositivo speciale simile a quello target, ma con alcune importanti differenze. In particolare questi dispositivi, chiamati bond-out chips, hanno gli stessi pin del microcontrollore target, più altri che consentono di trasferire il programma memorizzato nelle RAM esterne alle memorie interne del target in tempo reale. Inoltre altri pin servono per controllare l'esecuzione del programma, accedere ai registri interni e alla memoria dati.

In alternativa alla realizzazione di dispositivi speciali, alcuni costruttori realizzano tutte la funzionalità del microcontrollore target su un dispositivo programmabile (PLD), perciò di tipo standard e ri-utilizzabile (per esempio per diversi componenti della stessa famiglia di microcontrollori). Tuttavia, in questo modo si opera "soltanto" con un modello del dispositivo target, più lontano da questo di quanto non possa essere un bon-out chip. Tipicamente, il PLD è più complesso e lento del microcontrollore target, perciò la verifica è meno significativa.

Architettura degli emulatori

Unità base e Probecard

Molti emulatori consistono di una Unità Base (Base Unit) e una Probecard, come indicato in Figura 5.

Figura 5: Tecnica di debugging che utilizza Unità base e Probecard

La Unità Base è connessa la PC di sviluppo e contiene la maggior parte dell'elettroncia di emulazione, con l'esclusione del dispositivo di emulazione, che è realizzato nella forma di bond-out chip ed è montato su una piccola scheda chiamata Probecard. Quest'ultima è connessa alla Unità Base ed ha un adattatore di pin che le consente di essere inserita nella scheda del sistema embedded la posto del microcontrollore target.

Questo tipo di architettura consente di utilizzare la stessa Unità Base per diversi micontrollori cambiando semplicemente la probecard. Inoltre quest'ultima può essere molto piccola, poiché la maggior parte dell'elettronica è contenuta nell'Unità di Base, e ciò semplifica i problemi di inserimento nella scheda del sistema, minimizzando le distanze reali e, di conseguenza, anche le differenze in termini di propagazione, rumore, accoppiamenti,... rispetto alla realtà che si intende emulare.

Una variante dell'architettura appena descritta prevede che le due schede (Base Unit e Probe Card) siano alloggiate insieme in un unico package, da cui fuoriesce il pin adapter. In questo caso Base Unit è normalmente chiamata "Scheda Madre" (Motherboard) e la Probe Card "Scheda figlia" (Daughthercard).

Debug Board Modules (DBM)

Una architettura alternativa a quella appena descritta, e quella in cui tutto l'hardware necessario (incluso il dispositivo di emulazione) è realizzato su una unica scheda, piuttosto grande e complessa, che di solito non viene racchiusa in un contenitore per ridurre il costo.

In questo caso, la connessione con il sistema target avviene mediante un cavo che termina con un adattatore di pin (Figura 6)

Figura 6: Tecnica che utilizza Debug Board Modules

Questa soluzione ha alcuni svantaggi rispetto alla precedente in quanto tutti i segnali del sistema, compresi quelli eventuali di tipo analogico, debbono attraversare il nastro di connessione del sistema con il modulo di debugging con evidenti differenze rispetto al sistema vero per quanto riguarda sia i ritardi che gli accoppiamenti. Inoltre, ogni modulo di debugging è costruito per uno specifico microcontrollore e risulta perciò meno flessibile.

Emulazione "non completamente trasparente"

Poiché i moderni microcontrollori contengono memorie Non Volatili di tipo Flash di notevole dimensione e che possono venir riprogrammate più volte, recentemente si è affermato anche un tipo di emulazione che utilizza parte della la memoria interna (Flash) del dispositivo come memoria per il programma di debugging. In questo caso, si evita l'uso di RAM esterne e il dispositivo di debugging coincide con quello target, con notevoli vantaggi, perché si agisce direttamente sulla scheda finale del sistema.

Naturalmente, l'operazione di debugging richiede l'utilizzo di alcuni pin del dispositivo le cui funzionalità debbono essere ricreate mediante un piccolo hardware connesso tra il microcontrollore target e il PC di sviluppo.

Questo approccio presenta lo svantaggio di una velocità limitata per via della necessità di caricare il programma di debugging ogni volta che questo deve essere modificato, operazione che richiede un tempo piuttosto lungo per via della limitata velocità di scrittura delle memorie Flash.

4. Validazione elettrica del sistema

La fase di verifica e de-bugging del progetto di un sistema embedded normalmente richiede anche l'utilizzo di strumentazione da laboratorio per la misura di segnali e grandezze elettriche sulla scheda.

In particolare, analizzatori logici ed oscilloscopi sono normalmente utilizzati per verificare le interazioni tra il processore e gli altri chip sulla scheda, in quanto possono avere accesso solo ai segnali che sono al di fuori del processore. Quindi, non potendo controllare il flusso di esecuzione del software, debbono essere necessariamente associati all'utilizzo per esempio di un remote debugger o di un emulatore.

L'analizzatore logico è uno strumento progettato esclusivamente per la risoluzione dei problemi nell' hardware digitale. Può avere decine o centinaia di input, ognuno capace di verificare se il segnale elettrico al quale è attaccato è, in quel momento, al valore logico alto oppure basso, e di mostrare l' andamento dell' insieme di segnali considerati in un grafico come quella della Figura 7.

Figura 7. Un tipico display di un analizzatore logico.

L'oscilloscopio è un altro strumento usato per esaminare ogni segnale elettrico, analogico o digitale, in qualsiasi punto osservabile dell' hardware, tuttavia con un numero di input molto minore rispetto all'analizzatore logico (tipicamente circa quattro). Come per quest' ultimo, vale il fatto che è utile nel debugging di schede solo se usato insieme ad altri strumenti.

Ciascuno degli strumenti di cui si è parlato assume massima utilità solo se entra in gioco nel momento e nel modo opportuno all' interno di un processo di sviluppo di software. Ad esempio, oscilloscopi ed analizzatori logici sono spesso usati per risolvere problematiche hardware, i simulatori durante le prima fasi di debugging, mentre i debug monitor e gli emulatori durante le successive.

Quando c'è necessità di fare un test su tutto il sistema, è necessario far eseguire il codice sul target mentre si fa il debugging dell'hardware.

Infine vale la pena di segnalare che esistono specifici tool di sviluppo che consentono di testare l'hardware indipendentemente dal software embedded e viceversa, e che aiutano ad individuare eventuali problemi legato all'una piuttosto che all'altra componente del sistema.

5. Programmazione delle memorie

Una parte rilevante dello sviluppo di un sistema embedded riguarda la programmazione delle memorie Non Volatili (Flash e OTP) del microcontrollore. In fase di debugging, dove l'esigenza di programmare velocemente le memorie è relativa, questa programmazione può essere eseguita mediante l'hardware stesso di debugging, il quale, però, non è adatto per la programmazione dei sistemi in produzione perché non realizzato per sopportare centinaia o migliaia di inserimenti "fisici" dei microcontrollori da programmare sugli appositi socket.

Da questo punto di vista, si possono allora distinguere due strategie differenti.

La prima prevede la programmazione dei microcontrollori prima che questi siano inseriti nella scheda del sistema. In questo caso la programmazione è detta "fuori dal circuito" (out-of-circuit) ed avviene mediante hardware specializzato per l'inserimento rapido e facile dei dispositivi sui socket di programmazione.

La seconda soluzione prevede la programmazione dei microcontrollori dopo che questi sono stati saldati sulla scheda del sistema (In-System Programming -- ISP). In questo caso è necessario disporre in opportuno hardware per generare i segnali elettrici (ad alta tensione) necessari per la programmazione della memorie Non Volatili (In- System Programmer), mentre la programmazione "logica" avviene mediante una delle interfacce di comunicazione del micro (tipicamente UART).

Questo tipo di programmazione è piuttosto lento, ma offe il vantaggio di non richiedere adattamento di pin e di evitare un passo di produzione (quello dedicato appunto alla programmazione separata delel memorie), il che può consentire notevoli vantaggi economici.

6. Jtag

Praticamente tutti i microcontrollori più recenti contengono l'hardware di tipo JTAG, acronimo che sta per Joint Test Action Group, nome utilizzato per le standard IEEE 1149.1 intitolato "Standard Test Access Port and Boundary Scan Architecture" che è utilizzato per collaudare schede mediante l'approccio del Boundary scan.

Questo sistema consente, in generale, un accesso (seriale) ai dispositivi presenti sulla scheda e in particolare ad hardware specializzato per il debugging del software.

L'interfaccia di comunicazione JTAG comprende 4/5 pin e utilizza un protocollo di comunicazione seriale, mentre lo standard è costruito in modo che tutti i chip che contengono il sistema JTAG possono esser raggiunti e testati separatamente attraverso un lungo registro a scorrimento che percorre tutta la scheda in fase di test.

7. Cosa c' è da migliorare

Per concludere, la Figura 8 illustra i risultati di un'indagine sulla necessità di avere a disposizione degli strumenti per lo sviluppo di software embedded più evoluti.

Figura 8. Importanza attribuita al miglioramento dei tool disponibili.

L' asse x in Figura è proporzionale al numero di gruppi intervistati che hanno risposto relativamente a quello strumento. Risulta che la tecnologia dei compilatori ha maggiormente bisogno di miglioramenti, per permettere ai progettisti di esprimere i propri algoritmi in HLL piuttosto che in assembler. Segue quella dei simulatori di set di istruzioni, che devono raggiungere la velocità di un milione di istruzioni eseguite al secondo. Il terzo tool più citato è il debugger a source-level. Questo strumento lega il codice oggetto compilato, che è eseguito sul simulatore di istruzioni o sul processore, per tornare indietro al codice C durante il debug.