Utilizzare 4 Rotary Encoder occupando soltanto 3 ingressi di un microprocessore (con routine di interrupt).

Postato il Aggiornato il

encoders

Questa soluzione, realizzata con una scheda a microprocessore ed una manciata di componenti esterni, permette di “leggere” 4 Rotary Encoder utilizzando soltanto 3 ingressi del microprocessore: un ingresso interrupt, un ingresso analogico ed un ingresso generico (analogico o digitale).

L’idea nasce dall’ esigenza di progettare un dispositivo in grado di offrire le seguenti specifiche:

  • basso costo;
  • piccole dimensioni;
  • limitato utilizzo del numero di ingressi/uscite;
  • timing del processore ottimizzato mediante lettura basata su interrupt piuttosto che su polling.

Il programma/sketch non è elegantissimo (sorry ma sono alle prime armi con Arduino e con la programmazione in C 🙂 ) ma è molto semplice e leggero perché non utilizza librerie esterne, protocolli di comunicazione seriale o manipolazioni di dati complesse.

Il sistema si presenta, quindi, come una valida alternativa a soluzioni più eleganti ma impegnative per il processore quali, ad esempio, quelle basate sull’utilizzo di port expander, e preserva buona parte delle risorse del processore stesso lasciando ampio margine all’aggiunta di ulteriori funzionalità/componenti.

Prima di passare alla descrizione della soluzione, riepilogo brevemente le caratteristiche e la configurazione dei Rotary Encoder (RE) utilizzati in questo progetto. Per approfondimenti sui RE vi invito a  leggere qui.

I Rotary Encoder

I RE utilizzati sono di tipo incrementale, con uscite “in quadratura”, con detent su “OFF-OFF”, vale a dire che i 2 switch interni sono aperti quando il rotore è in posizione di riposo (detent). Considerando che sono collegati in pull-up, sulle uscite A e B a riposo si troverà una tensione di 5V (Fig.1)
ATTENZIONE: questo sistema funziona soltanto con RE con DETENT!

dettaglio-encoder-2
Fig.1 – Rotaty Encoder

Il segnale A viene utilizzato come “clock”, ovvero come segnale di riferimento che, in corrispondenza di una sua variazione di stato (in particolare di un fronte di discesa) avvia una routine di interrupt (ISR) che, inoltre, indica al processore l’inizio di un movimento meccanico del rotore del RE.

Il segnale B, una volta avviata la routine di interrupt e, quindi, una volta rilevato un movimento, in base al suo stato in corrispondenza del fronte di discesa di A, fornisce indicazione sul verso del movimento del rotore (direzione).

Ad esempio, ipotizzando che il rotore si muova in senso orario, si leggano i 2 segnali di Fig.1 da sinistra verso destra: in corrispondenza di ogni fronte di discesa di A lo stato di B è sempre basso. Viceversa, muovendo il rotore in senso opposto (anti orario), si leggano i 2 segnali di Fig.1 da destra verso sinistra: in corrispondenza di ogni fronte di discesa di A lo stato di B è sempre alto.

Quindi la routine SW di decodifica del movimento dei RE seguirà questo flusso:

untitled-diagram

Il Partitore VDM

La soluzione realizza la “parallelizzazione” dei Rotary Encoder mediante un partitore di tensione che fornisce, su un’ unica uscita, una tensione diversa per ogni encoder (Fig. 2).
Questa tecnica è molto simile a quella utilizzata per la realizzazione dei pulsanti multimediali presenti sugli auricolari dei telefoni cellulari e, da un certo punto di vista, è anche simile alla tecnica di multiplazione VDM (Voltage Division Multiplexing). Per comodità da qui in poi chiamerò questo dispositivo partitore VDM.

schema-a-blocchi-partitore
Fig.2 – Partitore di tensione VDM

In dettaglio, gli impulsi dei RE vengono invertiti dal partitore ed, in funzione dell’ ingresso (Ix) a cui sono collegati, vengono riportati in un’ unica uscita (U) con tensioni diverse. La Fig.2 mostra la manipolazione di 4 impulsi inviati su ingressi diversi ed in istanti (t) diversi e mette in evidenza la corrispondenza tra l’ ingresso e la tensione di uscita: I1->v1; I2->v2… etc. In questo modo il processore, leggendo la tensione in uscita al partitore, riconosce l’encoder che sta generando l’impulso.

Dal punto di vista elettrico il partitore VDM viene realizzato in questo modo (Fig.3):

dettaglio-schematic-partitore
Fig.3 – schema elettrico del partitore di tensione.

Il circuito completo

Lo schema seguente (Fig.4) descrive il funzionamento del circuito che fornisce i 3 segnali necessari al processore per effettuare la decodifica dei movimenti dei singoli RE:

schema-a-blocchi-partitore-completo
Fig.4 – Schema a blocchi completo del circuito esterno al microprocessore.

A valle del partitore di tensione, il blocco Comp, che in realtà è un amplificatore operazionale in configurazione comparatore_invertente, a partire dal segnale VDM, ripristina la forma d’onda originale degli impulsi provenienti dai pin A dei RE, invertendoli e riportandoli nel range 0-5V, tuttavia mantenendoli uniti su un unico filo.

I pin B dei rotary encoder vengono uniti semplicemente cortocircuitandoli tra loro.

I 3 segnali generati vengono interpretati dal processore in questo ordine cronologico:

  1. Segnale di interrupt (Clock) – è il segnale che fornisce l’indicazione dell’ inizio movimento del rotore di uno dei 4 RE ed avvia la routine di interrupt;
  2. Segnale VDM – è il segnale che indica quale dei 4 RE si è mosso;
  3. Segnale di direzione – è il segnale che fornisce l’indicazione relativa alla direzione del movimento del rotore.

Di seguito lo schema elettrico (Fig.5):

partitore-1
Fig.5 – Schema elettrico completo

Realizzazione pratica del sistema

Per la realizzazione di questa soluzione ho utilizzato un clone di Arduino UNO ma ipotizzo che qualsiasi altra scheda basata su microprocessore con caratteristiche simili o superiori (o anche inferiori, entro certi limiti) possa funzionare.

Di seguito lo schema di realizzazione del prototipo su Breadboard (Fig.6):

4-encoder_bb
Fig.6 – Realizzazione del sistema su breadboard

La lista dei componenti è scaricabile da QUI. Tutti i condensatori sono da 100nF.

Il programma/Sketch

Di seguito lo Sketch per Arduino che è possibile scaricare da QUI.

/* This sketch, in combination with a few external components, reads 4 encoders using only 3 microprocessor inputs: one interrupt input,
 * one analog input and one GPIO (in this example digital pin n.4 is used, but an analog input could be used indifferently).
 * 
 * The encoders are connected in parallel through a voltage divider that provides interrupt signals at a specific voltage for each encoder.
 * Depending on the interrupt signal voltage the microprocessor identifies the "active" encoder.
 * After the voltage divider the signal is normalized again to 0-5V to be used as interrupt signal.
 * The system works only with encoders with detent (both encoder contacts normally open or closed).
 * For further details on the external circuit and system working limitations please refer to: https://massimoriggi.wordpress.com/2016/12/12/4-rotary-encoders/
 */

#define intpin 2 //rotary encoder interrupt pin
#define dirpin 4 //rotary encoder direction pin
#define analogreadpin A0 //pin for interrupt signal analog reading

/* The following lines declare:
 *  - variables for Rotary encoders values/position, from rotary encoder n.1 to rotary encoder n.4 (pot1 to pot4);
 *  - variable for analog reading of the interrupt signal (analogvalue);
 *  - variable for encoders direction reading (dir).
 */
volatile int pot1=500; 
volatile int pot2=500; 
volatile int pot3=500;
volatile int pot4=500;
volatile int analogvalue;
volatile int dir;

void setup() {
pinMode(intpin,INPUT);
pinMode(dirpin,INPUT);
pinMode(analogreadpin,INPUT);
attachInterrupt(digitalPinToInterrupt(intpin),readenc,FALLING);
Serial.begin(250000);
}

/* The main loop only prints encoder positions on terminal window. The ISR (readenc function) does all the job! */
void loop() {
  Serial.print(pot4); 
  Serial.print("\t");
  Serial.print(pot3);
  Serial.print("\t");
  Serial.print(pot2);
  Serial.print("\t");
  Serial.println(pot1);
}

void readenc(){
  dir=digitalRead(dirpin); //reads the direction pin state
  analogvalue=analogRead(analogreadpin); //reads the voltage of the interrupt

/* the following lines split the analog reading in 4 different voltage ranges and update (increment or decrement) only the variable of the corresponding encoder */
  if(analogvalue>300&&analogvalue<400){
    if(dir==HIGH){
    pot1++;
    }
  else{
    pot1--;
    }
  }
  if(analogvalue>500&&analogvalue<600){
    if(dir==HIGH){
    pot2++; 
    }
  else{
    pot2--;
    }
  }
  if(analogvalue>700&&analogvalue<800){
    if(dir==HIGH){
    pot3++; 
    }
  else{
    pot3--;
    }
  }
  if(analogvalue>900&&analogvalue<1023){
    if(dir==HIGH){
    pot4++; 
    }
  else{
    pot4--;
    }
  }
}

Video dimostrativo

Il sistema funziona in maniera molto fluida, veloce, non sembra perdere colpi e riesce a decodificare anche il movimento contemporaneo (in realtà direi “pseudo-contemporaneo”!) di più RE, anche se in teoria questo non dovrebbe essere possibile. Nel prossimo paragrafo farò qualche considerazione in dettaglio su questo aspetto.

 

Limitazioni/espansioni del sistema, approfondimenti e altre considerazioni…

Espansione del numero di RE
Il partitore VDM è stato progettato in modo da fornire in uscita soltanto 4 valori di tensione, entro il range di conversione A/D di Arduino (0-5V), più o meno equidistanti tra di loro, oltre al valore di 0V che corrisponde alla condizione di riposo.
Il range reale del partitore VDM è leggermente più stretto di 0-5V per motivi legati alla tecnologia costruttiva dell’ integrato 74hc125 ed è, precisamente: 138mV-4,88V.
Il grafico seguente (Fig.7) mostra l’andamento reale dei segnali provenienti dai 4 ingressi, in uscita al partitore VDM, generati in sequenza a partire da I1:

4-valori-vdm
Fig.7 – andamento dell’ uscita del partitore per 4 impulsi su 4 ingressi in sequenza

Il numero ed il valore delle possibili tensioni che il partitore VDM può generare dipendono, rispettivamente, dal numero di ingressi/buffer necessari e dalla configurazione della rete resistiva che collega le uscite di tutti i buffer. In questo caso le tensioni disponibili sono le seguenti: 4,88V (I4), 3,7(I3), 2,6(I2) e 1,7(I1).
Ho preferito non scendere al di sotto degli 1,7V per minimizzare l’effetto di eventuali disturbi nei dintorni dello 0V e gli effetti prodotti da eventuali soglie dei semiconduttori utilizzati a valle del partitore, pur mantenendo una distanza consistente tra i vari valori per minimizzare errori di lettura analogica dovuti a tolleranze o disturbi.
Tuttavia, da misurazioni sperimentali, ho verificato che la conversione A/D rileva sempre il valore atteso con una tolleranza di circa 10mV, mentre lo sketch accetta variazioni intorno al valore teorico nell’ ordine di ±250mV. Pertanto, ritengo che con l’utilizzo di componenti di precisione e/o di qualità sia possibile espandere il numero di RE utilizzabili anche fino a circa 15/20.

Intervento contemporaneo di più RE
Per le sue caratteristiche costruttive il partitore VDM garantisce la lettura soltanto di un encoder alla volta. Nel caso di movimento contemporaneo di 2 o più RE la tensione rilevata all’ uscita del partitore sarà quella relativa all’ encoder collegato all’ ingresso più alto (più vicino  all’ ingresso dell’ OP-AMP). Tuttavia non è garantita la corretta decodifica del movimento a causa dell’ impossibilità di discriminare lo stato delle uscite B degli encoder che intervengono nello stesso istante.
Ciononostante, come si può osservare dal video, viste le alte velocità di switching in gioco e vista la bassa probabilità che 2 impulsi si sovrappongano nell’ intervallo di qualche millisecondo, il sistema sembra reagire bene anche nei casi di “pseudo-contemporaneità” di intervento di più RE.

Velocità di rotazione dei RE
Per le reti di debounce utilizzate (Condensatore da 100nF e Resistenza da 10kΩ) e per la velocità degli altri componenti utilizzati, il sistema garantisce l’utilizzo di RE con velocità di rotazione capaci di produrre fino a circa 50 o 60 impulsi per secondo. Ad esempio RE con 24 PPR (Pulses Per Revolution) e velocità di rotazione di 150 RPM (Revolution Per Minute). Eventuali limiti di utilizzo potrebbero dipendere dal processore e dalla routine SW utilizzati.

Freeze del sistema in caso di blocco su “NON-DETENT”
I RE con detent sono realizzati in modo da portare il rotore in posizione di detent in situazione di riposo. Ovvero, quando viene rilasciato il rotore in seguito ad un movimento, la meccanica dovrebbe “richiamare” il rotore nella posizione di detent più vicina. Tuttavia, per problemi di qualità costruttiva, questo non accade sempre, come nel caso dei RE utilizzati per questo progetto.
Nel caso in cui uno o più RE dovessero bloccarsi in posizione di NON-DETENT (cioè tra 2 detent consecutivi) gli altri RE non vengono più decodificati.
Questo accade perché uno o entrambi gli switch dei RE bloccati in NON-DETENT restano chiusi forzando l’uscita del partitore VDM ad una tensione fissa maggiore di 0V che maschera tutti i segnali degli altri RE, in particolare i segnali di interrupt.
Ovviamente questo problema si può superare utilizzando RE di qualità superiore.

Annunci

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...