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:
un disco rotativo di codifica (o rotore): è generalmente di plastica ed è calettato (unito ad incastro) all'albero dell'elemento in rotazione da controllare, diviso in zone chiare (trasparenti) e scure (opache).
fotoemettitori: forniscono il segnale luminoso che passa attraverso le zone trasparenti del disco.
collimatore o maschera stazionaria (o statore) per la collimazione della sorgente di luce. E' identico al rotore, montato sullo stesso asse, solo che resta fisso. La sua funzione e’ quella di rendere piu’ preciso il fascio luminoso ed eliminare gli effetti di bordo. E' quindi presente negli encoder ad alta risoluzione.
fotorilevatore che riceve il segnale luminoso e che a sua volta invierà un segnale di output (1 se passa la luce, 0 se non passa).
elettronica di conversione da analogico a digitale
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:
La risoluzione che è data dalla seguente relazione: = 360°/N dove N è il numero di fori praticati sul disco. La Risoluzione ci da la precisione degli spostamenti angolari. La risoluzione è pari ad un ciclo λ delle fenditure fotoincise sulla maschera, dove per ciclo λ si intende la sequenza fenditura chiara (trasparenti) - fenditura scura (opache) corrispondente ai 360° dell’armonica fondamentale.
Sensibilità: valore dipendente dalla risoluzione che come sappiamo rappresenta la minima variazione rilevabile dal trasduttore
Range di funzionamento: tra 0 e 360°
![]() |
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:
si mette una riga al di sotto della sequenza delle codifiche di Gray a n-1 bit
si specchia la sequenza originale con n-1 bit invertendone l'ordine
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:
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;
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:
con il metodo “polling”: ad ogni loop() del programma si misura lo stato elettrico dei pin collegati all’encoder. Si decodifica lo stato che varia nel tempo e si estrapola l’informazione. Con questo metodo si deve decidere a priori (in base al tipo/velocità del segnale elettrico) ogni quanto (timer) misurare dello stato dei segnali elettrici. Scelte errate possono portare alla perdita di scatti/spostamento poichè il microprocessore potrebbe essere impegnato ad eseguire altre parti del programma durante il cambio di stato dell'encoder. Con questa modalità si possono utilizzare indistintamente tutti i pin della scheda Arduino.
con il metodo “interrupt”: ogni volta che all’ingresso del pin c’è una variazione del segnale elettrico viene chiamata una funzione speciale detta “interrupt”. Questa funzione è speciale poichè ha la priorità massima, cioè il microprocessore della scheda Arduino ferma la riga di comando che sta eseguendo per eseguire la funzione “interrupt”. Si possono utilizzare solo i pin che possono generare interrupt hardware. Ad esempio nella scheda Arduino UNO solo il pin digitali 2 e 3 sono fisicamente collegati agli interrupt esterni INT0 (digital pin 2) e INT1 (digital pin 3). Nel caso si voglia utilizzare un qualsiasi pin della scheda Arduino UNO si può utilizzare in alternativa la libreria chiamata PinChangeInt.
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:
la rotazione in senso orario incrementa una variabile contatore encoderCount,
la rotazione in senso antiorario invece la decrementa,
se premo il pulsante il conteggio riparte da 0.
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:
Interrupt: specifica l'interrupt a da considerare
funzione: è la funzione attached che viene chiamata quando si verifica l’interrupt; questa funzione non deve avere parametri e non deve restituire alcun valore. All’interno della funzione utilizzata in attachInterrupt si tenga presente che:
delay() non funziona;
il valore restituito dalla funzione millis() non verrà incrementato.
i dati seriali ricevuti durante l’esecuzione della funzione di interrupt potrebbero essere sbagliati.
qualsiasi variabile globale modificabile all’interno della funzione attached devono essere dichiarate come volatili.
modo: specifica la modalità di rilevazione della variazione di stato sui pin di Arduino e può assumere quattro valori
LOW: l’interrupt viene eseguito quando il livello del segnale è basso
CHANGE: l’interrupt viene eseguito quando avviene un cambiamento di stato sul pin
RISING: l’interrupt viene eseguito quando si passa da un livello LOW ad un livello HIGH
FALLING: l’interrupt viene eseguito quando si passa da un livello HIGH ad un livello LOW
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.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