-

hey viewer, we're moving!

We are currently transitioning to a new web system, so we are not updating this wikisite anymore.

The public part of the new web system is available at http://www.ira.disco.unimib.it


SPI Accelerometers

From Irawiki

Jump to: navigation, search

Informatica Industriale

Anno Accademico 2008/2009

Firmware: Comunicazione parallela tramite protocollo SPI con 3 accelerometri, acquisizione delle accelerazioni misurate e loro trasmissione tramite USB.

Alto livello: Elaborazione di base e presentazione grafica.

Team di progettazione e sviluppo:


Contents

Introduzione

Lo scopo del progetto consiste nel rilevare i movimenti di un oggetto attraverso la misurazione delle accelerazioni dell’oggetto stesso. I dati sono letti dai sensori da un microcontrollore della serie PIC18 della Microchip e trasmessi a PC mediante una connessione USB. I sensori utilizzati sono gli accelerometri triassiali LIS3LV02DQ della STMicroelectronics (Datasheet LIS3LV02DQ), che vengono venduti già saldati su un modulo dalla ditta RobotItaly ([1]). L’interfacciamento previsto comprende sia la soluzione SPI, utilizzata nel progetto, che l’interfaccia I2C. Questi accelerometri funzionano a 3,3V quindi sono stati utilizzati i convertitori di tensione MCP1702 per l’adattamento 5V/3V. Per minimizzare l’errore di lettura del singolo accelerometro è stato predisposto un array di tre sensori letti in parallelo secondo le modalità descritte nelle sezioni successive. Il microcontrollore utilizzato per il controllo degli accelerometri è il PIC18LF2550, ovvero la versione LowPower della serie PIC18F2455/2550/4455/4550, che può essere alimentata nel range 2-5.5V consentendo l’alimentazione e la comunicazione con gli accelerometri a 3.3V senza bisogno di partitori di tensione. Il PIC18LF2550 è dotato dell’interfaccia seriale USB fino a velocità Full-Speed (12Mbit/s). Verranno descritti ulteriori dettagli nelle sezioni successive.

File:Schema a blocchi.jpg

Organizzazione

Per cercare di minimizzare il rumore dei sensori, nell’ipotesi che si trattasse di rumore bianco (a media nulla) abbiamo utilizzato 3 sensori identici, per poi effettuare una media tra i 3 valori di accelerazione rilevata. Per fare questo è necessaria una lettura dell’accelerazione contemporanea e molto veloce da tutti i sensori. Al fine di ottenere letture sincrone abbiamo utilizzato il canale SPI degli accelerometri e “simulato” un bus SPI sul microcontrollore, che permette con N cicli di clock (dove N=numero di bit della variabile accelerazione, nel nostro caso interi a 16 bit) di ottenere la lettura del valore dei registri. Gli accelerometri LIS3LV02DQ possono essere configurati per fornire un segnale di Interrupt/DataReady ogniqualvolta è sono pronti a trasmettere un nuovo dato. La frequenza di generazione di dati è impostabile mediante la scrittura dei bit dei control-registers a valori di 40Hz, 160Hz, 640Hz e 2560Hz. Nel nostro caso abbiamo utilizzato la frequenza base di 40Hz. Siccome utilizziamo più sensori, il segnale interrupt in ingresso al PIC è stato ottenuto mediante una operazione di AND dei segnali DataReady dei tre accelerometri. Questa operazione logica è stata ottenuta con 4 porte NAND, scelta dettata dalla disponibilità in laboratorio del circuito integrato. Quando tutti gli acclerometri segnalano la presenza di un nuovo dato da leggere (tutti i segnali DataReady a 1) l’uscita del circuito integrato andrà quindi ad alzare un interrupt sul microprocessore che scatenerà l’operazione di lettura SPI.

SPI Bus Interface

Il bus SPI permette letture e scritture dei registri della periferica LIS3LV02DQ ed è utilizzato nella variante a 4 cavi: CS, SPC, SDI, SDO. Rimandiamo al datasheet per la spiegazione completa del protocollo, riportiamo unicamente il grafico che descrive il funzionamento del bus:

File:2 Lettura SPI.png

File:3 Scrittura SPI.gif

La necessità di leggere in parallelo i valori di tre periferiche rende impraticabile la soluzione SPI integrata (ovvero l’utilizzo del MASTER SYNCHRONOUS SERIAL PORT –MSSP MODULE) in quanto è concepita per l’utilizzo con una singola periferica. Per ovviare a questo problema è stata utilizzata la seguente tecnica: le linee di controllo dei sensori, ovvero le linee CS, SPC ed SDI sono condivise da tutti e tre i sensori, mentre le linee SDO sono ovviamente collegate a tre diverse porte del microprocessore. I segnali di controllo vengono quindi inviati contemporaneamente a tutti e tre gli accelerometri, mentre i dati vengono letti parallelamente su 3 porte del PIC.

File:4 Descrizione SPI.gif

Nella tabella seguente viene descritta la modalità di lettura di un qualsiasi registro in modalità SPI. La procedura di lettura è composta da due fasi: nella prima fase viene specificato alla periferica l’indirizzo del registro da leggere (mediante una scrittura) mentre la seconda fase è la lettura vera e propria. La comunicazione SPI è un trasferimento sincrono, quindi le operazioni di lettura e scrittura sono simili dal punto di vista implementativo. L’operazione di scrittura equivale a quella di lettura (il primo byte inviato corrisponde all’indirizzo dove il successivo byte andrà scritto)

File:5 Dettaglio comunicazione SPI.png

File:Codice 1.jpg

File:Codice 2.jpg

Realizzazione del circuito

Componenti

Per la realizzazione del circuito sono stati utilizzati i seguenti componenti:

  • Microcontrollore: è stato utilizzato il pic18LF2550, essenzialmente per i seguenti motivi:
    • Supporta la connessione USB
    • E' possibile alimentarlo a 3.3V. In questo modo non è necessario costruire nessun partitore di tensione per gli accelerometri i quali devono appunto essere alimentati a 3.3V.
    • Dispone di un sufficiente numero di porte (pin).
  • MCP1702: Regolatore di tensione il qual permette di ridurre la tensione proveniente dall'usb (5v) a 3.3V.
  • QUARZO: E' stato utilizzato un quarzo a 20MHz per riuscire a sfruttare la connessione USB a full speed.
  • PORTA NAND: Integrato contenente 4 porte NAND triggerate HCF4093BE . Grazie a una serie di combinazioni degli ingressi si riesce a sincronizzare gli accelerometri: quando un accelerometro è pronto per comunicare il dato mette a 1 il pin INT, la porta NAND ha come output 1 appena i pin INT dei 3 accelerometri sono tutti a 1. L'output della porta NAND è quindi connesso a un pin del PIC che viene gestito come un interrupt esterno.
  • Pulsanti: Abbiamo inserito due pulsanti, il pulsante RESET necessario per resettare il pic e un secondo pulsante utilizzato per far partire la comunicazione dei dati dopo che è stato premuto
  • LED:Sono presenti due led i quali sono utilizzati per comunicazione di errori:
    • Appena viene data corrente alla scheda i 2 led si accendono per qualche istante, in questo modo si riesce a capire se il PIC si resetta a causa di overflow
    • Errori generali. (Per approfondire guardare la documentazione del codice)
  • Connettore USB: e' stata utilizzata la porta USB di tipo B
  • Connettore ICD2 (J1): Connettore utilizzato per collegare il programmatore ICD2 per poter programmare il PIC.
  • Connettori Accelerometri: i connettori presenti sulla scheda che permettono di connettere gli accelerometri sono PHOENIX 254.
  • CONNETTORE RS232: abbiamo previsto la possibilità di utilizzare eventualmente la connessione seriale. Per sfruttare tale connessione bisogna utilizzare il connettore J2 (non presente sulla board).

Schematico

Lo schematico del circuito è stato realizzato con la versione 5.3.0 di Eagle, progettato su su singola faccia. Per problemi pratici è necessario creare alcuni cavallotti manualmente:

  • Chip select
  • GND accelerometri
  • VDD accelerometri

La maggior parte delle piste ha dimensione 0.032 mm in modo tale da facilitare la stampa del circuito. Alcune piste hanno dimensioni ridotta (ad esempio perché devono passare in mezzo ai piedini della porta NAND) a 0.016mm.

File:Board.gif

File:Schematico.png

Stampa del circuito

La tecnica utilizzata per la stampa del circuito è quella del trasferimento per “stiratura” che prevede la stampa dello schematico su carta fotografica con una stampante laser. Dopo numerosi tentativi di trasferimento possiamo consigliare le seguenti accortezze:

  • Stampare con la stampante impostata in alta qualità (per trasferire più toner possibile)
  • Il tipo di carta fotografica utilizzata è determinante per la buona riuscita del trasferimento. Ottimi risultati sono stati raggiunti con la carta Hp Photo Paper, mentre si è dimostrata insoddisfacente la carta Epson.
  • Ritagliare la basetta di rame della stessa dimensione dello schematico. Questo perché nella fase di stiratura il calore del ferro non verrà disperso in zone “inutili”.

File:Carta.jpeg

  • Procedere nella stiratura in questo ordine
    • Utilizzare un ferro da stiro impostato al massimo calore.
    • Una volta stampato lo schematico sul supporto cartaceo, bagnarlo abbondantemente prima di appoggiarlo alla basetta di rame (eventualmente immergendolo).

File:B bagnata.jpeg

File:Immer.png

    • Appoggiare il foglio bagnato sulla basetta di rame. Mettere della carta soffice (carta da cucina, assorbente) sopra il supporto di carta prima di stirare (permette di premere uniformemente sul supporto).
    • Premere energicamente con il ferro da stiro caldo sopra la basetta. Il tempo di esposizione varia a seconda del tipo di carta utilizzato: abbiamo ottenuto ottimi risultati con la carta HP con durate di 45 secondi circa. Un tempo troppo breve non permette il completo trasferimento, un tempo troppo lungo fissa il toner sulla carta e non sulla basetta.

File:Ferro1.jpeg

    • Una volta finita la stiratura immergere la basetta in acqua, senza staccare il foglio con lo schematico; prima di procedere al distacco, premere delicatamente sulla carta in modo da farla bagnare ed ammollare; si vedranno pian piano le tracce di toner attaccarsi al rame ed il foglio di carta staccarsi dalla basetta.
    • Una volta trasferito il toner sul rame, asciugarlo ed immergere la basetta in un mix di acido muriatico ed acqua ossigenata; il composto ossiderà ed eliminerà il rame in eccesso (dove non presente il toner);
    • Per rendere inerte il composto prima di smaltirlo, utilizzare del bicarbonato in un ambiente ben areato (aggiungendolo poco alla volta, aspettando che la reazione si esaurisca)

File:Bic.jpeg

File:Cir.jpeg

Gestione del canale USB

La gestione della trasmissione USB microchip è poco documentata sul datasheet e rimanda ad implementazioni ed arrangiamenti hobbistici. Abbiamo trovato poca documentazione sulla rete riguardo allo specifico microcontrollore da noi utilizzato (Oscloscopio USB - Proyecto de fin de carrera 2005 - Universidad ORT) ed abbiamo riscontrato diversi errori durante l’utilizzo di questo codice. Abbiamo quindi riscritto la gestione del buffer di invio USB mediante un buffer NON circolare. Di seguito vengono elencate le funzioni che lo gestiscono:

  • USBTasks: (parte dello stack microchip) richiama le funzione di gestione dell'usb (keepalive del canale, supporto all'invio dati). Deve essere chiamata almeno ogni 1 ms, altrimenti la connessione usb muore (messaggio di windows "Una delle periferiche USB collegate al computer non ha funzionato correttamente").
  • myUsbtxPush: riceve una stringa presente nella ROM (tutte le stringhe passate alla funzione direttamente tra doppi apici) e un intero che indica l'eventuale header da anteporre alla stringa. Scrive i dati nel buffer chiamando la funzione usbtxPushVAR. In caso di errore richiama la routine di errore. Introduce inoltre un delay per garantire il corretto invio dei dati (vedi usbtxSend per dettagli).
  • myUsbtxPushVAR: come myUsbtxPush, ma riceve una stringa presente nella RAM (tutte le variabili).
  • usbtxPushVAR: si occupa di scrivere i dati nel buffer. Riceve in input un puntatore ad una stringa presente nella ROM (memoria programma). Restituisce 1 se la stringa è più grande del buffer.
  • usbtxSend: si occupa di inviare gli eventuali dati presenti nel buffer: verifica che il canale sia libero chiamando la funzione mUSBUSARTIsTxTrfReady (parte dello stack microchip), e invia il buffer chiamando mUSBUSARTTxRam (parte dello stack microchip).

Nota bene: mUSBUSARTTxRam non è bloccante, ed invia il dato alla successiva chiamata di usbTask. Poiché l'effettivo invio dei dati tramite l'usb dipende dalla chiamata asincrona di usbTask, è stato necessario introdurre un delay nella funzione myUsbtxPush per garantire che il buffer non venga modificato prima che usbTask venga chiamata. Non è stato introdotto il delay in myUsbtxPushVAR perché nella configurazione attuale i tempi di campionamento degli accelerometri e di chiamata della usbTask sono tali da garantire il corretto invio dei dati. Nel caso questi tempi vengano modificati sarà necessario valutarne l'impatto sulla scrittura dei dati nel buffer.

Descrizione del codice

Il codice è stato commentato abbondantemente per permettere una facile lettura. Tuttavia riteniamo utile fare una breve descrizione del progetto, diviso in sezioni. Nella prima parte del codice sono presenti gli headers utilizzati. Notiamo l’include delle definizioni del PIC18F2550 (che è lo stesso della versione LF), l’include dei timer (utilizzati per il keepalive del bus usb) ed altri di uso comune.

Abbiamo settato i configuration bits direttamente nel codice con dei pragma. MPlab più di una volta ha fatto confusione durante l’apertura di progetti ed il setup dei configuration bits nel progetto stesso (ed inoltre versioni diverse di Mplab hanno comportamenti diversi); consigliamo quindi l’impostazione dei CBits direttamente nel codice.

Seguono le maschere di INTERRUPT. I gestori degli interrupt, suddivisi in interrupt di alto e basso livello, devono essere memorizzati in specifiche aree di memoria. Abbiamo utilizzato due tipi di interrupt, di alto e basso livello. Gli interrupt sono sollevati sia da eventi interni (ovvero i timer nel nostro caso) che da eventi esterni (segnale di DATAREADY degli accelerometri). I timer utilizzati sono due, rispettivamente di alto e basso livello e servono per il keepalive del bus usb e per la trasmissione dei dati presenti nel buffer.

La main procedure del programma si comporta in questo modo:

  • Inizializza il canale SPI; setta le porte del microcontrollore come INPUT/OUTPUT in base alle necessità; inizializza gli interrupt ed i timer; inizializza il convertitore analogico-digitale delle porte.
  • Esegue un test per individuare eventuali reset del dispositivo in seguito a buffer-overflow del canale usb.
  • Inizializza il canale USB (USBConnection)
  • Controlla se gli accelerometri sono connessi (verifica lettura registro WHO_AM_I)
  • Stampa configurazione dei registri degli accelerometri
  • While infinito
  • I timer, come gli accelerometri, restano attivi e scateneranno i gestori degli interrupt che manterranno in vita la connessione ed invieranno i dati provenienti dagli accelerometri ad ogni segnale DATAREADY generato dai sensori stessi.

Codice alto livello

La parte di codice di alto livello dovrà occuparsi di elaborare i dati provenienti dal circuito e rappresentarli graficamente. Per primo si crea un oggetto Elaborazione e si crea un oggetto SerialPort che permette la comunicazione con il circuito, settando il nome della porta e la frequenza di trasferimento.

elaboratore = new Elaborazione(time,this);
usb = new SerialPort();
usb.PortName = nome_porta;
usb.BaudRate = Convert.ToInt32(frequenza);

Dopo aver creato la porta, la si apre e successivamente si crea un Thread che permette la lettura dei dati direttamente dal circuito

// apertura porta
usb.Open();
//thread
readThread = new Thread(Read);
readThread.Start();

Il metodo Read() è il metodo che deve leggere i dati dall’USB e inviarli alla classe che deve elaborarli, nel caso in cui verrà rilevato un errore di lettura verrà generata un’eccezione.

while (true) {
 try   {
   //leggo i dati e li trasmetto all'elaboratore
   if (usb.IsOpen)
    {
     string message = usb.ReadLine();
     Console.WriteLine(message);
     elaboratore.Elabora(message);
    }
   }
   catch (Exception e) { Console.WriteLine(e.ToString()); }
 }

Formato dati

I dati che vengono ricevuti sotto forma di pacchetti, preceduti la un’intestazione, e possono essere di due diverse tipologie:

  • Comunicazione: i quali ci indicano le informazioni relative al circuito, ovvero, la frequenza di campionamento degli accelerometri, i parametri del PIC ecc...

Essi sono nella seguente forma “COM;messaggio;”

  • Dati: il pacchetto contenete i dati invece ci restituisce le accelerazioni rilevate dai tre accelerometri e la media relativa ai tre assi.

Essi sono nella forma “DAT;num_seq;accx1;accy1;accz1;accx2;accy2;accz2; accx3;accy3;accz3; mediax;mediay;mediaz;”


Elaborazione

Dopo che il messaggio viene ricevuto il metodo Read() lo gira al metodo Elabora(stringa). Il metodo Elabora(string stringa) funziona nel seguente modo:

  • Riceve in input una stringa
  • La stringa viene suddivisa in sottostringhe
foreach (string substringa in s.Split(delimitatore))
  • Se viene trovato un COM significa che è arrivato un nuovo pacchetto e visualizziamo il vecchio pacchetto
if (substringa.Equals("COM"))
{
 //visualizzazione messaggio
 messaggio = messaggio + "\r\n";
 form.logText.BeginInvoke(new UpdateTextCallback(UpdateText), new object[] { messaggio });
  • Quello che abbiamo ricevuto dopo il COM viene accodato a una stringa finchè non arriverà un nuovo COM o il primo DAT
  • Arriva il primo DAT, arrivano i dati
if ((substringa.Length != 5)&&(indice!=1)&&(indice!=14)) errore = true;
try
 {
  if (indice == 1) datiin[indice-1] = Convert.ToDouble(substringa);
  else datiin[indice-1] = Convert.ToDouble(substringa) * 9.81 / 1000;
  messaggio = messaggio + substringa + ";";                            
 }
catch (Exception e)
{
 if(substringa.Equals("\r\n")) errore=false;
}
  • Controlliamo la lunghezza del dato, se è diverso da 5 viene generato un errore
  • Convertiamo le stringhe in double, se non possiamo convertire viene generata un’eccezione
  • Si continua a convertire le sottostringhe finché non arriva un nuovo DAT
  • Quando arriva un nuovo DAT prima di inviare i dati alla funzione Calcola(dati), controlliamo che il pacchetto sia completo e che non vi sia un errore, se è tutto giusto viene richiamata la funzione Calcola(dati), altrimenti visualizziamo il problema che è stato riscontrato
if ((indice == 14) && (errore == false))
{ 
 //pacchetto corretto
 messaggio = messaggio + "\r\n";
 form.logText.BeginInvoke(new UpdateTextCallback(UpdateText), new object[] { messaggio });
 messaggio = "";
 indice = 0;
 Calcola(datiin);
 errore = false;
}
else
{
 // visualizzazione errore
 messaggio = messaggio + "\r\n";
 form.logText.BeginInvoke(new UpdateTextCallback(UpdateText), new object[] { messaggio });
 messaggio = "";
 //differenzazione dei tipi di errori
 if (errore) messaggio = "ERRORE: lunghezza campi dati errata\r\n";
 else messaggio = "ERRORE: pacchetto incompleto\r\n";

Il metodo Calcola( double[] dati) funziona nel seguente modo:

  • Riceve in input un array di double
  • I primi 40 pacchetti vengono scartati perché contengono troppo rumore
  • I successivi 50 pacchetti vengono utilizzati per la calibrazione degli accelerometri, visto che sono leggermente sfalsati:
    • Sommiamo i dati tra di loro
mediax1 = mediax1 + dati[1];
mediax = mediax + dati[10];
    • Calcoliamo la media
mediax1 = mediax1 ;
mediax = mediax / 50;
    • Calcoliamo il delta
//calcolo dei  delta
if (mediax1 < 0){
 if (mediax < 0) deltax1 = mediax1 - mediax;
 else if(mediax>=0)  deltax1 = mediax - mediax1;
}
if(mediax1>=0){
 if (mediax < 0) deltax1 = mediax1 + mediax;
 else if (mediax > 0) deltax1 = mediax1 - mediax; 
}
    • ricalibrazione dei dati
if(dati[1]< mediax) dati[1] = dati[1] + Math.Abs(deltax1);
else if (dati[1] > mediax) dati[1] = dati[1] - Math.Abs(deltax1);
    • Dopo che è avvenuta la calibrazione i successivi dati possono essere utilizzati per essere visualizzati graficamente e essere utilizzati per calcolare gli spostamenti basandoci sulle regole dello spazio e velocità:
      • Eliminiamo il rumore
for (int i = 1; i < 10; i++)
{
 if ((i == 2) || (i == 5) || (i == 8))
 {
  if ((dati[i] > (-10.40)) && (dati[i] < (-9.60))) dati[i] = 0;
 }
 else
 {
  if (Math.Abs(dati[i]) < 0.4) dati[i] = 0;
 }
}
      • Calcoliamo la velocità e lo spostamento
sacc1[i] = sacc1[i] + velocitaacc1[i] * t + 0.5 * dati[i + 1] * (t * t);
velocitaacc1[i] =  velocitaacc1[i] + dati[i+1] * t;
      • Calcoliamo la velocità e lo spostamento
sacc1[i] = sacc1[i] + velocitaacc1[i] * t + 0.5 * dati[i + 1] * (t * t);
velocitaacc1[i] =  velocitaacc1[i] + dati[i+1] * t;

Interfaccia grafica

Per rappresentare graficamente i dati provenienti dal PIC abbiamo utilizzato la libreria grafica Zedgraph, che permette la realizzazione di grafici 2D. Attraverso la Form visualizzeremo tutti i messaggi in un logText, sia quelli relativi alla comunicazione che quelli contenenti i dati.

http://irawiki.disco.unimib.it/irawiki/index.php/Image:Testcomm.jpg

http://irawiki.disco.unimib.it/irawiki/index.php/Image:Log1.jpg

I grafici relative all’accelerazione sono tre uno per ogni asse nei quali vengono rappresentati le accelerazioni di ogni accelerometro e l’accelerazione media sull’asse.

http://irawiki.disco.unimib.it/irawiki/index.php/Image:Gacc2.jpg

I grafici relativi allo spostamento, a differenza dei grafici dell’accelerazione, sono quattro uno per asse, ognuno dei quali conterrà gli spostamenti dei singoli accelerometri e lo spostamento medio, e un quarto grafico che ci indica come si sta muovendo la basetta sull’asse x e sull’asse z.

http://irawiki.disco.unimib.it/irawiki/index.php/Image:Gspost.jpg

Codice e documentazione

File:Accelerometro SPI - firmware e codice.zip File:Accelerometro SPI - schematico e board.zip File:Accelerometro SPI - documentazione codice.zip File:Pdf utili.zip

Personal tools