UNIVERSITÀ DEGLI STUDI DI URBINO CARLO BO Facoltà di Scienze e Tecnologie Corso di Laurea in Informatica Applicata Tesi di Laurea ANALISI DI UN SISTEMA OPERATIVO PER DISPOSITIVI A MEMORIA LIMITATA: APPLICAZIONE ALLA MISURA DI PARAMETRI AMBIENTALI Relatore: Chiar.mo Prof. Massimo Zandri Candidato: Andrea Monacchi Correlatore: Dott. Andrea Seraghiti Anno Accademico 2009-2010 Alle mie nonne iii iv Indice 1 Introduzione 1 2 Il sistema operativo Contiki 3 1.1 1.2 2.1 2.2 2.3 2.4 Contesto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Organizzazione . . . . . . . . . . . . . . . . . . . . . . . . . . . Il supporto alla multiprogrammazione in Contiki La gestione dei processi . . . . . . . . . . . . . . La temporizzazione degli eventi: i timer . . . . . Il modulo µIP . . . . . . . . . . . . . . . . . . . . 3 Sviluppo di sistemi integrati 3.1 3.2 3.3 3.4 3.5 3.6 La piattaforma di sviluppo . . . . . . . . . . . . . Utilizzare Contiki . . . . . . . . . . . . . . . . . . Il sistema di build di Contiki . . . . . . . . . . . Preparazione dell'ambiente di sviluppo . . . . . . 3.4.1 Windows . . . . . . . . . . . . . . . . . . 3.4.2 Linux . . . . . . . . . . . . . . . . . . . . 3.4.3 Instant Contiki . . . . . . . . . . . . . . . Contiki e la Crumb644 . . . . . . . . . . . . . . . 3.5.1 Creazione di una nuova piattaforma . . . 3.5.2 Modica dei le dipendenti dall'hardware La prima applicazione in Contiki . . . . . . . . . 3.6.1 Testing del sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Realizzazione di un'applicazione per il controllo ambientale 4.1 4.2 L'hardware . . . . . . . . . . . . . . . . . 4.1.1 L'interfaccia SPI . . . . . . . . . . 4.1.2 Il sensore di accelerazione . . . . . 4.1.3 Il prototipo . . . . . . . . . . . . . Il software . . . . . . . . . . . . . . . . . . 4.2.1 Gestione della SPI . . . . . . . . . 4.2.2 Gestione dell'accelerometro . . . . 4.2.3 Implementazione dell'applicazione v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 3 11 20 21 25 25 26 28 29 29 29 29 29 30 32 36 37 39 39 39 41 45 47 47 47 48 5 Valutazione dei risultati ottenuti 5.1 5.2 5.3 5.4 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Verica dell'hardware . . . . . . . . . . . . . . . . . 5.1.2 Verica del software . . . . . . . . . . . . . . . . . . 5.1.3 Comportamento in condizioni di quiete . . . . . . . . 5.1.4 Comportamento in presenza di vibrazioni periodiche 5.1.5 Trasmissione dei campioni . . . . . . . . . . . . . . . Reattività dell'interfaccia di rete . . . . . . . . . . . . . . . 5.2.1 Valutazione mediante ping . . . . . . . . . . . . . . . 5.2.2 Valutazione mediante il tool hping3 . . . . . . . . . . Conclusioni . . . . . . . . . . . . . . . . . . . . . . . . . . . Sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . . A Implementazione di applicazioni in Contiki A.1 A.2 A.3 A.4 A.5 Gestione dei processi . . . . . . . . . . . Protothread . . . . . . . . . . . . . . . . Supporto al multithreading . . . . . . . Gestione dei timer . . . . . . . . . . . . Supporto alla connettività TCP/IP . . . A.5.1 Gestione delle connessioni di rete A.5.2 Gestione delle protosocket . . . . . . . . . . . . . . . . . . . . . . . . . B Il codice sorgente dell'applicazione B.1 La gestione dell'interfaccia SPI . . . . . . . . B.1.1 spiman.h . . . . . . . . . . . . . . . . B.1.2 spiman.c . . . . . . . . . . . . . . . . . B.2 La gestione del modulo STEVAL-MKI009V1 . B.2.1 lis3lv02dl.h . . . . . . . . . . . . . . . B.2.2 lis3lv02dl.c . . . . . . . . . . . . . . . B.3 La piattaforma avr-chip45 . . . . . . . . . . . B.3.1 contiki-chip45-main.c . . . . . . . . . . B.3.2 Makele.avr-chip45 . . . . . . . . . . . B.4 L'applicazione . . . . . . . . . . . . . . . . . . B.4.1 tesi.c . . . . . . . . . . . . . . . . . . . B.4.2 Makele . . . . . . . . . . . . . . . . . B.4.3 Makele.tesi . . . . . . . . . . . . . . . C Il codice sorgente dell'applicazione client C.1 Il package graphics . . . C.1.1 Main.java . . . . C.1.2 GUIClient.java . C.1.3 GUIListener.java C.2 Il package client . . . . . C.2.1 Controller.java . . . . . . . . . . . . . . . . . . . vi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 50 50 51 53 54 58 59 60 61 64 65 66 66 70 71 72 73 73 74 77 77 77 77 78 78 79 81 81 82 83 83 85 85 86 86 86 86 88 90 90 C.2.2 ConnectionManager.java . . . . . . . . . . . . . . . . . . C.2.3 FileManager.java . . . . . . . . . . . . . . . . . . . . . . 94 95 Bibliograa 96 Ringraziamenti 99 vii Elenco delle gure 2.1 Modello a thread e modello ad eventi. . . . . . . . . . . . . . . 5 3.1 3.2 La board Crumb644 di Chip45. . . . . . . . . . . . . . . . . . . Il webserver minimale di Stefan Perzborn . . . . . . . . . . . . 26 30 4.1 4.2 4.3 4.4 4.5 4.6 L'interfaccia SPI . . . . . . . . . . . . . . . Il modulo STEVAL-MKI009V1 . . . . . . . La lettura di un registro . . . . . . . . . . . La scrittura di un registro . . . . . . . . . . La connessione Crumb/STEVAL-MKI009V1 Il prototipo . . . . . . . . . . . . . . . . . . 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 Il Il Il Il Il Il Il Il Il Il Il Il Il Il . . . . . . . . . . . . . . . . . . . . . . . . 40 41 45 45 46 46 client nello stato non connesso . . . . . . . . . . . . . . client nello stato connesso . . . . . . . . . . . . . . . . . graco del test in condizioni di quiete . . . . . . . . . . graco n.1 del test per l'operazione di lavaggio dei capi graco n.2 del test per l'operazione di lavaggio dei capi graco n.1 del test per l'operazione di centrifuga . . . . graco n.2 del test per l'operazione di centrifuga . . . . graco n.3 del test per l'operazione di centrifuga . . . . graco n.4 del test per l'operazione di centrifuga . . . . graco n.5 del test per l'operazione di centrifuga . . . . graco n.6 del test per l'operazione di centrifuga . . . . graco del throughput . . . . . . . . . . . . . . . . . . . graco dei pacchetti persi . . . . . . . . . . . . . . . . . graco del throughput sui pacchetti persi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 52 53 54 55 55 56 56 57 57 58 63 63 64 viii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Elenco delle tabelle 2.1 2.2 2.3 2.4 Il Il Il Il tipo tipo tipo tipo event_data in Contiki process in Contiki . . . timer in Contiki . . . . etimer in Contiki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 14 20 20 4.1 4.2 4.3 4.4 I registri nel LIS3LV02DL Il registro CTRL_REG1 . Il registro CTRL_REG2 . Il registro CTRL_REG3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 43 43 44 5.1 5.2 verica della latenza della connessione TCP . . . . . . . . . . . Il test mediante hping3 . . . . . . . . . . . . . . . . . . . . . . . 59 62 . . . . . . . . ix x Capitolo 1 Introduzione 1.1 Contesto Nell'ultimo decennio si sta assistendo a quella che i sociologi hanno denito rivoluzione digitale. Il progresso tecnologico in campo elettronico ha portato alla progressiva miniaturizzazione dei dispositivi e ne ha permesso l'abbattimento dei costi ed una larga diusione. Questo ha prodotto una serie di cambiamenti negli assetti sociali, politici ed economici mondiali che hanno portato alla società dell'informazione o new economy, dove riveste un ruolo fondamentale la produzione e lo scambio a livello globale dei servizi e delle informazioni. In questa situazione ha inuito pesantemente la diusione delle reti di telecomunicazione che ha determinato il progresso o la recessione delle realtà produttive locali. In futuro la connettività si allargherà sempre più agli oggetti che ci circondano secondo il modello di calcolo noto come Internet of things (o intelligenza ambientale) dove l'elaborazione delle informazioni è eettuata azionando oggetti della vita di tutti i giorni in cui sono stati integrati sistemi di calcolo. La ricerca in questa direzione è strettamente legata al calcolo distribuito, improntato alle comunicazioni senza li (come nel caso delle Wireless Sensor Network dove una rete di nodi è utilizzata per acquisire dati relativi ad un fenomeno monitorato all'interno di un ambiente) o ai sistemi di controllo e di automazione (per lo più wired), che trovano applicazione in campo industriale, militare e domotico. L'alto costo di queste tecnologie non ne ha però permesso la diusione all'interno della società e al contrario rischiano di divenire sempre più applicazioni di nicchia. Per questo motivo sempre più prodotti sono realizzati con connettività ethernet, che permette un abbattimento dei costi sia per l'azienda (che può acquisire risorse e know-how in maniera semplice vista la maturità di questo campo), che per il consumatore che vede convergere verso questa tecnologia anche altri servizi come VOIP, IPTV, sistemi d'intrattenimento e videocitofonia. Vista l'importanza che rivestiranno in futuro sarà necessario ricercare strumenti che permettano di abbattere tempi e costi di sviluppo. 2 1.2 Organizzazione Scopo di questo elaborato è analizzare il sistema operativo Contiki e riportare un caso studio che mostri le modalità di implementazione di applicazioni. Si realizzerà un sistema di controllo remoto per la rilevazione di vibrazioni ambientali, mediante l'utilizzo di un sensore d'accelerazione e di una interfaccia ethernet. Durante la fase di sviluppo si evidenzieranno le soluzioni intraprese e gli strumenti adottati, per fornire al lettore metodi e risorse a cui avvalersi nella creazione di sistemi integrati. 1.2 Organizzazione Nel capitolo 2 sarà analizzata la struttura del kernel ad eventi mostrando particolare attenzione al meccanismo di gestione e intercomunicazione dei processi. Nella parte nale sarà introdotto il modulo µIP utile per applicazioni su reti TCP/IP. Nel capitolo 3 sarà approfondita la struttura del sistema e saranno riportate le operazioni che hanno portato all'adattamento del codice per la piattaforma di sviluppo scelta. Nella parte nale sarà implementata una semplice applicazione d'esempio e ne sarà valutato il funzionamento. Nel capitolo 4 saranno mostrate le potenzialità di Contiki attraverso la realizzazione di un'applicazione di controllo ambientale mediante l'utilizzo di un sensore d'accelerazione. Nel capitolo 5 sarà riportato il test ed alcune considerazioni sul lavoro svolto insieme ad eventuali riessioni sui miglioramenti introducibili in futuro. Capitolo 2 Il sistema operativo Contiki Contiki è un sistema operativo multi-tasking per sistemi embedded orientati alla connettività wired e wireless, sui protocolli IP e 802.15.4. Deve il suo nome al termine Kon-tiki che identica la zattera con la quale lo scrittore norvegese Thor Heyerdahl arontò la traversata del Pacico per dimostrare la capacità dei popoli pre-colombiani di spostarsi dal sud America alla Polinesia. Contiki è mantenuto da Adam Dunkels dello Swedish Institute of Computer Science e nasce come estensione del ben noto modulo uIP che permette la gestione dello stack TCP/IP anche su dispositivi con risorse limitate. La compattezza lo rende particolarmente adatto alle reti di sensori wireless, dove le risorse a disposizione dei nodi sono solitamente limitate ed è particolarmente rilevante il consumo di potenza. Contiki è scritto nel linguaggio C ed è disponibile sotto licenza BSD per diverse architetture come Atmel AVR e MSP430. 2.1 Il supporto alla multiprogrammazione in Contiki I primi sistemi software non fornivano alcun supporto alla multiprogrammazione e solo in seguito all'introduzione del concetto di processo o task e di quello di schedulazione è stato possibile realizzare sistemi concorrenti di tipo timesharing il cui grado di interattività e produttività dipende dall'algoritmo di scheduling utilizzato. Tra i sistemi multitasking è possibile distinguere quelli cooperativi, in cui un processo lascia il controllo della CPU solo al termine della sua esecuzione (può potenzialmente monopolizzarne l'accesso e bloccare l'intero sistema), oppure quelli preemptive, in cui, grazie ad un interrupt di tipo hardware che temporizza il sistema, è possibile eettuare un cambio di contesto (context switch) anche prima del termine dell'esecuzione del processo. Un'ulteriore evoluzione è stata l'introduzione del multi-threading ovvero la possibilità di specicare all'interno di uno stesso processo più ussi di esecuzione concorrenti. In questo modo all'interno di uno stesso processo è possibile 4 2.1 Il supporto alla multiprogrammazione in Contiki denire diverse attività che condividono una sezione di codice e dati (variabili globali e identicatori statici) e possiedono ognuna un proprio usso di esecuzione descritto da uno stack per i record di attivazione, uno heap per le strutture allocate dinamicamente e una struttura per la memorizzazione dello stato dei registri utile per i context switch e i ritorni dalle procedure. L'utilizzo di queste strutture per ogni diverso thread comporta però un certo impiego di memoria e implica latenze durante le operazioni di cambio di contesto. Lo scopo della schedulazione e dell'avvicendamento dei processi è infatti l'ottimizzazione dell'utilizzo della CPU in modo da migliorarne la produttività; un processo rimane in esecuzione no al momento in cui non deve porsi in attesa di un evento (come ad esempio la conclusione di un'operazione I/O) e quando ciò accade il controllo della CPU, che si trova in uno stato di inattività, è ceduto (da parte del dispatcher) al processo scelto dallo scheduler (in base a determinati criteri) tra quelli caricati in memoria e pronti ad essere eseguiti. È possibile modellare il comportamento del processo come usso di eventi (e quindi come serie di transizioni di stato) all'interno di una macchina a stati niti [4]. In questo modo non è più necessario tenere traccia delle strutture per il contesto d'esecuzione di ogni thread (stack, registri, heap) in quanto può essere utilizzato un solo usso di esecuzione con un considerevole risparmio di risorse. Ciò comporta una semplicazione anche dal punto di vista dei meccanismi di sincronizzazione. Nei modelli basati sui thread infatti la concorrenza all'interno del processo è raggiunta sospendendo i thread bloccati su un'operazione I/O e riprendendo thread dierenti. Questo richiede un'attenta protezione dei dati condivisi mediante lock, oltre al fatto che la caratteristica di concorrenzialità pura tra thread (gura 2.1) richiede l'impiego di meccanismi di sincronizzazione (per esprimere eventuali relazioni di dipendenza) che potrebbero portare ad un codice meno comprensibile e con maggiore probabilità d'errore. Un sistema orientato agli eventi è invece composto da una sezione di rilevazione (che può avvenire mediante polling o interruzioni hardware) e da un event-dispatcher che provvede ad invocare le routine di gestione degli eventi (event-handlers) denite. Un'applicazione denisce tali procedure. Eventuali identicatori locali deniti nell'handler non saranno più accessibili nel momento in cui il controllo viene ritornato al dispatcher in quanto il record di attivazione della procedura che li contiene è disallocato. Essendo il usso del dispatcher unico, ogni event-handler non può denire statement esplicitamente bloccanti (come ad esempio wait) perché ciò ne provocherebbe l'interruzione del usso. L'esecuzione di un handler termina (ritornando il controllo al dispatcher) solo nel momento in cui tutti gli statement contenuti in esso sono stati eseguiti o quando è necessario attendere un evento (gli handlers non sono preemptive). Questo signica che l'utilizzo dei sistemi orientati agli eventi è giusticabile nel caso di processi che fanno largo uso delle interfacce I/O (I/O bound) in quanto permettono l'avvicendamento dei processi e quindi la massimizzazione del rendimento della CPU. Al contrario programmi con prevalenza 2.1 Il supporto alla multiprogrammazione in Contiki 5 d'elaborazione (CPU bound), come routine di calcolo matematico, rischiano di monopolizzare l'utilizzo del processore visto che l'esecuzione degli eventhandlers non è preemptive. È quindi possibile stabilire relazioni di dipendenza tra blocchi di statement realizzando un comportamento simile a quello che avveniva con l'utilizzo dei thread ma in maniera più robusta (visto che non sono previsti lock o altri meccanismi di sincronizzazione espliciti) e soprattutto senza la necessità di utilizzare le strutture dati per la gestione del controllo di usso necessarie in ogni thread. Per tali motivi in molti sistemi embedded si preferisce un modello Figura 2.1: Modello a thread e modello ad eventi. [6] orientato agli eventi a quello multi-thread classico (più adatto invece per sistemi general purpose come i pc). Ciò ha però complicato la scrittura del codice vista la necessaria organizzazione come macchina a stati niti. Contiki OS ha perciò introdotto una libreria per la gestione dei protothread che astrae il modello ad eventi permettendo uno stile di programmazione più vicino al modello threaded ed introducendo il costo di due byte per ogni protothread necessari a stabilire punti di recupero [3]. Quando si deve attendere la comparsa di un evento è necessario arrestare l'esecuzione e stabilire un punto di continuazione che permetta di rappresentare lo stato del sistema in quel punto e riprendere l'esecuzione appena l'evento si presenta. Per implementare questo meccanismo potremmo pensare di salvare lo stato dei registri del processore ma questo non garantirebbe la portabilità delle librerie in quanto strettamente dipendente dall'architettura in uso. In alternativa potremmo utilizzare le estensioni del compilatore gcc che permettono l'uso di goto a label del codice, ma non sarebbe garantita la conformità agli standard del linguaggio e nemmeno la compatibilità tra compilatori diversi.[1] I protothread sono implementati mediante il meccanismo delle local continuations che permettono di stabilire punti di recupero all'interno del codice mediante il costrutto switch del linguaggio ANSI C garantendo compatibilità verso tutti i compilatori a scapito della possibilità di utilizzarlo all'interno del codice. I due byte necessari alla gestione di ogni protothread corrispondono alla dimensione del dato di tipo unsigned short utilizzato per tenere traccia della linea di codice in cui il usso di esecuzione è stato interrotto. Ovviamente il maggior numero di istruzioni comporta un overhead in termini di cicli di 6 2.1 Il supporto alla multiprogrammazione in Contiki clock e di dimensione del codice. Le macro per la gestione dei protothread sono denite nel le pt.h. Se ne mostra il funzionamento riportando un esempio dell'utilizzo. int nome_protothread(struct pt *pt) { /* eventuali statement sopra al begin saranno eseguiti ogni volta che il pt verra` schedulato */ PT_BEGIN(pt); /* in questa sezione e` possibile inserire codice di qualsiasi tipo, eccetto switch */ /* blocco il flusso di esecuzione in attesa di un evento (condizione) */ PT_WAIT_UNTIL(pt, condizione); /* una volta che la condizione sara` verificata sara` possibile eseguire il codice successivo */ /* termino il protothread */ PT_END(pt); } Si espandono le macro per rendere visibile il meccanismo delle local continuations. int nome_protothread(struct pt *pt) { /* eventuali statement sopra al begin saranno eseguiti ogni volta che il pt verra` schedulato */ char PT_YIELD_FLAG = 1; LC_RESUME(pt->lc); /* in questa sezione e` possibile inserire codice di qualsiasi tipo, eccetto switch */ /* blocco il flusso di esecuzione in attesa di un evento (condizione) */ LC_SET(pt->lc); if(!condizione){ return PT_WAITING; } /* una volta che la condizione sara` verificata sara` possibile eseguire il codice successivo */ /* termino il protothread */ LC_END(pt->lc); PT_YIELD_FLAG = 0; PT_INIT(pt); return PT_ENDED; } A questo punto si espandono anche le local continuations per ottenere il codice completo. int nome_protothread(struct pt *pt) { /* eventuali statement sopra al begin saranno eseguiti 2.1 Il supporto alla multiprogrammazione in Contiki 7 ogni volta che il pt verra` schedulato */ char PT_YIELD_FLAG = 1; switch(pt->lc) { case 0: /* in questa sezione e` possibile inserire codice di qualsiasi tipo, eccetto switch */ /* blocco il flusso di esecuzione in attesa di un evento (condizione) */ pt->lc = __LINE__; case __LINE__: if(!condizione){ return PT_WAITING; } /* una volta che la condizione sara` verificata sara` possibile eseguire il codice successivo */ /* termino il protothread */ } PT_YIELD_FLAG = 0; pt->lc = 0; return PT_ENDED; } In questo modo la prima chiamata al protothread provoca l'esecuzione delle istruzioni all'interno del primo case e la restituzione del controllo al chiamante in caso di condizione non vericata. Non appena il protothread è invocato viene valutata nuovamente la condizione e nel caso sia vera è eseguito il blocco di istruzioni compreso tra il punto di interruzione ed il termine dello switch. Prima di ritornare il controllo al chiamante lo stato del protothread è resettato in modo da prepararlo ad eventuali esecuzioni future. Ciò signica che in un dato istante è possibile conoscere lo stato di un protothread (tra quello di esecuzione e quello exited) leggendo il valore del campo lc. Se lc vale zero il protothread non è in esecuzione (e quindi non potrà nemmeno trovarsi bloccato su di un evento). Gli svantaggi derivanti dall'utilizzo di questo meccanismo sono, come visto, l'impossibilità di utilizzare il costrutto switch e di accedere ad eventuali identicatori locali deniti in invocazioni precedenti, in quanto, una volta disallocato il record di attivazione corrispondente al protothread che li contiene, anche essi sono perduti. Nel primo caso il problema si risolve utilizzando il costrutto if. È possibile evitare di salvare sullo stack gli identicatori locali dichiarandoli statici (in questo modo verranno deniti all'interno della sezione dati del programma). Secondo il creatore Adam Dunkels [6], i vantaggi ottenuti utilizzando questo meccanismo sono evidenti: utilizzo di un solo stack, riduzione della complessità del codice di circa il 33% rispetto al modello ad eventi classico, occupazione di RAM di due byte per ogni protothread ed incremento di circa dieci cicli di clock per l'esecuzione del codice di gestione. Tale modello permette inoltre una 8 2.1 Il supporto alla multiprogrammazione in Contiki disposizione gerarchica dei protothread in quanto è possibile bloccarsi in attesa del completamento dell'esecuzione di eventuali protothread gli mediante apposite macro. Come anticipato, il modello ad eventi che costituisce Contiki può avere problemi nel caso di programmi che fanno uso intensivo della cpu. Il meccanismo dei protothread non gode della proprietà di prelazione (preemption) e questo potrebbe portare alla monopolizzazione del controllo della risorsa da parte della applicazione. Il sistema operativo TinyOS, nato per reti di sensori, utilizza un modello ad eventi la cui problematica è proprio quella appena descritta. L'intento di un sistema operativo di questo tipo è, però, la minimizzazione del consumo di energia e delle risorse in dispositivi dove non dovrebbero essere svolte elaborazioni intensive. L'impiego di Contiki trova invece applicazione in domini molto più ampi rispetto a TinyOS e ciò è dimostrato anche dal fatto che viene fornita una libreria per la gestione del multithreading preemptive. Essa si divide in una parte dipendente dall'architettura (mtarch.c e mtarch.h) ed una parte che l'astrae e fornisce al programmatore un'interfaccia di programmazione ben denita (mt.c e mt.h). La preemption è implementata utilizzando un timer interrupt. In tal modo viene restituito il controllo al kernel che può copiare lo stato dei registri del thread interrotto e riprenderne in seguito l'esecuzione. In questo caso ogni thread possiede un suo stack. Un thread è rappresentato dalla struttura mt_thread: struct mt_thread { int state; process_event_t *evptr; process_data_t *dataptr; struct mtarch_thread thread; }; dove il campo thread di tipo mtarch_thread comprende i registri della CPU che ne permettono l'esecuzione. struct mtarch_thread { unsigned short stack[MTARCH_STACKSIZE]; unsigned short *sp; void *data; void *function; }; Si riporta un esempio di utilizzo della libreria mt.h tratto dalla documentazione di Contiki: #include "contiki.h" #include "sys/mt.h" static char *ptr; PROCESS(multi_threading_process, "Multi-threading process"); 2.1 Il supporto alla multiprogrammazione in Contiki AUTOSTART_PROCESSES(&multi_threading_process); /*-------------------------------------------------------------*/ static void thread_func(char *str, int len) { ptr = str + len; mt_yield(); if(len) { thread_func(str, len - 1); mt_yield(); } ptr = str + len; } /*-------------------------------------------------------------*/ static void thread_main(void *data) { while(1) { thread_func((char *)data, 9); } mt_exit(); } /*-------------------------------------------------------------*/ PROCESS_THREAD(multi_threading_process, ev, data) { static struct mt_thread alpha_thread; static struct mt_thread count_thread; static struct etimer timer; static int toggle; PROCESS_BEGIN(); mt_init(); mt_start(&alpha_thread, thread_main, "JIHGFEDCBA"); mt_start(&count_thread, thread_main, "9876543210"); etimer_set(&timer, CLOCK_SECOND / 2); while(1) { PROCESS_WAIT_EVENT(); if(ev == PROCESS_EVENT_TIMER) { if(toggle) { mt_exec(&alpha_thread); toggle--; } else { mt_exec(&count_thread); toggle++; } puts(ptr); } etimer_set(&timer, CLOCK_SECOND / 2); } mt_stop(&alpha_thread); mt_stop(&count_thread); mt_remove(); } PROCESS_END(); 9 10 2.1 Il supporto alla multiprogrammazione in Contiki Se la libreria per la gestione del multithreading è strettamente dipendente dall'architettura, questo non è vero per quella dei protothread che può essere utilizzata anche al di fuori del sistema operativo Contiki. Il meccanismo dei protothread è, infatti, uno stile di programmazione che garantisce migliore leggibilità e permette l'applicazione dei costrutti orientati agli eventi come PT_WAIT_UNTIL. La schedulazione di un protothread avviene mediante invocazione della funzione in linguaggio C che ne costituisce la denizione. Tale operazione è gestita dall'applicazione che invoca ripetutamente il protothread riottenendo il controllo ogni qualvolta esso si mette in attesa di un evento o termina l'esecuzione del codice. Si riporta un esempio (tratto dalla documentazione uciale) dell'utilizzo dei protothread in Contiki. /** * This is a very scriptsize example that shows how to use * protothreads. The program consists of two protothreads that * wait for each other to toggle a variable. */ /* We must always include pt.h in our protothreads code. */ #include "pt.h" #include <stdio.h> /* For printf(). */ /* Two flags that the two protothread functions use. */ static int protothread1_flag, protothread2_flag; /** * The first protothread function. A protothread function * must always return an integer, but must never explicitly * return - returning is performed inside the protothread * statements. * The protothread function is driven by the main loop * further down in the code. */ static int protothread1(struct pt *pt) { /* A protothread function must begin with PT_BEGIN() which takes a pointer to a struct pt. */ PT_BEGIN(pt); /* We loop forever here. */ while(1) { /* Wait until the other protothread has set its flag. */ PT_WAIT_UNTIL(pt, protothread2_flag != 0); printf("Protothread 1 running\n"); /* We then reset the other protothread's flag, and set our own flag so that the other protothread can run. */ protothread2_flag = 0; protothread1_flag = 1; /* And we loop. */ } /* All protothread functions must end with PT_END() which takes a pointer to a struct pt. */ PT_END(pt); } /** * The second protothread function. This is almost the same * as the first one. */ static int protothread2(struct pt *pt) 2.2 La gestione dei processi { 11 PT_BEGIN(pt); while(1) { /* Let the other protothread run. */ protothread2_flag = 1; /* Wait until the other protothread has set its flag. */ PT_WAIT_UNTIL(pt, protothread1_flag != 0); printf("Protothread 2 running\n"); /* We then reset the other protothread's flag. */ protothread1_flag = 0; /* And we loop. */ } PT_END(pt); } /** * Finally, we have the main loop. The protothreads * are initialized and scheduled. First, however, we define the * protothread state variables pt1 and pt2, which hold the state * of the two protothreads. */ static struct pt pt1, pt2; int main(void) { /* Initialize protothread state variables with PT_INIT() */ PT_INIT(&pt1); PT_INIT(&pt2); /* * Then we schedule the two protothreads by repeatedly * calling their protothread functions and passing a pointer * to the protothread state variables as arguments. */ while(1) { protothread1(&pt1); protothread2(&pt2); } } 2.2 La gestione dei processi Un'applicazione che voglia sfruttare le funzionalità messe a disposizione dal sistema operativo Contiki, deve essere organizzata in processi che devono poter essere schedulati ed eseguiti dal kernel. L'esecuzione di un processo è conseguente alla comparsa di un evento ad esso destinato. In Contiki, ogni processo è un protothread a cui sono state associate informazioni di stato utili alla schedulazione. Il sistema mantiene una lista dei processi attivi ed un puntatore al processo in esecuzione. Dopo la fase di inizializzazione, il sistema avvia i processi eseguendoli uno dopo l'altro no alla comparsa di statement bloccanti. Ogni processo può generare eventi in favore degli altri. In questo modo una volta eseguiti tutti i processi per la prima volta è possibile risolvere gli eventi raccolti e risvegliare i processi destinatari che si trovano bloccati in attesa di una condizione. Il loro codice è eseguito no allo statement bloccante successivo ed eventuali nuovi eventi sono raccolti ed utilizzati per riprendere altri processi. Anche eventi esterni, come interruzioni hardware, possono inuenzare il sistema. 12 2.2 La gestione dei processi Il sottosistema del kernel per la gestione dei processi denisce le modalità di schedulazione ed intercomunicazione, stabilendo il concetto di evento e le funzionalità con la quale esso può essere gestito. Sono denite le seguenti tipologie di evento: PROCESS_EVENT_NONE PROCESS_EVENT_INIT PROCESS_EVENT_POLL PROCESS_EVENT_EXIT PROCESS_EVENT_SERVICE_REMOVED PROCESS_EVENT_CONTINUE PROCESS_EVENT_MSG PROCESS_EVENT_EXITED PROCESS_EVENT_TIMER PROCESS_EVENT_COM PROCESS_EVENT_MAX Ogni evento è rappresentato dalla struttura event_data. Questa contiene un campo process_event_t (unsigned char) per la tipologia dell'evento, un campo per i dati ausiliari, intesi come attributi o parametri associati all'evento, e un campo puntatore al processo destinatario, che può assumere valore PROCESS_BROADCAST nel caso si voglia noticare l'evento a tutti gli altri processi. Campo ev data p tipo process_event_t process_data_t *process Tabella 2.1: Il tipo event_data in Contiki Gli eventi sono raccolti in un vettore (array) di elementi di tipo event_data la cui dimensione è stabilita dalla costante PROCESS_CONF_NUMEVENTS a 32. Per la loro gestione si fa uso di un marcatore fevent che indica la posizione del primo evento disponibile e di un dato nevents che indica il numero di eventi disponibili (ovvero l'oset a partire dal primo elemento). In questo modo è possibile vedere la struttura in maniera circolare ed inserire elementi in seguito a (fevent + nevents) % PROCESS_CONF_NUMEVENTS solo se il numero di eventi non raggiunge la dimensione del vettore. Quando il numero di eventi in attesa di essere processati raggiunge la dimensione del vettore, si ha una situazione di soft panic in cui non è possibile aggiungere (e quindi nemmeno noticare) altri eventi e viene restituito al chiamante lo stato d'errore PROCESS_ERR_FULL. La gestione dei processi e degli eventi è denita in process.h e process.c. Si riporta un esempio dell'utilizzo dei processi tratto dalla documentazione uciale 2.2 La gestione dei processi di Contiki. #include "contiki.h" #include <stdio.h> /* For printf() */ /*-------------------------------------------------------------*/ PROCESS(hello_world_process, "Hello_world_process"); AUTOSTART_PROCESSES(&hello_world_process); /*-------------------------------------------------------------*/ PROCESS_THREAD(hello_world_process, ev, data) { PROCESS_BEGIN(); printf("Hello, world\n"); } PROCESS_END(); Si sostituiscono le macro per mostrare il meccanismo dei protothread. #include "contiki.h" #include <stdio.h> /* For printf() */ /*-------------------------------------------------------------*/ PROCESS_THREAD(hello_world_process, ev, data); struct process hello_world_process = {NULL, "Hello_world_process", process_thread_hello_world_process}; AUTOSTART_PROCESSES(&hello_world_process); /*-------------------------------------------------------------*/ PROCESS_THREAD(hello_world_process, ev, data) { PROCESS_BEGIN(); printf("Hello, world\n"); } PROCESS_END(); Ovvero: #include "contiki.h" #include <stdio.h> /* For printf() */ /*-------------------------------------------------------------*/ static PT_THREAD( process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)); struct process hello_world_process = {NULL, "Hello_world_process", process_thread_hello_world_process}; AUTOSTART_PROCESSES(&hello_world_process); /*-------------------------------------------------------------*/ static PT_THREAD( process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)) { PT_BEGIN(process_pt); printf("Hello, world\n"); } PT_END(process_pt); 13 14 2.2 La gestione dei processi L'applicazione è divisa in due sezioni: una di dichiarazione ed una di denizione dei processi. La dichiarazione di un processo consiste nella valorizzazione della struttura process che lo rappresenta all'interno del sistema. Essa contiene un campo puntatore al processo successivo (next), un campo stringa per il nome (name), uno per il protothread che lo descrive (thread), uno (pt) per la gestione delle local continuations, uno che ne descrive lo stato e, inne, uno (needspool) che segnala la presenza di eventi poll ad esso associati. Campo next name thread pt state needspoll tipo *process *char_data_t * thread pt unsigned char unsigned char Tabella 2.2: Il tipo process in Contiki Al momento della risoluzione della macro AUTOSTART_PROCESSES (specicata nei le autostart.h e autostart.c), il preprocessore raccoglie i processi, la cui posizione è denita staticamente, in una struttura dati (autostart_processes) che ne permette il recupero da parte del kernel. #define AUTOSTART_PROCESSES(...) \ struct process * const autostart_processes[] = {__VA_ARGS__, NULL} Il sistema Contiki è gestito mediante una funzione main, suddivisa in una sezione di booting ed una di schedulazione ed esecuzione dei processi. Dopo l'avvio, si provvede all'inizializzazione di eventuali interfacce (es: rs232, clock) presenti. In seguito viene inizializzato il modulo per la gestione dei processi valorizzandone le strutture di supporto. La fase di boot termina con l'avvio dei servizi di sistema e dei processi utente che sono eseguiti no alla comparsa del primo statement bloccante. A questo punto si procede alla schedulazione dei processi precedentemente avviati risolvendone gli eventi associati. /* inclusione delle librerie */ /* sottoscrizione dei servizi di sistem da far partire */ PROCINIT(&servizio1, &servizio2, &servizio3); Int main(void) { /* inizializzazione dell'hardware (es: interfacce, clock) */ /* inizializzazione del sottosistema per la gestione dei processi utente */ process_init(); 2.2 La gestione dei processi 15 /* inizializzazione ed avvio dei servizi del sistema */ procinit_init(); /* inizializzazione dei processi utente */ autostart_start(autostart_processes); /* il sistema e` stato avviato ovvero i processi che lo costituiscono sono stati inizializzati */ /* ora si procede con la loro schedulazione */ while(1) { process_run(); } } return 0; L'inizializzazione del sottosistema del kernel per la gestione dei processi, ottenuta mediante l'invocazione della funzione process_init, consiste nella valorizzazione a Null del puntatore alla lista dei processi e del puntatore al processo attualmente in esecuzione. I marcatori fevent e nevents sono invece inizializzati a zero. La successiva invocazione alla funzione procinit_init (denita in procinit.c e procinit.h) è utile ad invocare process_start sui processi che deniscono servizi di sistema e sono stati precedentemente sottoscritti mediante la macro PROCINIT. I processi utente sono sottoscritti mediante la macro AUTOSTART_PROCESSES. L'invocazione della funzione autostart_start (denita in autostart.c e autostart.h) provoca l'invocazione della funzione process_start (denita in process.c) per ogni processo della struttura autostart_processes. Questa aggiunge il processo alla lista dei processi presente nel kernel ed invoca la funzione process_post_synch per destinargli un evento sincrono di tipo PROCESS_EVENT_INIT. In questo modo evita che l'evento sia accodato nell'array di gestione (secondo l'approccio asincrono) e procede direttamente con l'esecuzione del processo destinatario. L'esecuzione termina non appena il usso del protothread si arresta su un wait o uno yield statement, o nel momento in cui il blocco di istruzioni è concluso. Gli eventi scatenati all'interno del protothread, sono accodati nel vettore degli eventi nel caso di chiamate asincrone o risolti direttamente nel caso di chiamate sincrone. Il sistema mette quindi a disposizione due primitive per lo scambio di messaggi per mezzo di eventi: una asincrona (process_post) ed una sincrona (process_post_synch). Per la gestione degli interrupt di sistema, Contiki fornisce la chiamata process_poll che va parametrizzata con il processo destinatario dell'evento. Questa funzione utilizza il campo needspoll della struttura che rappresenta il processo, per marcarlo e indicare la necessità di essere eseguito con maggiore priorità rispetto ai normali eventi post. A questo scopo il kernel 16 2.2 La gestione dei processi gestisce un ag poll_requested che indica in maniera globale la presenza o assenza di eventi poll. Si è visto che all'interno della funzione principale (main) che caratterizza il sistema, è presente un ciclo innito nella quale si procede con ripetute invocazioni alla funzione process_run. Questa funzione determina la presenza di eventi poll controllando lo stato del ag poll_requested e, in caso aermativo, invoca do_poll con la quale viene visitata la lista dei processi in modo da provvedere all'esecuzione di quelli in cui è marcato il campo needspoll (che viene poi disabilitato). In seguito il controllo è ritornato alla funzione process_run che può risolvere un evento di tipo post invocando la funzione do_event e restituire il controllo al main insieme al numero di eventi ancora in attesa (nell'array). Int process_run(void) { /* Risolvo gli eventi di tipo poll se presenti */ if(poll_requested) { do_poll(); } /* Risolvo un evento di tipo post dalla coda degli eventi */ do_event(); } return nevents + poll_requested; La funzione do_event controlla la presenza di eventi nella coda e in caso positivo estrae l'elemento nella posizione denita da fevent. Dall'elemento ricava l'evento, il processo destinatario ed i dati ausiliari che lo caratterizzano. Avendo estratto un elemento, aggiorna l'indice fevent ((fevent + 1) % PROCESS_CONF_NUMEVENTS) e decrementa il numero di eventi in attesa nevents. In seguito controlla la tipologia dell'evento, se è destinato a tutti i processi il destinatario dell'evento è PROCESS_BROADCAST. In questo caso è necessario noticare l'evento a tutti i processi invocandone l'esecuzione diretta. Per mitigare il costo di questa operazione si controlla la presenza di eventi di tipo poll in modo da poter rispondere in qualunque momento ad eventuali interrupt esterni. Al contrario, se l'evento è destinato ad un solo processo la notica dell'evento si limita all'esecuzione del processo destinatario. Per esecuzione di un processo si intende l'invocazione della funzione call_process parametrizzata dal processo da eseguire, dall'evento e dai dati ausiliari che ne hanno scaturito il risveglio dalla situazione di attesa in cui si trovava. La funzione call_process controlla che il processo sia ancora attivo e sia presente il protothread che ne descrive il comportamento. In questo caso il puntatore globale al processo in esecuzione viene associato al processo attuale e viene eseguito il codice del protothread. static void do_event(void) { static process_event_t ev; 2.2 La gestione dei processi 17 static process_data_t data; static struct process *receiver; static struct process *p; if(nevents > 0) { /* There are events that we should deliver. */ ev = events[fevent].ev; data = events[fevent].data; receiver = events[fevent].p; /* Since we have seen the new event, we move pointer upwards and decrese the number of events. */ fevent = (fevent + 1) % PROCESS_CONF_NUMEVENTS; --nevents; } } /* If this is a broadcast event, we deliver it to all events, in order of their priority. */ if(receiver == PROCESS_BROADCAST) { for(p = process_list; p != NULL; p = p->next) { /* If we have been requested to poll a process, we do this in between processing the broadcast event. */ if(poll_requested) { do_poll(); } call_process(p, ev, data); } } else { /* This is not a broadcast event, so we deliver it to the specified process. */ /* If the event was an INIT event, we should also update the state of the process. */ if(ev == PROCESS_EVENT_INIT) { receiver->state = PROCESS_STATE_RUNNING; } /* Make sure that the process actually is running. */ call_process(receiver, ev, data); } L'esecuzione del protothread termina quando all'interno di esso viene eseguita un'istruzione di tipo wait/wait-until, yield/yield-until, exit o nel momento in cui tutte le istruzioni sono state eseguite. In base alla tipologia dell'evento ed in base al risultato restituito dall'esecuzione, il sistema è in grado di discriminare le diverse situazioni. In particolare provvede alla terminazione di un processo nel momento in cui un altro processo ne abbia fatto esplicita richiesta mediante la generazione di un evento post di tipo PROCESS_EVENT_EXIT. Allo stesso modo durante l'esecuzione del protothread è possibile invocare volontariamente la macro PT_EXIT che causa la restituzione del valore PT_EXITED al chiamante. Inne, ogni qualvolta si conclude l'esecuzione delle istruzioni del protothread viene restituito il valore PT_ENDED. In questi tre casi si deve provvedere alla terminazione del processo mediante invocazione della funzione exit_process parametrizzata con il riferimento al processo mittente e destinatario della richiesta. La terminazione del processo consiste nella notica 18 2.2 La gestione dei processi sincrona di un evento di tipo PROCESS_EVENT_EXITED a tutti gli altri processi attivi. In questo modo eventuali processi in attesa della terminazione del processo sono sbloccati e possono continuare la loro esecuzione. Nel caso in cui la richiesta di terminazione sia avvenuta ad opera dello stesso processo (mittente = destinatario) è certo che tutte le istruzioni sono state eseguite. Ciò non è vero nel caso in cui la richiesta di terminazione sia stata generata da un processo diverso dal destinatario. Potrebbe infatti accadere che il processo vittima sia rimasto bloccato su istruzioni di tipo wait o yield e la terminazione debba essere ancora noticata. In questo caso viene eseguito nuovamente il protothread del processo da terminare che può aver denito un blocco di codice da eseguire nel caso dell'evento di tipo PROCESS_EVENT_EXITED. A questo punto è accertata la terminazione del usso di esecuzione del protothread associato al processo. Non rimane che eliminarlo dalla lista dei processi connettendo il suo predecessore con il suo successore. Le tipologie di evento viste sono utilizzate dal kernel nella comunicazione e schedulazione dei processi e per questo dovrebbero essere utilizzate con molta cautela. Per semplicare l'interazione tra processi Contiki permette la denizione di nuove tipologie di evento mediante la funzione process_alloc_event. Al momento dell'inizializzazione del sottosistema del kernel per la gestione dei processi, viene inizializzata una variabile statica last_event dello stesso tipo degli eventi (process_event_t) con l'indice massimo associato ad un evento. In questo modo nel momento in cui viene invocata la funzione process_alloc_event per creare un nuovo evento è suciente incrementare il valore di last_event ed assegnarlo ad una nuova variabile. Ora l'evento creato può essere utilizzato per le funzionalità di comunicazione di tipo post. Si riporta un esempio di gestione degli eventi tratto da [13] che implementa un processo controllore dello stato di un sensore che lancia un nuovo evento ad ogni nuova lettura, dando modo ad un altro processo di poter ottenere i dati inviati. #include "contiki.h" #include "dev/leds.h" #include <stdio.h> /* For printf() */ /* Driver Include */ #include "ds1722.h" /* Variables: the application specific event value */ static process_event_t event_data_ready; /*-------------------------------------------------------------*/ /* We declare the two processes */ PROCESS(temp_process, "Temperature process"); PROCESS(print_process, "Print process"); /* We require the processes to be started automatically */ AUTOSTART_PROCESSES(&temp_process, &print_process); /*-------------------------------------------------------------*/ /* Implementation of the first process */ PROCESS_THREAD(temp_process, ev, data) { // variables are declared static to ensure their values are // kept between kernel calls. static struct etimer timer; static int count = 0; static int average, valid_measure; 2.2 La gestione dei processi // those 3 variables are recomputed at every run, therefore // it is not necessary to declare them static. int measure; uint8_t msb, lsb; // any process mustt start with this. PROCESS_BEGIN(); /* allocate the required event */ event_data_ready = process_alloc_event(); /* Initialize the temperature sensor */ ds1722_init(); ds1722_set_res(10); ds1722_sample_cont(); average = 0; // set the etimer module to generate an event in one second. etimer_set(&timer, CLOCK_CONF_SECOND/4); while (1) { // wait here for the timer to expire PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); leds_toggle(LEDS_BLUE); // do the process work msb = ds1722_read_MSB(); lsb = ds1722_read_LSB(); measure = ((uint16_t)msb) << 2; measure += (lsb >> 6) & 0x03; average += measure; count ++; if (count == 4) { // average the sum and store valid_measure = average >> 2; // reset variables average = 0; count = 0; } // post an event to the print process // and pass a pointer to the last measure as data process_post(&print_process, event_data_ready, &valid_measure); // reset the timer so it will generate another event etimer_reset(&timer); } // any process must end with this, // even if it is never reached. PROCESS_END(); } /*-------------------------------------------------------------*/ 19 20 2.3 La temporizzazione degli eventi: i timer /* Implementation of the second process */ PROCESS_THREAD(print_process, ev, data) { PROCESS_BEGIN(); while (1) { // wait until we get a data_ready event PROCESS_WAIT_EVENT_UNTIL(ev == event_data_ready); // display it printf("temperature = %u.%u\n", (*(int*)data)>>2, ((*(int*)data)&0x3)*25); } } PROCESS_END(); 2.3 La temporizzazione degli eventi: i timer Il timer è un meccanismo utile alla pianicazione degli eventi. Grazie ad esso è infatti possibile denire intervalli temporali allo scadere dei quali viene prodotto un evento. In Contiki la gestione dei timer è denita nei le etimer.c, etimer.h, timer.c e timer.h. Un timer è rappresentato mediante la struttura timer contenente un campo start ed un campo interval, entrambi di tipo clock_time_t (unsigned short). Il campo start rappresenta l'istante di tempo in cui il timer è stato avviato, mentre interval la durata, ovvero il tempo per cui esso è considerato attivo. Una volta scaduto, il timer produ- Campo start interval tipo clock_time_t clock_time_t Tabella 2.3: Il tipo timer in Contiki ce un evento per il processo a cui è associato. Il sistema Contiki gestisce i timer mediante una struttura etimer contenente un campo timer, un campo puntatore al processo interessato e un campo puntatore all'elemento etimer successivo che è utile per gestire una lista dei timer attivi. Il modulo etimer Campo timer next p tipo timer *etimer *process Tabella 2.4: Il tipo etimer in Contiki è gestito da un apposito processo etimer_process (denito in etimer.c) che 2.4 Il modulo µIP 21 è invocato nel momento in cui un processo termina (noticando in broadcast un evento PROCESS_EVENT_EXITED) o quando un processo eettua modiche alla lista dei timer mediante inserimenti, modiche e/o rimozioni. Quando etimer_process viene eseguito, controlla la presenza di un evento PROCESS_EVENT_EXITED e, in caso aermativo, provvede a rintracciare il timer associato al processo terminato per rimuoverlo dalla lista. In seguito, scorre nuovamente la lista in cerca di processi il cui timer risulta scaduto (un timer diviene scaduto nel momento in cui la dierenza tra il tempo di sistema attuale e il tempo di inizio del timer supera l'intervallo di durata) e per ognuno di essi genera un evento post di tipo PROCESS_EVENT_TIMER. Al momento della notica dell'evento l'array potrebbe essere pieno; in questo caso viene ritornato l'errore PROCESS_ERR_FULL e viene richiesta la riesecuzione di etimer_process mediante una chiamata sincrona (poll). In alternativa la pubblicazione dell'evento potrebbe essere stata compiuta correttamente; in questo caso il timer viene svincolato dal processo (impostando il campo process a PROCESS_NONE), rimosso dalla lista dei timer attivi e aggiornato (incrementato) il tempo di sistema. Per utilizzare i timer (si veda la sezione A.4 in appendice) è necessario indicare il processo etimer_process tra i servizi di sistema da eseguire (macro PROCINIT all'interno del main). 2.4 Il modulo µIP Il modulo µIP implementa lo stack TCP/IP in una versione ridotta che può gestire una sola interfaccia di rete. È composto da una parte dipendente dall'architettura dove sono denite ed ottimizzate funzionalità computazionalmente costose, come ad esempio il calcolo del checksum. Vi è poi una parte indipendente dall'architettura che costituisce l'interfaccia di programmazione (API). µIP può essere utilizzato all'interno di Contiki eseguendo come servizio di sistema il processo tcpip_process (in maniera analoga a quanto visto per i timer). Al programmatore sono fornite due diverse alternative. La prima soluzione prevede l'utilizzo esplicito dell'interfaccia dello stack µIP, che ha il compito di raccogliere eventi e noticarli invocando la funzione C in cui l'applicazione è denita. Contiki fornisce una soluzione alternativa che astrae le funzionalità del modulo µIP e fornisce al programmatore un'interfaccia di programmazione simile a quella delle Berkeley Socket ma basata sui protothread (e per questo chiamata ProtoSockets library). Si riporta di seguito un esempio dell'utilizzo delle protosocket tratto dalla documentazione uciale. Si rimanda alla documentazione in appendice e al sito internet del progetto Contiki per ulteriori approfondimenti. /* * This is a scriptsize example of how to write a TCP server using 22 2.4 Il modulo µIP * Contiki's protosockets. It is a simple server that accepts * one line of text from the TCP connection, and echoes back * the first 10 bytes of the string, and then closes the * connection. * The server only handles one connection at a time. * */ #include <string.h> /* * We include "contiki-net.h" to get all network definitions * and declarations. */ #include "contiki-net.h" /* * We define one protosocket since we've decided to only * handle one connection at a time. If we want to be able to * handle more than one connection at a time, each parallell * connection needs its own protosocket. */ static struct psock ps; /* * We must have somewhere to put incoming data, and we use * a 10 byte buffer for this purpose. */ static char buffer[10]; /*-------------------------------------------------------------*/ /* * A protosocket always requires a protothread. The protothread * contains the code that uses the protosocket. We define the * protothread here. */ static PT_THREAD(handle_connection(struct psock *p)) { /* * A protosocket's protothread must start with a * PSOCK_BEGIN() with the protosocket as argument. * * Remember that the same rules as for protothreads apply: * do NOT use local variables unless you are very sure what * you are doing! * Local (stack) variables are not preserved when the * protothread blocks. */ PSOCK_BEGIN(p); /* * We start by sending out a welcoming message. The message * is sent using the PSOCK_SEND_STR() function that sends a * null-terminated string. */ PSOCK_SEND_STR(p, "Welcome, please type something and press return.\n"); /* * Next, we use the PSOCK_READTO() function to read incoming * data from the TCP connection until we get a newline * character. The number of bytes that we actually keep is 2.4 Il modulo µIP * dependant of the length of the input buffer that we use. * Since we only have a 10 byte buffer here (the buffer[] * array) we can only remember the first 10 bytes received. * The rest of the line up to the newline simply is discarded */ PSOCK_READTO(p, '\n'); /* * And we send back the contents of the buffer. * The PSOCK_DATALEN() function provides us with the length * of the data that we've received. Note that this length * will not be longer than the input buffer we're using. */ PSOCK_SEND_STR(p, "Got the following data: "); PSOCK_SEND(p, buffer, PSOCK_DATALEN(p)); PSOCK_SEND_STR(p, "Good bye!\r\n"); /* * We close the protosocket. */ PSOCK_CLOSE(p); /* * And end the protosocket's protothread. */ PSOCK_END(p); } /*-------------------------------------------------------------*/ /* * We declare the process. */ PROCESS(example_psock_server_process, "Example protosocket server"); /*-------------------------------------------------------------*/ /* * The definition of the process. */ PROCESS_THREAD(example_psock_server_process, ev, data) { /* * The process begins here. */ PROCESS_BEGIN(); /* * We start with setting up a listening TCP port. Note how * we're using the HTONS() macro to convert the port number * (1010) to network byte order as required by the * tcp_listen() function. */ tcp_listen(HTONS(1010)); /* * We loop for ever, accepting new connections. */ while(1) { /* * We wait until we get the first TCP/IP event, which * probably comes because someone connected to us. */ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); 23 24 2.4 Il modulo µIP /* * If a peer connected with us, we'll initialize the * protosocket with PSOCK_INIT(). */ if(uip_connected()) { /* * The PSOCK_INIT() function initializes * the protosocket and binds the input * buffer to the protosocket. */ PSOCK_INIT(&ps, buffer, sizeof(buffer)); /* * We loop until the connection is aborted, closed, * or times out. */ while(!(uip_aborted() || uip_closed() || uip_timedout())) { /* * We wait until we get a TCP/IP event. Remember * that we always need to wait for events inside * a process, to let other processes run while we * are waiting. */ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); } } } } /* * Here is where the real work is taking place: * we call the handle_connection() protothread * that we defined above. This protothread uses * the protosocket to receive the data that we * want it to. */ handle_connection(&ps); /* * We must always declare the end of a process. */ PROCESS_END(); Capitolo 3 Sviluppo di sistemi integrati Lo sviluppo di sistemi hardware e software integrati (embedded) richiede la conoscenza approfondita dell'architettura hardware su cui il software sarà eseguito. L'utilizzo di un sistema operativo, anche minimale, semplica molto la realizzazione delle applicazioni perché permette di operare ad un livello di astrazione più elevato e diminuire i tempi di accesso alla tecnologia e quindi i costi di progetto. La comunità degli sviluppatori di Contiki ha provveduto al porting del sistema su piattaforme hardware dierenti, in modo da facilitarne l'impiego da parte di programmatori e integratori di sistemi. Tuttavia esso risulta decisamente essibile ed è possibile adattare piattaforme esistenti in modo da coprirne altre non ancora supportate tramite la modica di alcuni le strettamente legati all'hardware. La portabilità del codice è resa possibile dall'utilizzo della versione standard del linguaggio C che, insieme alla tecnica della cross-compilazione, permette di produrre codice binario per architetture dierenti. 3.1 La piattaforma di sviluppo La piattaforma di sviluppo scelta per l'utilizzo di Contiki è la Crumb644 di Chip45. Essa è costituita da un microcontrollore ATMEL Atmega644P con architettura AVR, un controller ethernet CP2201 ed un convertitore USB UART CP2102 entrambe da Silicon Laboratories [18]. I motivi che hanno portato a questa decisione sono principalmente di compatibilità, visto che l'architettura AVR trova larga diusione in campo accademico e sono rilasciati gratuitamente strumenti di sviluppo anche per sistemi linux. Inoltre esistono già tentativi di porting parziale di Contiki, soprattutto per quanto riguarda il modulo µIP [15][20] e ciò permette di ottenere in maniera relativamente veloce la documentazione e le conoscenze necessarie alla implementazione di applicazioni di rete su questa piattaforma. La programmazione del microcontrollore può avvenire mediante programmatore standard ISP AVR oppure tramite l'intefaccia USB che sfrutta il bootloader precaricato dal produttore e l'applicazione gratuita 26 3.2 Utilizzare Contiki Figura 3.1: La board Crumb644 di Chip45. [15] messa a disposizione da Chip45 per il caricamento delle applicazioni. La seconda soluzione è possibile solo in ambienti Windows e solo previa istallazione del driver per la gestione dell'interfaccia seriale. Questa soluzione comporta spesso problemi di compatibilità e non sempre è assicurato il corretto funzionamento. La scelta è perciò ricaduta su un programmatore ISP a basso costo, l'USBasp [16]. Questo programmatore è direttamente compatibile con i sistemi MAC e Unix ed in particolare con il noto uploader AVRdude [17]. 3.2 Utilizzare Contiki Contiki è scaricabile dal sito uciale del progetto. È disponibile nella versione macchina virtuale per VMWare chiamata Instant Contiki costituita da un sistema Ubuntu Linux e da tool di sviluppo e compilazione tra cui l'IDE Eclipse. In alternativa è possibile scaricare il codice sorgente strutturato in cartelle. Il progetto è costituito da: apps/ contiene alcune applicazioni utilizzabili nel sistema come browser, webserver, client twitter. Alcune presentano anche un'interfaccia graca che può essere visualizzata in vecchi sistemi come Apple2 e Commodore64. core/ denisce il kernel del sistema e le librerie per la gestione delle interfacce di rete e graca in maniera indipendente dalla architettura. Contiene le cartelle: core/cfs/ 3.2 Utilizzare Contiki 27 contiene le librerie per la gestione del le-system chiamato Contiki-le-system (Cfs). core/ctk CTK sta per Contiki toolkit. Denisce le librerie per la gestione dell'interfaccia graca. core/dev/ Fornisce funzionalità per accedere ad elementi hardware come led, pulsanti e sensori. core/lib/ contiene librerie indipendenti dalla architettura e utili ad esempio alla manipolazione di liste o alla generazione di numeri casuali. core/loader/ contiene le librerie per la gestione del loader dinamico ovvero delle funzionalità di loading dinamica di applicazioni che risulta utile nelle reti di sensori per propagare e caricare a run time un'applicazione. core/net/ contiene le librerie per la gestione dei moduli Rime e µIP. core/sys/ contiene le librerie per la gestione dei processi, thread, protothread, local continuations, timer di sistema, stima del consumo energetico,... cpu/ denisce i le dipendenti dalla architettura della CPU. Contiene diverse cartelle, una per ogni architettura supportata: 6502/ cpu/arm/ cpu/avr/ cpu/cc2430/ cpu/mc1322x/ cpu/msp430/ cpu/native/ cpu/x86/ cpu/z80/ doc/ 28 3.3 Il sistema di build di Contiki contiene i le delle documentazione di Contiki che sono processati dal generatore automatico di documentazione Doxygen. examples/ contiene applicazioni d'esempio compilabili con il sistema. platform/ contiene le piattaforme di sviluppo in cui Contiki può essere applicato. tools/ contiene strumenti esterni al sistema Contiki e utili ad esempio alla simulazione o al controllo delle reti di sensori. 3.3 Il sistema di build di Contiki Contiki rende semplice il processo di compilazione del sistema per piattaforme hardware dierenti, dando la possibilità di parametrizzare il comando make con l'architettura target su cui il sistema sarà eseguito. L'invocazione del make non seguita dal parametro TARGET provoca la compilazione del sistema operativo insieme all'applicazione scelta, nella forma di eseguibile per il sistema in cui avviene la compilazione (che viene detta interfaccia nativa). In questo modo è possibile simulare il funzionamento del software prima della programmazione del microcontrollore. Per evitare di dover specicare nuovamente la piattaforma target, è possibile impostare al momento dell'attuale invocazione l'opzione savetarget che permette di sovrascrivere il target di default (inizialmente l'interfaccia nativa) con quello attualmente selezionato. Il sistema di build utilizza vari makele che sono specicati in posizioni dierenti. Un makele presente nella cartella del progetto, è scritto dal programmatore della applicazione e deve essere il più semplice possibile, limitandosi alla specica della posizione del progetto Contiki e delegando ad altri makele il compito della compilazione vera e propria. Si riporta come esempio il makele del progetto Hello-world presente nella cartella example. CONTIKI_PROJECT = hello-world all: $(CONTIKI_PROJECT) CONTIKI = ../.. include $(CONTIKI)/Makefile.include Il makele specica la posizione del progetto contiki ed include il makele globale Makele.include che si trova nella root di Contiki. Esso contiene la denizione dei le del core del sistema e quando eseguito include il makele Makele.$(TARGET) che si trova in platform/$(TARGET)/ e contiene 3.4 Preparazione dell'ambiente di sviluppo 29 la lista dei le dipendenti dalla piattaforma (denita dalla variabile CONTIKI_TARGET_SOURCEFILES) che devono essere aggiunti al sistema Contiki. A sua volta è incluso il makele Makele.$(CPU) denito in cpu/$(CPU)/ che contiene i le dipendenti dalla architettura della CPU e può contenere espressioni condizionali che permettono l'utilizzo di diversi compilatori. Il makele della CPU può anche essere inglobato in quello della piattaforma. 3.4 Preparazione dell'ambiente di sviluppo Si riportano in questa sezione le operazioni da seguire per preparare l'ambiente di sviluppo. 3.4.1 Windows Atmel fornisce gratuitamente un ambiente di sviluppo integrato (AVRStudio) ed un compilatore. Essi sono scaricabili direttamente dal sito internet [21]. Per la programmazione mediante usbasp è necessario istallare il driver usb per windows [16]. Un programma per windows compatibile con il programmatore usbasp è eXtreme Burner. Per poter sfruttare la comunicazione USB-UART del microcontrollore (anche come debugger) è necessario istallare il driver scaricabile dal sito di Silicon Laboratories. 3.4.2 Linux Per sviluppare applicazioni per la piattaforma Atmel AVR è necessario istallare i pacchetti: binutils-avr, gcc-avr e avr-libc [14]. Per l'utilizzo del programmatore usbasp può essere utilizzato AVRdude. È disponibile anche il tool simulavr per la simulazione ed avr-gdb per il debugging. Per la gestione della comunicazione USB/UART (utile per il debugging) è necessario istallare microcom o picocom. 3.4.3 Instant Contiki Nel caso si voglia utilizzare un ambiente già pronto si può caricare la macchina virtuale Instant Contiki che contiene al suo interno l'IDE Eclipse e i pacchetti per la compilazione dei sorgenti per diverse piattaforme. 3.5 Contiki e la Crumb644 Dal sito web di Chip45 [15] è scaricabile un'applicazione d'esempio che permette l'esecuzione di un webserver minimale che sfrutta in maniera diretta il 30 3.5 Contiki e la Crumb644 modulo µIP di Contiki. Per acquisire familiarità con gli strumenti di sviluppo è stato compilato il progetto ed è stato programmato sulla board. Prima della compilazione è stato necessario modicare alcune impostazioni nel makele in modo da adattarlo al programmatore usbasp e alla relativa porta usb. L'indirizzo IP è denito all'interno del driver dell'interfaccia ethernet (le 2200.c). Il funzionamento dell'applicazione e della board sono stati vericati eettuando un ping (si veda il protocollo ICMP) all'indirizzo IP specicato e successivamente è stata valutata la correttezza della applicazione webserver mediante un semplice browser. Oltre ad una homepage introduttiva il webserver mostra Figura 3.2: Il webserver minimale di Stefan Perzborn [19] statistiche riguardanti i le e la rete e permette la gestione (accensione e spegnimento) di uno dei due diodi led presenti sulla scheda. Le applicazioni costruite sul sistema non godono di alcun criterio di modularità nè tantomento di schedulazione dei processi in quanto tutto è gestito dalla funzione main. Per sfruttare le potenzialità messe a disposizione da Contiki è bene poterlo compilare per la piattaforma Crumb644 ma questa non risulta tra le supportate dalla versione 2.4 attualmente disponibile. Per questo motivo è stato necessario adattare il sistema standard alla nostra board integrandolo con le parti platform-specic incluse nel progetto webserver. 3.5.1 Creazione di una nuova piattaforma Dopo aver scaricato Contiki dal sito uciale è stata aperta la cartella platform e vi è stata creata una nuova cartella chiamata avr-chip45. A questo punto considerando piattaforme simili come la avr-rcb e la avr-raven è stato scritto il makele ed il main. Makele CONTIKI_TARGET_DIRS = . dev net loader CONTIKI_CORE=contiki-chip45-main CONTIKI_TARGET_MAIN = ${CONTIKI_CORE}.o CONTIKI_TARGET_SOURCEFILES += contiki-chip45-main.c CONTIKI_TARGET_SOURCEFILES += CP2200.c net_service.c 3.5 Contiki e la Crumb644 CONTIKIAVR=$(CONTIKI)/cpu/avr CONTIKIBOARD=. CONTIKI_PLAT_DEFS = -DF_CPU=20000000UL -DAUTO_CRC_PADDING=2 MCU=atmega644 # opzione avr-dude AVRDUDE_OPTIONS=-V include $(CONTIKIAVR)/Makefile.avr Main #include #include #include #include #include <avr/pgmspace.h> <avr/fuse.h> <avr/eeprom.h> <avr/io.h> <stdio.h> #include #include #include #include #include #include #include #include "lib/mmem.h" "loader/symbols-def.h" "loader/symtab.h" "contiki.h" "contiki-net.h" "contiki-lib.h" "dev/rs232.h" "dev/net_service.h" #define #define #define #define #define ALE 3 nWR 4 nRD 5 nCS 6 nLRST 7 FUSES = { }; .low = 0xe2, .high = 0x99, .extended = 0xff, // sottoscrivo al sistema i servizi da far partire PROCINIT(&etimer_process, &tcpip_process, &net_process); void init_lowlevel(void) { rs232_init(RS232_PORT_0, USART_BAUD_19200, USART_PARITY_NONE | USART_STOP_BITS_1 | USART_DATA_BITS_8); rs232_redirect_stdout(RS232_PORT_0); } int main(void) { uip_ipaddr_t ipaddr; /****************** Booting ***********************/ /* setto l'interfaccia Input/Output */ DDRA = 0x00; 31 32 3.5 Contiki e la Crumb644 DDRB = 0xFF; DDRC = 0xFF; PORTA = 0x00; PORTB = 0x00; PORTC = 0xF7; PORTC PORTC PORTC PORTC PORTC |= |= &= &= |= (1 << nRD); (1 << nWR); ~(1 << ALE); ~(1 << nCS); (1 << nLRST); // inizializzo l'interfaccia RS232 init_lowlevel(); /* Clock */ clock_init(); /* inizializzo il sottosistema per la gestione dei processi */ process_init(); // specifico la netmask uip_ipaddr(&ipaddr, 255,0,0,0); uip_setnetmask(&ipaddr); // indirizzo ip del modulo uip_ipaddr(&ipaddr, 10,0,0,100); uip_sethostaddr(&ipaddr); // indirizzo ip del gateway uip_ipaddr(&ipaddr, 10,0,0,10); uip_setdraddr(&ipaddr); /* Register initial processes */ procinit_init(); /* Autostart processes */ autostart_start(autostart_processes); /******************** Scheduling ************************/ while(1) { process_run(); } } return 0; 3.5.2 Modica dei le dipendenti dall'hardware A questo punto è stata aperta la cartella cpu/avr/dev dove sono presenti i le dipendenti dall'architettura ed è stata introdotta la Crumb644. In particolare sono stati modicati i le rs232.c e rs232.h che deniscono la gestione dell'interfaccia seriale e successivamente il le clock-avr.h. Si riportano le modiche introdotte. Interfaccia seriale (rs232.h) Nel le rs232.h è stato aggiunto il supporto al microcontrollore ATmega644 denendo la libreria da cui importare le impostazioni. Si riporta il blocco di 3.5 Contiki e la Crumb644 33 codice aggiunto. #elif defined (__AVR_ATmega644__) #include "dev/rs232_atmega644.h" #else Interfaccia seriale (rs232.c) Nel le rs232.c è stato introdotto il microcontrollore ATmega644 denendone la USART. Si riporta il blocco di codice aggiunto. #elif defined(__AVR_ATmega644__) // una sola USART typedef struct { volatile uint8_t * UDR; volatile uint8_t * UBRRH; volatile uint8_t * UBRRL; volatile uint8_t * UCSRB; volatile uint8_t * UCSRC; volatile uint8_t txwait; int (* input_handler)(unsigned char); } rs232_t; static rs232_t rs232_ports[1] = { // UART0 { &UDR0, &UBRR0H, &UBRR0L, &UCSR0B, &UCSR0C, 0, NULL }, }; // il microcontrollore ATmega644 possiede solo la usart0 /*-------------------------------------------------------------*/ ISR(USART0_TX_vect) { rs232_ports[RS232_PORT_0].txwait = 0; } /*-------------------------------------------------------------*/ ISR(USART0_RX_vect) { unsigned char c; c = *(rs232_ports[RS232_PORT_0].UDR); if(rs232_ports[RS232_PORT_0].input_handler != NULL) { rs232_ports[RS232_PORT_0].input_handler(c); } } #else Congurazione dell'interfaccia seriale (rs232_atmega644.h) Il le rs232.h denisce le caratteristiche della usart del microcontrollore mediante l'inclusione del le rs232_atmega644.h. Questo le è il risultato della modica del le rs232_atmega1284.h. Il microcontrollore ATmega644 ha infatti una sola usart. Le due porte presenti nell'AT1284 sono state collegate all'unica USART. #define RS232_PORT_0 0 #define RS232_PORT_1 0 34 3.5 Contiki e la Crumb644 Successivamente sono state inserite le velocità di trasmissione per la frequenza di 20 Mhz copiando quelle per la frequenza a 8Mhz. #elif MCU_MHZ == 20 #define USART_BAUD_2400 207 #define USART_BAUD_4800 103 #define USART_BAUD_9600 51 #define USART_BAUD_14400 34 #define USART_BAUD_19200 64 //25 #define USART_BAUD_28800 16 #define USART_BAUD_38400 12 #define USART_BAUD_57600 8 #define USART_BAUD_76800 6 #define USART_BAUD_115200 3 #define USART_BAUD_230400 1 #define USART_BAUD_250000 1 #define USART_BAUD_500000 0 #else Clock (clock-avr.h) È stato aggiunto il microcontrollore at644 a quelli già presenti. #elif defined (__AVR_ATmega1284P__) || (__AVR_AT90USB1287__) || (__AVR_ATmega1281__) || (__AVR_ATmega644__) Successivamente è stato analizzato il driver per la gestione della CP2201 (controller di rete di Silicon Laboratories) presente nel progetto webserver di Stefan Perzborn e nella versione modicata presente nel progetto di Stefan Frings. È stato importata la versione di Frings (le CP2201.c e CP2201.h) nella cartella dev (in cpu/avr) e tenendo conto del driver per Realtek RTL8019 presente nella stessa cartella è stato implementato un processo gestore che può essere eseguito come servizio di sistema all'interno di Contiki. Gestore dell'interfaccia di rete (net-service.h) #ifndef __NET_SERVICE_H__ #define __NET_SERVICE_H__ #include "contiki.h" PROCESS_NAME(net_process); u8_t cp2201_output(void); #endif /* __NET_SERVICE_H__ */ Gestore dell'interfaccia di rete (net-service.c) #include "contiki-net.h" #include "CP2200.h" #include "net/uip-neighbor.h" #define BUF ((struct uip_eth_hdr *)&uip_buf[0]) 3.5 Contiki e la Crumb644 PROCESS(net_process, "cp2201 driver"); /*-------------------------------------------------------------*/ u8_t cp2201_output(void) { uip_arp_out(); network_device_send(); return 0; } /*-------------------------------------------------------------*/ static void pollhandler(void) { process_poll(&net_process); uip_len = network_device_read(); if(uip_len > 0) { #if UIP_CONF_IPV6 if(BUF->type == htons(UIP_ETHTYPE_IPV6)) { uip_neighbor_add(&IPBUF->srcipaddr, &BUF->src); tcpip_input(); } else #endif /* UIP_CONF_IPV6 */ if(BUF->type == htons(UIP_ETHTYPE_IP)) { uip_len -= sizeof(struct uip_eth_hdr); tcpip_input(); } else if(BUF->type == htons(UIP_ETHTYPE_ARP)) { uip_arp_arpin(); /* If the above function invocation resulted in data that should be sent out on the network, the global variable uip_len is set to a value > 0. */ if(uip_len > 0) { network_device_send(); } } } } /*-------------------------------------------------------------*/ PROCESS_THREAD(net_process, ev, data) { PROCESS_POLLHANDLER(pollhandler()); PROCESS_BEGIN(); init_CP2200(); tcpip_set_outputfunc(cp2201_output); process_poll(&net_process); PROCESS_WAIT_UNTIL(ev == PROCESS_EVENT_EXIT); } PROCESS_END(); 35 36 3.6 La prima applicazione in Contiki 3.6 La prima applicazione in Contiki Per vericare le modiche fatte è stato necessario realizzare una semplice applicazione. In questo modo sono state apprese le modalità di implementazione di applicazioni per il sistema e la gestione delle interfacce input/output del microcontrollore. È stata aperta la cartella examples ed è stata creata una cartella helloworld. All'interno di questa è stato creato un makele secondo il modello indicato nella sezione 3.3, un le hello-world.c contenente i processi e un makele più generale che denisce i comandi clean e load. In questo modo è possibile compilare il progetto invocando make, caricarlo tramite l'uploader avrdude ed il programmatore usbasp invocando make load, ed eliminare i le compilati mediante il comando make clean. Ciò velocizza signicativamente il processo di building. Il makele di progetto (Makele) all: make -f Makefile.helloworld TARGET=avr-chip45 hello-world.elf avr-objcopy -O ihex -R .eeprom -R .fuse hello-world.elf \ hello-world.hex avr-size -C --mcu=atmega644 hello-world.elf clean: make -f Makefile.helloworld TARGET=avr-chip45 clean rm symbols.c symbols.h hello-world.elf hello-world.hex rm -rf obj_avr-chip45 load: avrdude -P /dev/ttyUSB0 -p m644p -c usbasp -e -U \ flash:w:hello-world.hex Il makele dell'applicazione (Makele.helloworld) CONTIKI_PROJECT = helloworld all: $(CONTIKI_PROJECT) CONTIKI = ../.. include $(CONTIKI)/Makefile.include Si veda la sezione 3.3 per ulteriori informazioni sul funzionamento del meccanismo di build mediante makele. L'applicazione d'esempio (hello-world.c) #include "contiki.h" #include "dev/rs232.h" /*-------------------------------------------------------------*/ PROCESS(hello_world_process, "Hello world process"); PROCESS(blink_process, "LED blink process"); AUTOSTART_PROCESSES(&hello_world_process, &blink_process); /*-------------------------------------------------------------*/ PROCESS_THREAD(hello_world_process, ev, data) 3.6 La prima applicazione in Contiki { 37 // le variabili sono dichiarate statiche per mantenere // il proprio valore tra una chiamata e la successiva static struct etimer timer; PROCESS_BEGIN(); while(1) { etimer_set(&timer, CLOCK_SECOND); PROCESS_WAIT_UNTIL(etimer_expired(&timer)); rs232_print (RS232_PORT_1, "Tick\n"); etimer_set(&timer, CLOCK_SECOND); PROCESS_WAIT_UNTIL(etimer_expired(&timer)); rs232_print (RS232_PORT_0, "Tack\n"); } PROCESS_END(); } /*-------------------------------------------------------------*/ PROCESS_THREAD(blink_process, ev, data) { static struct etimer timer; static uint8_t leds_state = 0; PROCESS_BEGIN(); while (1) { // setto la durata del timer a un secondo etimer_set(&timer, CLOCK_SECOND); // attendo che il timer sia scaduto PROCESS_WAIT_UNTIL(etimer_expired(&timer)); // se il led e' acceso lo spengo if(leds_state){ PORTC |= (1<<PC0); // set PC0 = LED verde OFF PORTC |= (1<<PC1); // set PC0 = LED rosso OFF leds_state = 0; }else { // altrimenti se e' spento lo accendo PORTC &= ~(1<<PC0); // clear PC0 = LED verde ON PORTC &= ~(1<<PC1); // clear PC0 = LED rosso ON leds_state = 1; } } } PROCESS_END(); L'applicazione si divide in due processi, il primo (hello_world_process) scrive tick e tack ad intervalli regolari (della durata di un secondo) sull'interfaccia seriale mentre il secondo (blink_process) accende e spegne ripetutamente i due led, di colore verde e rosso, rispettivamente connessi al microcontrollore alla porta PC0 e PC1. 3.6.1 Testing del sistema Dopo la compilazione e la memorizzazione del le binario nel microcontrollore è stata alimentata la scheda di sviluppo con un segnale continuo a 5 Volt. È 38 3.6 La prima applicazione in Contiki stata poi avviata l'applicazione picocom per monitorare la porta usb connessa alla uart del microcontrollore ed è stata vericata la corretta trasmissione delle stringhe tick e tack. Come previsto dal processo blink_process, i led posti sulla scheda hanno iniziato a lampeggiare ad intervalli di un secondo. In seguito è stata vericata la presenza della connessione di rete mediante ping all'indirizzo IP 10.0.0.100 specicato all'interno del main. La scheda è stata lasciata accesa per diverso tempo (alcune ore) per vericare eventuali difetti o malfunzionamenti. L'applicazione costruita funziona correttamente. Questo signica che le conoscenze acquisite e l'ambiente di sviluppo preparato pongono nella condizione di poter realizzare applicazioni complesse. Capitolo 4 Realizzazione di un'applicazione per il controllo ambientale In questo capitolo si mostrano le fasi che hanno portato alla realizzazione di un sistema di controllo ambientale. Dopo una descrizione del prototipo hardware utilizzato si passa alla delineazione della applicazione software. 4.1 L'hardware Vista la disponibilità del modulo Crumb, introdotto nel capitolo precedente, si è pensato di utilizzare un sensore di accelerazione esterno per realizzare un sistema di controllo remoto. La mancanza di ingressi analogici liberi (in quanto connessi al controllore ethernet CP2201) ci ha fatto propendere per un modulo sensore con interfaccia digitale SPI. In questa sezione sarà introdotto il funzionamento dell'interfaccia SPI. In seguito verranno descritte le caratteristiche principali del sensore di accelerazione scelto ed, inne, sarà riportato lo schema circuitale del prototipo realizzato. 4.1.1 L'interfaccia SPI L'interfaccia SPI è un sistema di comunicazione seriale, sincrono e full-duplex, utile alla interconnessione di diversi microcontrollori o moduli esterni. Il sistema realizza una rete a bus dove un nodo master coordina la trasmissione e la ricezione dei dati mediante un segnale di clock condiviso da un set di slave. La trasmissione dei dati sul bus è basata sul meccanismo dei registri a scorrimento. Ogni terminale possiede un registro a scorrimento interno della dimensione di 1 byte. Quando un master attiva uno slave con un'apposita linea, detta di Slave Select (o Chip Select), produce un segnale periodico di clock, che lo slave utilizza per sincronizzarsi con il master. Quando il master desidera eettuare 40 4.1 L'hardware un'operazione sullo slave, la scrive all'interno del registro a scorrimento locale e ad ogni ciclo di clock shifta di una posizione il contenuto del registro inviando l'elemento estratto su una linea detta MOSI (Master Output Slave Input) o SDO (Serial Data Output), connessa a tutti gli slave. Ad ogni ciclo del segnale di temporizzazione, anche lo slave abilitato fa scorrere di una posizione i dati all'interno del registro. In questo modo dopo diversi cicli di clock il comando è spostato dal registro locale al master, a quello dello slave. Lo slave, che implementa una macchina a stati, può gestire il comando inviato o rigettarlo in caso di errore. A questo punto il master può inviare una serie di dati fasulli (detti don't care o dummy) per spingere allo shift lo slave e ottenere così la risposta al comando impartito. La trasmissione è di tipo full-duplex in quanto Figura 4.1: L'interfaccia SPI [21] la direzione dei dati è bidirezionale. Ad ogni bit inviato dal master sulla linea MOSI corrisponde un bit inviato dallo slave sulla linea MISO. Quindi dopo otto cicli di clock il contenuto del registro del master viene passato allo slave e viceversa. Questo tipo di comunicazione coinvolge le 4 linee viste (CS/SS, MISO, MOSI, SCK). Esiste una variante a 3 linee che utilizza un unica linea dati bidirezionale in modalità half-duplex. La sincronizzazione delle stazioni è eettuata secondo quattro diverse modalità. Mentre i dispositivi slave sono costruiti secondo una di queste, il master può essere adattato per comunicare con stazioni di tipo diverso. A questo proposito si deniscono due parametri modicabili: CPOL e CPHA. Il primo denisce il livello logico assunto dal clock nello stato di riposo. Se CPOL è zero, il clock è normalmente a livello basso ed il segnale in arrivo dallo slave è campionato sul fronte di salita. Il secondo parametro stabilisce il fronte di clock in cui il ricevente campiona il segnale ricevuto (in ingresso). Se CPHA è zero, il segnale è campionato sul fronte di discesa. La modalità più comune prevede sia CPOL che CPHA a zero. In certi casi, anche la modalità di rappresentazione dei dati può essere modicata determinando la posizione del bit più signicativo rispetto a quello 4.1 L'hardware 41 meno signicativo. 4.1.2 Il sensore di accelerazione Il sensore scelto per la rilevazione dell'accelerazione è il LIS3LV02DL di STMicroelectronics. Il componente è disponibile nel package LGA16 (Land Grid Array) per l'inserzione sul circuito o nel kit di valutazione STEVAL-MKI009V1 nel formato standard dual line a 20 pin che ne permette l'impiego anche nella classica piastra millefori. Il dispositivo contiene un accelerometro con sensibilità ±2g/±6g selezionabile ed un'interfaccia di comunicazione digitale compatibile con lo standard I2C ed SPI. Si riporta il pinout del modulo tratto dal datasheet uciale del produttore. Figura 4.2: Il modulo STEVAL-MKI009V1 [22] La gestione del sensore Il sensore è gestito mediante l'invio di comandi sull'interfaccia SPI. Tali comandi operano la lettura e la scrittura di registri in cui sono memorizzate informazioni di stato, impostazioni e i dati rilevati. Il registro WHO_AM_I è una locazione di memoria a sola lettura utilizzata per vericare il funzionamento del dispositivo. Contiene il valore 3Ah. I registri di OFFSET e GAIN sono caricati all'avvio dalla memoria ash del dispositivo e sono imposti dal costruttore, utili alla calibrazione dello strumento di misura. 42 4.1 L'hardware Registro WHO_AM_I OFFSET_X OFFSET_Y OFFSET_Z GAIN_X GAIN_Y GAIN_Z CTRL_REG1 CTRL_REG2 CTRL_REG3 HP_FILTER RESET STATUS_REG OUTX_L OUTX_H OUTY_L OUTY_H OUTZ_L OUTZ_H FF_WU_CFG FF_WU_SRC FF_WU_ACK FF_WU_THS_L FF_WU_THS_H FF_WU_DURATION DD_CFG DD_SRC DD_ACK DD_THSI_L DD_THSI_H DD_THSE_L DD_THSE_H Tipo Indirizzo (Hex) r rw rw rw rw rw rw rw rw rw r rw r r r r r r rw rw r rw rw rw rw rw r rw rw rw rw 0F 16 17 18 19 1A 1B 20 21 22 23 27 28 29 2A 2B 2C 2D 30 31 32 34 35 36 38 39 3A 3C 3D 3E 3F Tabella 4.1: I registri nel LIS3LV02DL 4.1 L'hardware 43 I registri di controllo sono utili all'attivazione delle funzionalità del dispositivo. Il registro CTRL_REG1 denisce l'abilitazione del dispositivo e degli assi nonchè la frequenza di campionamento. Il registro CTRL_REG2 denisce la scala di misurazione, la modalità di rap- Bit Campo b5, b4 DF1, DF0 b3 ST b2 Zen b1 Yen b0 Xen b7, b6 PD1, PD0 Note Controllo Power Down (00 = power down; 01, 10, 11 dispositivo attivo) Controllo fattore di decimazione (00: 40Hz, 01:160Hz, 10: 640Hz, 11: 2560Hz) Abilitazione Self Test (0: modalità normale; 1: self-test attivo) Abilitazione asse Z (0: asse disabilitato; 1: asse abilitato) Abilitazione asse Y (0: asse disabilitato; 1: asse abilitato) Abilitazione asse X (0: asse disabilitato; 1: asse abilitato) Tabella 4.2: Il registro CTRL_REG1 presentazione dei dati e di comunicazione mediante SPI. Il registro CTRL_REG3 specica le impostazioni del clock esterno e del ltro Bit Campo Note b7 FS b6 BDU b5 BLE b4 b3 BOOT IEN b2 b1 b0 DRDY SIM DAS Selezione del fondo scala (0: ±2g; 1: ±6g) Aggiornamento dati (0: aggiornamento continuo; 1: successivo lettura dati) Selezione modalità Big/Little Endian (0: little endian; 1: big endian) Ricarica i registri con le impostazioni predenite Abilitazione dell'interrupt (0: utilizzo del pin RDY, 1: interrupt su RDY) Abilitazione del pin DRDY (dati pronti) Selezione del tipo di SPI (0: 4 linee; 1: 3 linee) Selezione dell'allineamento dei dati (0: 12 bit giust. a destra, 1: 16bit giust. a sinistra) Tabella 4.3: Il registro CTRL_REG2 passa alto. Il registro di stato STATUS_REG descrive lo stato dei dati campionati sugli 44 4.1 L'hardware Bit Campo b6 HPDD b5 HPFF b4 FDS b3 b2 b1, b0 reserved reserved CFS1, CFS0 b7 Note ECK Clock interno/esterno (0: clock da oscillatore interno; 1: clock esterno) Abilit. ltro passa-alto per la Direction Detection (0: ltro disabilitato; 1: ltro abilitato) Abilit. ltro passa-alto per la FreeFall e WakeUp (0: ltro disabilitato; 1: ltro abilitato) Sorgente dati (0: ltro interno aggirato; 1: dati da ltro interno) Campo riservato Campo riservato Selezione frequenza di taglio del ltro passa-alto Hpc = (00:512, 01:1024, 10:2048, 11:4096) Tabella 4.4: Il registro CTRL_REG3 assi, denendo appositi ag che sono posti alti dopo ogni rilevazione secondo la politica scelta con l'impostazione del bit BDU all'interno del CTRL_REG2. I registri OUTX, OUTY ed OUTZ contengono i dati delle accelerazioni sui tre assi x,y e z. I dati sono rappresentati nel formato 12 bit giusticati a destra o 16 bit giusticati a sinistra. Ogni registro è diviso in due parti, la parte con i bit più signicativi e quella con i bit meno signicativi. È possibile rappresentare i dati nella modalità little endian o big endian. Nel primo caso i bit meno signicativi niscono nel registro LOW e la parte alta nel registro HIGH. I registri FF deniscono le impostazioni e lo stato della funzionalità di rilevazione di caduta libera, che non sarà utilizza in questo progetto. I registri DD deniscono le impostazioni e lo stato della funzionalità di rilevazione della direzione (Direction Detection), che non sarà utilizzata per questo progetto. Gestione dei registri Per la gestione dei registri il produttore ha denito un protocollo di comunicazione che prevede un campo per la selezione del tipo di operazione (read o write), uno per l'eventuale ripetizione dell'operazione sulle locazioni successive ed un campo per l'indirizzo del registro su cui l'operazione avrà eetto, seguito da eventuali dati nel caso della scrittura. Il secondo campo è utile per la ripetizione di un'operazione. Un apposito contatore interno ne incrementa il valore ad ogni ciclo del clock SCK. In questo modo è possibile eseguire letture e scritture su range di registri consecutivi, velocizzando le richieste ed evitando il reinvio dei campi iniziali. 4.1 L'hardware 45 Figura 4.3: La lettura di un registro Figura 4.4: La scrittura di un registro 4.1.3 Il prototipo Il modulo Crumb ed il modulo STEVAL-MKI009V1 sono stati montati su una piastra millefori. Successivamente sono state connesse le alimentazioni: +5V per il primo e 3.3V per il secondo. Si rimanda a [15] per lo schema circuitale del modulo Crumb e la disposizione dei segnali sui connettori CON1 e CON2, a [21] per il pinout e le caratteristiche del microcontrollore ATmega644. Il modulo sensore ha in realtà una duplice alimentazione, quella del core funzionante a 3.3V e quella dei pin I/O denibile tra 0 e la tensione di alimentazione (per permetterne il funzionamento con integrati di tipo diverso). È stato scelto di utilizzare la stessa alimentazione sia per il core che per l'interfaccia I/O. I segnali dell'interfaccia SPI dei due moduli devono essere adattati in quanto i dati in uscita dai pin MOSI, SCK e SS del microcontrollore ATmega644 considerano livello logico alto +5V e ciò rischia di danneggiare l'integrato LIS3LV02DL che come detto lavora a 3.3V. Per tali motivi le uscite del master sono state connesse ad un partitore di tensione che abbassa il segnale al valore previsto secondo il calcolo: Vout = Vin ∗ R2 /(R1 + R2 ) ovvero Vout = 5 ∗ 2200/(1000 + 2200) = 3.43V 46 4.1 L'hardware Figura 4.5: La connessione Crumb/STEVAL-MKI009V1 Figura 4.6: Il prototipo 4.2 Il software 47 4.2 Il software In questa sezione sono riportate le modiche al sistema Contiki che hanno permesso la gestione dell'accelerometro e dell'interfaccia SPI. Nella parte conclusiva sarà descritta l'applicazione di rete. I sorgenti sono riportati in appendice. 4.2.1 Gestione della SPI La gestione dell'interfaccia SPI è stata denita in un'apposita libreria (spiman.c e spiman.h) posta nella directory cpu/avr/dev. Questa denisce tre funzioni: spi_init, spi_send e spi_receive. Per una maggiore chiarezza si consiglia la consultazione del datasheet del microcontrollore ATMega644 [21] ed in particolare la sezione 16. La funzione spi_init denisce la direzione delle porte nel Data Direction Register, provvede all'abilitazione dell'interfaccia SPI impostandola come terminale master e stabilisce la frequenza della trasmissione valorizzando il prescaler a 000 per ottenere 1/4 della frequenza di clock (20Mhz/4 = 5Mhz). Il modulo sensore, secondo quanto scritto nel datasheet, può lavorare senza problemi no a 8Mhz. La funzione spi_send viene parametrizzata da un carattere e provvede all'invio del dato lungo il bus, bloccando l'esecuzione no al termine della trasmissione. Tale operazione è eettuata copiando il dato nel registro SPDR e ciclando sullo stato del ag SPIF presente nel registro di stato SPSR. La funzione spi_receive restituisce il contenuto del buer di ricezione. È compito dell'applicazione provvedere all'invio di byte don't care (dummy) per provvedere al suo riempimento. 4.2.2 Gestione dell'accelerometro La gestione dell'accelerometro è stata denita in una libreria (lis3lv02dl.h e lis3lv02dl.c). Per facilitare la scrittura delle applicazioni, la libreria mappa localmente l'indirizzamento dei registri fornendo all'utente la possibilità di specicarne la locazione attraverso il nome. Insieme ad esse sono state collocate apposite macro per semplicare l'abilitazione del modulo sensore e per scrivere all'interno dei registri PORT del microcontrollore. La libreria denisce quattro funzioni per l'inizializzazione, lettura e scrittura dei registri: acc_init, acc_read, acc_getAxes e acc_write. La funzione acc_init denisce il valore dei registri di controllo. Nel registro CTRL_REG1 viene disabilitata la modalità power down, viene stabilita la frequenza di campionamento a 2560Hz, disabilitata la funzionalità di self test e vengono abilitati i sensori di tutti gli assi. Nel registro CTRL_REG2 viene impostata la scala a ±2g e richiesto l'accesso esclusivo alla lettura dei registri 48 4.2 Il software (bit BDU) in modo da non generare inconsistenze durante il loro aggiornamento. Oltre a questi viene abilitato il pin di data ready, scelta la versione a 4 linee della SPI e il formato dei dati impostato a 12 bit giusticati a destra. La funzione acc_read viene parametrizzata con il carattere rappresentante l'indirizzo del registro da cui leggere. Dopo aver abilitato lo slave gli viene inviata la richiesta di lettura all'indirizzo specicato, seguito da un byte dummy utile a recuperare il valore letto per mezzo della funzione spi_receive. La funzione acc_getAxes è utile al recupero del valore di accelerazione rilevato sugli assi. Per farlo invia una richiesta di lettura multipla al modulo sensore, specicando l'indirizzo di partenza con quello del registro OUTX_L. Da esso si sposta no all'indirizzo OUTZ_H raccogliendo tutti i valori letti in un array globale chiamato axes che condivide con l'applicazione cliente della funzione. La funzione acc_write riceve l'indirizzo del registro e i dati da scrivere e provvede ad inviare sul bus SPI una richiesta di scrittura al registro specicato. Dopo aver attivato il modulo invia il bit write a 0 seguito dal bit Single, anch'esso a 0. Di seguito trasmette l'indirizzo ed i dati indicati. Modica del Main Anchè le modiche introdotte possano essere propagate al sistema Contiki, sono state importate le librerie prodotte all'interno del main le. Nella funzione main è stato necessario inizializzare la SPI invocando la funzione spi_init e provvedere alla specica del funzionamento del modulo sensore richiamando la funzione acc_init. Modica del Makele I le introdotti nel progetto Contiki devono essere inclusi durante la fase di compilazione. Mediante una modica al makele della piattaforma (Makele.avrchip45) è stato possibile indicare i sorgenti da includere nell'applicazione target. 4.2.3 Implementazione dell'applicazione Per implementare l'applicazione di controllo è stata creata la cartella tesi in examples. All'interno di essa è stato copiato ed adattato il makele del progetto Helloworld (si veda sezione 3.6) ed è stato creato un nuovo makele del progetto Contiki (Makele.tesi). L'applicazione è stata denita nel le tesi.c. Essa è composta da tre processi: un processo server in attesa di connessioni di rete sulla porta 1988, un processo raccoglitore delle rilevazioni dal modulo sensore e un processo visualizzatore dei campioni. Il processo server_process riprende l'esempio arontato nella sezione 2.4. Esso cicla in maniera indenita in attesa di una richiesta di connessione sulla porta 1010. In questo momento viene inizializzata una protosocket di gestione, 4.2 Il software 49 che viene invocata no a quando la connessione non termina, e viene spento il diodo led rosso posto sulla PORTC nella posizione PC1. La protosocket procede all'invio dei dati presenti nell'array axes denito nella libreria di gestione del modulo accelerometro ed in seguito attende la scadenza di un timer per evitare di monopolizzare la CPU. La protosocket conclude nel momento in cui il client termina la connessione o quando questa non è più disponibile. In tale situazione viene riacceso il led rosso ed il processo si rimette in attesa di nuove richieste di connessione. Tale servizio implementato è di certo monoutente ma questo non è un problema per il caso specico in cui sarà utilizzato. Il processo samples_retriever raccoglie i dati rilevati dal modulo sensore mediante l'invocazione della funzione acc_getAxes. In seguito provvede alla conversione del formato essendo i campioni suddivisi in parte bassa (registro OUT_L) e parte alta (registro OUT_H) e memorizza i risultati in tre variabili globali di tipo unsigned int a 16 bit nominate x, y e z. All'interno dello stesso processo gestisce l'accensione e lo spegnimento del led verde, utile alla segnalazione dello stato di raccolta dei dati. Il processo samples_viewer è stato inserito nell'applicazione in quanto utile ai ni del debugging e alla valutazione dell'interfaccia RS232 del modulo Crumb. I dati possono essere ricevuti utilizzando un'applicazione come picocom in ascolto sulla porta seriale. Il processo provvede alla stampa dei campioni e, quindi, può essere utile in assenza di una connessione di rete per la valutazione locale dello stato del sensore. Capitolo 5 Valutazione dei risultati ottenuti In questo capitolo sono riportate le veriche sul sistema costruito e le misurazioni eettuate. Nella parte conclusiva sono valutati i risultati ottenuti e sono formulate conclusioni sul lavoro svolto corredate da eventuali miglioramenti applicabili in futuro. 5.1 Testing La fase di testing è utile alla verica del funzionamento del sistema in diverse situazioni. È quindi necessario mostrare l'eettivo funzionamento sia della parte hardware che di quella software. 5.1.1 Verica dell'hardware La correttezza dell'hardware è stata vericata mediante le seguenti prove: • La corretta interconnessione dei pin è stata vericata mediante Ohmetro con funzione acustica. • La tensione sui pin MOSI, SCK, SS è stata vericata mediante Voltmetro prima dell'inserzione del modulo STEVAL-MKI009V1. Per fornire una tensione su tali porte è stata utilizzata un'applicazione in C che ne imposta la direzione in uscita e ne assegna il valore al livello logico alto. La tensione rilevata è di 3.40V circa (è necessario considerare la tolleranza oro, del 5% rispetto al valore nominale, dei resistori utilizzati). • Il corretto funzionamento delle linee è stato vericato mediante un'applicazione in C che abilita l'interfaccia SPI e richiede la lettura del registro WHO_AM_I. Nel caso in cui il valore ritornato corrisponde a 5.1 Testing 51 quello del registro richiesto viene acceso il led verde, altrimenti il rosso. Anche l'interfaccia SPI funziona correttamente e con essa anche l'integrato LIS3LV02DL. I segnali scambiati sono stati vericati mediante oscilloscopio digitale. • Mediante l'oscilloscopio è stato vericato il corretto funzionamento dell'interfaccia SPI e dell'integrato LIS3LV02DL all'aumentare della frequenza SCK. Partendo da una frequenza di f/32 è stata aumentata no a f/4 (5Mhz). Entrambi i moduli funzionano correttamente con le frequenze utilizzate. Secondo il datasheet il limite dell'integrato LIS3LV02DL è posto a 8Mhz. 5.1.2 Verica del software La verica del software implementato è stata vericata mediante le seguenti prove: • L'attività del processo samples_retriever è mostrata dal lampeggio continuo del led verde. • La correttezza del processo samples_viewer è stata vericata mediante gli applicativi picocom e minicom opportunamente congurati. Una volta scelta la porta USB e impostato il baudrate a 19200bps sono stati ricevuti dati relativi agli assi monitorati dal processo samples_retriever. • La correttezza dei dati campionati è stata vericata posizionando l'apparato in modo che uno degli assi fosse parallelo alla forza di gravità. In tal modo l'asse assume il valore 1g (o -1g se il verso è opposto). La prova è stata ripetuta per gli altri due assi. • La corretta impostazione dei registri di controllo è stata vericata mediante lettura alla locazione indicata dal datasheet. • La correttezza del processo server_process è stata vericata con un'applicazione costruita appositamente per la fase di testing. Preparazione dell'ambiente di testing Per la verica del sistema si è pensato di implementare un'applicazione client mediante il linguaggio Java. L'applicazione costruita, il cui codice è riportato in appendice, si connette al modulo Crumb alla porta 1988, all'indirizzo IP specicato durante la fase di login. Una volta connessa inizia a ricevere i campioni rilevati mediante il modulo STEVAL-MKI009V1 e li visualizza in un'area di testo. I dati ottenuti possono essere salvati su le cliccando sul bottone salva campioni o utilizzati per la stampa di un graco cliccando sul bottone stampa graco. È possibile scegliere il nome del le in cui sono salvati i campioni e i graci. Il salvataggio con un nome già utilizzato sovrascrive la 52 5.1 Testing versione precedente. La realizzazione dei graci è possibile grazie alla libreria opensource JFreeChart [23]. L'applicazione è suddivisa in due package: client Figura 5.1: Il client nello stato non connesso Figura 5.2: Il client nello stato connesso e graphics. All'interno del primo sono denite le classi ConnectionManager, Controller e FileManager. ConnectionManager è un'entità attiva (thread) che istanzia la connessione con il server e si mette in attesa di dati in arrivo. Per ogni stringa ricevuta provvede ad invocare la funzione refreshText sul Controller. Il Controller è denito come thread autonomo che aggiorna lo stato dell'interfaccia nel momento in cui nuovi dati arrivano dal ConnectionManager. Fornisce tutte le funzionalità utili all'interno dell'interfaccia graca (come connect, disconnect, saveSamples e saveChart), secondo il modello noto come Model-ViewController. La classe FileManager è utile al salvataggio su testo dei campioni rilevati. Essa istanzia uno stream verso il le e fornisce al Controller i metodi write e close con la quale può scrivere ed in seguito chiudere lo stream. Il package graphics, al contrario, denisce le classi: GUIClient, GUIListener e la classe Main. GUIClient descrive l'interfaccia graca, specica la posizione e la dimensione dei singoli componenti ed istanzia il listener per la gestione degli eventi su di essi. Il listener di tipo GUIListener implementa le interfacce WindowListener 5.1 Testing 53 ed ActionListener denendo le operazioni da eseguire in prossimità di eventi sui bottoni di connessione, disconnessione, salvataggio diagramma e salvataggio campioni. Queste operazioni sono denite sul controllore che è istanziato dal GUIClient ed è riferibile anche dal listener. Il Main istanzia un oggetto di tipo GUIClient. Per ulteriori informazioni si rimanda al codice in appendice. 5.1.3 Comportamento in condizioni di quiete Nella prima prova, si è voluto vericare il comportamento del sistema in condizioni di quiete (ovvero senza imporre alcun movimento volontario sul dispositivo). Per questo motivo, esso è stato posto sul piano di calpestio ed alimentato, l'applicazione client è stata avviata e i dati sono stati salvati in un le. In gura 5.3 si riporta il graco prodotto dalla applicazione. Il sistema si trova in Figura 5.3: Il graco del test in condizioni di quiete piano, come è possibile notare dal graco, in quanto l'accelerazione sugli assi X e Y è prossima allo zero, mentre l'accelerazione sull'asse Z è circa 1g. Ciò risulta corretto visto che gli assi X e Y si trovano perpendicolari alla forza di gravità, mentre l'asse Z è parallelo a tale forza e posto in modo da avere lo stesso verso. Chiaramente vi sono alcune piccole vibrazioni che possono essere dovute a vibrazioni del piano, rumore interno al dispositivo di rilevazione e alla tipologia di connessioni realizzate sul prototipo che all'aumentare della frequenza dovrebbero seguire gli accorgimenti previsti per le linee di trasmissione (in termini di impedenza, capacità parassite, ...). Anche il rumore ambientale (in termini elettromagnetici) può inuire sulle misurazioni, essendo le linee scoperte in alcuni punti esse si comportano come antenne. 54 5.1 Testing 5.1.4 Comportamento in presenza di vibrazioni periodiche Non potendo attendere la comparsa di un terremoto, seppur frequenti, è stato pensato di vericare il funzionamento del dispositivo in presenza di vibrazioni periodiche. L'idea, è quella di visualizzare sui graci costruiti dalla applicazione client, le vibrazioni prodotte da un elettrodomestico di uso comune: la lavatrice. Si riportano le prove relative al ciclo di lavaggio e a quello di centrifuga dei capi. Test per il ciclo di lavaggio Il ciclo di lavaggio è svolto mediante cicli di rotazione/pausa del cestello atti a favorire l'ingresso del liquido detergente all'interno dei tessuti. Nella gura Figura 5.4: Il graco n.1 del test per l'operazione di lavaggio dei capi 5.4 si notano quattro cicli di rotazione. Nella gura 5.5 si riporta la prova complessiva del ciclo di lavaggio. Si possono notare nove diversi cicli di rotazione. Test per l'operazione di centrifuga dei capi La centrifuga dei capi è utile alla riduzione dell'acqua di lavaggio presente all'interno dei tessuti. Avviene mediante rotazione, ad alta velocità, del cestello. Nella gura 5.6 si riporta lo stato del dispositivo prima dell'attivazione della centrifuga. Nelle gure 5.7, 5.8, 5.9, 5.10, 5.11 si possono notare le vibrazioni prodotte dall'elettrodomestico durante tale operazione. 5.1 Testing Figura 5.5: Il graco n.2 del test per l'operazione di lavaggio dei capi Figura 5.6: Il graco n.1 del test per l'operazione di centrifuga 55 56 5.1 Testing Figura 5.7: Il graco n.2 del test per l'operazione di centrifuga Figura 5.8: Il graco n.3 del test per l'operazione di centrifuga 5.1 Testing Figura 5.9: Il graco n.4 del test per l'operazione di centrifuga Figura 5.10: Il graco n.5 del test per l'operazione di centrifuga 57 58 5.1 Testing Figura 5.11: Il graco n.6 del test per l'operazione di centrifuga 5.1.5 Trasmissione dei campioni I test mirano alla verica del funzionamento del modulo STEVAL-MKI009V1 e non devono essere visti come accurato strumento di analisi dei campioni rilevati. Come è possibile notare nel codice riportato in appendice, il sensore campiona i dati alla frequenza massima di 2560Hz. La trasmissione dei campioni, che vede il suo inizio al momento della connessione del client, è temporizzata dal timer timerAcc che potremmo idealmente impostare no a CLOCK_SECOND / 125 (si veda il modulo clock.h) ovvero no a 8ms. Alcune prove a riguardo hanno dimostrato che le latenze introdotte nella gestione della connessione TCP non permettono l'invio di più di (circa) 120 rilevazioni al minuto, ovvero due ogni secondo (CLOCK_SECOND / 2), che è un numero insuciente a garantire la consistenza dei dati ricevuti sul client nonchè l'utilizzo all'interno di sistemi realtime. La prova è stata eseguita sia con gli altri processi particolarmente attivi, che con un timer di attesa alto. Ogni prova ha una durata di 30 secondi. Nella tabella 5.1 viene indicata CK_SND la costante CLOCK_SECOND. La soluzione formulata prevede la distribuzione del costo di gestione della protosocket su più rilevazioni. L'idea è quella di accumulare all'interno di un buer di 32 posizioni i dati e trasmetterli tutti insieme. Una piccola modica al codice ha permesso di valutare questa alternativa e di constatare l'eettivo aumento delle rilevazioni ricevute dal client. Anche il processo samples_retriever sta in realtà compiendo una decimazione sulla sequenza campionata dal modulo sensore (visto che può acquisire i dati al massimo ogni CLOCK_SECOND/125). Per automatizzare la lettura dei campioni si potrebbe utilizzare l'interrupt di sistema, sul pin RDY/INT del modulo sensore, per aggiornare il valore degli assi all'interno di un'ISR (Interrupt Service Routine). 5.2 Reattività dell'interfaccia di rete timerAcc CK_SND/125 CK_SND/25 CK_SND/5 CK_SND/4 CK_SND/2 CK_SND CK_SND/125 CK_SND/4 CK_SND/2 CK_SND timerRetrieval CK_SND/125 CK_SND/125 CK_SND/125 CK_SND/125 CK_SND/125 CK_SND/125 CK_SND*60 CK_SND*60 CK_SND*60 CK_SND*60 timerView CK_SND CK_SND CK_SND CK_SND CK_SND CK_SND CK_SND*60 CK_SND*60 CK_SND*60 CK_SND*60 59 rilevazioni ricevute 60 60 61 61 61 20 61 61 61 20 Tabella 5.1: verica della latenza della connessione TCP L'utilizzo di una frequenza di campionamento così alta ci spinge a diverse considerazioni: • in certi domini applicativi potrebbero risultare sucienti frequenze più basse e ritenuti validi anche i risultati visti • per applicazioni dove è richiesto un certo grado di reattività (es: realtime) potrebbe essere pensabile l'impiego di un ulteriore microcontrollore, incaricato della gestione del modulo sensore e di una prima fase di elaborazione dei dati, il modulo Crumb dovrebbe incaricarsi solo dell'invio dei dati elaborati o, in casi estremi, della sola propagazione dello stato dell'elaborazione (es: rilevazione dei dati ed elaborazione sul primo microcontrollore, propagazione dello stato di allarme mediante il modulo Crumb). 5.2 Reattività dell'interfaccia di rete Nelle reti di telecomunicazione, il throughput di un canale rappresenta la frequenza media di messaggi correttamente scambiati sul canale di comunicazione. In questa sezione si valuterà il throughput (e quindi approssimativamente le performance) dell'interfaccia di rete del modulo Crumb. Per risultati accurati dovrebbero essere utilizzati appositi tool di benchmark, basati su trasmissioni TCP e UDP. Questa soluzione richiede la presenza di una applicazione appositamente costruita, che eseguita sul modulo Crumb dovrebbe dialogare con lo strumento di test. In mancanza di tali strumenti si preferisce utilizzare il protocollo icmp, implementato dal sistema Contiki. Si procederà secondo due diverse modalità che fanno uso di questo protocollo: le utility ping e hping3. 60 5.2 Reattività dell'interfaccia di rete 5.2.1 Valutazione mediante ping Si invoca il comando ping per inviare richieste icmp al modulo Crumb che deve rispondere con lo stesso messaggio di richiesta (echo). Si imposta la dimensione minima al pacchetto (-s 1), la modalità quiet (-q, i dati sono stampati al termine) e quella ooding (-f). root@PhenomXXX:/home/pilillo/Scrivania/doc# time ping -q -s 1 -f 10.0.0.100 PING 10.0.0.100 (10.0.0.100) 1(29) bytes of data. ^C --- 10.0.0.100 ping statistics --73702 packets transmitted, 73701 received, 0% packet loss, time 60238ms , pipe 2, ipg/ewma 0.817/0.000 ms real 1m0.242s user 0m0.740s sys 0m1.900s Si ricevono 73701/60.242 = 1223.415 pacchetti al secondo. Ad ogni pacchetto di risposta corrisponde una richiesta, quindi il throughput, ovvero i pacchetti scambiati nell'unità di tempo, vale 1223.415∗2 = 2446.83 pps. Per ottenere un risultato più accurato si esegue il comando su diversi terminali e si sommano i valori medi trovati. Esecuzione simultanea di 4 comandi ping 26533 packets transmitted, 26532 received, 0% packet loss, time 59945ms, pipe 2, ipg/ewma 2.259/0.000 ms real 0m59.948s 26265 packets transmitted, 26263 received, 0% packet loss, time 60026ms, pipe 2, ipg/ewma 2.285/0.000 ms real 1m0.034s 26228 packets transmitted, 26226 received, 0% packet loss, time 59976ms, pipe 2, ipg/ewma 2.286/0.000 ms real 0m59.981s 26603 packets transmitted, 26599 received, 0% packet loss, time 60016ms, pipe 2, ipg/ewma 2.256/0.000 ms real 1m0.019s throughput = ((26532/59.948)*2) + ((26263/60.034)*2) + ((26226/59.981)*2) + ((26599/60.019)*2) = 3520.932 pps Esecuzione simultanea di 7 comandi ping 15217 packets transmitted, 15137 received, 0% packet loss, time 59336ms, pipe 2, ipg/ewma 3.899/0.000 ms real 0m59.341s 15389 packets transmitted, 15300 received, 0% packet loss, time 59617ms, pipe 2, ipg/ewma 3.874/0.000 ms real 0m59.622s 15784 packets transmitted, 15707 received, 0% packet loss, time 59596ms, pipe 2, ipg/ewma 3.775/0.000 ms 5.2 Reattività dell'interfaccia di rete 61 real 0m59.599s 16392 packets transmitted, 16315 received, 0% packet loss, time 59768ms, pipe 2, ipg/ewma 3.646/0.000 ms real 0m59.772s 17194 packets transmitted, 17119 received, 0% packet loss, time 60024ms, pipe 2, ipg/ewma 3.491/0.000 ms real 1m0.029s 15867 packets transmitted, 15789 received, 0% packet loss, time 59469ms, pipe 2, ipg/ewma 3.748/0.000 ms real 0m59.474s 15661 packets transmitted, 15579 received, 0% packet loss, time 59595ms, pipe 2, ipg/ewma 3.805/0.000 ms real 0m59.599s throughput = ((15137/59.341)*2) + ((15300/59.622)*2) + ((15707/59.599)*2) + ((16315/59.772)*2) + ((17119/60.029)*2) + ((15789/59.474)*2) + ((15579/59.599)*2) = 3720 pps Come è possibile notare, all'aumentare del numero di esecuzioni simultanee del comando ping, il valore del throughput tende a stabilizzarsi attorno ai 3500/3700 pps. 5.2.2 Valutazione mediante il tool hping3 Hping è un assemblatore e analizzatore di pacchetti per reti TCP/IP. Si esegue il comando: time hping3 10.0.0.100 -i tempo_attesa_in_microsecondi --icmp e si riportano i dati nella tabella 5.2. Dalla tabella si ricavano i graci mostrati nelle gure 5.12, 5.13 e 5.14. Nel graco di gura 5.12 è ben visibile la relazione tra throughput e tempo di pausa tra un reinvio ed il successivo. Aumentando il tempo d'attesa, diminuisce il numero di pacchetti inviati e di quelli persi (l'interfaccia non è più congestionata e torna attiva); il numero di pacchetti ricevuti si stabilizza attorno ad un valore limite dalla quale inizia a scendere nel momento in cui anche il numero di pacchetti di richiesta decrementa. Nel graco di gura 5.13 si può notare la relazione tra il numero di pacchetti inviati e quelli persi. Aumentando le richieste icmp aumenta anche il numero di pacchetti ricevuti. Superati i 90000 (circa) si inizia a perdere pacchetti, no ad una situazione limite nella quale non vi sono pacchetti di risposta (100% di pacchetti persi). Nel graco 5.14 si può notare l'andamento del throughput rispetto ai pacchetti persi. Si ha una zona iniziale nella quale all'aumentare dei pacchetti inviati viene corrisposto lo stesso numero di pacchetti ricevuti. Si ha l'aumento del throughput e un numero di pacchetti persi intorno allo 0%. Aumentando ulteriormente il numero di pacchetti spediti aumenta il numero dei pacchetti persi a causa della saturazione dell'interfaccia. Il throughput tende a stabilizzarsi attorno al suo valore limite per poi degenerare completamente nel momento in cui il numero dei pacchetti persi è massimo (100%). 62 5.2 Reattività dell'interfaccia di rete t pausa 5 10 20 30 40 50 60 70 80 90 100 200 250 300 350 400 450 500 600 650 800 900 1000 2000 2500 3000 3500 4000 5000 6000 inviati ricevuti 2253173 2263265 1470856 719387 534469 439078 375821 352332 310148 285786 278708 170189 141396 121508 108069 96640 85422 79491 68058 60838 53273 48574 43765 23738 18342 15884 13880 12144 10152 8141 0 0 8271 82208 79374 75269 77042 81721 79546 80391 82020 82997 83047 84703 87571 90656 83840 76404 63840 60043 52658 48128 43353 23737 18340 15884 13878 12143 10152 8140 persi t prova (s) throughput 100% 100% 99,44% 88,57% 85,15% 82,86% 79,50% 76,81% 74,35% 71,87% 70,57% 51,23% 41,27% 30,29% 18,97% 6,19% 1,85% 3,88% 6,20% 1,31% 1,15% 0,92% 0,94% 0% 0,01% 0% 0,01% 0,01% 0% 0,01% 60,065 60,340 59,768 60,389 60,272 59,957 60,128 60,241 60,084 59,881 59,938 60,037 60,053 60,192 59,810 60,109 60,061 59,721 59,821 60,032 59,800 59,355 60,076 60,232 60,140 60,032 59,957 60,004 60,561 60,384 Tabella 5.2: Il test mediante hping3 0,000 0,000 276,770 2722,615 2633,860 2510,766 2562,600 2713,136 2647,826 2685,025 2736,828 2764,862 2765,790 2814,427 2928,306 3016,387 2791,828 2558,698 2134,368 2000,366 1761,137 1621,700 1443,272 788,186 609,910 529,184 462,932 404,740 335,265 269,608 5.2 Reattività dell'interfaccia di rete Figura 5.12: Il graco del throughput Figura 5.13: Il graco dei pacchetti persi 63 64 5.3 Conclusioni Figura 5.14: Il graco del throughput sui pacchetti persi 5.3 Conclusioni Contiki risulta un sistema essibile che permette la scrittura di applicazioni in tempi veramente brevi, rendendo semplici parti complesse, pur lasciando al programmatore la possibilità di gestire registri e meccanismi di basso livello. Il progetto di tesi vuole mostrare le potenzialità di questo sistema operativo fornendo al lettore gli strumenti di sviluppo (meglio se opensource) che permettano di realizzare in maniera celere applicazioni complesse. Il caso studio riportato (rilevazione vibrazioni ambientali) è solo un esempio dell'utilizzo del sistema Contiki e il sistema non deve essere visto come prodotto nito. La piattaforma utilizzata, la Crumb644 di chip45, risulta uno strumento adatto ai primi test di Contiki, ma non può essere di certo utilizzata anche come unità di calcolo in applicazioni più complesse. Tuttavia, potrebbe essere vista come strumento autonomo per la gestione della connettività ethernet all'interno di sistemi di calcolo, dove la comunicazione tra i microcontrollori potrebbe avvenire mediante SPI. I risultati ottenuti possono essere ritenuti soddisfacenti. Le conoscenze acquisite in Contiki e nella programmazione dei microcontrollori, rendono possibile la realizzazione d'applicazioni complesse con costi relativamente bassi. Ne consegue un potenziale vantaggio competitivo sul mercato e la completa riusabilità delle risorse software prodotte. Se si volesse approfondire il problema della rilevazione delle vibrazioni ambientali, si potrebbe modicare il sistema in modo che sia in grado di rilevare scosse telluriche e sia compatibile con sistemi di rilevazione distribuiti come Quake Catcher. 5.4 Sviluppi futuri 65 5.4 Sviluppi futuri I risultati ottenuti dal lavoro di tesi svolto non sono che il punto di partenza rispetto alle modiche che saranno apportate in futuro al prototipo. Sarebbe auspicabile l'inserimento di ulteriori sensori per la rilevazione di temperatura ed altri fenomeni sici. Ciò sarà possibile mediante l'utilizzo dell'interfaccia SPI, il cui funzionamento è già stato approfondito. Un'ulteriore modica apportabile potrebbe essere quella che vede la sostituzione del modulo Crumb644 con un modulo wireless che implementi protocolli di comunicazione per reti di sensori come ad esempio ZigBee. Questa modica diminuirebbe il consumo di energia e aumenterebbe il grado di autonomia del sistema. Sarebbe pensabile in questo caso l'impiego di fonti d'energia rinnovabile, come ad esempio il fotovoltaico, per l'alimentazione prolungata del sistema. Inne, ma non meno importante, l'inserimento di Relais che permettano di utilizzare il sistema non solo come strumento di rilevazione ma anche come attuatore. Questa soluzione lo vedrebbe particolarmente appetibile in sistemi domotici facilmente integrabili vista la connettività ethernet disponibile sul modulo Crumb. Appendice A Implementazione di applicazioni in Contiki Di seguito si riportano le principali primitive utilizzate nella programmazione di applicazioni per Contiki. Per ulteriori chiarimenti si rimanda alla documentazione uciale da cui queste informazioni sono state tratte. A.1 Gestione dei processi Si riportano le primitive per la gestione dei processi denite in process.h e process.c. PROCESS_BEGIN() Va inserito all'interno della denizione di un processo e serve a marcarne l'inizio PROCESS_END() Va inserito all'interno delle denizione di un processo e serve a marcarne la ne PROCESS_WAIT_EVENT() Blocca l'esecuzione del processo no a quando non gli viene noticato un evento post PROCESS_WAIT_EVENT_UNTIL(condizione) Blocca l'esecuzione del processo no a quando non gli viene noticato un evento e viene valutata vera la condizione A.1 Gestione dei processi 67 PROCESS_YIELD() Forza la sospensione del processo che esegue lo statement in favore della esecuzione di altri processi. PROCESS_YIELD_UNTIL(condizione) Forza la sospensione del processo che esegue lo statement no a quando la condizione non diventa vera. PROCESS_WAIT_UNTIL(condizione) Sospende l'esecuzione del processo no a quando la condizione non diventa vera. PROCESS_WAIT_WHILE(condizione) Sospende l'esecuzione del processo no a quando la condizione è vera. PROCESS_EXIT() Il processo che la esegue termina la sua esecuzione ritornando il controllo al kernel. PROCESS_PT_SPAWN(pt, thread) Salta alla esecuzione del processo indicato come parametro e si mette in attesa della sua conclusione. PROCESS_PAUSE() Sospende il processo che la esegue e genera un evento asincrono di tipo post per farlo risvegliare dopo poco tempo. PROCESS_POLLHANDLER(handler) Questa direttiva viene utilizzata prima del process_begin per denire all'interno del processo un blocco di istruzioni da eseguire al momento di un evento PROCESS_EVENT_POLL. L'handler potrebbe anche essere un protothread esterno al processo. 68 A.1 Gestione dei processi PROCESS_EXITHANDLER(handler) Questa direttiva viene utilizzata prima del process_begin per denire all'interno del processo un blocco di istruzioni da eseguire al momento di un evento di tipo PROCESS_EVENT_EXIT. L'handler potrebbe anche essere un protothread esterno al processo. PROCESS_THREAD(name, ev, data) Serve a denire il corpo di un processo. PROCESS_NAME(name) È utilizzata per dichiarare un processo in un le header. PROCESS(name, strname) Serve a dichiarare un processo. È parametrizzata dalla variabile valorizzata con la struttura che rappresenta il processo e da una stringa che descrive il processo. void process_start(struct process *p, const char *arg) Inizializza il processo preparandolo alla esecuzione. Il processo è aggiunto alla lista dei processi presenti nel kernel ed è successivamente eseguito. int process_post(struct process *p, process_event_t ev, void* data) Pubblica un evento in maniera asincrona ad un processo. Il parametro data permette il passaggio al processo di dati ausiliari. void process_post_synch(struct process *p, process_event_t ev, void* data) Pubblica un evento in maniera sincrona ad un processo. In questo caso il processo destinatario è subito eseguito (proprietà di sincronismo). Una volta terminata l'esecuzione del processo destinatario il controllo è restituito al processo che stava eseguendo precedentemente. A.1 Gestione dei processi 69 void process_exit(struct process *p) Invita il processo indicato nel parametro alla terminazione. L'invito alla terminazione è realizzato mediante la pubblicazione di un evento di tipo PROCESS_EVENT_EXIT al processo vittima che può così eseguire l'eventuale exithandler denito. Tutti gli altri processi sono avvisati mediante un evento sincrono di tipo PROCESS_EVENT_EXITED della terminazione del processo. In questo modo eventuali processi bloccati in attesa della terminazione dei gli possono essere sbloccati e riprendere l'esecuzione. PROCESS_CURRENT() Restituisce il puntatore alla struttura che rappresenta il processo attualmente in esecuzione. void process_poll(struct process *p) Pubblica un evento poll al processo passato come parametro. void process_init(void) Viene invocato dal sistema durante la fase di boot per inizializzare le strutture dati presenti nel sottosistema del kernel per la gestione dei processi. int process_run(void) Viene invocata dal sistema (main) in maniera ripetuta e causa la processazione di tutti gli eventuali eventi poll disponibili e di almeno un evento post. int process_is_running(struct process *p) Restituisce zero se il processo non è in esecuzione. Un processo non è in esecuzione nel momento in cui è terminato. int process_nevents(void) Restituisce il numero di eventi post in attesa di essere processati. PROCESS_LIST() Restituisce la lista dei processi attivi ovvero la lista dei processi attualmente in esecuzione. 70 A.2 Protothread A.2 Protothread PT_INIT(pt) Inizializza un protothread. Il campo lc della struttura di gestione viene inizializzato a zero. PT_THREAD(name_args) Viene utilizzata per dichiarare un protothread. PT_BEGIN(pt) Marca l'inizio del corpo di un protothread. Tutti gli statement precedenti al begin saranno eseguiti ad ogni invocazione del protothread. PT_END(pt) Marca la ne del corpo di un protothread. PT_WAIT_UNTIL(pt, condition) Blocca l'esecuzione del protothread no a quando la condizione non diviene vera. PT_WAIT_WHILE(pt, cond) Blocca l'esecuzione del protothread no a quando la condizione è vera. PT_WAIT_THREAD(pt, thread) Blocca l'esecuzione del protothread in attesa della conclusione del thread indicato come parametro. PT_SPAWN(pt, child, thread) Blocca l'esecuzione del protothread in attesa della conclusione del thread. PT_RESTART(pt) A.3 Supporto al multithreading 71 Blocca l'esecuzione del protothread e lo reinizializza in modo da farlo partire dall'inizio alla esecuzione successiva. PT_EXIT(pt) Termina l'esecuzione di un protothread. PT_SCHEDULE(f) Invoca l'esecuzione del thread passato come parametro. PT_YIELD(pt) Sospende l'esecuzione del protothread dando modo ad altri di eseguire. PT_YIELD_UNTIL(pt, cond) Sospende l'esecuzione del protothread no a quando la condizione non diviene vera. A.3 Supporto al multithreading Riporto le primitive per la gestione del multithreading denite in mt.c, mt.h, mtarch.c e mtarch.h. void mt_init(void); Inizializza la libreria per la gestione del multithreading. void mt_remove(void); Disabilita il supporto al multithreading. void mt_start(struct mt_thread *thread, void (* function)(void *), void *data); Prepara il sistema alla esecuzione del thread allocando le strutture dati di gestione. 72 A.4 Gestione dei timer void mt_exec(struct mt_thread *thread); È invocata da un processo di Contiki per eseguire il thread indicato dal parametro no a quando esso non esegue yield o viene interrotto (preempted). void mt_yield(void); Il thread che la esegue restituisce (volontariamente) il controllo del processore al kernel. void mt_exit(void); Provoca la terminazione del thread che la esegue. void mt_stop(struct mt_thread *thread); È invocata da un processo di Contiki per disallocare le strutture dati di gestione di un determinato thread dopo la sua terminazione. A.4 Gestione dei timer void etimer_set(struct etimer *et, clock_time_t interval) Imposta un timer. Riceve il puntatore alla struttura etimer che lo rappresenta e la durata. void etimer_reset(struct etimer *et) Resetta un timer scaduto impostandone il campo start all'istante attuale (t>start += t->interval) e lo avvia. void etimer_restart(struct etimer *et) Resetta il timer impostandone il campo start al tempo di sistema attuale (t>start = clock_time()) e lo avvia. void etimer_adjust(struct etimer *et, int td) Modica la durata di un timer incrementandone il campo start con l'oset specicato. A.5 Supporto alla connettività TCP/IP 73 clock_time_t etimer_expiration_time(struct etimer *et) Ritorna il momento in cui il timer scadrà calcolandolo come somma tra il campo start e la durata. clock_time_t etimer_start_time(struct etimer *et) Ritorna il campo start che rappresenta l'istante in cui il timer ha iniziato il conteggio. int etimer_expired(struct etimer *et) Ritorna true (1) se il timer è scaduto altrimenti false (0). void etimer_stop(struct etimer *et) Rimuove il timer dalla lista dei timer attivi. A.5 Supporto alla connettività TCP/IP A.5.1 Gestione delle connessioni di rete TCP void tcp_attach(struct uip_conn *conn, void *appstate) Associa il processo che la esegue alla connessione tcp specicata. In questo modo sarà in grado di scambiare dati. void tcp_listen(u16_t port) Apre una porta TCP in ascolto. Al momento dell'attivo di una richiesta di connessione sarà noticato un evento di tipo tcpip_event. void tcp_unlisten(u16_t port) Chiude la porta TCP su cui era in ascolto. struct uip_conn *tcp_connect(uip_ipaddr_t *ripaddr, u16_t port, void *appstate) 74 A.5 Supporto alla connettività TCP/IP Crea una connessione TCP con l'indirizzo e la porta specicata. Può essere passato anche il parametro appstate con la quale sarà rappresentato lo stato della connessione. tcpip_poll_tcp(struct uip_conn *conn) Causa la valutazione immediata della connessione TCP indicata. Eventuali dati da inviare sono speriti immediatamente senza attendere il normale ciclo di gestione del modulo microIP. UDP void udp_attach(struct uip_udp_conn *conn, void *appstate) Associa il processo che la esegue alla connessione UDP specicata. struct uip_udp_conn *udp_new(const uip_ipaddr_t *ripaddr, u16_t port, void *appstate) Crea una connessione UDP verso l'indirizzo e la porta specicata. Il parametro appstate è utilizzato per rappresentare lo stato della connessione. uip_udp_conn *udp_broadcast_new(u16_t port, void *appstate) Crea una connessione UDP di tipo broadcast verso la porta specicata. void tcpip_poll_udp(struct uip_udp_conn *conn) Causa la valutazione immediata della connessione UDP indicata. In questo modo eventuali dati da inviare sono spediti immediatamente senza attendere il normale ciclo di gestione del modulo microIP. A.5.2 Gestione delle protosocket PSOCK_INIT(psock, buffer, buffersize) Inizializza una protosocket e va invocata prima del suo utilizzo PSOCK_BEGIN(psock) A.5 Supporto alla connettività TCP/IP 75 Delimita l'inizio del corpo del protothread in cui è denita la protosocket PSOCK_SEND(psock, data, datalen) Chiamata bloccante per l'invio dei dati sulla protosocket. PSOCK_SEND_STR(psock, str) Chiamata bloccante per l'invio di una stringa sulla protosocket. PSOCK_GENERATOR_SEND(psock, generator, arg) Invoca la funzione generatore (che è passata come parametro) per ottenere i dati e poi li invia sulla protosocket attendendo no a che tutti i dati non siano stati inviati. PSOCK_CLOSE(psock) Chiude la protosocket. PSOCK_READBUF(psock) Legge il buer dei dati ricevuti no a quando i dati sono presenti. In caso di buer vuoto il protothread di gestione viene bloccato in attesa che siano inseriti caratteri. PSOCK_READTO(psock, c) Legge il buer dei dati ricevuti della protosocket no al carattere c specicato. In caso di buer vuoto il protothread di gestione viene bloccato in attesa che siano inseriti caratteri. PSOCK_DATALEN(psock) Restituisce la dimensione dei dati nel buer di input ovvero la quantità di dati ricevuti. PSOCK_EXIT(psock) 76 A.5 Supporto alla connettività TCP/IP Causa la terminazione del protothread in cui la protosocket è denita. PSOCK_CLOSE_EXIT(psock) Chiude la protosocket e termina il protothread in cui la protosocket è denita. PSOCK_END(psock) Marca la ne del corpo del protothread in cui la protosocket è denita. PSOCK_NEWDATA(psock) Controlla la presenza di dati in arrivo. PSOCK_WAIT_UNTIL(psock, condition) Corrisponde a PT_WAIT_UNTIL, serve a dierenziare l'utilizzo all'interno del protothread da quello all'interno della protosocket ma il comportamento è lo stesso. PSOCK_WAIT_THREAD(psock, condition) Corrisponde a PT_WAIT_THREAD, serve a dierenziare l'utilizzo all'interno del protothread da quello all'interno della protosocket ma il comportamento è lo stesso. Appendice B Il codice sorgente dell'applicazione Di seguito si riporta il codice sorgente dell'applicazione realizzata nel capitolo 4. B.1 La gestione dell'interfaccia SPI B.1.1 spiman.h #ifndef __SPIMAN_H__ #define __SPIMAN_H__ // inizializzazione della SPI void spi_init(void); // invio di un carattere void spi_send(char c); // lettura di un carattere char spi_receive(void); #endif B.1.2 spiman.c // gestione della interfaccia SPI #include "dev/spiman.h" #include <string.h> #include <avr/io.h> // inizializzazione della SPI void spi_init(void){ // imposto la direzione dei dati per la portaB DDRB |= (1<<PB4)| // il pin SS lo imposto in uscita (1<<PB5)| // MOSI e` in uscita (1<<PB7); // il clock e` in uscita // imposto il registro SPCR SPCR = (1<<SPE) | // abilito la SPI (1<<MSTR); // imposto il dispositivo come master 78 // // // // } B.2 La gestione del modulo STEVAL-MKI009V1 la frequenza massima utilizzabile per l'accelerometro e` 8Mhz utilizziamo il prescaler a 000 per ottenere la frequenza f/4 = 5Mhz lascio a 0 il bit SPR0 e SPR1 di SPCR, lascio a 0 anche il bit SPI2X del registro SPSR. // invio di un carattere sul bus void spi_send(char c){ // la scrittura consiste nella scrittura all'interno di SPDR SPDR = c; // attendo che la trasmissione del carattere sia stata completata while(!(SPSR & (1<<SPIF))); // non appena il flag SPIF va ad 1 la trasmissione e` completata } // lettura di un carattere dal bus char spi_receive(void){ // restituisco il contenuto del buffer di ricezione return SPDR; } B.2 La gestione del modulo STEVAL-MKI009V1 B.2.1 lis3lv02dl.h /* Gestione dell'accelerometro lis3lv02dl */ #ifndef __LIS3LV02DL_H__ #define __LIS3LV02DL_H__ // definizione dei registri #define WHO_AM_I 0x0F //00001111 #define OFFSET_X 0x16 //00010110 #define OFFSET_Y 0x17 //00010111 #define OFFSET_Z 0x18 //00011000 #define GAIN_X 0x19 //00011001 #define GAIN_Y 0x1A //00011010 #define GAIN_Z 0x1B //00011011 #define CTRL_REG1 0x20 //00100000 #define CTRL_REG2 0x21 //00100001 #define CTRL_REG3 0x22 //00100010 #define HP_FILTER_RESET 0x23 //00100011 #define STATUS_REG 0x27 //00100111 #define OUTX_L 0x28 //00101000 #define OUTX_H 0x29 //00101001 #define OUTY_L 0x2A //00101010 #define OUTY_H 0x2B //00101011 #define OUTZ_L 0x2C //00101100 #define OUTZ_H 0x2D //00101101 #define FF_WU_CFG 0x30 //00110000 #define FF_WU_SRC 0x31 //00110001 #define FF_WU_ACK 0x32 //00110010 #define FF_WU_THS_L 0x34 //00110100 #define FF_WU_THS_H 0x35 //00110101 #define FF_WU_DURATION 0x36 //00110110 #define DD_CFG 0x38 //00111000 #define DD_SRC 0x39 //00111001 #define DD_ACK 0x3A //00111010 #define DD_THSI_L 0x3C //00111100 #define DD_THSI_H 0x3D //00111101 B.2 La gestione del modulo STEVAL-MKI009V1 #define DD_THSE_L 0x3E //00111110 #define DD_THSE_H 0x3F //00111111 // definizione delle operazioni #define SINGLE_WRITE 0x00 // 00-000000 #define MULTI_WRITE 0x40 // 01-000000 #define SINGLE_READ 0x80 // 10-000000 #define MULTI_READ 0xC0 // 11-000000 // definisco un byte don't care #define DONTCARE 0x00 // definizione delle macro #define scrivi(porta, posizione, valore) \ { if((valore)>0) (porta) |= (1<<posizione); \ else (porta) &= ~(1<<posizione); } #define leggi(porta, posizione) (((porta) >> (posizione)) & 1) #define ACC_ENABLE { scrivi(PORTB, PB4, 0); } // SS = 0; #define ACC_DISABLE { scrivi(PORTB, PB4, 1); } // SS = 1 unsigned char axes[6]; // dichiarazione delle funzioni void acc_init(void); char acc_read(char registro); void acc_getAxes(void); void acc_write(char registro, char data); #endif B.2.2 lis3lv02dl.c /* Gestione dell'accelerometro lis3lv02dl */ #include "lis3lv02dl.h" #include <avr/io.h> #include "spiman.h" extern unsigned char axes[]; // inizializzazione del modulo void acc_init(){ // attivo l'accelerometro portandolo fuori dal power down mode // CTRL_REG1 = 11110111 // powerdown 11 : powerdown disabilitato // samplerate 11 : 2560 Hz // SelfTest function 0 : disabilitato // asse-Z : 1, attivato // asse-Y : 1, attivato // asse-X : 1, attivato ACC_ENABLE; // SINGLEWRITE | CTRL_REG1 = CTRL_REG1 spi_send(CTRL_REG1); spi_send(0xF7); ACC_DISABLE; // // // // // // definisco il control register2 CTRL_REG2 = 01000100 scale: 0 +-2g BDU: 1 accesso esclusivo alla lettura dei registri BLE: 0 modalita` di memorizzazione little endian BOOT: 0, refresh internal register non necessario 79 80 } B.2 La gestione del modulo STEVAL-MKI009V1 // IEN: 0, data ready signal abilitato // DRDY: 1, data ready pin abilitato // SIM (SPI interface mode) : 0, 4 wire // DAS: 0 formato dati 12bit giustificati a destra ACC_ENABLE; // SINGLEWRITE | CTRL_REG2 = CTRL_REG2 spi_send(CTRL_REG2); spi_send(0x44); ACC_DISABLE; // il control register3 va lasciato a impostazioni di default // lettura di un registro char acc_read(char registro){ char valore; // attivo l'accelerometro ACC_ENABLE; // richiedo la lettura del registro spi_send(SINGLE_READ | registro); // invio un byte di don't care spi_send(DONTCARE); // recupero il valore letto valore = spi_receive(); // disattivo l'accelerometro ACC_DISABLE; } return valore; void acc_getAxes(){ // attivo l'accelerometro ACC_ENABLE; spi_send(MULTI_READ | OUTX_L); spi_send(DONTCARE); // OUTX_L axes[0] = SPDR; spi_send(DONTCARE); // OUTX_H axes[1] = SPDR; spi_send(DONTCARE); // OUTY_L axes[2] = SPDR; spi_send(DONTCARE); // OUTY_H axes[3] = SPDR; spi_send(DONTCARE); // OUTZ_L axes[4] = SPDR; spi_send(DONTCARE); // OUTZ_H axes[5] = SPDR; // disattivo l'accelerometro ACC_DISABLE; } // scrittura singola in un registro void acc_write(char registro, char data){ // attivo l'accelerometro ACC_ENABLE; // scrivo la richiesta spi_send(SINGLE_WRITE | registro); spi_send(data); // disattivo l'accelerometro ACC_DISABLE; } B.3 La piattaforma avr-chip45 B.3 La piattaforma avr-chip45 B.3.1 contiki-chip45-main.c #include #include #include #include #include <avr/pgmspace.h> <avr/fuse.h> <avr/eeprom.h> <avr/io.h> <stdio.h> #include #include #include #include #include #include #include #include #include #include "lib/mmem.h" "loader/symbols-def.h" "loader/symtab.h" "contiki.h" "contiki-net.h" "contiki-lib.h" "dev/rs232.h" "dev/spiman.h" "dev/net_service.h" "dev/lis3lv02dl.h" #define #define #define #define #define ALE 3 nWR 4 nRD 5 nCS 6 nLRST 7 FUSES = { .low = 0xe2, .high = 0x99, .extended = 0xff, }; // sottoscrivo al sistema i servizi da far partire PROCINIT(&etimer_process, &tcpip_process, &net_process); int rs232_input_byte(unsigned char c); void init_lowlevel(void){ rs232_init(RS232_PORT_0, USART_BAUD_19200, USART_PARITY_NONE | USART_STOP_BITS_1 | USART_DATA_BITS_8); rs232_redirect_stdout(RS232_PORT_0); } int main(void){ uip_ipaddr_t ipaddr; /********************** Booting *************************/ // setto l'interfaccia Input/Output DDRA = 0x00; DDRB = 0xFF; DDRC = 0xFF; PORTA = 0x00; PORTB = 0x00; PORTC = 0xF7; PORTC PORTC PORTC PORTC |= |= &= &= (1 << nRD); (1 << nWR); ~(1 << ALE); ~(1 << nCS); 81 82 B.3 La piattaforma avr-chip45 PORTC |= (1 << nLRST); // inizializzo l'interfaccia RS232 init_lowlevel(); // inizializzo l'interfaccia SPI spi_init(); // Clock clock_init(); printf_P(PSTR("\n\r********BOOTING CONTIKI*********\n\r")); // inizializzo il sottosistema per la gestione dei processi process_init(); // specifico la netmask uip_ipaddr(&ipaddr, 255,0,0,0); uip_setnetmask(&ipaddr); // indirizzo ip del modulo uip_ipaddr(&ipaddr, 10,0,0,100); uip_sethostaddr(&ipaddr); // indirizzo ip del gateway uip_ipaddr(&ipaddr, 10,0,0,10); uip_setdraddr(&ipaddr); printf_P(PSTR("System online.\n\r")); /* Register initial processes */ procinit_init(); /* Autostart processes */ autostart_start(autostart_processes); // inizializzo l'accelerometro acc_init(); /******************** Scheduling ***********************/ while(1) { process_run(); } } return 0; B.3.2 Makele.avr-chip45 CONTIKI_TARGET_DIRS = . dev net loader CONTIKI_CORE=contiki-chip45-main CONTIKI_TARGET_MAIN = ${CONTIKI_CORE}.o CONTIKI_TARGET_SOURCEFILES += contiki-chip45-main.c CP2200.c CONTIKI_TARGET_SOURCEFILES += net_service.c spiman.c lis3lv02dl.c CONTIKIAVR=$(CONTIKI)/cpu/avr CONTIKIBOARD=. CONTIKI_PLAT_DEFS = -DF_CPU=20000000UL -DAUTO_CRC_PADDING=2 MCU=atmega644 B.4 L'applicazione # opzione avr-dude AVRDUDE_OPTIONS=-V include $(CONTIKIAVR)/Makefile.avr B.4 L'applicazione B.4.1 tesi.c #include "contiki.h" #include "contiki-net.h" #include <string.h> #include "dev/rs232.h" #include "dev/lis3lv02dl.h" static struct psock ps; static char buffer[10]; static uint16_t x,y,z; /*-------------------------------------------------------------*/ PROCESS(samples_viewer, "Sample viewer process"); PROCESS(server_process, "Server process"); PROCESS(samples_retriever, "Sample retriever process"); AUTOSTART_PROCESSES(&samples_viewer, &samples_retriever, &server_process); /*-------------------------------------------------------------*/ PROCESS_THREAD(samples_viewer, ev, data) { static struct etimer timerView; char messaggio[40]; PROCESS_BEGIN(); rs232_print(RS232_PORT_0, "-- Campioni raccolti:\n\r"); while(1) { etimer_set(&timerView, CLOCK_SECOND / 4); PROCESS_WAIT_UNTIL(etimer_expired(&timerView)); } sprintf(messaggio, "X: %d, Y: %d Z: %d \n\r", x, y, z); rs232_print(RS232_PORT_0, messaggio); PROCESS_END(); } /*-------------------------------------------------------------*/ PROCESS_THREAD(samples_retriever, ev, data) { static struct etimer axes_timer; static uint8_t leds_state = 0; char messaggio[40]; PROCESS_BEGIN(); while(1) { etimer_set(&axes_timer, CLOCK_SECOND / 16); 83 84 B.4 L'applicazione PROCESS_WAIT_UNTIL(etimer_expired(&axes_timer)); acc_getAxes(); x = axes[0] + (axes[1]<<8); y = axes[2] + (axes[3]<<8); z = axes[4] + (axes[5]<<8); } } if(leds_state){ // se il led e` acceso lo spengo scrivi(PORTC, PC0, 1); // LED verde OFF leds_state = 0; }else { // altrimenti se e` spento lo accendo scrivi(PORTC, PC0, 0); // LED verde ON leds_state = 1; PROCESS_END(); } /*-------------------------------------------------------------*/ static PT_THREAD(handle_connection(struct psock *p)) { static struct etimer timerAcc; char messaggio[40]; PSOCK_BEGIN(p); // *** stampa assi while(1){ etimer_set(&timerAcc, CLOCK_SECOND / 4); PSOCK_WAIT_UNTIL(&ps, etimer_expired(&timerAcc)); sprintf(messaggio, "X: %d Y: %d Z: %d \n", x, y, z); PSOCK_SEND_STR(p, messaggio); } // *** PSOCK_CLOSE(p); } PSOCK_END(p); PROCESS_THREAD(server_process, ev, data) { static uint8_t first_time; PROCESS_BEGIN(); tcp_listen(HTONS(1988)); while(1) { scrivi(PORTC, PC1, 0); // led rosso ON (PORTC &= ~(1<<PC1)) first_time = 1; PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); if(uip_connected()) { PSOCK_INIT(&ps, buffer, sizeof(buffer)); while(!(uip_aborted()||uip_closed()||uip_timedout())){ PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); // se e` la prima volta che mi connetto if(first_time){ scrivi(PORTC, PC1, 1); // led rosso OFF (PORTC |= (1<<PC1)) first_time = 0; B.4 L'applicazione } } } } handle_connection(&ps); } PROCESS_END(); B.4.2 Makele all: make -f Makefile.tesi TARGET=avr-chip45 tesi.elf avr-objcopy -O ihex -R .eeprom -R .fuse tesi.elf tesi.hex avr-size -C --mcu=atmega644 tesi.elf clean: make -f Makefile.tesi TARGET=avr-chip45 clean rm symbols.c symbols.h tesi.elf tesi.hex rm -rf obj_avr-chip45 load: avrdude -P /dev/ttyUSB0 -p m644p -c usbasp -e -U flash:w:tesi.hex B.4.3 Makele.tesi CONTIKI_PROJECT = tesi all: $(CONTIKI_PROJECT) #valorizzando apps si include la cartella della # applicazione in apps/nomeapplicazione/ #APPS = webserver CONTIKI = ../.. include $(CONTIKI)/Makefile.include 85 Appendice C Il codice sorgente dell'applicazione client Di seguito si riporta il codice sorgente dell'applicazione client analizzata nel capitolo 5. C.1 Il package graphics C.1.1 Main.java package graphics; public class Main { } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new graphics.GUIClient().setVisible(true); } }); } C.1.2 GUIClient.java package graphics; import import import import java.awt.Color; java.awt.Container; java.awt.Dimension; javax.swing.*; public class GUIClient extends JFrame { // *** Attributi *** private client.Controller controller; // form di connessione public JTextField address; public JButton buttonConnect; C.1 Il package graphics // pannello connesso public JTextField samplesFileName; public JButton buttonDisconnect; public JButton buttonSaveSamples; public JTextArea textArea; public JScrollPane scroll; public JTextField chartFileName; public JButton buttonSaveChart; // *** Metodi *** // costruttore public GUIClient() { controller = new client.Controller(this); // setto il titolo del frame e gli attributi setTitle("Client"); this.setMinimumSize(new Dimension(325, 60)); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); // istanzio i componenti grafici contenuti nel frame address = new JTextField("10.0.0.100"); buttonConnect = new JButton("Connetti"); buttonDisconnect = new JButton("Disconnetti"); buttonSaveSamples = new JButton("Salva campioni"); samplesFileName = new JTextField("samples"); textArea = new JTextArea(""); scroll = new JScrollPane(textArea); chartFileName = new JTextField("chart"); buttonSaveChart = new JButton("Stampa grafico"); // inizializzo i componenti inseriti initComponents(); } pack(); /** * inizializza i componenti grafici della applicazione */ private void initComponents() { // creo un pannello contenitore Container pane = this.getContentPane(); pane.setLayout(null); pane.setBackground(Color.ORANGE); // aggiungo i campi del modulo al contenitore pane.add(address); pane.add(buttonConnect); pane.add(buttonDisconnect); pane.add(samplesFileName); pane.add(buttonSaveSamples); pane.add(scroll); pane.add(chartFileName); pane.add(buttonSaveChart); address.setLocation(20, 5); address.setSize(100, 20); 87 88 C.1 Il package graphics buttonConnect.setLocation(125, 5); buttonConnect.setSize(180, 20); textArea.setLocation(20, 30); textArea.setSize(285, 200); textArea.setEditable(false); scroll.setLocation(20, 30); scroll.setSize(285, 200); buttonDisconnect.setLocation(125, 5); buttonDisconnect.setSize(180, 20); samplesFileName.setLocation(20, 235); samplesFileName.setSize(100, 20); buttonSaveSamples.setLocation(125, 235); buttonSaveSamples.setSize(180, 20); chartFileName.setLocation(20, 260); chartFileName.setSize(100, 20); buttonSaveChart.setLocation(125, 260); buttonSaveChart.setSize(180, 20); // DISCONNESSO address.setVisible(true); buttonConnect.setVisible(true); buttonDisconnect.setVisible(false); buttonSaveSamples.setVisible(false); samplesFileName.setVisible(false); textArea.setVisible(false); scroll.setVisible(false); buttonSaveChart.setVisible(false); chartFileName.setVisible(false); // creo un ascoltatore GUIListener listener = new GUIListener(this, controller); addWindowListener(listener); } } buttonConnect.addActionListener(listener); buttonDisconnect.addActionListener(listener); buttonSaveSamples.addActionListener(listener); buttonSaveChart.addActionListener(listener); /** * visualizza un messaggio d'errore mediante dialog box * @param msg */ public void showError(String msg) { JOptionPane.showMessageDialog(null, msg); } C.1.3 GUIListener.java package graphics; import import import import client.Controller; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.WindowEvent; C.1 Il package graphics import java.awt.event.WindowListener; public class GUIListener implements WindowListener, ActionListener{ // *** Attributi *** private GUIClient gui; private Controller controller; // *** Metodi *** // costruttore public GUIListener(GUIClient gui, Controller controller) { this.gui = gui; this.controller = controller; } // metodi corrispondenti agli eventi scatenati sul listener public void windowOpened(WindowEvent e) { } public void windowClosing(WindowEvent e) { System.exit(0); } public void windowClosed(WindowEvent e) { } public void windowIconified(WindowEvent e) { } public void windowDeiconified(WindowEvent e) { } public void windowActivated(WindowEvent e) { } public void windowDeactivated(WindowEvent e) { } public void actionPerformed(ActionEvent e) { // distinguiamo gli oggetti che hanno generato l'evento // recupero l'oggetto che ha scatenato l'evento Object c = e.getSource(); if(c == gui.buttonConnect) { controller.connect(); } else if(c == gui.buttonDisconnect) { controller.disconnect(); } else if(c == gui.buttonSaveSamples) { controller.saveSamples(); } else if(c == gui.buttonSaveChart) { controller.saveChart(); 89 90 } C.2 Il package client } } C.2 Il package client C.2.1 Controller.java package client; import import import import import import import import import import import import import graphics.GUIClient; java.io.IOException; java.net.UnknownHostException; java.util.concurrent.Semaphore; java.util.concurrent.locks.Lock; java.util.concurrent.locks.ReentrantLock; java.io.File; org.jfree.chart.JFreeChart; org.jfree.chart.ChartUtilities; org.jfree.chart.ChartFactory; org.jfree.chart.plot.PlotOrientation; org.jfree.data.xy.XYSeries; org.jfree.data.xy.XYSeriesCollection; public class Controller extends Thread{ // *** Attributi *** private boolean ConnectionState; private GUIClient gui; private client.ConnectionManager connManager; private Semaphore permits; private Lock access; private String text; // *** Metodi *** // costruttore public Controller(GUIClient gui){ this.gui = gui; permits = new Semaphore(0); access = new ReentrantLock(); this.start(); } public void run(){ while(true){ try { // se c'e` del testo da aggiornare lo faccio altrimenti attendo permits.acquire(); } catch (InterruptedException ex) { // il connection manager e` stato terminato mentre ero bloccato } access.lock(); gui.textArea.append(text+'\n'); access.unlock(); } } /** * aggiunge nuovo testo da visualizzare * @param text */ C.2 Il package client public void refreshText(String text){ // aggiorno la struttura dati condivisa access.lock(); this.text = text; access.unlock(); // incremento il numero di permessi permits.release(); } /** * connette il client al server */ public void connect(){ boolean esito = false; // istanzio il gestore della connessione connManager = new client.ConnectionManager(this); // controllo il campo indirizzo if(gui.address.getText().isEmpty()){ gui.showError("Devi inserire l'indirizzo IP del server"); } else { try { // tento la connessione connManager.Connect(gui.address.getText()); esito = true; } catch (UnknownHostException ex) { gui.showError("Impossibile connettersi all'indirizzo specificato"); } catch (IOException ex) { gui.showError("Impossibile connettersi alla porta specificata"); } } } if(ConnectionState != esito){ ConnectionState = esito; refreshGUI(); } /** * disconnette il client dal server */ public void disconnect(){ boolean esito = true; try { // chiudo la connessione con il server connManager.endConnection(); esito = false; } catch (IOException ex) { gui.showError("Errore durante la chiusura della connessione"); } // a questo punto pulisco il testo in vista di riconnessioni future gui.textArea.setText(""); ConnectionState = esito; refreshGUI(); } /** * salva i campioni raccolti su un file di testo */ public void saveSamples(){ boolean esito = false; 91 92 C.2 Il package client FileManager fm = null; // controllo che il campo con il nome del file non sia vuoto if(gui.samplesFileName.getText().isEmpty()){ gui.showError("Devi inserire il nome del file in cui salvare i campioni"); } else { try { // istanzio un nuovo scrittore per il file fm = new FileManager(gui.samplesFileName.getText()); esito = true; }catch (IOException ex) { gui.showError("Errore durante l'apertura del file " +gui.samplesFileName.getText()); } } } if(esito){ try { // tento la scrittura del file fm.write(gui.textArea.getText()); fm.close(); } catch (IOException ex) { gui.showError("Errore durante la gestione del file " +gui.samplesFileName.getText()); } } /** * costruisce e salva il grafico per i campioni raccolti */ public void saveChart() { if(gui.chartFileName.getText().isEmpty()){ gui.showError("Devi inserire il nome del file in cui salvare il grafico"); }else{ JFreeChart chart = ChartFactory.createXYLineChart( "State of accelerometer sensor",// Title "sampling operations", // operazioni "acceleration", // accelerazione parseSamples(), // Dataset PlotOrientation.VERTICAL, // orientamento stampa true, // Legenda true, false); } } // tento di salvare il grafico come JPG try { ChartUtilities.saveChartAsJPEG(new File(gui.chartFileName.getText() +".jpg"), chart, 500, 300); } catch (IOException e) { gui.showError("Errore durante la creazione dell'immagine su disco"); } /* * elabora la stringa dei dati per ricavarne i campioni * @return il dataset da utilizzare per il grafico */ C.2 Il package client private XYSeriesCollection XYSeries seriesX = new XYSeries seriesY = new XYSeries seriesZ = new 93 parseSamples(){ XYSeries("X-axis"); XYSeries("Y-axis"); XYSeries("Z-axis"); // acquisisco i dati presenti all'istante // in cui il comando e` stato impartito access.lock(); String dati = gui.textArea.getText(); access.unlock(); String[] righe = dati.split("\n"); // recupero le righe int x,y,z; // scorro le righe per raccogliere i dati for(int i = 0; i < righe.length; i++){ // catturo la X x = Integer.parseInt(righe[i].substring(righe[i].indexOf("X")+3, righe[i].indexOf("Y")-1)); // aggiungo il valore all'asse X seriesX.add(i+1, x); // catturo la Y y = Integer.parseInt(righe[i].substring(righe[i].indexOf("Y")+3, righe[i].indexOf("Z")-1)); // aggiungo il valore all'asse Y seriesY.add(i+1, y); } // catturo la Z z = Integer.parseInt(righe[i].substring(righe[i].indexOf("Z")+3, righe[i].length()-1)); // aggiungo il valore all'asse Z seriesZ.add(i+1, z); XYSeriesCollection dataset = new XYSeriesCollection(); dataset.addSeries(seriesX); dataset.addSeries(seriesY); dataset.addSeries(seriesZ); } return dataset; /** * aggiorna l'interfaccia grafica in base allo stato della connessione */ public void refreshGUI(){ if(ConnectionState){ // CONNECTED gui.samplesFileName.setVisible(true); gui.buttonDisconnect.setVisible(true); gui.buttonSaveSamples.setVisible(true); gui.textArea.setVisible(true); gui.scroll.setVisible(true); gui.buttonSaveChart.setVisible(true); gui.chartFileName.setVisible(true); gui.address.setVisible(false); gui.buttonConnect.setVisible(false); gui.setSize(325, 325); } else { // DISCONNECTED 94 } C.2 Il package client } } gui.address.setVisible(true); gui.buttonConnect.setVisible(true); gui.textArea.setVisible(false); gui.buttonDisconnect.setVisible(false); gui.buttonSaveSamples.setVisible(false); gui.samplesFileName.setVisible(false); gui.scroll.setVisible(false); gui.buttonSaveChart.setVisible(false); gui.chartFileName.setVisible(false); gui.setSize(325, 30); C.2.2 ConnectionManager.java package client; import import import import import java.io.DataInputStream; java.io.DataOutputStream; java.io.IOException; java.net.Socket; java.net.UnknownHostException; public class ConnectionManager extends Thread { // *** Attributi *** // definisco la porta di livello trasporto da utilizzare private final int port = 1988; // definisco la socket da utilizzare per la connessione private Socket socket; // definisco gli stream per la gestione della socket private DataOutputStream output; private DataInputStream input; private Controller controller; // *** Metodi *** // costruttore ConnectionManager(Controller controller) { this.controller = controller; } /** * connette all'host specificato * @param host * @throws UnknownHostException * @throws IOException */ public void Connect(String host) throws UnknownHostException, IOException { // tento di stabilire un collegamento di rete socket = new Socket(host, port); // istanzio lo stream in output sul collegamento output = new DataOutputStream(socket.getOutputStream()); // istanzio lo stream in input sul collegamento input = new DataInputStream(socket.getInputStream()); this.start(); } @Override public void run(){ String str; char c; C.2 Il package client boolean flag = true; } } while(flag) { str = ""; try { while ( ( c = (char) input.read() ) != '\n') str += c; // aggiorno la stringa sul controllore controller.refreshText(str); } catch (IOException ex) { flag = false; } } /** * conclude la connessione con l'host * @throws IOException */ public void endConnection() throws IOException { socket.close(); } C.2.3 FileManager.java package client; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileManager { // *** Attributi *** private FileOutputStream fos; private DataOutputStream ds; // *** Metodi *** public FileManager(String name) throws IOException{ fos = new FileOutputStream(name+".txt"); ds = new DataOutputStream(fos); } /** * scrive sul file di testo * @param testo da scrivere * @throws IOException */ public void write(String text) throws IOException{ ds.writeUTF(text); } } /** * chiude il file e lo stream verso di esso * @throws IOException */ public void close() throws IOException{ ds.close(); } 95 Bibliograa [1] Swedish Institute of Computer Science, Contiki 2.x Reference Manual [2] Adam Dunkels, Bj Granvall, and Thiemo Voigt. Contiki - a lightweight and exible operating system for tiny networked sensors. Local Computer Networks, Annual IEEE Conference on. 2004. [3] Adam Dunkels, Oliver Schmidt and Thiemo Voigt. Using Protothreads for Sensor Node Programming [4] Frank Dabek, Nickolai Zeldovich, Frans Kaashoek, David Mazì res and Robert Morris. MIT Laboratory for Computer Science. Event-driven Programming for Robust Software [5] Adam Dunkels, Oliver Schmidt, Thiemo Voigt and Muneeb Ali, Protothreads: Simplifying EventDriven Programming of MemoryConstrained Embedded Systems [6] Adam Dunkels, Oliver Schmidt, Thiemo Voigt and Muneeb Ali. Protothreads - Simplifying Programming of MemoryConstrained Embedded Systems. ACM SenSys 2006. [7] Adam Dunkels. Swedish Institute of Computer Science. Programming Contiki crash Course. [8] Philip Levis, Sam Madden, Joseph Polastre, Robert Szewczyk, Kamin Whitehouse, Alec Woo, David Gay, Jason Hill, Matt Welsh, Eric Brewer and David Culler. TinyOS: An Operating System for Sensor Networks. 2007. [9] Abraham Silberschatz, Peter Baer Galvin, Greg Gagne. Operating System Concepts, Seventh Edition. John Wiley & Sons Inc. 2005 [10] Renato Spiandorello. Studio di interoperabilità tra i sistemi operativi Contiki e TinyOS per reti di sensori wireless. Laurea degree thesis, Università degli studi di Padova. 2010. Bibliograa 97 [11] Ferdinando Grossi. Intelligenza ambientale per il monitoraggio e l'assistenza domestica: un'infrastruttura di controllo basata su comunicazione IP. PhD Thesis, Università degli studi di Parma. 2009. [12] Daniele Caiti. Applicazione del database relazionale MySQL alle Wireless Sensor Networks (WSNs): monitoring e memorizzazione parametri ambientali. Laurea Specialistica degree thesis, Università di Modena e Reggio Emilia. 2009. [13] Contiki Application Example. Web site http://senstools.gforge.inria.fr/ [14] Website: http://www.ptlug.org/wiki/Installare_la_toolchain_GCC_per_AVR [15] Chip45 website: http://www.chip45.com/ [16] Usbasp project website: http://www.schl.de/usbasp/ [17] AVRdude website: http://www.nongnu.org/avrdude/ [18] Silicon Laboratories. Website: http://www.silabs.com/Pages/default.aspx [19] Minimal webserver with uIP module on Crumb644 by Stefan Perzborn. Website: http://www.perzborn.net/ [20] Contiki Port for Crumb644 by Stefan Frings. Website: http://www.meinemullemaus.de/software/crumb/index.html [21] Atmel Corporation. Website: http://www.atmel.com/ [22] STMicroelectronics. Website: http://www.st.com/ [23] JFreeChart project website: http://www.jfree.org/jfreechart/ 98 Bibliograa Ringraziamenti Ringrazio la mia famiglia per il supporto morale ed economico e soprattutto per gli sbalzi di umore sopportati a causa dello stress scolastico. Ringrazio le mie nonne per l'aetto e i pasti caldi senza la quale questo lavoro non sarebbe stato realizzato. Ringrazio il mio relatore Prof. Massimo Zandri ed il disponibilissimo correlatore Dott. Andrea Seraghiti per l'entusiasmo, gli spunti ed il materiale ecacemente proposto. Ringrazio i miei compagni di corso Gioele Luchetti e Matteo Dromedari per avermi accompagnato esame dopo esame in questi tre intensi anni e per aver condiviso con me interessi, progetti e discussioni. Inne un ringraziamento doveroso va a tutti i professori del corso di laurea per la pazienza e la disponibilità dimostrata di fronte alla mia innita curiosità.