Encoder rotativo

Anzichè aprire un nuovo argomento, faccio una domanda qui. Voglio fare un menù utilizzando un encoder rotativo con pulsante centrale, mi sono accorto che non è preciso, a volte per salire o scendere di uno, devo fare più giri, a volte anche girando sempre nello lato, mi dic che sta girando nel verso opposto e poi torna a girare nel verso giusto, ho creato questa funzione per leggere l'encoder:

void leggi_Encoder() {

  posizione = 0;
  premuto = 0;
  while(posizione==0 && premuto==0){
    delay(100);
    int a = digitalRead(Clk);
    int b = digitalRead(Dt);
    if (a == HIGH && b == LOW) {
      posizione--;
    } else if (a == LOW && b == HIGH) {
        posizione++;
      } 
    if (digitalRead(pin_pul) == 0) {
     premuto = 1;
    }
  }
}

Facendo una ricerca su internet ho visto che questi sono encoder meccanici di bassa qualità, esistono quelli ottici che sono più precisi ma non ho trovato chi li vende.
Il probelma riscontrato può dipendere dall'encoder o dal mio codice?

NO, ogni argomento ha la sua discussione, quindi è giusto aprire discussioni diverse. Grazie.

Ho provveduto io a dividere.

Guglielmo

Se vuoi una lettura ed un comportamento affidabile devi usare gli interrupt per leggere lo stato dei segnali.

Concordo, ed è fortemente consigliato anche un debounce hardware dei contatti.

Ciao, Ale.

Se devi solo leggere i "click" dell'encoder (presumendo che tu stia parlando di un'encoder meccanico con le posizioni fisse, di quelli a manopola con i click), allora ti basta un solo interrupt ... ogni volta che leggi un fronte di salita (o di discesa, ma NON uno stato) di uno dei due canali, controlli l'altro, se e' alto stai girando in un senso, se e' basso nell'altro.

Diverso il caso di encoder ottici o meccanici senza click e posizioni fisse, quelli a rotazione libera che si usano su servo o motori, li per essere sicuri di non perdere impulsi e' meglio leggere entrambi gli ingressi con degli interrupt ed usare un programma piu complesso, che tenga conto anche della possibilita' che la rotazione si interrompa o inverta "in mezzo" a due cambi di stato.

Non penso sia necessario un interrupt, dal codice si vede che legge in continuazioni i due pin dell'encoder e quello del pulsante. Quando uno dei 3 cambia torna alla routine che l'ha chiamata. Facendo delle prove ho visto che mettendo un delay (300) prima della chiamata a questa routine, legge molto meglio l'encoder, capita, solo ogni tanto, che girando sempre nella stessa direzione va una riga al contrario e poi torna a girare nella direzione giusta.

Come ho detto sopra sto utilizzando questo encoder per far scorrere una freccia in un menù. Ho un display LCD a 4 righe, i menù sono composti da 5 e più opzioni. Nel visualizzare e far scorrere la freccia su un menù a 4 opzioni non ci sono problemi, ma con un menù a 5 e più opzioni come devo comportarmi? Quando arrivo al quarto elemento e giro per andare al quinto, cancello tutto e stampo la quinta opzione nella prima riga o faccia scalare di un posto tutti i menù e stampo alla quarta riga la quinta opzione?

Guarda meglio il codice di @datman che se non ricordo male lavora correttamente non necessitando di delay, anzi la presenza di delay peggiora la reattività fino a comprometterne l'utilizzo. Non ci crederai ma è possibile scrivere applicazioni senza usare delay.

Ciao.

No: legge una volta, poi resta immobile per 100ms, poi legge di nuovo.

Non ho mai usato interrupt per leggere gli encoder manuali, però: https://forum.arduino.cc/t/encoder-rotativo-varia-molto-lentamente-le-variabili/627234/12

E il codice deve essere progettato non bloccante. Se in mezzo da qualche parte si mette anche un solo delay, o un ciclo for/while che monopolizza il tempo, non funziona più niente.

1 Like

Per leggere un encoder senza interrupt, il loop deve essere velocissimo (almeno 100Hz), oppure devi metterlo in un loop breve in attesa della pressione del pulsante. Naturalmente, in questo secondo caso si ferma tutto il resto finché non premi il pulsante.

Hai ragione, il delay l'ho tolto durante le prove, infatti, come ho detto l'ho aggiunto nella routine dove c'è la chiamata all'encoder.

Vorrei usare il tuo codice al posto del mio, questo è il tuo:

uint8_t S; // Stato dell'encoder.
uint8_t So; // Stato precedente dell'encoder.
uint8_t X; // Passaggio per lo zero, usato per evitare letture multiple.
int8_t E; // Lettura finale dell'encoder: +1, 0 o -1.

void leggi_Encoder(){
E=0;
//             PD 76543210
// S=3-(PIND>>3)&B00000011; // Se l'encoder è collegato a PD4 e PD3, devo scorrere a destra di 3 bit, per farli diventare i bit 1 e 0.
// S=3-PIND&B00000011; // Se l'encoder è collegato agli I/O 1 e 0, che sono PD1 e PD0, non devo scorrere a destra.
S=3-((PIND>>4) & B00000011); // Se gli I/O 5 e 4 sono PD5 e PD4, devo scorrere a destra di 4 bit.
  // Il valore binario di S rappresenta A e B. Il centrale dell'encoder è a massa, quindi faccio complemento a 3 (11).

// Volendo usare digitalRead (che è un po' più lento):
// S=3 -digitalRead(encoderA) -2*digitalRead(encoderB);
// digitalRead ritorna HIGH o LOW, definiti 1 e 0.

S^=S>>1; // Faccio XOR (^) fra S (gray) e il suo bit 1, facendolo scorrere a Dx: AB XOR A,
         // ottenendo un binario che per ogni scatto fa 0-1-2-3-0 oppure 0-3-2-1-0.
if(S!=So && S==0) X=0;
if(X==0)
  {
  if(S==2)
    {
    X=1;
    if(So==1) {E=1;}
    else if(So==3) {E=-1;}
    }
  else if(S==0) {E=0; X=0;}
  So=S;
  }
} // FINE Encoder().

Questo è il mio:

void leggi_Encoder() {
  direzione = 0;
  premuto = 0;
  while(direzione==0 && premuto==0){
    int a = digitalRead(Clk);
    int b = digitalRead(Dt);
    if (a == HIGH && b == LOW) {
      direzione--;
    } else if (a == LOW && b == HIGH) direzione++;
    if (digitalRead(pin_pul) == 0) premuto = 1;
  }
}

Ho definito così le variabili dell'encoder:

#define Clk 3
#define Dt 2
#define pin_pul 4

Il mio codice è molto più semplice da capire.
Nella pratica qual'è la differenza tra il mio e il tuo?
Nel mio, il ciclo gira finchè non viene premuto il tasto o girato la manopola, restituendo -1 o 1 in base alla direzione, quando torna alla routine che l'ha chiamata, in base al valore della variabile direzione, viene incrementata o diminuita un'altra variabile.
Del tuo ho capito, solamente, che legge direttamente il bit del registro adibito ai pin da 0 a 7.
Come si può trasformare il tuo codice in modo da ottenere in uscita le variabili che interessano a me, cioè: direzione e premuto?

al mio file su Arduino ho aggiunto questo:

I pin si possono leggere anche tramite i registri adibiti ai pin.
PINB è un registro a 8 bit, legge i pin da 8 a 13, il bit 0 corrisponde al pin 8, cosi via fino al bit 5 che corrisponde al pin 13
PINC è un registro a 8 bit, legge i pin da A0 a A5, il bit 0 corrisponde al pin A0, così via fino al bit 5 che corrisponde al pin A5
PIND è un registro a 8 bit, legge i pin da 0 a 7, il bit 0 corrisponde fino al pin 0, così via fino al bit 7 che corrisponde al pin 7

E' tutto giusto?

Sto facendo delle prove col registro, in setup metto DDRD= B00000000; per indicare che i pin da 0 a 7 sono in lettura, infatti utilizzo i pin 2,3,4.
Nella routine dell'encoder metto int a= PIND; poi leggo i bit che mi interessano, in questo caso il bit il 3,4 e 5.
Compilando mi dà il seguente errore:
error: 'DDRD' was not declared in this scope
error: 'PIND' was not declared in this scope
E' come se le vedesse delle variabili.

Quella che ti ho suggerito è una ISR che viene chiamata da un Pin Change Interrupt.
Quella a cui fai riferimento tu, invece, è una funzione che, in quel caso, legge un encoder sugli ingressi D4 e D5. Come ti ho già detto, però, se non usi gli interrupt, devi chiamare la funzione continuamente, quindi puoi usare quel metodo solo quando accetti che mentre fai un'impostazione dell'encoder tutto il resto rimanga fermo fino al momento in cui esci dall'impostazione premendo l'encoder. L'alternativa a tenere tutto fermo è usare un interrupt per la funzione principale, come faccio nello stroboscopio a LED da 100W.

Con che scheda stai compilando? Quei registri appartengono all'ATMega328, se usi la Uno R4 non vanno bene.

Ciao, Ale.

Hai ragione, uso uno R4. Quali sono i registri per la uno R4?

Ok, capito, ma nella pratica qual'è la differenza tra usare il tuo codice e quello che ho usato io a livello di precisone?

Non saprei, devi leggere il datasheet dell'MCU usata.
Comunque trovo quantomeno bizzarro che per un encoder tu voglia andare a ravanare nei registri dell'MCU (a mio parere non necessario) e non vuoi invece usare gli interrupt (che ti facilitano notevolmente la vita, poi credo che l'R4 ne abbia anche parecchi a disposizione).

Ciao, Ale.