Comportamento anomalo encoder rotativo

Saluti a tutti,

nell'ambito di un progetto che sto portando avanti :

https://forum.arduino.cc/index.php?topic=672044.0

avevo scritto uno sketch per utilizzare con arduino Leonardo un controllo tipo joystick, un encoder rotativo e alcuni pulsanti.

inizialmente tutto funzionava ma, ad un certo punto, mi sono visto costretto a cambiare scheda per questioni di spazio e sono quindi passato dalla Leonardo ad una Micro.

una volta installato la nuova scheda, vi ho caricato lo sketch e mi sono accorto che l'encoder rotativo non funzionava più correttamente e che la rotellina del mouse che comandava, muoveva sempre nella medesima direzione. Ho quindi provato a riportare il tutto sulla scheda Leonardo, ma anche qui l'encoder funzionava solo più in un senso. Nella mia enorme inesperienza ho quindi pensato di avere in qualche modo danneggiato l'encoder e ne ho acquistati altri che sono arrivati oggi.

Con mia sorpresa ho provato a collegarne uno ma il risultato non cambia.

Adesso non sò piu che pesci pigliare, cosa puè determinare questo problema ?

Ricordo di essere partito per scrivere la parte di codice che comandava l'encoder da uno sketch trovato in rete che mostrava la rotazione dell'encoder stampando un valore sul serial monitor

int valore = 0;
int letturaPrecedente = HIGH;

void setup() { 
  pinMode (3,INPUT_PULLUP);
  pinMode (4,INPUT_PULLUP);
  Serial.begin (9600);
}

void loop() { 
  int n = digitalRead(3);
  if ((letturaPrecedente == HIGH) && (n == LOW)) {
    if (digitalRead(4) == HIGH) {
      valore--;
    } else {
      valore++;
    }
    Serial.println(valore);    
  } 
  letturaPrecedente = n;
}

Anche in questo caso, ricordo bene che tutto funzionava e, sul monitor seriale, vedevo il valore crescere e decrescere a seconda della rotazione oraria o antioraria dell'encoder.

Ora invece vedo solo aumentare il valore indipendentemente dal senso di rotazione dell'encoder. Cosa puo' essere successo ?

Grazie dell'attenzione.
Lorenzo

Ciao,
forse la lettura viene fatta troppo lentamente e l'encoder ha già passato la posizione (lo sketch fa molte operazioni oltre a leggere l'encoder?) il mio consiglio è sempre quello di utilizzare le librerie già codificate quando possibile, questo per almeno due motivi, non si deve sempre reinventare la ruota (ok è interessante capire il funzionamento delle cose ma una volta capito si deve evitare in tutti i modi di perdere tempo a risolvere problemi già risolti da altri) è molto più facile gestire il progetto non dovendo soffermarsi su qualunque problematica

Ti consiglio di utilizzare una libreria encoder, ne esistono parecchie io ho installato quella codificata da Paul Stofferegen che sono certo utilizza gli interrupt (se disponibili ovviamente) ma credo tutte funzionino bene ed allo stesso modo.

Dino

@dinodf Non credo ci siano problemi di tempistiche perché se anche con quel piccolo sketch ha lo stesso effetto, non fa null'altro.

skeinon:
Ora invece vedo solo aumentare il valore indipendentemente dal senso di rotazione dell'encoder. Cosa puo' essere successo ?

Hm, scusa la banalità, ma sei sicuro di aver collegato l'encoder ai pin 3 e 4 (come vedo nello sketch di test) o che non sia in corto uno dei due pin dell'encoder? Perché il problema che lamenti sembra indicare che il pin 3 scenda e salga correttamente, mentre il 4 resta sempre LOW. Verifica bene i collegamenti, o se hai un altro encoder rotativo da provare prova a cambiarlo.

Però concordo con dinodf, non gestire gli encoder in quel modo così "manuale", usa la libreria che ti ha consigliato in quanto ben più efficiente!

D'altronde vorrei farti notare che quel codice persino nel playground Arduino viene specificato che il codice "non è ad alte prestazioni, è solo a scopo dimostrativo" e "potrebbe mancare le letture con encoder con una risoluzione più elevata o quando ruota molto rapidamente".

... usare un'interrupt per leggere uno dei pin e controllare lo stato dell'altro dentro la ISR ? ... non sarebbe il sistema piu semplice e veloce, per un'encoder a scatti ? ... :wink:

Etemenanki:
... usare un'interrupt per leggere uno dei pin e controllare lo stato dell'altro dentro la ISR ? ... non sarebbe il sistema piu semplice e veloce, per un'encoder a scatti ? ... :wink:

Esatto. Ed è quello che fa la libreria Encoder. :wink:

docdoc:
Perché il problema che lamenti sembra indicare che il pin 3 scenda e salga correttamente, mentre il 4 resta sempre LOW.

Se uno dei due canali dell'encoder restasse sempre in uno stato il risultato non sarebbe che il conteggio sale sempre indipendentemente dal senso di rotazione, il conteggio salirebbe e scenderebbe sempre di una sola unità, essendo un encoder...

A mio avviso ci potrebbe essere un errore nell'alimentazione, comunque ribadisco che per un buon funzionamento si devono utilizzare gli interrupt, quindi per semplificare il tutto passa all'uso di una libreria e vedrai che collegando in modo appropriato il tutto funzionerà al primo tentativo.

D.

dinodf:
Se uno dei due canali dell'encoder restasse sempre in uno stato il risultato non sarebbe che il conteggio sale sempre indipendentemente dal senso di rotazione, il conteggio salirebbe e scenderebbe sempre di una sola unità, essendo un encoder...

Si ma dipende da com'è fatto il codice. Ed in questo caso vedi che ha scritto:

  int n = digitalRead(3);
  if ((letturaPrecedente == HIGH) && (n == LOW)) {
    if (digitalRead(4) == HIGH) {
      valore--;
    } else {
      valore++;
    }
    Serial.println(valore);   
  }

Qui si vede che se il pin 3 andasse correttamene su e giù mentre il 4 fosse sempre LOW per qualche ragione (connessione sbagliata, encoder in corto, o altro) passerebbe sempre e solo per "valore++".

docdoc:
Si ma dipende da com'è fatto il codice. Ed in questo caso vedi che ha scritto:

  int n = digitalRead(3);

if ((letturaPrecedente == HIGH) && (n == LOW)) {
    if (digitalRead(4) == HIGH) {
      valore--;
    } else {
      valore++;
    }
    Serial.println(valore); 
  }




Qui si vede che se il pin 3 andasse correttamene su e giù mentre il 4 fosse sempre LOW per qualche ragione (connessione sbagliata, encoder in corto, o altro) passerebbe sempre e solo per "valore++".

Quindi hai risposto al quesito, l'implementazione è errata.
Come appena spiegato in un altro post gli encoder hanno una codifica in codice Gray Codice Gray - Wikipedia quindi per forza di cose l'unica condizione per passare da uno stato ad un altro è che uno ed uno solo dei due pin (per encoder a due bit ovviamente) cambi stato, le uniche combinazioni possibili in uscita sono:
00
01
11
10
a scendere si incrementa a salire si decrementa (le code sono collegate, da 10 si ritorna a 00 e viceversa)
Quindi l'implementazione è errata in quanto non controlla lo stato dei bit ma "fa un po' quello che le pare"

Ripeto perché reinventare (male anche) la ruota ogni volta? usate le librerie soprattutto se non conoscete bene il funzionamento di ciò che volete usare (scrivere un algoritmo per leggere la posizione di un encoder può essere un bell'esercizio didattico, ma senza l'uso degli interrupt e senza conoscere bene il funzionamento degli encoder è tempo perso).

D

dinodf:
Quindi hai risposto al quesito, l'implementazione è errata.

Non è errata, se i cablaggi sono fatti bene funziona. Non è efficiente (per questo si usano gli interrupt), ma funziona ed è esattamente il codice che è presente persino nel Playground Arduino, che avevo pure linkato: non vorrai mica dire che è sbagliato il playground, vero?

Ed infatti se leggi quel codice vedi che quando il pin A passa da LOW a HIGH (equivalente ad un interrupt RAISING) verifica lo stato del pin B, e se questo è LOW significa che deve decrementare, e se HIGH che deve incrementare.

Concordo che non sia necessario "reinventare la ruota" e che sia meglio usare le librerie, cosa che ho consigliato anche io all'OP, ma dato che so bene cosa sia un codice Gray, tanto più se parliamo del più semplie a 2 bit, ti ringrazio ma non hai bisogno di riportarmi Wikipedia.

docdoc:
Ed infatti se leggi quel codice vedi che quando il pin A passa da LOW a HIGH (equivalente ad un interrupt RAISING) verifica lo stato del pin B, e se questo è LOW significa che deve decrementare, e se HIGH che deve incrementare.

Ragione entrambi con un distinguo.
Entrambe le soluzioni funzionano, ma non sono uguali. La lettura completa prevede il controllo di tutta la sequenza gray (4 fronti), mentre quella di attendere un fronte del primo segnale e leggere il livello dell'altro è una forma semplificata, che di fatto rende l'encoder sensibile a piccoli spostamenti involontari (presi per comandi buoni), mentre la sequenza completa garantisce che il perno si sia spostato effettivamente sulla posizione successiva, e tra l'altro realizza in modo accidentale anche il debounce (che la forma semplificata non fa).

docdoc:
Non è errata, se i cablaggi sono fatti bene funziona.
...
è esattamente il codice che è presente persino nel Playground Arduino, che avevo pure linkato: non vorrai mica dire che è sbagliato il playground, vero?

Ed infatti se leggi quel codice vedi che quando il pin A passa da LOW a HIGH (equivalente ad un interrupt RAISING) verifica lo stato del pin B, e se questo è LOW significa che deve decrementare, e se HIGH che deve incrementare.

Concordo che non sia necessario "reinventare la ruota" e che sia meglio usare le librerie, cosa che ho consigliato anche io all'OP, ma dato che so bene cosa sia un codice Gray, tanto più se parliamo del più semplie a 2 bit, ti ringrazio ma non hai bisogno di riportarmi Wikipedia.

Il codice sul playground è corretto ma bisogna anche capire cosa dicono le note (che non ti traduco perché credo non ci sia «bisogno di riportare» la traduzione)

Note that the above code is not high performance. It is merely for demonstration purposes. It could miss reads with encoders with higher resolution, or when it rotates very quickly (think: motors), or when you extend it to accommodate multiple encoders.

Grazie a tutti, utilizzerò una libreria per la gestione encoder come mi avete consigliato.

Il mio breve esempio è solo per escludere che il problema sia imputabile ad un'eventuale verbosità del codice.

Credo però che inefficienza a parte il problema sia di altra natura.

Colgo l'occasione per augurare buona Pasqua a tutta la community

Note that the above code is not high performance. It is merely for demonstration purposes. It could miss reads with encoders with higher resolution, or when it rotates very quickly (think: motors), or when you extend it to accommodate multiple encoders.

Meno male che c'era una traduzione di @docdoc al post #2! :wink:

docdoc:
Esatto. Ed è quello che fa la libreria Encoder. :wink:

Dubbio atroce (magari solo atrocemente stupido, non so :stuck_out_tongue: :D) ... ma fra un pezzo di programma che compie una certa azione ed una libreria che compie la stessa azione, quale e' meglio come efficenza ed occupazione di memoria ? ... cosi da profano mi verrebbe da pensare il pezzo di programma, ma magari mi sfugge qualcosa ...

Poi non sto dicendo che le librerie non servono, sia chiaro, anche perche' in molti casi probabilmente risolvono i problemi a chi non riesce a scriversi il pezzo di programma (tipo il sottoscritto, a volte :P) ... solo una curiosita' ...

Una libreria in C++ è equivalente che scrivere una funzione (o pezzo di programma), anzi, a volte è molto più efficiente perché alcune librerie vengono implementate direttamente in assembly.
Premesso questo credo che docdoc non abbia nemmeno cercato di capire cosa faccia la libreria dato che se lo avesse fatto avrebbe notato immediatamente che è una ... pazzesca sostenere che viene controllato unicamente il cambio di stato di un solo bit...
Encoder/Encoder.h at master · PaulStoffregen/Encoder · GitHub dalla linea 152 viene spiegato (bene mi pare) il funzionamento e dalla riga 208 trovate la routine ottimizzata.

Vi auguro buona Pasqua e tanta salute

D

docdoc:
@dinodf Non credo ci siano problemi di tempistiche perché se anche con quel piccolo sketch ha lo stesso effetto, non fa null'altro.

Non è che non fa null'altro quello sketch, SE LO SAI LEGGERE CORRETTAMENTE noterai che richiama ad ogni esecuzione Serial.println(valore); ad una velocità di trasmissione di 9600 Baud, lascio a te il compito di calcolare il tempo impiegato dalla trasmissione e di conseguenza di valutare la massima frequenza di campionamento dei due bit dell'encoder.

Buon lavoro
Buona Pasqua e salute a volontà.

Dino

dinodf:
Premesso questo credo che docdoc non abbia nemmeno cercato di capire cosa faccia la libreria dato che se lo avesse fatto avrebbe notato immediatamente che è una ... pazzesca sostenere che viene controllato unicamente il cambio di stato di un solo bit...

Beh, grazie, ora mi hai tolto ogni dubbio, non capisci quello che scrivo o hai voglia di polemizzare dicendo sciocchezze per cui scusa ma evidentemente devo ripetere le cose magari in forma un po' diversa, forse così ci capiamo una volta per tutte.

Io credo di scrivere in italiano e di mantenere il soggetto della frase: quando ho scritto del controllo del cambio di stato di un solo pin stavo parlando del codice dell'OP che, come ho scritto, è praticamente uguale a quello del Playground (e lo avevo scritto!), e NON della libreria per la quale, ed anche in questo caso l'ho scritto esplicitamente, che quella usa gli interrupt ed implementa correttamente la gestione dell'encoder, e che il codice del Playground è indicato esplicitamente come inaffidabile/inefficiente. Quindi ripeto anche questo, ho consigliato all'OP di usare QUELLA libreria.

Spero che ora sia tutto chiaro, per cui chiudiamola qui, grazie.

docdoc:
Esatto. Ed è quello che fa la libreria Encoder. :wink:

Dinodf, con tutta la buona volontà e pazienza, non ho capito lo scopo di questo tuo quote, ma a me pare che tu stia confermando dei grossi problemi di comprensione dell'italiano. La parte che tu hai ora quotato, "Ed è quello che fa la libreria Encoder" era la MIA risposta alla frase che leggi esattamente sopra nel mio post, ossia quello che ha scritto etemenanki: "usare un'interrupt per leggere uno dei pin e controllare lo stato dell'altro dentro la ISR".

Per cui ora basta, ultimo avviso: per favore, passa ad altro, e cerca di non rispondermi, grazie.

Se continui ad essere convinto che la libreria, di cui ti ho linkato pure il sorgente indicando come funziona, faccia come dici tu ti lascio nelle tue convinzioni, ribadisco che la libreria non fa quello che sostieni, se lo facesse mi spieghi come potrebbe discernere tra le 5 possibili combinazioni d'uscita? ovvero: -2, -1, 0, +1, +2 controllando unicamente lo stato di un bit?

public:
	static Encoder_internal_state_t * interruptArgs[ENCODER_ARGLIST_SIZE];

//                           _______         _______       
//               Pin1 ______|       |_______|       |______ Pin1
// negative <---         _______         _______         __      --> positive
//               Pin2 __|       |_______|       |_______|   Pin2

		//	new	new	old	old
		//	pin2	pin1	pin2	pin1	Result
		//	----	----	----	----	------
		//	0	0	0	0	no movement
		//	0	0	0	1	+1
		//	0	0	1	0	-1
		//	0	0	1	1	+2  (assume pin1 edges only)
		//	0	1	0	0	-1
		//	0	1	0	1	no movement
		//	0	1	1	0	-2  (assume pin1 edges only)
		//	0	1	1	1	+1
		//	1	0	0	0	+1
		//	1	0	0	1	-2  (assume pin1 edges only)
		//	1	0	1	0	no movement
		//	1	0	1	1	-1
		//	1	1	0	0	+2  (assume pin1 edges only)
		//	1	1	0	1	-1
		//	1	1	1	0	+1
		//	1	1	1	1	no movement
/*
	// Simple, easy-to-read "documentation" version :-)
	//
	void update(void) {
		uint8_t s = state & 3;
		if (digitalRead(pin1)) s |= 4;
		if (digitalRead(pin2)) s |= 8;
		switch (s) {
			case 0: case 5: case 10: case 15:
				break;
			case 1: case 7: case 8: case 14:
				position++; break;
			case 2: case 4: case 11: case 13:
				position--; break;
			case 3: case 12:
				position += 2; break;
			default:
				position -= 2; break;
		}
		state = (s >> 2);
	}
*/

Poi se credete che sia corretto spiegare in modo errato il funzionamento delle cose solo perché "tanto funziona così" fate pure ma il micro-controllore fa quello che il codice gli dice di fare.