30.11.2022 Views

LEZIONE 23

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

LEZIONE 22 (11/5/22)

Diciamo una cosa aggiuntiva rispetto alla lezione precedente: nel protocollo SPI ci sarà sempre un

master e uno slave, anche per quanto riguarda le classi disponibili in ambiente mbed, in alcuni casi

(che non approfondiremo perché non sono casi studio del nostro corso) il microcontrollore può esso

stesso fungere da periferica, cioè da sensore ad esempio, e diventare uno slave.

Vi porto un esempio di quando questa cosa può essere utile: immaginiamo di progettare una

boardina (un PCB) dove sopra ci sta un chip che abbiamo visto in parte l’altra volta (AD8232,

boardina rossa che misura l’ECG ed è una boardina ma sopra il cuore è un circuito integrato

AD8232 prodotto dall’Analog Devices) che è un chip analogico perché prende in ingresso il

biopotenziale dell’ECG, lo amplifica, lo filtra ecc. ed esce ancora una volta un segnale analogico.

Uno potrebbe pensare di prendere questo chip e renderlo digitale, in che senso? Progetta una piccola

boardina, sopra ci mette l’AD8232 e affianco un piccolo microcontrollore, tipo come quello della

bluepill che prende in ingresso il segnale analogico, lo campiona e poi lo da in uscita su un bus SPI.

Quindi quel microcontrollore non è un microcontrollore che opera le funzionalità di unità centrale

ma serve soltanto a prendere il segnale analogico, convertirlo in digitale e darlo su un bus di uscita

al vero microcontrollore; l’idea dovrebbe essere la seguente:


Abbiamo un microcontrollore centrale nel mio sistema di elaborazione, poi con dei bus SPI

abbiamo i seguenti fili: MOSI, MISO, CLOCK, GND e l’alimentazione VCC. Questi fili a loro

volta vanno verso un microcontrollore che si interfaccia con il segnale analogico che proviene

dall’AD8232 che a sua volta ha tre terminali che vanno verso gli elettrodi. Quest’ultimo diventa per

noi un sensore ECG con interfaccia digitale SPI. Quindi, per fare questo, il primo microcontrollore

deve lavorare come master, mentre il secondo è diventato uno slave e quindi non possiamo

utilizzare la stessa classe in mbed e allora dobbiamo programmare quest’ultimo tramite la libreria

SPI Slave dove in realtà di differente rispetto alla precedente non presenta grandi differenze perché

abbiamo la classe per definire l’oggetto, il format perché anche lui deve sapere su quale fronte, con

quale polarità ecc., la frequenza, e in questo caso deve avere il comando receive che la precedente

non aveva perché la precedente aveva solo il write perché lui è un master che decide lui quando

cominciare a parlare, lo slave invece deve stare continuamente in ascolto sul bus per capire se c’è

qualche cosa e infatti vediamo che con il metodo secondo il quale, supponendo che l’oggetto si

chiama slave ECG, scriviamo slave ECG.receive in modo tale che interroga l’SPI per vedere se è

stato ricevuto un dato. Perché infatti lui che cosa dovrebbe fare? Fare il campionamento da

analogico a digitale a intervalli di tempo regolari e ascoltare se qualcuno gli sta chiedendo un

risultato; se il .receive è vero, è true, allora lui deve usare il comando reply. Quindi vuoi sapere da

me quanto è il valore? Allora dopo che ho fatto il .receive che mi ha ritornato true, io faccio .reply e

gli rispondo con i 16 bit e quindi gli devo inviare prima un byte e poi un altro byte della

conversione analogico/digitale del mio sensore.

A questo punto entriamo nel dettaglio di un possibile sensore. Questo è il caso studio di un sensore

digitale: è un accelerometro in grado di darci la misura delle accelerazioni lungo i tre assi x, y e z,

ha la possibilità di avere un’interfaccia sia SPI sia I2C (che non abbiamo ancora visto). Ora ci

soffermiamo sull’SPI. La presente boardina ha i seguenti pin: SCL (clock), MOSI, MISO, due pin

di interrupt, un chip select, l’alimentazione e il GND. Gli interrupt sono dei pin che possono essere

configurati, per capirci valutiamo il seguente esempio di applicazione: configuro il sensore per fare


in modo che se l’accelerazione lungo uno dei tre assi supera un certo valore allora si deve alzare

questo pin di interrupt. Oppure valutiamo un’applicazione stupida: immaginiamo di realizzare un

casco per operare in smart con all’interno un accelerometro e immaginiamo che con un infortunio

sul lavoro l’operaio cade e quindi c’è una grande variazione di accelerazione lungo l’asse z (se

l’asse z è quello perpendicolare) e allora il microcontrollore si potrebbe attivare con un interrupt

cioè un segnale di abilitazione che dice: abbiamo superato la soglia, c’è qualcosa che non va, allora

il microcontrollore si attiva e manda tramite wifi un warning.

A questo punto prima di capire come si fa il codice, dovremmo, in un caso reale, in cui bisogna

progettare un’applicazione che fa uso di un sensore digitale, andare a leggere il datasheet del

sensore e capire questo come comunica. Tra le varie informazioni bisogna andare a vedere i registri

interni perché ci sono alcuni sensori molto più spiccioli che non hanno bisogno di nessuna

configurazione (quindi li alimento e questi mi rispondono con il dato) ma la maggior parte vanno

configurati, come per esempio il fondo scala; in questo caso per l’accelerometro, quale deve essere

il suo fondo scala? Cioè la massima accelerazione che potrà misurare quale sarà? ±2g, ±4g e così

via. Quindi al power up del sensore ci devono essere alcune configurazioni. Ci sono i registri nei

quali sono memorizzati i dati dell’accelerazione e così via.


Andiamo quindi a vedere come è fatto il codice e come si usano questi registri. Questo è un

programma che interroga l’accelerometro.

Seguo i seguenti passaggi:

includo la libreria;

definisco un oggetto di nome ‘acc’ di classe SPI sui tre pin che definiscono MISO, MOSI e clock;

poi definisco a parte un Digital Out (il microcontrollore fa da master) che è il chip select;

poi definisco la porta seriale per andare verso il computer per inviare i dati;

poi definisco un buffer di 6 byte: 6 byte perché dovrà contenere le tre accelerazioni, ognuna delle

quali codificata su 16 bit. Quindi ogni accelerazione su 16 bit, cioè 2 byte, e avendo 3 accelerazioni:

3x2=6 byte. Quindi devo avere un buffer dove quando vado a leggere i dati che vengono dal

sensore, metto dentro i valori appena letti;

poi abbiamo un vettore di tre interi a 16 bit perché io una volta che ho letto il buffer che proviene

dal sensore che contiene i 6 byte della mia informazione, poi a coppie questi byte li devo unire e

ricostruire l’accelerazione lungo x, y e z.

Perché non posso definire direttamente data da 3? Perché li devo accomunare a due a due. Il sensore

mi restituisce un blocco, lui capisce che devo fare una lettura e quindi in successione mi spara fuori

6 byte, poi sarà un mio problema sapere che quei 6 byte li devo prendere a coppia, cioè a due a due,

metterli insieme e creare l’accelerazione di x, l’accelerazione di y e l’accelerazione di z.

Poi abbiamo 3 float che saranno il valore numerico convertito dell’accelerazione perché dentro data

io metterò il valore numerico del convertitore analogico/digitale, poi lo dovrò trasformare in

un’accelerazione che è un float.

Passo al main:

la prima cosa che faccio è deselezionare il sensore perché metto il chip select alto; quindi, non sto

parlando con il sensore, in questo caso il sensore sta in una condizione di stand-by.

Dapprima devo configurare il mio canale SPI e quindi definisco il format 8 bit in modalità 3, perché

modalità 3? Perché così ha deciso il costruttore dell’ADXL345;


Setto la frequenza a 2MHz, la massima disponile per questo sensore e anche questo lo ritrovo nel

datasheet;

Una volta che ho configurato il canale SPI posso parlare finalmente con il sensore. Poiché è in

logica negata il chip select, pongo cs=0;

Ora cominciamo a parlare, cosa faccio? Per prima cosa invio sul canale SPI il codice esadecimale

31, ma andiamo a vedere chi è questo 31; il commento sulla slide ci aiuta che dice ‘data format

register’, allora vado a vedere il datasheet:

Vediamo che dentro il sensore c’è un registro con indirizzo 31esadecimale. A che cosa serve questo

registro? Serve a configurare il fondo scala, serve a capire se l’output è su 10 bit oppure cambia

dinamicamente in funzione del fondo scala, setta la possibilità di avere degli interrupt attivi alti o

attivi bassi, se abbiamo la modalità 3 wire SPI o 4 wire SPI. Quindi ci permette di configurare tutta

una serie di informazioni sul sensore.

Allora come funziona? Ho acceso il sensore e lui parte con una configurazione di default, per

esempio, con il fondo scala ±2g; io potrei fregarmene e dopo averlo acceso comincio a chiedergli i

valori delle accelerazioni: questo è il primo caso, quindi non lo configuro, lui parte con una


configurazione di default però questo non è il modo di operare. Allora la prima cosa che faccio è

quella di configurarlo, ma come faccio a dire al sensore che voglio configurare e in particolare

voglio andare a scrivere nel registro data format (0x31) per configurare tutte queste informazioni?

Do un primo byte, quindi il sensore si è appena svegliato e la prima cosa che vede è che gli è

arrivato un messaggio con l’indirizzo 31esadeciamale e lui capisce e dice ok qui c’è qualcuno che

non vuole le accelerazioni ma mi vuole configurare, quindi dopo che ha letto 31 sul bus SPI, il

prossimo dato che riceve sarà il contenuto di questo registro e il contenuto di questo registro che noi

gli inviamo è 0B. Vediamo sulla calcolatrice (in modalità programmatore) quali sono gli 8 bit di

questo registro (0B): 0000 1011.

Vediamo se ci troviamo.

Questa dovrebbe essere la stringa, il commento sul programma dice che stiamo selezionando ±16g e

quindi il least significant bit (LSB), quindi ogni livello del convertitore analogico/digitale, varrà

0.004g.

NB: nel datasheet c’è un errore nel pdf: se i primi due bit valgono 00 = ±2g, se valgono 01 = ±4g,

se valgono 10 = ±8g, se valgono 11 = ±16g.

NB2: sto leggendo la stringa a partire da destra.

Quindi possiamo dire che stiamo procedendo bene.

Dopodiché, li vogliamo allineati a destra o a sinistra? In realtà non è che ci cambia molto, questo

dipende se poi il LSB sarà il primo da sinistra o il primo da destra. Abbiamo scelto 0, quindi right

justify result.

Poi il quarto bit è 1 (0 output is 10 bit always, quindi il nostro risultato indipendentemente dal fondo

scala ci verrà restituito sempre su 10 bit). Il quinto bit è sempre zero e tutti gli altri sono zero perché

in realtà non stiamo scegliendo una comunicazione half duplex quindi laddove la comunicazione

avviene in due tempi differenti ma mentre ricevo un video, quindi la comunicazione classica

dell’SPI, l’ultimo bit si setterebbe ad 1 solo per fare un self test del sensore per capire se sta

funzionando bene.

Quindi ho configurato il data format register.

Poi, finisco la trasmissione perché per il momento gli dovevo dare l’informazione, gliel’ho data, lui

si è configurato e lo rimetto di nuovo in una fase di riposo (cs=1).

Poi decido di volergli parlare ancora con il sensore, quindi metto di nuovo cs=0, lui si attiva un’altra

volta e capisce che qualcuno gli vuole dire qualche cosa. E che cosa faccio? Vado a configurare

quello che viene detto power control register.


Quindi, c’è un registro che io voglio ancora configurare, però il sensore quando si vedrà arrivare sul

bus SPI il dato 2D in esadecimale capirà questa volta che io voglio configurare un registro che sta

proprio in quell’indirizzo. Quindi che cosa faccio, voglio capire se Device quando lo accendo va in

standby mode cioè a seconda del bit che vado a configurare in questo registro lui può andare in

standby o in Measure mode.

Gli vado a configurare la stringa 08, come è fatta la stringa 08? 0000 1000

Dopodiché rimetto il chip select ad 1 e quindi lo spengo di nuovo.

Bisogna tener conto che queste operazioni stanno nel main, quindi vengono eseguite una sola volta

al power up.


Poi comincia il while true. All’interno di questo while che cosa vorrò fare? Vorrò interrogare il

sensore, prendermi i dati, accoppiarli a due a due, trasformarli in accelerazione e mandarli al

computer per vederli sullo schermo. Allora, che cosa faccio qua dentro? Questa cosa la faccio con

una certa tempificazione (in questo caso è gestita con il wait); con un wait (0.2) che cosa significa?

0.2 sarebbe 1/5, quindi sto chiedendo 5 campioni al secondo: mettendo questo wait, il while esegue

questo codice ogni 200ms e quindi vuol dire che invierà al computer i tre dati di accelerazione 5

volte al secondo.

Dopodiché comincio: cs=0, start a transmission: praticamente che cosa sto facendo qua? Sto

inviando un’informazione al sensore che è un’informazione aggregata, che cosa significa? La

stanghetta verticale che vediamo tra 80, 40 e 32 è la OR BITWISE (or bit a bit), quindi che cosa gli

sto inviando? Facciamo il seguente esercizio: (prima facciamo l’esercizio e poi capiamo perché

hanno fatto così)

0x80 corrisponde a 1000 0000, poi deve andare in OR perché c’è la stanghetta (ora bit a bit) con 40

cha vale 0100 0000 e poi con 32 che vale 0111 0010. Quindi in definitiva, facendo la OR bit a bit

ottengo: 1111 0010. Cerchiamo di capire: abbiamo che la nostra stringa inviata è 32 più qualche

cosa;


questo 32 è l’indirizzo del primo registro che contiene i dati di accelerazione. I dati di accelerazione

dentro al sensore sono memorizzati in tre registri che occupano 6 byte nella memoria del sensore;

gli indirizzi sono contigui, quindi in esadecimale 32, 33 fino a 37.

Quindi il sensore, dopo averlo configurato, si vede arrivare una successione di bit dove i primi bit,

partendo dal meno significativo, hanno l’informazione 32, quindi lui capisce che vogliamo stavolta

non configurare ma accedere al registro DATA partendo da DATAX0.

Dopodiché: partiamo dall’ultimo bit (7) a sinistra che vale 1 (quindi bit 7= R/|W: 1 for read, 0 for

write) e quindi dobbiamo leggere i dati di accelerazione e non forzarli all’interno del registro (ecco

perché abbiamo settato quell’1). Dopodiché bit 6: 1 for multiple byte, 0 for single, che cosa

significa? Significa che in realtà il penultimo bit, se lo metto a zero allora per poter leggere i dati di

accelerazione devo fare: dammi il registro 32 e lui mi restituisce la prima parte dell’accelerazione

lungo x, poi gli devo inviare un altro byte 33 e lui mi restituisce il secondo byte di x; poi gli devo

inviare 34 e lui mi restituisce la prima metà dell’accelerazione lungo y ecc. (questo serve se per

esempio non sono interessato alle accelerazioni lungo tutti gli assi ma nella mia applicazione voglio

leggere ad esempio soltanto l’accelerazione lungo x e quindi in questo caso è inutile che mi vado a

leggere tutti i 6 byte e quindi gli invio prima 32 e poi 33 così mi prendo l’accelerazione lungo x).

Nel caso in cui voglio tutte e tre le accelerazioni è molto più ragionevole dirglielo al sensore:

bisogna dire che a partire dall’indirizzo 32 voglio leggere tutti i byte, quindi che cosa succede?

Vediamolo nel codice:


Una volta che gli ho inviato questa configurazione, voglio leggere tutti i byte a partire dall’indirizzo

32.

NB: utilizziamo 0x80|0x40|0x32 perché risulta essere una cosa estremamente utile da parte di chi

scrive il firmware perché: se facciamo 80+40+23 in modalità esadecimale otteniamo F2; nel

firmware potevo scrivere direttamente .write F2 però quando saremmo andati a leggerlo non

avremmo capito cosa significasse e quindi è solo un trucco per rendere leggibile il codice.

A questo punto, una volta che gli ho detto questo, il sensore si è messo in una modalità in cui dice:

vuoi tutti i 6 byte però io sono slave quindi io non ti posso rispondere se tu non mi interroghi.

Infatti, ricordiamoci come funziona, consideriamo questa immagine:


Lo slave risponde ma il master a sua volta deve inviare qualcosa per chiudere la catena; se il master

non ha niente da dire, qualcosa deve pur mandare, che cosa manda? Tutti zeri.

A questo punto, io devo leggere i 6 byte delle accelerazioni, ma io non ho nulla da dire al sensore,

allora che cosa faccio? Faccio delle scritture fittizie, cioè scrivo 0, quante volte lo scrivo? Lo scrivo

sei volte con un ciclo for perché ogni volta che io gli invio otto zeri lui mi restituisce il primo byte;

cioè mentre io gli sto sparando tutti zeri lui mi sta rispondendo con il bit che compongono: una

volta il primo byte di x, una volta il secondo byte di x e così via. Quindi scrivo sei volte zero in

modo tale che nel buffer (di cui prima) vado a mettere il risultato ogni volta di questa scrittura di

otto zeri. Quindi qui dentro ho accumulato i sei byte delle accelerazioni. Finito ciò, il programma in

termini di SPI è finito perché metto di nuovo a riposo il sensore (cs=1); poi a questo punto che cosa

devo fare? Devo prendere i primi due byte, li devo azzeccare e creare l’accelerazione lungo x e così

via, che significa azzeccare? Significa che:

Ho la posizione buffer [0] che avrà b0, b1…..b7, poi buffer [1] avrà b8, b9…..b15; io devo prendere

questi due byte e li devo attaccare facendo in modo che dopo b7 venga b8. Quindi da due variabili

ad otto bit devo ottenere una variabile a 16 bit, quindi devo ottenere data: devo costruire un data [0]


che deve diventare b0, b1….b15. Come faccio a fare questa cosa? Prendo il secondo byte e lo shifto

(per l’esempio alla lavagna dovrei shiftare a destra) di otto posizioni e quindi ottengo 0 0 ………

b8, b9, b15 e poi faccio la OR BIT a BIT, quindi li sommo, ottenendo i miei 16 bit che

compongono il numero del convertitore analogico/digitale dell’accelerazione lungo x.

A questo punto devo solo trasformare questo numero in accelerazione, come faccio? Devo

moltiplicare il valore del convertitore analogico/digitale per il guanto di conversione che nel nostro

caso per ±16g è pari a 0,004 (per questo mi servivano quei tre float x, y, z perché trasformo dal

valore numero del convertitore analogico/digitale al valore di g).

Infine c’è un printf che è il comando che serve ad inviare al computer il valore dell’accelerazione.

In definitiva l’SPI è efficace, l’hardware non è particolarmente complicato e possiamo trasferire i

dati in maniera anche con elevata velocità. Quali possono essere i punti deboli? Non c’è un

‘acknowledgement’, che cosa significa? Se c’è qualche problema il master non si accorge di nulla;

non c’è un indirizzamento, quindi per ogni sensore a cui voglio parlare devo mettere un chip select;

non c’è un checking degli errori, cioè se c’è per qualche disturbo un dato che si corrompe lungo la

comunicazione o un bit che viene letto 1 al posto di 0, non c’è alcuna verifica; la prima verifica più

stupida per vedere se un byte è buono e non si è corrotto perché c’è stato un disturbo sulla linea? Il

bit di parità.

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!