ESERCITAZIONE # 11
Gestione di un Encoder in quadratura
Vediamo in questo esempio una gestione di un
encoder in quadratura.
Si tratta sempre di una semplice gestione di segnali digitali di I/O.
Nel nostro esercizio utilizziamo un encoder mod. 3315 di Bourns, che è un
componente veramente economico (costa meno di 5 €).
|
Si tratta di un encoder ad azionamento manuale, previsto per
sostituire potenziometri e commutatori si ogni genere di
apparecchiatura, dagli strumenti agli amplificatori BF, alle autoradio
ecc.
Ha forma e dimensione simile ad un piccolo potenziometro cermet e
dispone di tre terminali:
- un capo comune
- e due estremi collegati ad due interruttori.
|
|
Dal punto di vista elettrico si tratta di due interruttori con un capo
in comune, costituito da due lamine elastiche che scorrono su una pista
conduttiva non continua, alternando stati di aperto e chiuso.
|
|
Dal punto di vista costruttivo, le lamine elastiche scorrono su una pista sagomata collegata al polo
comune; la continuità o le interruzioni della pista forniscono i due
segnali di uscita.
Nell' immagine a lato, la sagoma delle parti conduttrici (in nero)
produce una sequenza di contatti aperti e chiusi che genera un codice
Gray a due bit.
Sostanzialmente si tratta di due interruttori con i contatti
striscianti; l' uscita va quindi collegata a resistenze di pull-up
dipendenti dalla Vdd del circuito logico a cui l' encoder si collega. |
Questo encoder, come detto, è previsto per essere ruotato
manualmente, quindi con un basso numero di giri.
Il foglio dati dichiara una
rotazione massima di 120 rpm, pari a 2 giri al secondo, al di sopra delle
possibilità reali ottenibili agendo su una manopola.
Del componente esistono diverse versioni; quella che qui usiamo è la 3315Y-001-016L
(Codice RS 692-8471), che fornisce 16 impulsi per giro, mentre la -06x dà
6 impulsi per giro.
Nell' impiego pratico si utilizzano normalemnte due resistenze di
pull-up collegate ai contatti, con il comune a massa
|
In questo modo:
- con i contatti aperti ci sarà una tensione positiva sui canali
- con i contatti chiusi, i pin dei canali saranno collegati alla
massa
|
Se applichiamo ai due contatti delle resistenze di pull-up dipendenti dalla
Vdd del microcontroller, abbiamo due segnali a livello logico che variano da 0 a
1 secondo il diagramma seguente:
|
Facendo ruotare l' asse dell' encoder a velocità costante, si
ottengono due uscite
ad onda quadra sfasate tra di loro di 90 gradi.
Se il segnale A passa dal livello basso a quello alto mentre il
segnale B si trova a livello basso, si ritiene per convenzione che il
disco dell' encoder stia girando in senso orario (clockwise o CW).
Se il segnale A passa dal livello basso a quello alto mentre il segnale
B si trova a livello alto, si ritiene che il disco dell' encoder stia
girando in senso antiorario (counter clockwise o CCW).
Attraverso l' analisi dei due segnali si può quindi determinare il senso di rotazione dell' asse.
Va notato che se la velocità di rotazione è costante, si avranno le
forme d'onda dello schema. Se la velocità non è costante quello che
cambia è la durata delle fasi, ma non la loro sequenza,
che dipende dallo strisciare dei contatti sulla superficie del disco
conduttore. |
La via più semplice per interpretare i due segnali è quella di
osservare il momento del cambio di livello di una fase e verificare il livello
dell' altra.
Se la fase A è alta e la fase B è passata da livello basso a livello alto
(fronte di salita), la direzione è CW.
Se la fase A è bassa e la fase B è in commutazione da livello basso a livello
alto (fronte di salita), la direzione è CCW.
Vediamo di raccogliere in una tabella l' andamento dei segnali rispetto alla
rotazione.
Per ogni variazione di stato, indicata dalla linee tratteggiate, tabelliamo il
livello dei due canali.
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
>>---- CW
---->> |
A |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
0 |
B |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
|
<<---- CCW
<<---- |
Se prendiamo ogni coppia di valori di A e B per ogni campionamento ci troviamo
ad avere la sequenza:
>>----
CW ---->>
|
00 01 11 10 00 01 11 10
00
|
<<----
CCW <<----
|
Questa tabella è valida per qualsiasi tipo di encoder in quadratura.
La sequenza dei valori degli ingressi nei successivi stati è un codice Gray, dove ad ogni stato successivo
cambia uno solo dei bit del numero. Questo viene ottenuto sagomando le
piste conduttrici del disco come nella figura precedente.
Perchè un codice
Gray?
binario |
Grey |
00 |
00 |
01 |
01 |
10 |
11 |
11 |
10 |
00 |
00 |
|
In una normale sequenza binaria, un incremento porta, ad esempio, da 11 diventa
00; in entrambi i casi vengono modificate due cifre per ogni passo.
Se una cifra, proveniente da un ingresso, soggetto a disturbi,
rimbalzi, ecc., cambiasse in leggero anticipo sull'
altra, potrebbe essere letto per errore un numero intermedio 01 o 10 prima che lo
stato 00 si stabilizzi.
Il codice Gray, cambiando un solo bit per volta, evita
questo problema. |
Una
particolarità del codice Grey ci consente di implementare un semplicissimo
algoritmo per determinare il senso di rotazione.
Se
prendiamo una qualsiasi coppia di successiva della sequenza CW,
ad esempio
i primi due:
00,
01
e calcoliamo l' OR esclusivo (XOR)
tra il bit di
destra del
primo numero e
il bit di sinistra del secondo (0
XOR 1
= 1)
il risultato sarà 1. E sarà 1 per qualsiasi coppia
!
Se facciamo lo
stesso per le coppie in senso CCW,
che sono invertite (ad esempio
i primi due:
01,
00),
l' XOR
darà come risultato 0. E
sarà 0 per qualsiasi coppia
!
In questo modo abbiamo discriminato la direzione di rotazione.
Se vogliamo calcolare anche gli impulsi, per ogni transizione potremo aggiungere
(CW) o sottrarre (CCW) una unità ad un contatore di transizioni.
Il valore del contatore sarà visualizzato su un display a 7 segmenti.
Il programma inizia
con la definizione delle porte di I/O come ingressi per l' encoder (PORTB) e
come uscite per il display (PORTC).
Il contatore viene
inizializzato a 0 e il valore viene riportato sul display.
Il PIC verifica ora
lo stato degli ingressi e lo memorizza per avere un punto di partenza, che salva
come immagine "old" dello stato dell' encoder.
Il loop principale analizza i livelli dei pin di ingresso: se non si sono
modificati rispetto alla situazione salvata, resta nel loop di verifica.
Se si sono modificati, il nuovo stato viene salvato in una locazione
"new" e viene elaborata la direzione attraverso l' algoritmo prima
descritto. Il fatto che ci sia stata una variazione viene inteso come un impulso
e quindi si aggiorna il contatore, a crescere se la direzione è CW, a diminuire
se la direzione è CCW.
Con questo sistema
viene contato un impulso per ogni variazione del segnale di riferimento e quindi
4 volte il numero dei passi della risoluzione dell' encoder.
Con "passo" si intende un ciclo tra due rising ( o falling edge)
successivi.
In questo caso,
avendo un solo display, il conteggio varierà da 0 a F quattro volte per
ogni giro dell' asse, a crescere in direzione CW e a decrescere in direzione CCW.
Così si è moltiplicata per 4 la risoluzione dell' encoder: se su un giro il
numero di passi è 16, la risoluzione è
360° /16 =
22.5°
con 4 *16 impulsi
contati si avrà una risoluzione di:
360° / (4*16) =
5.625°
Elaborati impulso e
direzione, il programma salva il nuovo stato dei bit nella
locazione "old" e procede al display del dato.
Il contatore è a 8 bit, il che vuol dire 256 step, ma il
display è solo a 1 cifra, che, in esadecimale, va da 0 a F, rappresentabili con il solo nibble basso del contatore.
Viene incrementato l' intero contatore, ma questo non ha importanza, in
quanto viene utilizzata proprio solo la sua parte bassa per prelevare
dalla tabella la configurazione dei segmenti da accendere.
All' inizio della tabella di scambio tra valore del contatore e codice per l'
accensione dei segmenti l' offset viene limitato al solo nibble basso (il che
orina una tabella proprio con 16 entries possibili).
Il loop continua a
ripetersi indefinitamente.
Una seconda
versione del programma è offerta con il conteggio dei passi limitato a 16.
Quindi si ricorre semplicemente alla divisione per 4 del contenuto del contatore
prima di effettuarne il display.
Da osservare che lo shift viene effettuato iniettando un carry=0 e con il
risultato nell' accumulatore WREG, mentre il contenuto del counter resta
inalterato.
Questo approccio,
molto semplice, ha delle limitazioni.
Per evitare gli errori
di 'slittamento', che si hanno nel caso in cui l' encoder vari di
posizione senza che il programma sia in grado di rilevare la situazione, la
verifica dello stato dell' encoder deve essere messa in atto almeno
una volta
ogni 1 / (risoluzione dell'encoder* max giri al secondo).
Nel nostro caso, l'encoder ha un massimo operativo di 120 rpm, ovvero 2 giri al
secondo, con una risoluzione di 16 impulsi per giro. Siccome noi valutiamo ogni variazione di stato di uno
dei due segnali, in un passo abbiamo 4 variazioni
da considerare.
Di con sequenza la verifica deve essere attivata ogni 1/ (16*4*2) secondi, ovvero
ogni 7.8 ms circa.
Nell' esempio, con un clock di 1MHz, questo tempo è ampiamente rispettato, dato
che le operazioni che il programma svolge tra un check dell' encoder e il
successivo durano pochi us.
Ma
in altre condizioni di hardware o di programma occorrerà una verifica precisa dei tempi e, dove
non è possibile rispettarli, è necessario un diverso approccio, attraverso un
campionamento a tempo costante o un interrupt.
Per curiosità, nel caso di una
interfaccia utente, più grande è il diametro della manopola sull' asse dell'
encoder, più
lungo è il tempo necessario a farle compiere un giro e più lento è l'ingresso. La
sostituzione di una manopola di controllo più grande può essere tutto ciò che
è necessario per ridurre la frequenza di campionamento.
Da quanto sopra,dunque, ne consegue che questo approccio non è certamente adeguato per encoder che ruotino ad
elevata velocità o con un grande numero di impulsi per giro.
Inoltre va tenuto presente un altro punto molto importante: se un encoder ottico produce segnali puliti, un
encoder meccanico invece genera segnali ricchi di rimbalzi (bounces) sui fronti
di commutazione, per cui è necessario applicare una strategia di debouncing.
Il foglio dati dell' encoder utilizzato precisa che i rimbalzi sono contenuti
entro 5 ms massimo.
-
Un primo metodo
di debounce è il classico RC, che crea una rete di ritardo alla
commutazione del fronte. Non è il massimo, dato che il suo inserimento
produce un rallentamento dei tempi di commutazione dei fronti e richiede
ingressi Schmitt trigger.
-
Molto meglio è
inserire un circuito dedicato, ad esempio il classico MC14490 o uno dei vari
MAX6816 e simili.
Questo sistema è sicuramente il più semplice e più sicuro da
implementare, ma significa l' aggiunta di un componente.
-
Per ultimo, un
debounce software è sempre possibile, ma non è facilmente inseribile nell'
algoritmo usato qui.
Da considerare anche che contatti nuovi avranno un rate di
rimbalzi inferiore a contatti utilizzati a lungo, a causa del consumo delle
piste dovuto alla strisciamento dei contatti.
Per ultimo può
essere importante ricordare che in una installazione reale definitiva può
trovarsi in condizioni molto diverse da quelle del Laboratorio di sviluppo; vanno
quindi presi in considerazione il rumore elettrico dovuto
a connessioni lunghe tra encoder e pin di ingresso del micro e il problema dell'
elettricità statica (ESD) portata dalla mano dell' utente. Questi due fattori,
se non considerati, potrebbero essere un problema che va risolto con gli opportuni accorgimenti.
Il Sorgente
I punti esenziali del sorgente sono i seguenti.
Vengono riservati alcuni bytes in RAM per il contatore, le locazioni new e
old di salvataggio degli stati dell' encoder.
; bank 0 - 128 bytes
; -------------------
CBLOCK 0x00
temp ; temp generico
counter ; contatore
old ; stato encoder precedente
new ; stato encoder attuale
end_Accbank0:0 ; dummy for overrun check
ENDC
if end_Accbank0 > 0x7F ; check for overrun
error "Access Bank 0 space overrun"
endif |
Viene definita anche una maschera che permette il filtraggio dei soli due bit
meno significativi di PORTB a cui sono collegati i canali dell' encoder.
; Gestione Encoder
EncoderPort EQU PORTB
EncoderTris EQU TRISB
; PORTb map
;| 7 | 6 | 5 | 4 | 3 | 2 |
1 | 0 |
;|-----|-----|---|---|---|---|-----|-----|
;| ICD | ICD | - | - | - | - |ch. B|ch. A|
#define EncoderA PORTB,0
#define EncoderB PORTB,1
EncoderMask EQU b'00000011'
; maschera per i due bit dell' encoder |
Dopo il setup degli IO, viene azzerato il contatore e il display, per la
gestione del qyuale viene utilizzata la tabella RETLW già vista nell' esercizio
precedente:
; azzera contatore e display
clrf counter
; azzera contatore
clrf WREG
; azzera W
rcall GetDigitCode
; converti in 7 segmenti
movwf DisplayPort
; e manda al display |
Il centro del programma è costituito da una iniziale analisi dello stato
delle uscite dell' encoder, seguito da un loop indeterminato che ripete l'
aggiornamento:
; memorizza lo stato iniziale dell' encoder
movf EncoderPort, w
; recupera stato encoder
andlw EncoderMask
; solo i due bit validi
movwf old
; e salva
; loop principale
loop
rcall chk_encoder
; test stato encoder
btfss WREG,0
; W=0 - no rotazione
bra loop
; quindi loop
; W=1 - c'è stata rotazione, recupera contatore
movf counter,w
rcall GetDigitCode
; converti in 7 segmenti
movwf DisplayPort
; e manda al display
bra loop
; quindi loop continuo
|
L' analisi dello stato dell' encoder è demandato ad una subroutine che
esegue l' algoritmo prima dettagliato. La routine rientra al loop con WREG=0 se
non ci sono state modifiche dall' ultimo test e con WREG=1 se nel
frattempo lo stato dell' encoder è mutato.
Per lo spostamento del contenuto delle celle di memoria viene utilizzata l'
istruzione movff
del set Enhanced, che è una istruzione a 32 bit.
;-----------------------------------------------------------------
; test dello stato dell' encoder
chk_encoder
movf
EncoderPort, w ; recupera stato encoder
andlw EncoderMask
; solo i due bit validi
movwf new
; e salva
movff new, temp
; copia salvato
; verifica se la condizione attuale (new) è uguale o diversa
; dalla precedente (old) attraverso un xor temp new, old
; Se il risultato dell' xor è 0, non c'è variazione
movf old,w
xorwf temp,f
bnz cke
; diversi - aggiorna contatore
retlw 0
; uguali, ritorna con 0
; senza altre operazioni
; diversi, aggiorna contatore determinando il senso di rotazione
; Uno shift a sinistra allinea old,0 con new,1
cke bcf STATUS, C
; clear carry per rotate (debug only)
rlcf old, f
; xor old_shiftato, new determina il senso di rotazione
; Il risultato dell' xor è effetuato in old, il cui contenuto sarà
; comunque sovrascritto e non serve più.
movf new,w
xorwf old,f
; se il bit 1 è = 1 la rotazione è oraria
btfsc old,1
bra up
; e incrementa
; altrimenti è antioraria e decrementa
down decf counter,f
bra cke1
up incf counter, f
; esce dalla procedura salvando l' attuale stato dell' encoder
; come old per la prossima comparazione e ritorna con W=1 per
; indicare il cambiamento avvenuto
cke1 movff new, old
retlw 1
|
Se si incontrano errori nella compilazione è opportuno verificarli con la
lista di descrizione degli errori e correggere dove si è sbagliato.
Il file compresso di questa esercitazione è scaricabile dall'
area di download.
|