quale sarebbe il modo migliore per gestire un'encoder incrementale libero ?

Leggevo il post degli interrupt per encoder nella sezione hardware, e mi e' venuto il dubbio su quale potrebbe essere il sistema migliore per gestire un'encoder incrementale libero (senza scatti) per il posizionamento di un servo o di un meccanismo ...

Leggendo del bit shift e del fatto che i bit extra vengono "scartati", mi e' venuto il dubbio se si potesse usare lo stesso sistema per gestire un'encoder del genere simulando un registro a scorrimento con una byte ... il problema e' che anche una byte sono 8 bit, mentre a me serve considerarne solo 4 nei vari controlli (4 possibili stati per ogni posizione dell'encoder ad ogni variazione di uno degli stati) ...

Fondamentalmente i miei dubbi sono due ...

Primo, se dovessi effettuare un controllo solo sui 4 bit meno significativi di una byte, e' sufficente che specifico solo quelli nell'operazione, ad esempio if(valore == 0b0000), oppure bisogna per forza forzare a zero gli altri oppure eliminarli in qualche altro modo ? ... se si in che modo ? (non mi risulta che arduino abbia formati di variabile a soli 4 bit ... o sbaglio ? )

Secondo, se infilo in una ISR solo una flag da usare in una funzione esterna che contenga tutto il resto delle operazioni, quante possibilita' ci sono poi di incasinare il tutto, rispetto al mettere tutte le operazioni nella ISR appesantendola ?

Se vuoi essere sicuro al 100% che usi solo 4 bit meno significativi, prendi il tuo uint8_t (che è il tipo più piccolo che gestisce arduino) e ne fai il 'and' con 0x0F eliminado così brutalmente i 4 bit più alti (... è cosa che si fa spesso con tipi superiori, esempio per avere l'indirizzo a 24 bit usando un uint32_t).

La tecnica che descrivi per la ISR è quella che io consiglio sempre e che, in ambiente FreeRTOS™, prende il nome di "deferred interrupt". Nella ISR alzi solo una flag, nel loop verifichi se la flag è alzata e fai tutto quello che avresti dovuto fare nella ISR con il vantaggio che ... NON hai gli interrupt disabilitati e quindi altri interrupt possono funzionare ed essere asserviti.

Guglielmo

gpb01:
... NON hai gli interrupt disabilitati e quindi altri interrupt possono funzionare ed essere asserviti.

Quella in effetti era la preoccupazione ... cerco di spiegarmi meglio con degli esempi (solo concettuali e buttati li al volo, quindi non iniziate a tirare torte se fanno schifo ... o almeno tirate torte buone :P) ... potrei infilare tutto in una ISR, piu o meno cosi ...

int inpe1 = 2; // ingressi pin encoder
int inpe2 = 3;
int valpe1 = 0; //valore pin encoder
int valpe2 = 0;
int valore = 0; //variabili per conversione
int valprec = 0;
int lettura = 0; 
long totale = 0; //variabile da incrementare/decrementare

void setup() {
   pinMode(inpe1, INPUT_PULLUP);
   pinMode(inpe2, INPUT_PULLUP);
   attachInterrupt(0, encoder, CHANGE);
   attachInterrupt(1, encoder, CHANGE);
}

void loop(){

.... programma ....

}

void encoder(){
   valpe1 = digitalRead(inpe1); //bit piu significativo
   valpe2 = digitalRead(inpe2); //bit meno significativo
   valore = (valpe1 << 1) |valpe2; //unisci i due bit letti
   lettura  = (valprec << 2) | valore; //alla lettura precedente
   if(lettura == 0b1101 || lettura == 0b0100 || lettura == 0b0010 || lettura == 0b1011) totale ++;
   if(lettura == 0b1110 || lettura == 0b0111 || lettura == 0b0001 || lettura == 0b1000) totale --;
   valprec = valore; //salva per il prossimo ciclo
}

Cosi avrei tutto nella ISR (sempre che il mio ragionamento sul come leggere gli stati funzioni, cosa che non ho provato perche' non ho il materiale per farlo al momento), non rischierei che un'altro interrupt mi sballi il controllo interrompendolo a meta', pero' mi sembra che nella ISR ci sia troppa roba, rispetto a come dovrebbero essere gestite al meglio le ISR ... e se arriva un secondo interrupt mentre effettuo i controlli lo perdo ...

Oppure fare in questo modo e gestire i controlli con un'if fuori dalla ISR

int inpe1 = 2; // ingressi pin encoder
int inpe2 = 3;
int valpe1 = 0; //valore pin encoder
int valpe2 = 0;
int valore = 0; //variabili per conversione
int valprec = 0;
int lettura = 0; 
long totale = 0; //variabile da incrementare/decrementare

void setup() {
   pinMode(inpe1, INPUT_PULLUP);
   pinMode(inpe2, INPUT_PULLUP);
   attachInterrupt(0, encoder, CHANGE);
   attachInterrupt(1, encoder, CHANGE);
}

void loop(){

.... programma ....


if (encodermosso == true) {
   valpe1 = digitalRead(inpe1); //bit piu significativo
   valpe2 = digitalRead(inpe2); //bit meno significativo
   valore = (valpe1 << 1) |valpe2; //unisci i due bit letti
   lettura  = (valprec << 2) | valore; //alla lettura precedente
   if(lettura == 0b1101 || lettura == 0b0100 || lettura == 0b0010 || lettura == 0b1011) totale ++;
   if(lettura == 0b1110 || lettura == 0b0111 || lettura == 0b0001 || lettura == 0b1000) totale --;
   valprec = valore; //salva per il prossimo ciclo
   encodermosso = false;
   }
}

void encoder(){
   encodermosso = true;
}

pero' cosi prima di tutto se il loop e' pieno di roba, l'if potrebbe essere eseguito anche parecchio in ritardo dopo la ISR, e secondo se mentre esegue i controlli nell'if (o prima di arrivarci) arriva un secondo interrupt, cosa succede, mi incasina tutto o perdo il primo interrupt ?

Quale dei due sistemi andrebbe meglio ? (meglio, relativamente parlando ... lo so che non esiste un sistema "perfetto", salvo forse usare un chip esterno o delle porte logiche per convertire i segnali dell'encoder in due diversi segnali, un DIR ed un PULSE in modo hardware come si faceva in passato, pero' cosi si complica di piu la parte hardware)

EDIT: o magari usare una seconda piccola MCU che faccia SOLO quello che in passato facevamo hardware, convertire la quadratura in segnali PULSE e DIR ...

Più che ovvio che il tempo di esecuzione dell'intero loop() DEVE essere molto più breve rispetto al tempo che intercorre tra due interrupt uguali, altrimenti ... va a farsi benedire tutto ...

Purtroppo è solo il "caso reale" che può mostrarti se va o non va, in funzione di quello che devi fare e, come sempre, non esiste UNA soluzione ottimale, ma esiste la migliore per l'applicazione che stai sviluppando :wink:

Guglielmo

P.S.: ... ed, in ogni caso, elimina i vari digitalRead() ed accedi direttamente alle porte !

Mentre scrivevo mi e' venuto in mente di cercare in rete se nel frattempo avessero gia fatto qualcosa di hardware ... e come al solito avrei dovuto pensarci prima :stuck_out_tongue: :smiley:

http://hades.mech.northwestern.edu/images/6/6e/LS7183.pdf

Praticamente quello che facevamo una volta con flip-flop e porte logiche varie (piu debounce e divisori extra) l'hanno impacchetato in un comodo SOP ad 8 pin ... resta da vedere come si comporta nella realta' in caso di sistemi instabili o che invertano fra due impulsi non previsti, ma gia anche cosi non sembra male ...

Io pero' continuo a studiarmi anche la parte software, magari e' la volta che imparo qualcosa di nuovo :stuck_out_tongue: ... l'accesso diretto alle porte rispetto alla digitalread quanto fa risparmiare, piu o meno ? ...

Etemenanki:
... l'accesso diretto alle porte rispetto alla digitalread quanto fa risparmiare, piu o meno ? ...

... guarda a memoria non ricordo, ma mi sembra fossero parecchie decine di volte ... leggi QUI :wink:

Guglielmo

Ah, pero' ... 2 cicli contro 56 ... 28 volte piu veloce (parla di write, ma immagino la read sia simile) ... mica male :smiley:

EDIT: aspetta ... il secondo esempio qui usa sia write che read, 121 cicli macchina contro sempre due con sbi() ? ... ancora meglio, ma sempre due cicli per fare entrambe le operazioni ? ... mi sa che devo studiarci sopra di piu ...

Il chip risulta anche in formato dip, non male.
Comunque si, la manipolazione diretta credo sia il secondo step prima di aumentare il clock del micro.

Etemenanki:
non rischierei che un'altro interrupt mi sballi il controllo interrompendolo a meta', pero' mi sembra che nella ISR ci sia troppa roba, rispetto a come dovrebbero essere gestite al meglio le ISR ... e se arriva un secondo interrupt mentre effettuo i controlli lo perdo

In ogni caso c'è un limite di velocità per cui o da una parte (dentro ISR) o dall'altra (loop lento) cominci a perdere qualcosa.
Ma questa logica per me deve stare tutta dentro la ISR, altrimenti dovresti comunque avere un loop almeno veloce come il susseguirsi di ISR.

La lettura diretta porte è circa 30 volte più veloce, e forse si potrebbe ottimizzare il resto con una macchina a stati con tabella di transizioni.

A questo livello ancora non ci sono (io vengo dal mondo "punta e mazzetta", ricordalo :D) e la programmazione la sto studiando un po, come posso dire, "una martellata alla volta" :smiley: ... devo studiarci sopra di piu appena riesco ... grazie comunque per i suggerimenti ...

Claudio_FF:
Ma questa logica per me deve stare tutta dentro la ISR, altrimenti dovresti comunque avere un loop almeno veloce come il susseguirsi di ISR.

Non sono d'accordo ... ripeto ... c'è da vedere cosa fa il loop(), ma, ad esempio, alzare una flag e fare poi le cose nel loop permette, ad esempio, in caso di uso della seriale, di continuare a ricevere caratteri mentre si fa quello che si sarebbe dovuto fare nella ISR (periodo in cui invece, all'interno della ISR, la ricezione di caratteri è inibita dato che gli interrupt sono disabilitati).

La cosa va studiata caso per caso e, come ho già detto, NON esiste la soluzione unica, ma la soluzione migliore in funzione di ciò che si deve fare. :slight_smile:

Guglielmo

P.S.: Ovvio che una grossa limitazione arriva dalla intrinseca lentezza dei vecchi AVR ... con MCU più veloci la cosa è ancora piû facile ... :slight_smile:

Etemenanki:
Mentre scrivevo mi e' venuto in mente di cercare in rete se nel frattempo avessero gia fatto qualcosa di hardware ... e come al solito avrei dovuto pensarci prima :stuck_out_tongue: :smiley:
http://hades.mech.northwestern.edu/images/6/6e/LS7183.pdf

Ma hai visto che prezzi ?? Mi pare trovo solo sui 18/20 euro per 1 chip

nid69ita:
Ma hai visto che prezzi ?? Mi pare trovo solo sui 18/20 euro per 1 chip

... io QUI lo vedo a 3.20 US$ ::slight_smile:

Guglielmo

Cercato su ebay/amazon/aliexpress pure dai cinesini al minimo vedevo 18 dollari
Forse x comprare poi hai spese alte di trasporto ? Non trovo il loro shop

EDIT: fedex economy: $46.68 e non vendono altro che robba encoder.

C'erano anche chip piu vecchi, mi sembra la serie HCTL20xx, ma facevano anche altre cose, tipo uscita su bus ad 8 bit e contatori interni ... oppure si potrebbero emulare con porte logiche e flip-flop, o forse anche con un'attiny di quelli ad 8 pin dedicato solo a fare quello (se facesse solo quello potrebbe essere abbastanza efficente, suppongo) ... ma alla fine quanto ci si risparmierebbe, sempre ammesso di risparmiarci ? ...

A proposito (ma qui esuliamo dal discorso arduino), cercando di documentarmi ho visto che in alcuni dei pic serie dspic33 e pic24 hanno inserito un decoder per encoder a quadratura come interfaccia interna (la chiamano QEI) ... suppongo nessuno ci pensera' mai per le serie di MCU tipo Atmel, comunque ...

Salve a tutti

Dopo aver iniziato a leggere

Etemenanki:
Leggevo il post degli interrupt per encoder nella sezione hardware, e mi e' venuto il dubbio ...

e

Etemenanki:
Quella in effetti era la preoccupazione ...

mi fischiavano le orecchie; quindi mi sono letto tutto fino a qui e mi sono infilato per portare un contributo pratico. Trovato un Arduino UNO e questo encoder

(E' un encoder ottico che si trova nei meccanismi di movimento porte degli ascensori).
ho fatto alcune prove con le librerie Encoder, QuadratureEncoder, RotaryEncoder ed un codice molto simile a quello scritto da Etemenanki (le parti commentate sembrano non servire e non essere chiamate nel codice)

/***********************************************************
  File name: _16_RotaryEncoderModule.ino
  Description: The information of rotary encoder module has been
             detected by UNO R3,and displayed in the serial monitor
             When the rotary encoder turns clockwise, the angular
             displacement is increased;when it turns counterclockwise,
             it’s decreased.If you press the switch on the rotary
             encoder, related readings will return to zero
  Website: www.adeept.com
  E-mail: support@adeept.com
  Author: Tom
  Date: 2016/06/15
***********************************************************/

#define APin 2 //Set the digital 2 to A pin Arduino 10 Sanguino
#define BPin 3 //Set the digital 3 to B pin Arduino 11 Sanguino

volatile int lastEncoded = 0;
volatile long encoderValue = 0;
//long lastencoderValue = 0;
float frequency;

//int lastMSB = 0;
//int lastLSB = 0;

void setup()
{
  
  pinMode(APin, INPUT_PULLUP);//initialize the A pin as input
  pinMode(BPin, INPUT_PULLUP);//initialize the B pin as input
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
  
  Serial.begin(9600); //opens serial port, sets data rate to 9600 bps

}

void loop()
{

  Serial.println(encoderValue);

}

void updateEncoder() {
  int MSB = digitalRead(APin); //MSB = most significant bit
  int LSB = digitalRead(BPin); //LSB = least significant bit
  int encoded = (MSB << 1) | LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
  if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;
  lastEncoded = encoded; //store this value for next time
 
}

Ora come si diceva qui

gpb01:
Più che ovvio che il tempo di esecuzione dell'intero loop() DEVE essere molto più breve rispetto al ...

dipende dal resto del codice; dalle mie prove di oggi l'encoder non perde passi con nessuna di queste librerie, ma mi sembra di ricordare che la QuadratureEncoder, (le altre non le avevo ai tempi della prova) utilizzata con il codice, relativamente lungo, di gestione una radio FM con 2 lcd 40 x 4, RTC DS3231, sensori BME 280 e DTH22, perdesse molti passi, mentre il codice postato, funziona bene e non perde passi nelle stesse condizioni sia con encoder a scatti che ottici.

Saluti , Valter

>walther68: Quando si quota un post, NON è necessario riportarlo (inutilmente) tutto; bastano poche righe per far capire di cosa si parla ed a cosa ci si riferisce, inoltre, se si risponde al post immediatamente precedente, normalmente NON è necessario alcun "quote" dato che è sottinteso. :slight_smile:

Gli utenti da device "mobile" (piccoli schermi) ringrazieranno per la cortesia :wink:

Guglielmo

P.S.: Ho troncato io i "quote" del tuo post qui sopra :wink:

Ne avevo tenuto conto e non avevo riportato tutti i post completi e quello che avevo lasciato mi sembrava utile nella mia inesperienza di frequentatore di forum.

Dalle correzioni fatte, ho capito che basta meno per un quote efficace; in futuro ne terrò conto.

Saluti , Valter

Valter: per gli encoder a mano, con gli scatti, il problema e' minore, piu che altro si presenta quando usi encoder liberi, senza scatti, calettati su motori da servomeccanismo ... se devono essere precisi si usano anche encoder da 400 o 800 o piu passi per giro, e se poi devono anche girare veloci, il rischio di perdere impulsi cresce ... a meno che ovviamente non si usino logiche ad elevata velocita', ma sullle piccole MCU che al massimo viaggiano a 20MHz, piu si riesce ad ottimizzare, meglio e' ...

Il codice che hai postato probabilmente deriva dagli stessi datasheet degli encoder che avevo trovato io, la tabella dei confronti di stato con i "sum" l'ho presa pari pari da un datasheet ... non capisco bene perche' alcune variabili sono dichiarate fuori ed altre dentro la ISR, magari funziona meglio cosi, io non ho encoder al momento con cui fare test ... ma se l'hanno fatto cosi e mi dici che funziona, forse e' la strada migliore ..

... a proposito, domanda per programmatori, perche' due di quelle variabili sono dichiarata come volatile, e perche' tutte quelle int se bastano 4 bit ? ... c'e' una ragione precisa ? ...

Etemenanki:
... a proposito, domanda per programmatori, perche' due di quelle variabili sono dichiarata come volatile, e perche' tutte quelle int se bastano 4 bit ? ... c'e' una ragione precisa ? ...

... tutte le variabili che si usano nelle ISR DEVONO essere dichiarate con l'attributo "volatile" per dire al compilatore che, al di fuori del flusso del programma ... c'è qualcuno che può modificarle inaspettatamente (la ISR) e quindi ... di NON procedere con strane ottimizzazioni che, magari, danno per assunto il fatto che la variabile non viene modificata nel codice e quindi ... non la considerano o la considerano con valore fisso.

Per le "int" purtroppo è una cattiva abitudine di chi arriva da sistemi tipo linux o simili, dove int è l'elemento base. Su piccole MCU a 8 bit è solo uno spreco di memoria.

Guglielmo