Go Down

Topic: Sketch controllo encoder con interrupt - ho bisogno di chiarimenti (Read 5552 times) previous topic - next topic

zioetzi

Ciao, ho trovato questo Sketch sulla lettura di encoder utilizzando l'interrupt nel libro "Arduino Cookbook".
Code: [Select]
/*
RotaryEncoderInterrupt sketch
*/
const int encoderPinA = 2;
const int encoderPinB = 4;
int Pos, oldPos;
volatile int encoderPos = 0; // variables changed within interrupts are volatile
void setup(){
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  Serial.begin(9600);
  attachInterrupt(0, doEncoder, FALLING); // encoder pin on interrupt 0 (pin 2)
}
void loop(){
  uint8_t oldSREG = SREG;
  cli();
  Pos = encoderPos;
  SREG = oldSREG;
  if(Pos != oldPos){
    Serial.println(Pos,DEC);
    oldPos = Pos;
  }
  delay(1000);
}

void doEncoder(){
  if (digitalRead(encoderPinA) == digitalRead(encoderPinB))
  encoderPos++; // count up if both encoder pins are the same
  else
  encoderPos--; //count down if pins are different
}


L'ho provato su Arduino MEGA e ovviamente funziona, però non mi è del tutto chiaro. In particolare non ho capito lo scopo del comando

uint8_t oldSREG = SREG;

e della funzione

cli();

Ho bisogno di capire bene come funziona perché dovrei modificarlo per fare in modo che mi incrementi o decrementi (a seconda della rotazione) una variabile "i" ogni sette impulsi ricevuti.
Il delay è per forza necessario?

Infine ho un'altra domanda: dovendo controllare con due encoder due assi i rotazione indipendenti, è sempre possibile usare l'interrupt o sono costretto a usare due schede Arduino?
Grazie e Buona Pasqua a tutti
Ezio

Maurotec

Il codice nel libro è ha scopo didattico per questo hanno usato un modo per spegnere e riaccendere globalmente gli interrupt. Il bit per accendere e spegnere gli interrupt si trova nel registro SREG.

cli() in sostanza spegne o disabilita gli interrupt globalmente, la riga seguente:
Code: [Select]
Pos = encoderPos;
viene eseguita con gli interrupt disabilitati.

Continuando gli interrupt vengono riabilitati senza usare sei(), ma scrivendo il valore del registro salvato in oldSREG,
in SREG. In questo caso oldSREG mantiene lo stato di SREG nel momento in cui gli interrupt sono abilitati.

Il libro dovrebbe consigliarti di usare una macro con cui creare codice eseguito atomicamente, continua leggere magari lo spiega più avanti. Se hai ancora dubbi posta pure, comunque fai una ricerca nel forum perché l'argomento è stato più volte affrontato anche in profondità.

In tutte le architetture hardware delle CPU c'è il concetto di interrupt, che trasportato nel mondo reale equivale
a chiamare qualcuno impegnato in una conversazione al telefono, la sequenza di ciò che accade è la seguente:
Il tizio senta la richiesta di interruzione e dice al tipo che sta all'altro capo del telefono; scusa ho una richiesta di interruzione, dopo di che ti presta attenzione ed esegue la ISR e al termine torna a parlare con il tipo al telefono.

Se la telefonata è di importanza vitale, tizio disabilita gli interrupt in modo che non possa essere interrotto durante la conversazione.

PS: il libro non l'ho letto.
PS1: Per comprendere nel dettaglio gli interrupt devi leggere il datasheet del microcontroller.

zioetzi

Grazie per la risposta, ma ho ancora dei dubbi. La variabile uint_8 ho visto che corrisponde a unsigned int da 8 bit, però mi chiedo se era necessario dichiararla proprio in questo modo.
Poi, le variabili SREG e oldSREG non sono state dichiarate perchè dovrebbero essere delle variabili di registro. Dovendo lavorare con due assi di rotazione indipendenti che ruotano contemporaneamente, mi chiedo quante di queste variabili ho a disposizione?
Grazie

Maurotec

Quote
Grazie per la risposta, ma ho ancora dei dubbi. La variabile uint_8 ho visto che corrisponde a unsigned int da 8 bit, però mi chiedo se era necessario dichiararla proprio in questo modo.


mmm... no uint8_t è una typedef di unsigned char. Quindi si tratta di un tipo definito nell'header della librerie avr-libc.
Si poteva anche scrivere:
Quote

unsigned char oldSREG = SREG;

Documentati sulla keyword: typedef
uint8_t oldSREG; // si legge da destra verso sinistra nel seguente modo: oldSREG è una variabile di tipi uint8_t
Quote

Poi, le variabili SREG e oldSREG non sono state dichiarate perchè dovrebbero essere delle variabili di registro. Dovendo lavorare con due assi di rotazione indipendenti che ruotano contemporaneamente, mi chiedo quante di queste variabili ho a disposizione?


Sono considerazioni fatte sulla base di; "cosa capita male". Per quanto riguarda SREG in effetti nel codice di esempio non compare dichiarazione, infatti è una macro del preprocessore fornita dall'header specifico della libreria avr-libc, questa macro si risolve in un accesso diretto ad un indirizzo di memoria in cui c'è il registro hardware SREG in base al microcontroller scelto l'indirizzo di SREG può cambiare, grazie alla macro SREG il programmatore è svincolato dal dovere accedere ad un indirizzo di memoria che dipende dal microprocessore.

ciao.

zioetzi

Ok, grazie. Proverò a cercare ancora qualche informazione. Non è un argomento banale per i neofiti.

leo72


Continuando gli interrupt vengono riabilitati senza usare sei(), ma scrivendo il valore del registro salvato in oldSREG,
in SREG. In questo caso oldSREG mantiene lo stato di SREG nel momento in cui gli interrupt sono abilitati.

Ho notato che anche in alcuni punti del core di Arduino si usa la tecnica di riattivare gli interrupt globali riscrivendo il valore precedente del registro SREG. Non capisco il vantaggio di fare in questo modo, oltretutto si va a consumare una variabile direi per nulla. Secondo me è più logico un sei().

PaoloP

@Leo
Leggevo ieri nel datasheet del 328p, pag 13 mi pare, che il registro di stato non é salvato in automatico durante l'esecuzione di una ISR ma é compito del software prevederlo. Questo é quello che si fa col cSREG e SREG.
Da quanto ho letto sei() non é necessario perché al rientro fa una ISR il registro globale degli interrupt é impostato attivo.

Anzi per essere precisi, durante la chiamata il registro è disabilitato e tutti gli interrupt sono spenti, in questo modo un altro interrupt non interrompe il corrente e vengono riabilitati in uscita.
Il valore che salvi in cSREG è quello con il registro interrupt disabilitato, riscrivendolo pari pari all'uscita lo lasci disabilitato e poi è il micro al rientro dalla ISR che li riabilita globalmente.
Quello che interessa di SREG sono gli altri parametri, non quello delle interrupt.

Ultimo datasheet --> http://www.atmel.com/Images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet.pdf

A pag. 14
Quote
When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled. The user software can write logic one to the I-bit to enable nested interrupts. All enabled interrupts can then interrupt the current
interrupt routine. The I-bit is automatically set when a Return from Interrupt instruction – RETI – is executed.


e ancora
Quote
When the AVR exits from an interrupt, it will always return to the main program and execute one more instruction
before
any pending interrupt is served.


e infine
Quote
Note that the Status Register is not automatically stored when entering an interrupt routine, nor restored when returning from an interrupt routine. This must be handled by software.


Comunque da quanto si legge non vengono eseguite 2 ISR di seguito ma sempre intervallate da almeno una istruzione del flusso principale. Infatti con l'istruzione sei() prima di un qualsiasi interrupt viene prima eseguita l'istruzione successiva.
Quote
When using the SEI instruction to enable interrupts, the instruction following SEI will be executed before any pending interrupts, as shown in this example.


Il datasheet è una miniera d'oro.

Maurotec

@PaoloP

Ci pensa il C/C++ a fare tutto, quella doc è fondamentale per chi programma in asm, utile per chi programma in C.

Forse non è chiaro ma il mostrato è equivalente a quello di seguito:
Code: [Select]

void loop(){

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      Pos = encoderPos;
  }
  if(Pos != oldPos) {
    Serial.println(Pos,DEC);
    oldPos = Pos;
  }
  delay(1000);
}


Per rendere atomica (cioè non interrompibile) una porzione di codice si usa quella macro oppure la soluzione mostrata nel libro, che didatticamente ha maggiore valore di usare la macro ATOMIC...

Ciao.

leo72

Scusa Paolo ma quelli che citi tu sono i comportamenti da osservare all'interno di una ISR. Io avevo chiesto il motivo perché invece viene fatto una riscrittura del vecchio valore del registro SREG per riattivare gli interrupt globali.
Ma se io disabilito gli interrupt globali, nessun interrupt viene chiamato, quindi nessuna istruzione RETI viene eseguita. Quindi nessuno mi tocca il bit SREG_I.

Quote
Il valore che salvi in cSREG è quello con il registro interrupt disabilitato, riscrivendolo pari pari all'uscita lo lasci disabilitato e poi è il micro al rientro dalla ISR che li riabilita globalmente.

Sì, ma come detto io stavo parlando di una condizione esterna ad una ISR. Cioè salvare lo stato del registro con interrupt globali abilitati, non disabilitati.
Difatti, la macro ISR istruisce il codice a mettere il salvataggio del registro

Meglio allora usare le macro predefinite, come ha suggerito Mauro, così siamo sicuri che il compilatore farà esattamente quel che si vuole.

http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html


zioetzi

Siete tutti molto preparati, ma ci capisco sempre meno... :~
C'è qualche testo o dispensa che possa aiutarmi comprendere quello che state dicendo?
Ho solo una conoscenza di base del C

leo72

Dal link che ho messo accedi a tutta la documentazione relativa alla gestione degli interrupt (e non solo) da parte del compilatore avr-gcc, quella è la tua bibbia.

Maurotec

@leo72
Non ho indagato di recente sul perché si usi salvare SREG, quello che al tempo lessi nella maillist di avr-libc e più o meno riassunto di seguito:

Serviva un modo per rendere atomica una porzione di codice, salvando SREG si risolve ma tutti gli RTOS usano delle macro per architettura per rendere atomica una porzione di codice e ci voleva qualcosa di semplice come START_ATOMIC(), END_ATOMIC(), l'obbiettivo alla fine la soluzione che ho mostrato è stata scelta.

Se apri il file atomic.h della librerie avr-libc trovi
Code: [Select]

#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG

L'obbiettivo quindi non è disabilitare a prescindere gli interrupt ma è salvare lo stato, disabilitare gli interrupt eseguire codice protetto e poi rendere attuale lo stato salvato in precedenza.
Capisci che se hai poche righe di codice sai che gli interrupt sono abilitati e quindi chiami cli(), ma potrebbero anche essere disabilitati, ci potrebbe essere qualche flag che serve, e potresti anche avere 20 o più moduli c,
potresti usare RTOS che internamente necessitano di eseguire codice atomico.

@ziooetzi
In effetti siamo andati in profondità e non ci aspettavamo proprio che ci seguissi nel dettaglio.
Sei all'inizio dell'apprendimento ed è naturale ritrovarsi a usare codice pur non sapendo cosa accade
dietro le quinte. Di guide in rete ne trovi in grande quantità, il problema è che non sai cosa cercare e se ti dessi
dei riferimenti finiresti per studiare quello che al momento non ti serve.
Ti serve una infarinatura sull'architettura Hardware dei microcontroller o CPU, prestando più attenzione
al meccanismo degli interrupt che è il motivo della tua domanda. Ti servono i concetti chiave, senza i quali non riesci a capire l'esempio del tizio al telefono.

A questo livello di apprendimento puoi chiedere e documentarti sui concetti chiave, andare oltre non è proficuo.
Inoltre da quello che hai scritto in merito a "uint8_t" deduco che ti serve studiare un poco meglio il C/C++, anzi le basi di C devi farle tue, capisci che non puoi confondere uint8_t, int16_t, byte, bool ecc per variabili.

Quindi affronta il C dalle basi.

Quante parole chiave ha il C++?
non mi ricordo 37 forse.

Che differenza c'è tra C e C++?
Sono molto simili, il C++ è una evoluzione del C

Quali sono le parole chiave?
auto, bool, char, if, while, for, switch ecc

Che tipo di linguaggio è?
Compilato, strutturato, procedurale, orientato agli oggetti (C++ solo)

Ciao.

leo72

Io generalmente agisco sui singoli bit dei registri, non manipolo quasi mai il registro per intero.
Difatti io disattivo gli interrupt così:
Code: [Select]
SREG &= ~(1<<SREG_I);

e li riattivo così:
Code: [Select]
SREG |= (1<<SREG_I);

In questo modo tocco solo il bit degli interrupt globali. Che poi è identico a fare cli() e sei().

Ora, c'è da fare un piccolo ragionamento.
SE, e dico SE, il bit SREG_I degli interrupt globali NON è settato, con il metodo del cli() e sei() io riattivi gli interrupt anche se questi erano disattivati. E forse è questo il motivo per cui viene usato il giochino del rimettere il valore di SREG.
PERO', stiamo lavorando su Arduino, ed il core di Arduino attiva gli interrupt globali SEMPRE perché da essi dipendono componenti fondamentali per il suo funzionamento. Come la seriale. O come delay(). Quindi è un problema che si pone il giusto, se ragioniamo di Arduino. Se invece parliamo di un altro ambiente, allora le cose possono cambiare.

Maurotec

Quote
PERO', stiamo lavorando su Arduino, ed il core di Arduino attiva gli interrupt globali SEMPRE perché da essi dipendono componenti fondamentali per il suo funzionamento. Come la seriale. O come delay(). Quindi è un problema che si pone il giusto, se ragioniamo di Arduino. Se invece parliamo di un altro ambiente, allora le cose possono cambiare.


mmmm...si e no, più che si, in ogni caso non ti dimenticare di questa cosa, potrebbe esserti di aiuto.

Anche per arduino ci sono RTOS e lo switch context con gli interrupt di abilitati non si fa.
Il C è statico, tuttavia non è detto che non si possa creare codice che si comporta dinamicamente e qui conviene
salvare SREG o usare la macro. Pensa ad un array di puntatori a funzione, ogni funzione può modificare i puntatori, ne viene fuori che in base alla evoluzione dinamica del codice si chiamerà una funzione anziché un'altra e chi mi dice che la funzione che mi ha chiamato ha disabilitato gli interrupt a fatto qualcosa e poi mi ha chiamato e io che sono la funzione chiamata ho porzioni di codice che devo eseguire atomicamente e non posso disabilitare e riabilitare gli interrupt devo salvare lo stato, altrimenti alla funzione che mi ha chiamato gli restituisco il controllo con gli interrupt abilitati. ps: ho preso le sembianze di una funzione, terrificante. 

Forse messa in questi termini ti fa più simpatia salvare lo stato:
Onde evitare che il programma costato mal di testa si comporti correttamente ora si ora no, ti metti al sicuro e salvi sempre lo stato, cioè la prendi come buona e saggia abitudine, in questo modo quando ti ritroverai potenzialmente nelle condizioni di introdurre side effect, la ATOMIC.... ti salva.

Ciao.

leo72

Senz'altro. Difatti ho chiuso il ragionamento proprio dicendo che lavorando su Arduino la cosa è indifferente perché so a priori lo stato del bit SREG_I, è 1 non si scappa.
Se invece avessi un RTOS o simil-tale allora la cosa cambia perché appunto non so cosa sta succedendo non tanto nei task ma nello scheduler, che usa appunto un interrupt come sys-tick. Se malauguratamente vado a riattivare gli interrupt globali quando in realtà dovrebbero essere disattivati, lo scheduler potrebbe ripartire e fare il cambio di un task quando in realtà non doveva farlo.

Go Up