Esercitazioni ASM - PIC18

 


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

 


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.

 

 

Copyright © afg. Tutti i diritti riservati.
Aggiornato il 21/09/11.