Corso su ARM: creiamo insieme una libreria

freescale logos

Ci siamo, è arrivata anche l'ultima puntata di questo corso. Oggi vogliamo fare le cose in grande provando a proporvi ancora un approfondimento ma questa volta guardando CodeWarrior e Processor Expert con un occhio leggermente differente. Oggi impareremo come si crea una libreria per un componente, spiegandovi i passi fondamentali nel dettaglio, gli strumenti presenti e la relativa programmazione in modo che possiate essere subito operativi dopo la lettura. Siete pronti?

Come dicevamo nell'introduzione questa è l'ultima puntata del nostro corso di programmazione su ARM. Abbiamo fatto tante prove, abbiamo approfondito diversi aspetti tecnici ed anche di programmazione. Ora, però, è arrivato il momento di scendere ancora di più nel dettaglio.
Fino a questo momento noi abbiamo utilizzato dei componenti che i programmi già conoscevano, delle librerie già dichiarate e quindi qualcosa che qualcun altro aveva già fatto, preparato e reso funzionante ma non ci siamo posti il problema di "come" lo abbia fatto…
Come si crea un componente?
Come si fa a dar vita ad una libreria?
Ecco che cosa vediamo oggi.
Lo diciamo adesso per chiarezza ma sappiate che ve lo ripeteremo nel corso della puntata: quello che vi stiamo per far vedere vale per qualunque genere di componente che possiate immaginare. Naturalmente cambieranno nomi, tipi, dimensioni dei registri, memorie ma il punto è che quello che vogliamo farvi vedere vi darà la possibilità di operare in maniera abbastanza trasversale, praticamente con qualsiasi dispositivo. Vi spiegheremo strumenti e tecniche alla base del funzionamento dei 2 software attraverso la realizzazione di una libreria funzionante.
Per fare questo naturalmente ci affideremo ai due software che abbiamo approfondito fino a questo momento sia perché abbiamo imparato a conoscerli sia perché effettivamente sono molto semplici da gestire. Le loro interfaccia, ma anche le loro molteplici possibilità, garantiscono versatilità anche per chi sta per cominciare o chi ha cominciato da poco.
Due strumenti irrinunciabili dunque che permettono al novizio come al professionista navigato di lavorare con tranquillità.
Se, quindi, esistono molti componenti definiti in Processor Expert, nella versione con CodeWarrior, molti altri mancano naturalmente e questo è abbastanza fisiologico: quando “esce” il software non smettono di uscire componenti e nuovi sensori che possono essere integrati su altre board. Questo è abbastanza fisiologico; ecco per quale motivo abbiamo bisogno di creare le nostre librerie per il nostro progetto embedded che lavori con i sensori che abbiamo a disposizione in quel momento. E naturalmente non stiamo parlando soltanto di sensori ma di dispositivi esterni più in generale, per esempio anche degli attuatori.

Creiamo una libreria

E per farlo prendiamo un dispositivo a caso, magari uno più complesso del semplice sensore di luminosità. Proviamo con l'accelerometro.
Il modello che la FRDM-KL46Z monta è il MMA8451Q ma naturalmente analoghi ragionamenti possono essere fatti per altri accelerometri. Inoltre quello che vi illustreremo oggi è un processo, lo ricordiamo, un procedimento, rigoroso e che tiene conto delle caratteristiche del dispositivo nonché degli strumenti di lavoro. Anche se cambiasse dispositivo in oggetto, quello su cui state lavorando, sicuramente potreste utilizzare lo stesso metodo perché si tratta di dichiarare le caratteristiche del sensore al programma e di dirgli come funzionano le interfacce di comunicazione e quali sono metodi funzioni che possono essere utilizzati e che lo caratterizzano.
 
L'accelerometro è uno strumento abbastanza complesso, ne abbiamo visti diversi nel corso del tempo, e le loro caratteristiche si evolvono anche abbastanza velocemente. Una delle più importanti è l'interfaccia di comunicazione, I2C. Un bus comodo con una struttura snella e che può essere ben controllato.
L'idea della dichiarazione della libreria è quella di avere a disposizione un driver per la lettura.
Lavoreremo con Processor Expert, che utilizza i suoi "componenti" (abbiamo visto la "Component Library", ricordate?) per generare il software e per farlo utilizzeremo uno script in linguaggio C.
 
Se provate ad esplorare e a guardare i dettagli quasi di qualunque componente troverete una serie di voci che lo caratterizzano: si tratta dei metodi, delle funzioni e delle proprietà di cui l'elemento è dotato.
Vediamone qualcuno che non abbiamo visto fino a questo momento per capire cosa possiamo trovare all'interno di un componente in CodeWarrior:

dicevamo, infatti, tante voci diverse, una per ogni metodo o evento e per ciascuna delle proprietà, ovvero parametri, da impostare per ciascun componente

e quindi, almeno in questo caso, la specificazione se si tratta di uscita o ingresso ed il pin di riferimento. Più complicato diventa il dispositivo cui volete accedere, maggiore sarà il numero di proprietà che dovrete gestire.
Non preoccupatevi dell'errore in questa immagine, naturalmente, sto utilizzando dei componenti qualunque giusto per darne un'idea. Quando avrete finito, e tutto sarà perfettamente configurato, non avrete alcun tipo di problema.
 
Quando parliamo di metodi stiamo parlando delle normali funzioni definite in C: in questo caso particolare, GetVal(), NegVal(). A proposito, quest'ultimo alla ricordate? L'abbiamo già vista insieme.
 
Quando parliamo di eventi, invece, generalmente si tratta di chiamate asincrone ai componenti.

Come si può notare, la scelta dei colori non è affatto casuale ma icone e colori aiutano visivamente il progettista a meglio comprendere cosa sta guardando.
Quando avremo finito con questo progetto e con questa spiegazione non avremo soltanto disposizione un nuovo componente ma un generico metodo per inizializzare un generico componente che lavori su interfaccia I2C. Un bel vantaggio, non trovate?
 
Il punto di partenza di qualunque tipo di lavoro vogliate fare è certamente un po' di codice scritto in C. Almeno per definire quelle che sono le funzioni fondamentali del vostro sensore. Spendete del tempo per cercare di capire da subito come deve funzionare, che cosa deve fare, quale dev'essere il metodo di funzionamento e quali azioni la lettura del sensore deve poter comandare perché in questo modo la programmazione sarà molto più semplice e si tratterà soltanto di capire dove si trovano le informazioni che vi servono all'interno dell'insieme di tutti i registri di cui il vostro sensore è dotato.
 
Per fare questo voi avrete bisogno sicuramente di un header file che contenga almeno le seguenti righe di codice:
#ifndef MMA8451_H_
#define MMA8451_H_
#include "PE_Types.h"

uint8_t MMA8451_GetRaw8XYZ(uint8_t xyz[3]);
uint8_t MMA8451_Init(void);

#endif
perché almeno la lettura dei valori e sicuramente almeno l'inizializzazione del sensore le dovrete avere. A questo file naturalmente si affiancherà il suo corrispondente *.c, all'interno del quale verranno sicuramente definiti i registri, in questo modo:
#define MMA8451_OUT_X_MSB 0x01
#define MMA8451_OUT_X_LSB 0x02
#define MMA8451_OUT_Y_MSB 0x03
#define MMA8451_OUT_Y_LSB 0x04
#define MMA8451_OUT_Z_MSB 0x05
#define MMA8451_OUT_Z_LSB 0x06
Ma non solo perché se avete I2C come bus di comunicazione avrete certamente bisogno di specificare l'indirizzo
#define MMA8451_I2C_ADDR (0x1C)
senza contare le funzioni che vi si richiederà certamente di creare per l'inizializzazione e poi per la gestione dei dati ed eventuali ulteriori elaborazioni che dovrete fare.
 
Dopodiché ci sarà sicuramente da lavorare con il file Application.c, una nostra vecchia conoscenza delle puntate passate all'interno del quale sicuramente dovrete includere almeno Application.h ed il file header del vostro dispositivo. Qui, inoltre, sarà fondamentale la definizione della funzione void APP_Run(void) { } in cui sicuramente dovrete includere MMA8451_Init() e comandare azioni corrispondenti alla lettura del sensore.
 
Ora che abbiamo le idee più chiare su quello che ci serve, vediamo di metterci davvero le mani sopra, apriamo CodeWarrion con Processor Expert e…

…iniziamo!

Per creare un nuovo componente la procedura non è così concettualmente diversa da quella per la creazione di un nuovo progetto: utilizzerete un Wizard la cui prima domanda sarà chiedervi a quale progetto farà capo la creazione del nuovo componente. Utilizzatene, magari, solo uno di appoggio almeno all'inizio, per cominciare.
Selezionate quindi File -> New -> Embedded Component Project.
Dopo aver inserito il nome del progetto, vi verrà chiesto il nome del componente, naturalmente.
Sembra quasi superfluo dirlo ma vale la pena di precisarlo: non vi sforzate troppo con la fantasia per dotare di nomi i vostri componenti: più rimanete fedeli al nome del componente vero più facile sarà trovarlo dopo tempo che avrete lasciato tutto questo e che vi sarete dimenticati che cos'avevate fatto. Cercate di utilizzare nomi o sigle molto facilmente riconoscibili o molto facilmente riconducibili al nome vero del componente stesso.
Tenete ben presente, comunque, che se il nome che avete dato al vostro componente non vi soddisfa potete sempre cambiarlo più avanti.
Una volta che avrete fatto click su "Finish", ve lo ritroverete tra i componenti disponibili.

Espandete il menù e troverete tutto quello che c'è di definibile per questo componente. Adesso è arrivato il momento, infatti, di spiegare a Processor Expert di che tipo di componente stiamo parlando.
 
Ecco, adesso nei dettagli potete definire il modo per richiamare velocemente il vostro componente, inserire dettagli come l'autore e la versione di questa espansione per il vostro programma, e così via dicendo.
Prendetevi del tempo per esplorare le categorie disponibili e così tutte le altre opzioni che troverete tra i dettagli.
Naturalmente potete riaccedere a questa finestra (schermata) facendo clic col tasto destro sul nome del vostro componente selezionando "Component Information".
Il menu contestuale che troverete durante questa operazione vi fornirà una serie di altre possibili opzioni, la prima delle quali serve a costruire la documentazione del componente.
Potrete anche esportarlo in maniera tale da averlo disponibile al di fuori del programma e magari poterlo inviare ad altri.
Tutte queste proprietà sono comunque modificabili per cui non preoccupatevi troppo se al primo approccio mancheranno delle informazioni o farete degli errori.
 
Vi accorgerete che la modifica al componente comporta che accanto al suo nome, dove trovate scritto "nomedelcomponente::Component Information" ci sarà un visibile "*"; come la grafica suggerisce, questo significa che le modifiche non sono ancora state salvate.
Potete utilizzare scorciatoie da tastiera oppure dirigervi all'interno dei menu per cercare l'opzione di salvataggio.
Vi consigliamo, come per tutti i programmi, di imparare quali sono le scorciatoie da tastiera e utilizzare mouse e tastiera con le scorciatoie piuttosto che dovendosi spostare costantemente all'interno della finestra vi rende il tutto molto più agevole e l'esperienza molto più fluida.
Utilizzate pertanto CTRL + S per salvare le vostre modifiche.
Nella versione che sto usando non si verifica un fenomeno che, però, almeno per alcuni, è particolarmente fastidioso: quando decido di salvare le modifiche invece di prenderle in automatico e modificare il componente, può succedere che si apra una finestra che richiede la conferma del salvataggio. Questa opzione, almeno nel mio caso, era già disabilitata; se volete lo sia anche per voi non vi resta che andare all'interno delle preferenze di CodeWarrior, selezionare quelle di Processor Expert e togliere il mark all'opzione "Enable Save Diaglog", che vedete riportata qui di seguito
Ok, adesso abbiamo il componente.
Però non è davvero un componente perché non fa nulla, non c'è niente al suo interno. È una scatola vuota. E allora vediamo di riempirla.
La prima cosa che dobbiamo fare è conferire al nostro sensore un indirizzo.
Esistono due possibilità diciamo canoniche, quelle che su Internet trovate ampiamente documentate e che vengono utilizzate quasi in tutti i codici relativi all'utilizzo dell'MMA8451Q: 0x1D o 0x1C. Il sensore può uno di questi due, scegliete quello che preferite (ecco il perché della scelta che abbiamo fatto nel codice riportato in precedenza).
Dobbiamo anche decidere come fare questa scelta e sarebbe carino il fatto che fosse l'utente a dover scegliere quale indirizzo assegnare. Magari con un menù a tendina, esattamente come quelli che abbiamo visto impiegati fino a questo momento per i componenti.
Esiste anche la possibilità di utilizzare soltanto del testo e di lasciare la libera possibilità per l'utente di inserire il proprio indirizzo.
Questa è una scelta di qualità per cui fatela sapendo che quel componente potreste non soltanto voi ma anche altre persone.
 
Cercate, allora, tra i tipi di proprietà associati al vostro sensore la voce "Add Enum Property" così come dimostriamo con la prossima figura

Quando avrete finito di creare il vostro elenco di possibilità, dovreste trovare qualcosa di questo tipo

Ah, ecco, un suggerimento: io non so quante volte vi sia capitato ma purtroppo lavorare al computer significa avere a che fare con una serie di problemi che derivano dal fatto che talvolta il sistema operativo (parlo di Windows, naturalmente) impazzisce, si spegne, si riavvia, e così via dicendo.
Vi consiglio di salvare spesso perché queste modifiche, se le perdete, potrebbero portarvi un notevole nervosismo piuttosto che grande soddisfazione. Anche perchè di solito non lavorerete mica solo per creare un componente e comunque non uno per volta…
 
A questo punto della nostra procedura di creazione della libreria è arrivato il momento di selezionare una nuova proprietà all'interno della voce "Properties".
Questo perché abbiamo detto che vogliamo che quella sia una lista di possibili valori.
Ecco che cosa dobbiamo impostare e come:

(fai click per ingrandire)

Naturalmente l'opzione di Default può essere personalizzata. Dato che sappiamo come abbiamo scritto il codice, per comodità combaciano.
 
Avete salvato i vostri progressi? Bene, andiamo avanti.
Per comunicare con l'accelerometro non abbiamo ancora finito: dobbiamo lavorare ancora sul protocollo I2C. Nello specifico quello che dobbiamo fare è interfacciarlo ad un componente I2C.
Ci sono due modi per fare questa cosa:
  • ereditare un componente;
  • creare un collegamento al componente.
Se si sceglie la prima strada il componente da cui si “effettua” l'eredità sarà parte del componente principale. Un caso tipico, tanto per intenderci, è l'esempio del LED che abbiamo fatto l'altra volta. Vi ricordate? Lo abbiamo già richiamato: il componente BitIO si trova all'interno del LED.
Se invece vogliamo creare un collegamento, il componente ereditato non è all'interno ma all'esterno.
Dal momento che potrebbero esserci altri componenti all'interno del progetto, nell'ottica di realizzare qualcosa di più completo e complesso, effettuare un'semplice collegamento potrebbe essere la strada migliore.
Tuttavia vi consiglierei di scegliere la prima.
Per farlo, la prima cosa da fare è definire che cos'è "l'interfaccia"; di fatto non si tratta di niente di diverso rispetto alla definizione di un metodo e degli eventi a cui mi voglio interfacciare.
Apriamo il menu contestuale del nostro sensore col tasto destro e selezioniamo "Inherit to this Component".
Quello che facciamo qui è definire l'interfaccia.
La finestra di dialogo che si apre vi indica che il componente deve necessariamente essere chiuso per evitare problematiche di ogni genere e sorta.
L'operazione che state per fare richiede il riavvio del componente perché non ci possono essere ambiguità di nessun tipo. Quando tutto sarà finito, e il processo si sarà concluso, vi sarà richiesto di selezionare il componente dal quale effettuare l'eredità.

procedendo nella creazione vi verrà chiesto in che modo intendete utilizzare il componente. Le possibilità sono quattro:
  • utilizzo esclusivo del componente per metodi ed eventi;
  • interfaccia del componente estesa, scrittura o pubblicazione dei metodi o degli eventi del componente (denominata true inheritance);
  • utilizzo condiviso dei metodi e degli eventi del componente;
  • interfaccia esistente.
Come si sarà capito, la scelta per la quale dobbiamo optare è la terza.
Al passo successivo è possibile specificare metodi ed eventi che vogliamo utilizzare. Ci muoviamo come indicato dalla prossima immagine sia per ReadAddress() sia per WriteAddress().
Abbiamo quasi finito ma nella schermata successiva troviamo una domanda che apre un intero capitolo nella programmazione: l'ereditarietà multipla.
Per il nostro componente è possibile ereditare altro da altri componenti. Vale sia per i metodi sia per gli eventi.
Naturalmente questo risulta un po' diverso dal concetto di ereditarietà multipla che c'è nella programmazione ad oggetti perché c'è un'interazione diretta tra più componenti qui.
Salvo che non abbiate diverse specifiche o che non vi stiate semplicemente divertendo sperimentare, continuate senza aggiungere nient'altro ed andate dritti verso la fine.
A questo punto dovrebbe esservi comparso qualcosa nel workspace, una nuova voce. Controllatelo perché se così non fosse avete sbagliato qualcosa.
 
Vi serve definire, adesso, le proprietà dell'interfaccia pertanto cercate la voce "Properties" ed aggiungetene una. Se tutto finora è andato a buon fine troverete qualcosa di questo tipo:

(fai click per ingrandire)

il nome dell'oggetto è quello che viene mostrato nell'interfaccia utente mentre il campo "simbolo" riporta il valore che viene utilizzato all'interno del codice.
Notate che il nome dell'interfaccia è "MMA8451Q\I2C.int".
 
Dal momento che prima abbiamo deciso di utilizzare "I2C" come nome dell'interfaccia, adesso abbiamo bisogno di un template in maniera tale che Processor Expert si renda conto che deve esistere un componente che sia compatibile.
All'interno del nostro componente esiste una cartella "Beans". Esplorando la arriverete a trovare un file che si chiama "I2C.int". Lo vedete? 
Ecco le righe che dovete trovare:
<Registration>
    <Template>I2C</Template>
  </Registration>
Il senso? È facile: abbiamo utilizzato un nome diverso per cui abbiamo necessità di definire il tutto come un modello a cui eventualmente altri componenti possano attenersi nel funzionamento. Questo vale soprattutto perché bisogna suggerire, come dicevamo, al Processor Expert che sebbene non siano diversi stiamo parlando di cose equivalenti.
Se nel futuro vorrete aggiungere altri componenti vi sarà sufficiente aggiungere qualche riga proprio in questa sezione.

Che manca?

Beh, c'è parecchia strada da fare ancora: mancano i metodi e mancano anche gli eventi.
Andiamo con ordine: abbiamo parlato di inizializzazione ed acquisizione dei valori per cui facendo clic col tasto destro su "Methods" dobbiamo necessariamente configurare un metodo che prenderà il nome di Init() ed uno GetRaw8XYZ().
Naturalmente, è superfluo che io vi faccia notare che i nomi devono coincidere perfettamente con quelli presenti all'interno dei file!
Verificare la correttezza delle informazioni selezionando la tabella "Source code": qui dentro troverete il codice sorgente associato al metodo.
 
Inoltre qui c'è da far notare una cosa: manca l'interfaccia! Non l'abbiamo configurata per cui adesso resta da fare questo utilizzando una sintassi del tipo
%@InterfaceName@'ModuleName'%.MethodName
Cioè sostanzialmente scrivendo al termine del codice una riga del tipo
return %@I2C@'ModuleName'%.WriteAddress(MMA8451_I2C_ADDR, (uint8_t*)&addr, ...
... sizeof(addr), (uint8_t*)&data, sizeof(data));
Questo naturalmente è solo l'inizio perché adesso quello che dobbiamo fare è parlare di registri e comunicarne gli indirizzi. Queste righe devono trovarsi, infatti, all'interno del file MMA8451Q.drv.
Ed anche qui dobbiamo necessariamente lavorare con l'I2C. In particolare all'interno del codice non lo avevamo scritto per esteso, specificando quale dei due ma ha senso farlo adesso?
Assolutamente no: lo abbiamo parametrizzato quindi tanto vale riscrivere il codice come segue:
#define MMA8451_I2C_ADDR  (%I2CSlaveAddress)
Analoghe considerazioni vanno fatte rispetto alla funzione di acquisizione dei valori. Qui andrà, inoltre, aggiunto un parametro utilizzando il comando "Add Parameter". Potete sfruttare la potenza dell'ambiente di sviluppo Eclipse based iniziali usando un parametro uint8_t *xyz che l'ambiente riconoscerà equivalente a uint8_t xyz[3].

Abbiamo finito?

In linea del tutto teorica, si. In pratica forse adesso possiamo dedicarci al divertimento e rifinire quello che abbiamo fatto aggiungendo un dettaglio che renderà la nostra libreria sicuramente più accattivante: che ne dite di inserire un'icona?
Per farlo dovete utilizzare immagini quadrate da 16 pixel x 16 pixel che dovranno essere necessariamente in formato bitmap. Potete crearle, naturalmente, con qualsiasi programma che voi preferiate.
Quando avete preparato la vostra immagine, inserita all'interno della cartella "Beans" dopodiché non vi resta che tornare all'interno delle informazioni del componente e dare uno sguardo più approfondito al fatto che in effetti esiste un campo "Icon (16×16)". Lo avevate già notato?
A questo punto selezionate "Open", trovate la vostra immagine ed il gioco è fatto.

Mettiamolo alla prova

Per sapere se tutto ha funzionato come dovrebbe l'unica strada è provare! Trattatelo adesso come avete visto trattare tutti gli altri componenti e gestitelo nel modo in cui ritiene più opportuno: aggiungetelo al progetto, inizializzate i valori, leggete quello che viene rilevato dal sensore ed utilizzate queste variabili per ogni genere di applicazione.
Ma prima di pensare in grande, andiamo per gradi:
  • lo trovate nella libreria dei componenti?
  • selezionandolo, l'inizializzazione funziona?
  • riuscite a lavorare con i suoi metodi?
Se le risposte a queste domande sono tutte positive, complimenti: avrete il vostro componente!
 
Ora sapete come dovete fare per creare il generico componente. Studiate con attenzione il suo Datasheet, valutatene caratteristiche e funzionalità, buttate giù una bozza di codice di quello che deve fare e poi lanciatevi all'interno dell'ambiente di sviluppo per spiegargli il dettaglio di come deve fare per dialogare direttamente col sensore.
Lo ammetto, la procedura è un po' macchinosa, specie all'inizio, ma si tratta semplicemente di un ambiente di sviluppo che funzioni da interprete e per questo motivo bisogna spiegargli come fare ad interpretare ciò che il sensore sa già fare di suo.
Se avete problemi, dubbi, domande o perplessità, ricordate che i commenti sono il luogo giusto per analizzarle.
E con questo siamo davvero alla fine.

 

                    

Per ulteriori informazioni potete scrivere a questa email: ggalletti@arroweurope.com

2 Comments

  1. Giorgio B. 19 dicembre 2013
  2. Max Caruso 19 dicembre 2013

Leave a Reply