ENCODER ROTATIVI

Un encoder rotativo o "trasduttore di posizione angolare" (Rotary Encoder) è un dispositivo che permette di codificare la rotazione di un albero in un segnale elettrico. Quindi l'encoder trasforma un movimento meccanico angolare o lineare in una serie di impulsi elettrici digitali.

Per capire come è fatto un encoder possiamo aprire un vecchio mouse di quelli con la pallina: per ogni asse (X e Y) la pallina mette in rotazione una ruota con tante fessure, da un lato di ciascuna ruota ci sono due diodi infrarossi e dall’altro due ricevitori.

Quando di fronte ad un diodo viene a trovarsi la finestra aperta, il raggio passa mandando in conduzione il ricevitore che gli sta di fronte il quale “chiude” un contatto, quando invece tra la coppia trasmettitore/ricevitore si trova la parte di ruota “piena”, il contatto rimane aperto. All’uscita del sensore avremo quindi un segnale ad onda quadra che è indicativo del movimento che sta effettuando la ruota.

L’encoder è composto da:

A - TECNICHE DI RILEVAMENTO

Negli encoder vengono utilizzate principalmente due tecniche per il rilevamento del movimento:

ENCODER OTTICI

I modelli di encoder più utilizzati si basano sulla tecnologia ottica (o fotoelettrica) per la rilevazione dei segnali;

Il principio su cui si basano è la trasmissione di energia luminosa tra una sorgente (diodo emettitore) ed un fotosensore di ricezione (fotodiodo o fototransistor), che risulta in funzione del moto relativo tra la coppia sorgente-ricevitore ed un corpo interposto, detto maschera mobile, caratterizzato da settori opachi accanto a settori trasparenti. Il segnale SA(x) raccolto dai fotosensori è trasdotto in differenza di potenziale elettrica che viene fornita in ingresso ad un circuito comparatore che presenta in uscita un segnale logico SD(x) alto se il segnale del fotosensore è maggiore della soglia Vr , basso nel caso opposto. Le transizioni del segnale digitale in uscita dal comparatore possono essere contate da un opportuno circuito logico (contatore) che fornisce un numero digitale.

L’uscita del fotosensore SA(x)  è una funzione periodica di forma variabile tra una armonica pura ed una funzione a dente di sega con valore minimo maggiore di zero. Il massimo corrisponde all’allineamento della coppia sorgente-ricevitore con il settore trasparente, il minimo con quello opaco. La maschera, in corrispondenza del settore opaco, non impedisce a tutta l’energia luminosa di raggiungere il fotosensore a causa di riflessioni interne allo strumento, tantomeno di annullare il bias (disturbi sistematici che alterano un valore di riferimento, la misura e la correzione) del fotosensore.

Il segnale del fotodiodo viene successivamente confrontato con la soglia di comparazione (nella figura sottostante due risultati dell’operazione di sogliatura) mediante un comparatore analogico collegando al suo ingresso invertente la tensione fissa di riferimento.

Esistono inoltre due modalità di scansione principali: trasmissiva e riflessiva.



Scansione ottica trasmissiva

Nell'encoder ottico di tipo trasmissivo il sistema di lettura si basa sulla rotazione di un disco graduato con un reticolo radiale formato da linee opache alternate a spazi trasparenti. Questo viene illuminato da una sorgente a raggi infrarossi, disposta perpendicolarmente rispetto al sensore. Il disco proietta così la sua immagine sulla superficie di vari ricevitori, opportunamente mascherati da un altro reticolo (collimatore) avente lo stesso passo del precedente. I ricevitori hanno il compito di rilevare le variazioni di luce che avvengono con lo spostamento del disco, convertendole in corrispondenti impulsi elettrici.

Scansione ottica riflessiva

Il sistema ottico riflessivo si basa anch'esso sulla scansione fotoelettrica di un disco graduato, ma in questo caso la sorgente di luce ed il ricevitore si trovano sullo stesso lato del dispositivo. La sorgente luminosa illumina un disco graduato formato da linee opache alternate a linee riflettenti, dove la luce si riflette e viene letta dal sensore (ricevitore), il quale convertirà le variazioni in corrispondenti impulsi, come nel sistema trasmissivo. Questa tipologia di lettura consente di ridurre le dimensioni del dispositivo garantendo comunque le stesse prestazioni. Rappresenta quindi una soluzione ideale per gli ambiti applicativi che richiedono encoder miniaturizzati ed elevata risoluzione.

ENCODER MAGNETICI

Gli encoder magnetici utilizzano un sistema di rilevazione dei segnali basato sulla variazione del flusso magnetico generato da un magnete (una o più coppie polari) posto in rotazione di fronte al sensore generalmente fissato all’albero dell’encoder. La variazione del campo magnetico viene campionata dal sensore e trasformata in un impulso elettrico che definisce la lettura.

Il vantaggio del sistema magnetico è principalmente l’assenza di contatto nella rilevazione, fattore che previene l’usura del dispositivo e risulta quindi vantaggioso dal punto economico, in quanto non richiede manutenzione ed ha una durabilità potenzialmente infinita.

Gli encoder magnetici sono particolarmente adatti all’applicazione in ambienti gravosi che richiedono un’elevata robustezza, velocità e resistenza termica, garantendo al tempo stesso un’affidabilità ottimale nella generazione dei segnali.

L'effetto Hall è la formazione di una differenza di potenziale, detta tensione di Hall (o impropriamente voltaggio di Hall), sulle facce opposte di un conduttore elettrico dovuta a un campo magnetico perpendicolare alla corrente elettrica che scorre in esso. La variazione del campo magnetico determina tale tensione

 L'encoder magnetico rappresenta la più immediata delle applicazioni dei sensori di Hall (SEH); dato che consente, in modo semplice ed economico, la misura della velocità di una ruota magnetizzata, od il numero di giri da questa compiuti. Perché ogni volta che la ruota espone la tacca al sensore di Hall, il campo magnetico varia e variano le reazioni del SEH.

B - TIPOLOGIE DI LETTURA

La tipologia di lettura può essere assiale (o lineare) e radiale.

ENCODER ROTATIVI

Gli encoder rotativi vengono utilizzati, soprattutto in ambito industriale, per il computo delle velocità degli alberi di rotazione. Erano usati nei vecchi mouse con la “pallina” per far muovere il puntatore, sono utilizzatissimi anche in robotica per determinare le velocità delle ruote dei robottini e quindi correggerne la traiettoria.



Un encoder rotativo altro non è che una serie di “contatti” (azionati in vari modi: meccanicamente, tramite infrarossi o sfruttando l’effetto hall) che chiudono ciclicamente un contatto quando l’albero ad essi collegato effettua una rotazione.

ENCODER LINEARI

I trasduttori di tipo lineare hanno la funzione di rilevare la posizione e la velocità dei movimenti meccanici in linea retta.  Utili quindi per determinare misure di lunghezza o di spessore.

C - TIPO DI ENCODER

Gli encoder possono essere di due tipi:

ENCODER INCREMENTALI

Segnalano unicamente gli incrementi (variazioni) rispetto a un'altra posizione assunta come riferimento (posizione zero). Questi incrementi sono indipendenti dal verso di rotazione il quale, non può essere rilevato utilizzando un unico sensore.

I modelli rotativi sono costituiti da un disco di materiale plastico, sul quale sono stati ricavati dei fori o, più comunemente, zone particolarmente trasparenti attraverso le quali è possibile far passare un fascio luminoso. Gli encoder incrementali sono composti da diverse parti:

Il disco viene agganciato in modo solidale con l'albero dell'apparecchiatura di cui si vuole rilevare lo spostamento angolare. Ad ogni rotazione dell'albero si ha uno spostamento uguale dell'encoder. In corrispondenza di un lato del disco vengono applicati dei dispositivi fotoemettitore, mentre sull'altra faccia i dispositivi fotorilevatore. L'attraversamento del fascio luminoso nei fori comporta l'attivazione dell'uscita dell'encoder.

La rilevazione dello spostamento angolare avviene mediante il "conteggio" degli impulsi generati dal fotorilevatore alla propria uscita.

La rilevazione dello spostamento angolare può essere così descritta:

Il numero degli impulsi contati è direttamente proporzionale allo spostamento angolare dell’encoder, vale a dire allo spostamento angolare dell’organo a cui è calettato.  L'encoder incrementale è adatto quindi a rilevare rotazioni, velocità ed accelerazioni in base al conteggio degli impulsi inviati dal circuito in ouput nell'unità di tempo.

In alcuni encoder vi è un ulteriore canale di output – chiamato zero o segnale di riferimento – che fornisce un impulso singolo per giro (fornisce la posizione dello zero dell’albero a cui è calettato l'encoder). È possibile utilizzare quest’impulso singolo per determinare in maniera precisa la posizione di riferimento. Nella maggior parte degli encoder, questo segnale viene definito come Z-Terminal o Fase Z

Nel trasduttore lineare incrementale il principio di funzionamento è analogo a quello dell’encoder rotativo incrementale, ma in questo caso il disco è sostituito da una riga graduata sulla quale si alternano zone opache e zone trasparenti. Questi trasduttori vengono impiegati in ambito industriale sulle macchine operatrici automatiche, in abbinamento a sistemi di conteggio, per realizzare posizionamenti a programma e avanzamenti a passi predeterminati.

I parametri dell'encoder incrementale sono:

1 - o-ring (o-ring)
2 - disco (disk)
3 - elettronica (electronics)
4 - custodia (housing)
5 - testina di lettura (reading)
6 - cuscinetti (ball nearings)
7 - albero (shaft)

ENCODER ASSOLUTI

Nel caso dell'encoder assoluto il disco è suddiviso in settori che andranno a comporre un codice (Binario oppure Gray). Il disco è diviso in n corone circolari e in 2n spicchi.

Ogni settore (spicchio) avrà n spezzoni di corone circolari dove le aree opacizzate corrispondono all'uno logico mentre quelle trasparenti allo zero logico. Ogni corona circolare è associata al singolo bit. Il bit meno significativo è quello della corona più interna. Per ogni corona circolare del disco abbiamo un fotoemettitore e un corrispondente fotorilevatore .

Per minimizzare gli errori di lettura invece del codice binario puro vengono utilizzati altri codici, tra i quali il più importante è il codice Gray. Nel codice Gray il passaggio da un numero al successivo avviene sempre variando un'unica cifra binaria, evitando così che nel passaggio tra la lettura di un numero e il successivo si possano avere letture errate.

L'encoder assoluto ha il vantaggio di dare informazioni che non vengono perdute in caso di mancanza di alimentazione, ma richiede una particolare cura nella collimazione dello zero logico con lo zero macchina, è più costoso di quello incrementale e non è in grado di effettuare delle misure sulla velocità.

L’encoder assoluto può essere:  1) monogiro - 2) multigiro:

Encoder assoluto monogiro

L'encoder assoluto monogiro permette di acquisire una codifica precisa sulla posizione angolare dell’albero a cui è accoppiato anche in caso di mancata alimentazione. Quindi ogni singola posizione angolare viene convertita in un codice di precisione proporzionale al numero di bit, in formato Gray o binario.

Encoder assoluto multigiro

L'encoder assoluto multigiro è uno strumento avanzato che permette di estendere in maniera significativa il campo di azione degli encoder. Oltre alla lettura angolare del singolo giro, viene mantenuto anche il conteggio del n° di giri.

D - CODICE DI GRAY

Il codice Gray è un codice binario a lunghezza fissa utilizzato per la decodificazione del segnale; questo codice presenta la variazione di 1 bit da un numero al suo successivo assicurando un elevato tasso di affidabilità per quanto riguarda la generazione e la decodificazione del codice.

Si possono usare codici di Gray di tutte le lunghezze: il codice di lunghezza s è costituito da tutte le 2s sequenze di s bit e consente di rappresentare tutti gli interi da 0 a 2s-1.

Esso differisce dalla notazione posizionale binaria degli interi in quanto prevede che si passi da un intero al successivo modificando un solo bit. Questa caratteristica (detta a cambio 1) semplifica e rende meno soggette ad errori le operazioni dei dispositivi elettronici che devono scorrere informazioni organizzate in sequenze. La codifica di Gray non è adatta per rappresentare interi da sottoporre ad operazioni come somme o prodotti.

Diversi dispositivi elettronici di acquisizione di posizione, tra cui gli encoder, codificano il valore digitale della posizione chiudendo o aprendo una serie di contatti elettrici o barriere ottiche. Il problema è che a causa delle tolleranze meccaniche è improbabile che due o più bit di una cifra possano commutare esattamente nello stesso istante. Viene a crearsi una configurazione fisica intermedia in cui è codificato un valore indesiderato, che può generare errore nella successiva elaborazione.

Negli encoder che utilizzano Gray, il passaggio da un valore al successivo o precedente comporta la commutazione di un unico circuito, eliminando ogni possibile errore dovuto a codifiche binarie intermedie.
Va notato che anche nel passaggio dall'ultima alla prima parola del codice cambia solamente un bit.

Un codice Gray ad n-bit si costruisce attraverso un algoritmo ricorsivo, abbastanza semplice.

a) Si parte dal primo bit (quello meno significativo) e si mette uno 0 sopra ed un 1 sotto.

b) Al passo successivo, si mette una riga al di sotto dell'1, e come se fosse uno specchio, si ricopiano le cifre invertendone l'ordine.

c) Si termina inserendo uno 0 davanti ai primi 2 bit e un  1 davanti ai 2 successivi. Otteniamo quindi un codice con 2 bit.

d) Iterando i passi precedenti:

  1.  si mette una riga al di sotto della sequenza delle codifiche di Gray a n-1 bit

  2. si specchia la sequenza originale con n-1 bit invertendone l'ordine

  3. si aggiunge uno 0 davanti ad ogni codifica della sequenza originale e un 1 davanti a quelle della sequenza specchiata.

Per convertire un numero rappresentato in base due in codifica di Gray occorre seguire questo procedimento:

Il primo bit (MSB) in codifica binaria rimane uguale e viene, quindi, riportato pari pari; si esegue poi lo XOR tra il numero in codifica binaria e lo stesso numero shiftato di una cifra verso destra, come nel seguente esempio:

Il procedimento di conversione da codice di Gray a codifica binaria normale è molto simile a quello appena descritto ma con qualche piccola differenza. Il primo bit (MSB) rimane uguale e viene quindi riportato, poi si esegue lo XOR tra ciascun bit ottenuto (quello del codice binario) e il bit successivo (da sinistra verso destra) del codice Gray, come in questo esempio:

E - ENCODER DI QUADRATURA

Con un unico contatto possiamo valutare quante volte il contatto viene chiuso. Calcolando il tempo impiegato per effettuare un giro completo, possiamo trovare la velocità di rotazione dell’albero. Pertanto se ci serve sapere la velocità di rotazione un unico contatto può bastare.

Per riconoscere il verso di rotazione occorrono due coppie sorgente–ricevitore sfasate di λ/4 (dove il ciclo λ è la sequenza fenditura chiara {trasparente} - fenditura scura {opaca} ) ovvero di 90°, comunemente identificate come canale A e canale B. Comparando la fase tra i due canali si riconosce il verso del moto. In figura vengono mostrate le sequenze di uscita delle due coppie in caso di moto in verso positivo e negativo. Se il segnale sul canale B risulta essere in ritardo allora il moto è positivo, viceversa se è in anticipo il moto è negativo.


Nei trasduttori rotativi incrementali i sensori A e B sono posizionati sempre sfasati di un quarto di ciclo (λ/4) oppure l'encoder presenta una seconda identica pista sfalsata sempre di λ/4.

Effettuando un controllo dei fronti di salita degli impulsi in uscita A e B, un sistema logico riesce a stabilire il verso di rotazione del disco. 

Immaginiamo che i due sensori dell'encoder siano posizionati sulla ruota, suddivisa in settori, come rappresentato nella figura stilizzata sottostante (le zone scure sono opache mentre quelle bianche trasparenti).

I due contatti si trovano sulla ruota. I settori bianchi (trasparenti) della ruota fanno chiudere il contatto (livello 1) mentra quelli neri (opachi) fanno aprire il contatto (livello 0). Quando la ruota gira, ogni contatto produrrà un’onda quadra (transizioni tra contatto chiuso e contatto aperto). Poichè i due contatti sono posizionati sfalsati di un quarto di ciclo λ come in figura, le due onde quadre prodotte risultano anch’esse sfalsate di 90°.

Osservando i grafici notiamo che al cambio di stato di A (interrupt) quando i livelli logici dei 2 canali sono uguali (entrambi alti o entrambi bassi) allora stiamo ruotando l'albero in senso orario (oppure quando il nostro robottino si sta muovendo in avanti) mentre quando i due livelli logici risultano differenti la rotazione è in senso antiorario (o il nostro robottino sta camminando all’indietro).

Abbiamo visto come la presenza di due segnali sfalsati permette di stabilire il verso in cui l’albero sta ruotando. Questo ci permette, ad esempio in una manopola di controllo del volume, di decidere se stiano alzando oppure abbassando il volume dello stereo.

Chiaramente la posizione dei sensori A e B determina un differente comportamento di fase tra i 2 segnali:

Compensazione degli ingressi di disturbo

Diversi effetti di disturbo quali temperatura, polvere ed impurità accumulate all’interno dello strumento, invecchiamento, disallineamento meccanici, etc., provocano variazioni nella potenza emessa dalle sorgenti luminose, nella potenza raccolta e nella sensibilità dei fotoricevitori. Tali effetti producono una variazione del livello medio del segnale e quindi del duty-cycle (In elettronica, in presenza di un segnale sotto forma di onda rettangolare, il duty-cycle o ciclo di lavoro è il rapporto tra la durata del segnale "alto" e il periodo totale del segnale, e serve a esprimere per quanta porzione di periodo il segnale è a livello alto o attivo).

Per compensare il fenomeno di disturbo è possibile porre due coppie di sensori ad una distanza di λ/2, a cui è possibile aggiungere un numero intero di cicli λ necessario per accomodare gli ingombri delle componenti.
Allo scopo di evidenziare il principio di compensazione modelliamo opportunamente le due uscite mediante due sinusoidi SA(X) e SA(X), che variano in funzione del moto relativo X :

 SA(X) =A·sin(X)+MA

 SA(X) =A·sin(X)-MA

La loro somma è:

SA(X) +SA(X) =A·sin(X)+MA+A·sin(X)-MA

In considerazione del fatto che sia le sorgenti che i fotosensori appartengono allo stesso lotto di produzione, e quindi verosimilmente hanno subito lo stesso invecchiamento e sono stati sottoposti alle medesime condizioni ambientali, i segnali avranno livelli comparabili in termini sia di ampiezza (A≈A) che di valor medio (MA≈ MA), per cui:

SA(X) +SA(X) ≈ 2·A·sin(X)

Dall’equazione appena ricavata si evincono due considerazioni:

  1.  il valor medio è circa pari a zero per cui, impiegando una soglia a potenziale nullo, si ottiene una funzione con duty-cycle circa del 50% anche al variare delle condizioni operative;

  2.  raddoppiando l’ampiezza del segnale analogico è garantito un miglior rapporto segnale/rumore.

Aumento della risoluzione mediante maschera fissa

La luce emessa deve essere estremamente collimata per aumentare la risoluzione. Se il fascio emesso dalla sorgente interessa più di una fenditura si potrebbe non rilevare variazione di potenza luminosa incidente;

Per aumentare la risoluzione si potrebbe pensare di diminuire la dimensione delle fenditure a piacimento. Tuttavia, quando la dimensione delle fenditure è inferiore alla larghezza della superficie di raccolta del fotosensore, la variazione di energia rilevata (vedi figura sottostante) sarà minore.

Una soluzione che può essere adottata è la riduzione della superficie degli elementi sensibili. Tale soluzione viene effettivamente praticata nel caso di risoluzioni non inferiori a circa 0.1 mm accoppiando ad esempio i fotosensori con sorgenti laser. Tuttavia, per risoluzioni minori, una ulteriore riduzione della superficie di raccolta provocherebbe una diminuzione del segnale e quindi del rapporto segnale rumore, prima della operazione di sogliatura. Per ovviare a tale inconveniente si inserisce tra maschera mobile e fotosensore una maschera fissa.

Quando le due maschere sono allineate si ha passaggio di luce. Quando sono sfasate di λ/2 non si ha passaggio di luce verso il ricevitore, purché tutti i raggi dell’emettitore siano paralleli o la distanza tra le maschere sia nulla.
In questo modo si ottiene il duplice vantaggio di spostare dai fotosensori alle fenditure il requisito di accurato posizionamento e di aumentare l’energia luminosa raccolta potendo impiegare fotoricevitori a superficie relativamente ampia. Ricavando entrambe le maschere, la mobile e la fissa, su supporto in vetro mediante fotoincisione, è possibile garantire ottima stabilità ed accuratezza.

Mettendo assieme gli accorgimenti visti precedentemente si perviene ad uno schema più vicino alla realizzazione pratica.

Vengono impiegati quattro fotosensori ricavati nel supporto di un medesimo integrato. La fase relativa tra i segnali viene regolata tramite la griglia della maschera fissa.

Aumento della risoluzione mediante elaborazione digitale

Una tecnica per aumentare la risoluzione impiega l’OR-esclusivo (XOR) tra i canali A e B. L’uscita è alta quando solo uno dei due ingressi è al valore logico alto. L'uscita viene successivamente ritardata mediante una rete RC e fornita in ingresso, assieme all'uscita originale, ad un secondo XOR. Il risultato è un incremento di quattro volte del numero di transizioni basso-alto in un periodo spaziale pari a λ (ho quindi quadruplicato la risoluzione).

Per incrementare ulteriormente la risoluzione è necessario ricorrere ai circuiti interpolatori che impiegano direttamente i segnali analogici provenienti dai fotoricevitori.

F - ENCODER DI QUADRATURA E ARDUINO

Passiamo ora ad un’applicazione pratica. Sfruttiamo un encoder per incrementare/decrementare una variabile. Premendo poi sul pulsante associato al pomello resettiamo tale variabile a zero. Questo sistema è utilizzabile per qualsiasi applicazione che richieda due pulsanti up/down. L’utilizzo di un encoder è sicuramente molto più professionale: ad esempio per un controllo di volume, la regolazione di un livello, di una temperatura, di un tempo, il posizionamento di un servocomando etc.

Per i progetti successivi ho utilizzato un encoder rotativo meccanico di fascia economica. Tale encoder si presenta come un comune potenziometro che ha in più un contatto che si chiude quando l’alberino viene premuto verso il basso.

I pin CLK e DT corrispondono ai due contatti che forniscono due segnali sfalsati di 90°. Il pin SW corrisponde al pulsante interno associato al pomello. Mentre VCC (+) e GND sono i due pin di alimentazione.

Tratteremo i segnali provenienti dai due contatti CLK e DT dell’encoder come se fossero un normale pulsante. Trattandosi di un’encoder meccanico, la chiusura dei contatti non è pulita per cui dovremo implementare anche qualche meccanismo di antirimbalzo (de-bounce). Utilizzando invece encoder ottici o magnetici la chiusura del contatto è pulita e quindi non dovremo preoccuparci dei rimbalzi.

Il circuito anti-rimbalzo viene utilizzato in elettronica per produrre un solo impulso stabile quando l'ingresso risulta elettricamente rumoroso. Di solito viene usato in presenza di un deviatore o di un pulsante e viene utilizzato per eliminare i vari impulsi spuri tipici di una contattazione meccanica. L'origine dei suddetti disturbi in presenza di pulsanti o interruttori è dovuta al fatto che la lamina di contatto non garantisce, una volta commutata, un impulso immediatamente stabile. La semplice chiusura del contatto elettromeccanico genera frequentemente, al posto di un impulso preciso e continuo, un treno di impulsi più stretti e ripidi, dovuti sia agli effetti di rimbalzo della lamina di contatto del componente elettromeccanico che ad una degradazione delle capacità conduttive dei contatti stessi, dovuta ad esempio a sporcizia o a ossidazione degli stessi.

Le tipologie di circuiti antirimbalzo più diffuse sono:

Circuito analogico
L'impulso di disturbo ha la caratteristica di essere composto da impulsi piuttosto brevi e con fronti ripidi e quindi il suo spettro è concentrato nelle alte frequenze. Un approccio possibile nella risoluzione del problema è, quindi, l'utilizzo di un filtro passa basso di tipo R-C che tende ad eliminare, appunto, le componenti in alta frequenza che costituiscono la maggior parte del disturbo stesso. La squadratura finale del segnale ripulito avviene tramite un circuito a comparatore di tipo Schmitt Trigger (un circuito che consente di trasformare un segnale analogico in un'uscita che varia soltanto tra due valori di tensione).

Circuito digitale
Una versione più sofisticata di circuito antirimbalzo può essere realizzata tramite porte logiche. In particolare, vengono utilizzate porte NAND o NOR collegate tra loro a realizzare un flip flop di tipo Set-Reset. La funzione di antirimbalzo si basa sulla caratteristica di tale circuito di mantenere lo stato dell'uscita indipendentemente dal numero di impulsi ricevuti sul corrispondente ingresso.Questo tipo di circuito ha un'immunità al disturbo enormemente maggiore rispetto alla versione analogica, a scapito però di una maggiore complessità circuitale (e quindi di costo realizzativo).

Antirimbalzo software
Anche se non si tratta di un vero e proprio circuito antirimbalzo classico, una funzione analoga può essere svolta via software, ad esempio, dai microcontrollori.

Abbiamo visto precedentemente che i due segnali elettrici CLK (A) e DT (B) vanno “interpretati”. Se si è interessati a misurare solo la velocità/spostamento del motore basta contare il totale degli impulsi in un dato arco di tempo. Se invece serve conoscere anche la direzione dello spostamento allora occorre confrontare la fase tra gli impulsi A e B.

Se non ci serve calcolare la velocità ma solo la quantità di rotazione del perno di un encoder non occorre tenere conto del tempo trascorso, ma basta rilevare il numero di impulsi prodotto. Questo semplifica il programma di Arduino e l'esempio che andremo a sviluppare ha proprio questo scopo.

Per rilevare il numero degli impulsi prodotti da un encoder ci sono 2 modi:

Per i progetti successivi implementiamo il seguente circuito:

Ecco l'immagine reale del circuito


Tutti e 3 i progetti che vedremo eseguono le medesime funzioni:

Per dimostrarne il corretto funzionamento il valore della variabile encoderCount sarà mostrato nel Serial Monitor. In basso un esempio di esecuzione che mostra la variazione del contatore a seconda degli spostamenti dell'encoder.

Progetto 1 (modalità polling)

Il primo progetto utilizza la modalità di polling (leggo lo stato dell'encoder all'interno della funzione loop() ).

// ----------------------------------------------------------------
// Programma che utilizza un rotary encoder a quadratura
// ......::  M O D A L I T A'   P O L L I N G   ::......
// ----------------------------------------------------------------
#define encoderCLK 2 // CLK (canale A) connesso al pin D2
#define encoderDT 4  // DT (Canale B) connesso al pin D4
#define encoderSW 3  // SW (bottone) connesso al pin D3
int encoderCount = 0; // Contatore
int actCLKState;      // Lettura attuale del canale CLK (A)
int prevCLKState;     // Lettura precedente del canale CLK (A)

void setup() 
{ 
    // I pin sono impostati come ingresso
    pinMode(encoderCLK, INPUT);
    pinMode(encoderDT, INPUT);   
    // Setto la resistenza di pull-up - E' equivalente a:
    // pinMode(encoderSW, INPUT);   
    // digitalWrite(encoderSW, HIGH); 
    pinMode(encoderSW, INPUT_PULLUP);
    // inizializza la comunicazione seriale a 9600 bit per secondo:
    Serial.begin(9600);
    // Memorizzo l'ultimo stato del canale CLK
    prevCLKState = digitalRead(encoderCLK);
}

void loop()
{ 
    actCLKState = digitalRead(encoderCLK);
    // Se lo stato dello switch SW diventa LOW significa che ho premuto l'asta dell'encoder
    if (!digitalRead(encoderSW) && encoderCount!=0)
    {
        encoderCount = 0;
        Serial.println(encoderCount);
    }
    // Se cambia lo stato di CLK significa che il pomello e' stato ruotato
    if (actCLKState != prevCLKState)
    {
        /* ---------------------------------------------------------------
        * Per determinare il verso della rotazione occorre leggere DT (B) 
        * Se i 2 canali CLK e DT sono uguali la rotazione è oraria           
        * Se i 2 canali CLK e DT sono diversi la rotazione è antioraria
        *  
        *           Orario                              Antiorario
        * 1    ----      ----      --              ----      ----      --   
        *     |    |    |    |    |    CLK (A)    |    |    |    |    |     CLK (A)
        * 0 --      ----      ----              --      ----      ----    
        * 1 ----      ----      ----                 ----      ----      
        *       |    |    |    |    |  DT (B)       |    |    |    |    |   DT  (B)
        * 0      ----      ----                 ----      ----      ----  
        --------------------------------------------------------------- */
        encoderCount += (actCLKState == digitalRead(encoderDT) ? 1 : -1);
        Serial.println(encoderCount);
        prevCLKState = actCLKState;
  } 
} 

Progetto 2 (modalità interrupt)

Per utilizzare l’Encoder in quadratura con la modalità interrupt dobbiamo utilizzare obbligatoriamente i pin interrupts 0 o 1 di Arduino UNO corrispondenti ai pin digitali 2 e 3.

Con il termine interrupt (interruzione) intendiamo un segnale asincrono che indica la necessità di “attenzione” da parte di una periferica collegata ad Arduino.L’interrupt viene generato quando si verifica una variazione di stato sul piedino di Arduino associato a tale interrupt. Normalmente il microcontrollore esegue le istruzioni all’interno del loop() in modo sequenziale e ripetitivo, ma quando si verifica un interrupt tale flusso di istruzioni viene interrotto all’interno del loop() e vengono invocate altre routine (create dall’utente). Quando queste routine terminano il flusso del programma riprende normalmente dal punto in cui era stato interrotto.
L’utilizzo dell’interrupt è particolarmente utile quando abbiamo la necessità di gestitire istantaneamente un’operazione nel caso si manifesti un evento asincrono esterno.
Un ritardo nel controllo dello stato di diversi pin di input all'interno della funzione Loop()potrebbe causare che il programma non si accorga del cambiamento di stato su uno dei pin, Utilizzando gli interrupt evitiamo questo tipo di errore.

Le funzioni di arduino correlate alla gestione degli interrupt sono:

attachInterrupt(interrupt,funzione,modo)

E' l'istruzione che specifica la funzione da chiamare quando si verifica l'nterrupt indicato come primo argomento. Sostituisce ogni altra precedente funzione che era stata collegata a quel’interrupt.

Gli argomenti sono:

detachInterrupt(interrupt)

E' la funzione che interrompe la gestione del’interrupt. Il parametro “interrupt” definisce il numero dell’interrupt da disabilitare (in Arduino UNO posso usare solo i valori 0 oppure 1)

interrupts()
noInterrupts()

Riabilita la gestione degli interrupt dopo che questi sono stati disabilitati mediante l'istruzione noInterrupts. Queste istruzioni risultano utili poichè gli interrupt possono variare leggermente il tempo di esecuzione di particolari sezioni critiche per cui conviene disabilitarli temporaneamente mediante noInterrupts.

void loop()
{
   // codice iniziale ...
   noInterrupts(); // sospendo la gestione degli interrupt
   // ... sezione critica, codice dipendente dal tempo
   interrupts(); // riattivo la gestione degli interrupt
   // ... codice successivo
}

Colleghiamo quindi i 2 segnali CLK e SW ai pin con interrupt hardware di arduino UNO. Tramite attachInterrupt facciamo in modo che i cambi di stato dei contatti CLK e SW generino un interrupt mentre colleghiamo l’altro segnale DT ad un qualsiasi ingresso che non genera interrupt (ad esempio 4).

Quando ruotiamo l’albero ad un certo punto scatterà un interrupt perchè il contatto che fornisce il segnale CLK passa da aperto a chiuso o viceversa (cambia stato logico). A questo punto intercettiamo l’interrupt e confrontiamo il livello logico attuale del segnale CLK con il livello logico attuale del segnale DT. Se entrambi i livelli logici sono uguali (entrambi alti o entrambi bassi) possiamo dire che l’albero sta ruotando in senso orario (oppure che il nostro robottino si sta muovendo in avanti), se invece i due livelli logici sono differenti, allora l’encoder sta ruotando in senso antiorario (o il nostro robottino sta camminando all’indietro).

// ----------------------------------------------------------------
//   Programma che utilizza un rotary encoder a quadratura
// ......::  M O D A L I T A'   I N T E R R U P T   ::......
// ----------------------------------------------------------------
#define encoderCLK 2 // CLK (canale A) connesso al pin D2
#define encoderDT 4  // DT (Canale B) connesso al pin D4
#define encoderSW 3  // SW (bottone) connesso al pin D3
#define interrupt0 0 // Associato al pin 2 di Arduino
#define interrupt1 1 // Associato al pin 3 di Arduino
int encoderCount = 0; // Contatore
int actCLKState;      // Lettura attuale del canale CLK (A)
int prevCLKState;     // Lettura precedente del canale CLK (A)
 
void setup()
{
    // I pin sono impostati come ingresso
    pinMode(encoderCLK, INPUT);    // interrupt 0
    pinMode(encoderDT, INPUT);   
    // Setto la resistenza di pull-up - E' equivalente a:
    // pinMode(encoderSW, INPUT);  
    // digitalWrite(encoderSW, HIGH); 
    pinMode(encoderSW, INPUT_PULLUP); // interrupt 1
    // Attivo l'interrupt 0 quando lo stato del canale CLK "cambia" poiche'
    // questo devo effettuare la lettura di DT ad ogni variazione di CLK
    attachInterrupt(interrupt0, CLKChanged, CHANGE);
    // Imposto l'interrupt 1 sul fronte di discesa poichè quando premo il pomello dell'encoder 
    // il segnale (canale SW) da alto diventa basso
    // A) Se metto RISING la routine di interrupt viene eseguita quando rilascio il pomello;
    //    Se provo a ruotare premendo contemporaneamente il pomello osservo che il contatore 
    //    visualizzato sul Serial Monitor non si azzera fino a quando non rilascio il pomello
    // B) Se mettessi CHANGE eseguo la routine di interrupt sia quando premo, sia quando rilascio
    //    il pomello. Se imposto l'interrupt su CHANGE e provo a premere il pomello 
    //    osservo che il contatore si azzera! Se continuo la rotazione, mantenendo premuto
    //    il pomello dell'albero, noto che il contatore si riazzera solo quando rilascio il pomello
    attachInterrupt(interrupt1, SWPressed, FALLING);
    // inizializza la comunicazione seriale a 9600 bit per secondo:
    Serial.begin(9600);
    // Memorizzo l'ultimo stato del canale CLK
    prevCLKState = digitalRead(encoderCLK);
}
 
void loop() {}

// ---------------------------------------------------------------------------
// Routine relativa alla rotazione dell'albero associata all'interrupt 0
// ---------------------------------------------------------------------------
void CLKChanged()
{
    int actCLKState = digitalRead(encoderCLK);// Leggo il canale A (CLK)
    // Questo if serve per gestire chiamate multiple alla routine di interrupt 
    // causate dal cosiddetto bouncing: ogni volta che si ruota l'albero vengono
    // in realtà generate diverse variazioni (per ognuna viene scatenato
    // l'interrupt!), dovute al funzionamento meccanico del rotore. Si possono
    // determinare effetti indesiderati come ad esempio la ripetizione di numeri
    // ma con questo IF vengono evitati.
    if (prevCLKState != actCLKState)
    {
        /* ---------------------------------------------------------------
        * Per determinare il verso della rotazione occorre leggere DT (B) 
        * Se i 2 canali CLK e DT sono uguali la rotazione è oraria           
        * Se i 2 canali CLK e DT sono diversi la rotazione è antioraria
        *  
        *           Orario                              Antiorario
        * 1    ----      ----      --              ----      ----      --   
        *     |    |    |    |    |    CLK (A)    |    |    |    |    |     CLK (A)
        * 0 --      ----      ----              --      ----      ----    
        * 1 ----      ----      ----                 ----      ----      
        *       |    |    |    |    |  DT (B)       |    |    |    |    |   DT  (B)
        * 0      ----      ----                 ----      ----      ----  
        --------------------------------------------------------------- */
        encoderCount += (actCLKState == digitalRead(encoderDT) ? 1 : -1);
        Serial.println(encoderCount);
        prevCLKState = actCLKState;
    }
}

// ---------------------------------------------------------------------------
// Routine relativa alla pressione sull'albero associata all'interrupt 1
// ---------------------------------------------------------------------------
void SWPressed()
{
    // Questo if serve per gestire chiamate multiple alla routine di interrupt 
    // causate dal cosiddetto bouncing: ogni volta che si preme un pulsante reale
    // vengono in realtà generate diverse pressioni e rilasci (per ognuno dei quali
    // viene scatenato un interrupt!), dovute al funzionamento meccanico del pulsante. 
    // Lo stesso accade anche quando lo si rilascia. 
    if (encoderCount != 0) 
    {
        encoderCount = 0;
        Serial.println(encoderCount);
    }
}

Progetto 3 (modalità library)

E' necessario scaricare le seguenti librerie: Encoder-arduino e Timer1. Per inserire le librerie si utilizza il menu nella figura sottostante:

e poi si verifica la corretta installazione

Ecco il programma equivalente ai precedenti ma che fa uso delle librerie appena citate.

// ----------------------------------------------------------------
//   Programma che utilizza un rotary encoder a quadratura
// ......::  M O D A L I T A'   L I B R A R Y   ::......
// ----------------------------------------------------------------
#include <ClickEncoder.h>
using Button=ClickEncoder::Button; // Definisco un alias al tipo enumerativo Button
#define POMELLO_CLICCATO ClickEncoder::Clicked // Define per evitare l'uso dell'operatore 
                                               // di risoluzione dell'ambito (scope) ::
#include <TimerOne.h>
#define encoderCLK 2 // CLK (canale A) connesso al pin D2
#define encoderDT 4  // DT (Canale B) connesso al pin D4
#define encoderSW 3  // SW (bottone) connesso al pin D3
int16_t encoderCount = 0; // Contatore
int16_t lastencoderCount; // precedente lettura del contatore
ClickEncoder *encoder;

#define VERBOSECASE(b,label) case label: Serial.println(#label); \
                                         encoderCount=0; \
                                         if (b==ClickEncoder::DoubleClicked) \
                                         { \
                                              encoder->setAccelerationEnabled(!encoder->getAccelerationEnabled()); \
                                              Serial.print(">> Stato accelerazione e': "); \
                                              Serial.println((encoder->getAccelerationEnabled()) ? "enabled" : "disabled"); \
                                         } \
                                         break;
void timerIsr() 
{
  // E' eseguita ogni millisecondo. Serve a stabilire gli stati dell'encoder (quantifica
  // il delta relativo di rotazione del rotore e lo stato del bottone)
  encoder->service();
}

void setup() 
{
    // Imposto i pin collegati all'encoder
    encoder = new ClickEncoder(encoderCLK, encoderDT, encoderSW);
    Timer1.initialize(1000); // imposto il timer ogni millisecondo
    Timer1.attachInterrupt(timerIsr); // Definisco la funzione associata all'interrupt
    // inizializza la comunicazione seriale a 9600 bit per secondo.
    Serial.begin(9600);
    lastencoderCount = 0;
}

void loop() 
{  
    Button b; // al posto di: ClickEncoder::Button b;
    encoderCount += encoder->getValue(); // incremento per la varizione
    
    if (encoderCount != lastencoderCount) 
    {
        // Stampo solo se il conteggio varia
        lastencoderCount = encoderCount;
        Serial.println(encoderCount);
    }
    
    b = encoder->getButton();
    if (b != ClickEncoder::Open) 
    {
      Serial.print("Bottone: ");
      switch (b) 
      {
          VERBOSECASE(b,ClickEncoder::Pressed);
          VERBOSECASE(b,ClickEncoder::Held)
          VERBOSECASE(b,ClickEncoder::Released)
          VERBOSECASE(b,POMELLO_CLICCATO)
          VERBOSECASE(b,ClickEncoder::DoubleClicked)
      }
    }    
}

L'immagine successiva mostra il funzionamento del programma nel Serial Monitor.

Libreria TimerOne.h

La libreria TimerOne è una collezione di routines per configurare il timer hardware a 16 bit chiamato Timer1 sul chip ATmega168/328. Le funzioni principali sono:

initialize(period);

Va chiamato prima di utilizzare qualsiasi altro metodo appartenente alla libreria.  L'argomento è la durata del periodo misurato in microsecondi e di default è  impostato a un secondo (1.000.000). La massima durata assegnabile è 8.388.480 microsecondi (circa 8,3 secondi).

attachInterrupt(function, period);

Indica la funzione function chiamata ad ogni intervallo (in microsecondi) definito con il parametro period. Attenzione nell'eseguire funzioni troppo complicate con un periodo associato troppo breve: la CPU potrebbe non rientrare nel loop() mandando in blocco il programma. Ecco un esempio di utilizzo del timer uno.

#include "TimerOne.h"
int conta;

void setup()
{
    conta=0;
    Timer1.initialize(1000000);         // inizializza il timer ogni secondo
    Timer1.attachInterrupt(fInterrupt); // associa callback() all'overflow del timer
    // Posso variare la frequenza di chiamata mettendo i microsecondi nel 2° argomento
    // di attachInterrupt. Ad esempio:  Timer1.attachInterrupt(fInterrupt,1000); 
    Serial.begin(9600);
}

void fInterrupt()
{
  conta++;
  Serial.println(conta);
}

void loop() {}

I timer di Arduino

Arduino dispone di tre timer distinti definiti Timer0, Timer1 e Timer2.

Al Timer0, a cui sono collegati i pin 5 e 6, sono associate le funzioni di ritardo interne al micro controllore come ad esempio delay() e millis() ed è riferimento per la classe Servo ed altre librerie che sfruttano il Timer0 dell’Atmega328/168. E' preferibile quindi non modificarne via codice la frequenza operativa di tale timer.

A ciascun Timer sono collegati 2 comparatori (Output Compare – OC ) definiti OCxA e OCxB dove x corrisponde al numero del timer associato ovvero 0,1 e 2.  Ad ogni comparatore è collegato un pin PWM di Arduino:

Ogni modulo, essendo pilotato da un diverso Timer, ha la possibilità di operare ad una frequenza diversa dagli altri. E’ ovvio che i due pin appartenenti allo stesso modulo opereranno sempre alla stessa frequenza. Guardando siosserva che il Timer0 ha una Frequenza doppia rispetto agli altri due che si ripercuote alla fine della tabella sulla Frequenza PWM di Default che per il pin 5 e 6 è doppia rispetto a quella dei pin 3,9,10 e 11

Queste frequenze così basse potrebbero andare bene per variare la luminosità di un led ma non certo per pilotare un motore o generare un segnale analogico, come purtroppo molto spesso si vede.

Il sistema per poter variare le frequenze alle quali lavorano i moduli Output Compare dell’ATmega328 è quello di agire sui registri di configurazione TCCRxA e TCCRxB (dove x è 0,1 o 2) dei Timer che li controllano.

Il registro A controlla più propriamente le modalità operative dei Timer (normale, Fast PWM, Phase-Correct PWM ecc - https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM ). Il registro B consente, tra le altre cose, di impostare il prescaler agendo sui bit 0,1 e 2, chiamati CSx0, CSx1 e CSx2.

La soluzione più rapida di tutte è quella di modificare unicamente il prescaler dei Timer lasciando invariate tutte le altre impostazioni, agendo sui primi 3 bit dei registri TCCRxB, utilizzando la seguente comoda funzione disponibile sul playground di Arduino:

void setPwmFrequency(int pin, int divisor)
{
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) 
  {
    switch(divisor) 
    {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) 
      TCCR0B = TCCR0B & 0b11111000 | mode;
    else
      TCCR1B = TCCR1B & 0b11111000 | mode;
  } 
  else if(pin == 3 || pin == 11) 
  {
    switch(divisor) 
    {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

A dispetto di quello che potrebbe far capire il nome della funzione (setPwmFrequency), non andiamo ad impostare direttamente la frequenza del PWM bensì il divisore del prescaler associato al timer che controlla il pin.

Per i pin 9,10, 11 e 3 si parte dalla frequenza di base di 31250Hz, per cui su tali pin impostando il parametro divisor sul valore 64, ad esempio, otterremo una frequenza PWM pari a 31250/64 = 488Hz (la frequenza di default).

La frequenza di base per i pin 6 e 5 è invece 62500Hz, per cui impostando anche qui divisor a 64, ad esempio, otteniamo 62500/64=976Hz (di nuovo la frequenza di default!). I valori validi per il parametro divisor sono: 1, 8, 64, 256 e 1024.

Per i pin 3 e 11 sono disponibili anche i valori 32 e 128 oltre a quelli elencati prima. I valori 32 e 128 per i pin 5,6,9 e 10, non sono disponibili perchè il Timer0 e il Timer1 hanno due impostazioni per i bit CS0 ÷ CS2 che prevedono il clocking dei timer da una sorgente esterna, opzione non disponibile per il Timer2 che in compenso ha due valori di divisore in più.
Chiaramente resta sconveniente variare la frequenza operativa del modulo OC0 (pin 6 e 5) dal momento che funzioni come delay() e millis() si appoggiano al Timer0.

Segnale PWM

Un segnale PWM è un'onda digitale quadra con frequenza costante, ma la frazione di tempo in cui il segnale è alto (duty cycle) varia tra 0% and 100%.

Normalmente chi utilizza Arduino, per poter sfruttare il PWM, è abituato ad utilizzare la seguente funzione:

analogWrite(pin, valore);

dove pin assume il valore 6, 5, 9, 10, 11 o 3, e valore è un numero tra 0 (duty cycle:0%) e 255 (duty cycle:100%).

G - LINK SUGGERITI

http://www.electronicproducts.com/Electromechanical_Components/Choosing_a_sensor_to_measure_rotation.aspx

http://www.tamagawa-seiki.com/english/encoder/

http://www.ame.it/?id=322&sezId=69

https://www.pjrc.com/teensy/td_libs_Encoder.html

Altri siti interessanti:

http://www.guiott.com/ - Sito di robotica

http://www.sciamannalucio.it/fv44-oscilloscopio-digitale-open-source-fai-da-te-gratis.html - Sito sugli oscilloscopi

http://www.settorezero.com/wordpress/come-arduino-gestisce-il-pwm-esempio-pilotaggio-cent4ur-con-arduino/

http://overpowered.it/arduino-libreria-anti-rimbalzo/

http://www.instructables.com/id/Arduino-Timer-Interrupts/