Interrupt per contare impulsi non funziona

Salve, è la prima volta che pubblico qualcosa in un forum quindi chiedo perdono per eventuali ingenuità che commetterò.

Sto cercando di contare i giri che compie un piccolo motore in corrente continua. L'idea è quella di misurare i giri utilizzando un sensore IR del tipo SG035-SZ. Quindi ho applicato al rotore un piccolo dischetto di cartone bianco e ci ho attaccato un pezzettino di scotch nero. A questo punto l'idea è quella di andare a contare gli impulsi che si presentano nel pin out del sensore di modo da ricavare gli rpm.

Dato il codice qui sotto :

volatile int rev=0;
void setup() {
Serial.begin(9600);
pinMode(2,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), isr, RISING );
}
void loop() {
delay(1000);
noInterrupts();
Serial.println(rev);
rev=0;
interrupts(); 
}
void isr(){
rev++;
}

con questo codice mi aspetto che nel monitor seriale mi venga stampato a schermo il numero di impulsi che il pin 2 riceve in un secondo. Quello che succede è che mi trovo stampati una serie di zeri con cadenza di un secondo se non giro il rotore e una serie di numeri casuali se giro il rotore con le mani, del tipo: 0 0 0 8 9 32 300......

Allego anche la foto dello schema che utilizzo.

luca1590:
... scusatemi ma nn ho trovato il pulsante per inserire il codice ho letto il regolamento ma ho un menu differente boh ...

Devi farlo utilizzando il bottone More -> Modify che si trova in basso a destra del tuo post[/i]) e racchiudere il codice all'interno dei tag CODE (... sono quelli che in edit, in modalità WYSIWYG inserisce il bottone con icona fatta così: </>, tutto a sinistra).

In pratica, tutto il tuo codice dovrà trovarsi racchiuso tra due tag: [code] _il _tuo_ codice_ [/code] così da non venire interpretato e non dare adito alla formazione di caratteri indesiderati o cattiva formattazione del testo. Grazie :slight_smile:

Guglielmo

Nel loop se metti un delay(1000) arduino si blocca per un secondo non facendo più niente pertanto non leggerà il pin 2.
Devi usare la funzione mill().

>luca1590: DEVI modificare come ti ho indicato (cortesemente, leggete attentamente quanto vi si scrive) il post iniziale, NON copiarlo e duplicarlo. Grazie.

Guglielmo

Arco_500:
Nel loop se metti un delay(1000) arduino si blocca per un secondo non facendo più niente pertanto non leggerà il pin 2.
Devi usare la funzione mill().

Ciao grazie mille del consiglio, ma non credo sia quello il problema per due motivi:
A) nella documentazione ufficiale del delay dice che gli interrupt funzionano anche con il delay

B)Ho già provato con millis() e mi dava lo stesso problema

ciao,

un consiglio...cambia nome alla funzione associata all'interrupt...questo perchè, magari, isr() potrebbe essere usata da altre librerie che includerai...chiama la tua funzione "contaGiri"...

una cosa che a me sembra strana, ma sono ignorante in materia e quindi possibile di smentita, tu dichiari il pin 2 come INPUT_PULLUP...quindi, lasciami dire, questo avrà sempre i 5 volt presenti e riconosce il cambio di stato quando lo collegi a GND (da 5 a 0)...lo hai collegato al tuo sensore sul suo pin di OUT...questo pin di OUT va a GND?...altrimenti non hai il passaggio da LOW ad HIGH che ti serve.

>luca1590: io farei una cosa ... collegherei il sensore ad un pin di input e semplicemente leggerei e visualizzerei sulla seriale il comportamento del pin OUT ... così da essere sicuri di cosa esce/non esce da quel pin ed in che condizioni, così da capire come usare quel segnale con un interrupt.

Confermo che mentre sei fermo in una delay(), gli interrupt della MCU sono funzionanti anche perché, in caso contrario ... non funzionerebbe la delay() (... basata sull'interrupt di Timer_0) :grin:

Guglielmo

Conoscendo per esperienza l'inaffidabilità di quel "sensore", secondo me hai una serie di false letture nelle fasi di transizione on/off che tu non vedi, ma il micro intercetta senza problemi proprio perché fai uso degli interrupt.

Prova ad aggiungere un passa basso software o magari anche hardware e vedi se le cose migliorano.

Direi che il problema non lo crea il delay(), ma te lo crei tu. Mi spiego meglio.....

Ad intervalli di 1 secondo + il tempo di esecuzione necessari per la trasmissione seriale tu visualizzi il valore dei giri contati dalla isr() e poi azzeri il contatore per prepararlo al successivo ciclo di lettura.

Ci sono almeno tre errori:

  1. Disabilitando gli interrupt durante la trasmissione, se durante questa arrivano degli impulsi, li perdi.

  2. Al variare del numero di cifre di 'rev', cambia il tempo impiegato per trasmettere i dati. Quindi la base tempi su cui fai il conteggio non è sempre la stessa. (poi bisognerebbe vedere se la trasmissione seriale è bloccante o meno)

  3. In casi come questi, è meglio che tu agisca in questo modo: Ad intervalli di tempo fissi fai la lettura atomica della variabile 'rev'. Poi ne sottrai il valore da quello che aveva al ciclo precedente salvandolo in una variabile, che poi trasmetterai e quindi memorizzi il valore di rev precedente in una variabile 'old_rev'. Così facendo disabiliti le interruzioni solo per un brevissimo tempo e di fatto eviti la perdita di impulsi.... Se il numero di giri da leggere in un secondo è minore di 255, puoi anche definire 'rev' come byte e così non serve nemmeno la lettura atomica.
    Riguardo il come ottenere intervalli di tempo fissi, puoi vedere i vari esempi di utilizzo della funzione millis() per un delay non bloccante.

Ultimo come ti ha detto ORSO2001 verifica che il sensore che hai usato sia di tipo NPN oppure Push-pull, altrimenti anche se il SW è corretto non funziona nulla oppure funziona a casaccio.

Allora, intanto SG035-SZ ha già una pullup interna per cui puoi evitare di definire il pin con INPUT_PULLUP.
Poi come hanno detto, meglio usare millis() non solo perché è bene abituarsi ad usarlo, ma soprattutto perché immagino/spero che poi il tuo programma debba anche fare "altro", e con delay() sei fregato. :wink:
Infine io metterei l'interrupt su FALLING pià che RISING.

Vedi come ti ho modificato il codice (ovviamente chiedi se hai dubbi), e poi però provalo:

#define P_SENSOR 2
volatile int rev=0;
unsigned long tmrLast;

void setup() 
{
  Serial.begin(9600);
  pinMode(P_SENSOR,INPUT);
  attachInterrupt(digitalPinToInterrupt(P_SENSOR), contaImpulsi, FALLING);
  tmrLast = millis();
}


void loop() 
{
  if ( millis() - tmrLast > 1000 ) {
    Serial.println(rev);
    rev=0;
    tmrLast = millis();
  }
}

void contaImpulsi()
{
  rev++;
}

Ovviamente se il rotore è fermo darà sempre zero, e se lo giri con le mani avrai sempre un numero "variabile" di valori: usa un motorino o un avvitatore e vedi se a giri costanti hai valori abbastanza regolari. Inoltre l'affidabiltà di questo metodo dipende da come hai messo il dischetto, come hai regolato la sensibilità di quel sensore, dall'eventuale presenza di altre fonti di luce, eccetera.

Se dopo aver fatto tutte queste prove non ne vieni a capo, il problema secondo me è che quel sensore è più che altro pensato come sensore di ostacoli che come encoder e fargli "vedere" un riflesso di un dischetto potrebbe non essere affidabile: in tal caso al posto tuo penserei ad un altro tipo di sensore, ad esempio usando sempre quello stesso sensore ma dissaldando il led per metterlo dietro al dischetto, e su quest'ultimo fare un foro.

PS: dimenticavo, ricorda di indentare il codice.

Non è meglio così ?

  if ( (millis() - tmrLast) > 1000 ) {

tecno67:
Non è meglio così ?

Indifferente, l'operatore '>' ha una priorità inferiore all'operatore '-' per cui le parentesi non sono necessarie.

'Precedenza' degli operatori in ordine decrescente ...


Guglielmo

E' vero! Le mie scuse a docdoc!

In generale preferisco una sintassi più verbosa al rischio di valutare male la precedenza degli operatori (soprattutto con espressioni più complesse di questa). Ma è una scelta personale.

Non c'è bisogno di scusarsi.. :wink: In realtà ci sono cose nella programmazione che spesso sono essenzialmente "convenzioni": alcune sono globali (come l'uso di maiuscole per i simboli e costanti) altre meno globali (la graffa di apertura alla fine del relativo comando o su una linea separata?), altre infine estremamente personali (come nel tuo caso, raggruppare alcuni termini per evidenziare meglio gli elementi),.

Per quanto riguarda quella if(), effettivamente una piccola correzione ci sarebbe, ossia mettere un ">=" e non solo ">":

 if ( millis() - tmrLast >= 1000 ) {

:wink:

... considerate poi che ... al compilatore, quella riga, di come la scrivete (con o senza parentesi) ... "nun je ne po' frega' de meno" ... la ottimizza comunque lui secondo le regole. Però, le parentesi, la rendono effettivamente più leggibile magari per i meno avvezzi :slight_smile:

Guglielmo

:sweat_smile: :sweat_smile: :-X

Giusta considerazione!

Capita spesso di pensare erroneamente che il contrario di '>' è '<' e dimenticarsi che c'è una terza condizione nel mezzo ('=').

Nel caso di cui sopra porta un errore minimo. In altri casi (se > fai A seguito da un se < fai B) può avere conseguenze più gravi perché succede che provi il tutto e funziona, ma poi in quelle rare occasioni in cui si verifica la terza condizione, non l'hai gestita e magari qualcosa ti va in crash, oppure passi ore a non capire dove sbagli.

gpb01:
>luca1590: io farei una cosa ... collegherei il sensore ad un pin di input e semplicemente leggerei e visualizzerei sulla seriale il comportamento del pin OUT ... così da essere sicuri di cosa esce/non esce da quel pin ed in che condizioni, così da capire come usare quel segnale con un interrupt.

Confermo che mentre sei fermo in una delay(), gli interrupt della MCU sono funzionanti anche perché, in caso contrario ... non funzionerebbe la delay() (... basata sull'interrupt di Timer_0) :grin:

Guglielmo

Dunque se io dimentico gli interrupt e faccio una lettura dello stato del pin2 allora non mi da "false letture" ad effettivamente un giro sta ad un incremento della variabile.

docdoc:
Allora, intanto SG035-SZ ha già una pullup interna per cui puoi evitare di definire il pin con INPUT_PULLUP. .....

Ti ringrazio del consiglio, purtroppo anche con il tuo codice mi da lo stesso identico problema cioè ad un passaggio corrisponde un incremento della variabile che non è unitario, potrebbe esse 3 o 5 o 48.
Ho pensato potrebbe essere un problema di riflessione ma anche per dire passando davanti un dito mi da letture sbagliate.
Ho provato anche a non fargli incrementare la variabile ma semplicemente scrivere a schermo "ho visto un pezzo di scotch" e nulla me lo stampa random più volte.
Si potrebbe essere anche che il sensore non sia adatto a questo tipo di applicazione, ma quello che è strano e che anche quando legge 3 giri al posto di uno il led del sensore non sfarfalla 3 volte ma lui si accende come dovrebbe una volta sola (dico 3 giri perché sono d'accordo che quando mi esce 48 se fosse il led che blinka 48 volte non me ne accorgerei).

cotestatnt:
Conoscendo per esperienza l'inaffidabilità di quel "sensore", secondo me hai una serie di false letture nelle fasi di transizione on/off che tu non vedi, ma il micro intercetta senza problemi proprio perché fai uso degli interrupt.

Prova ad aggiungere un passa basso software o magari anche hardware e vedi se le cose migliorano.

cotestatnt:
Conoscendo per esperienza l'inaffidabilità di quel "sensore", secondo me hai una serie di false letture nelle fasi di transizione on/off che tu non vedi, ma il micro intercetta senza problemi proprio perché fai uso degli interrupt.

Prova ad aggiungere un passa basso software o magari anche hardware e vedi se le cose migliorano.

Mettendo un filtro RC con R=10 e C=10 micro farad sembra funzionare meglio apparentemente conta corretto, devo provare con il motore acceso però. Grazie adesso faccio un po' di tentativi e poi vi aggiorno.

luca1590:
Si potrebbe essere anche che il sensore non sia adatto a questo tipo di applicazione, ma quello che è strano e che anche quando legge 3 giri al posto di uno il led del sensore non sfarfalla 3 volte ma lui si accende come dovrebbe una volta sola (dico 3 giri perché sono d'accordo che quando mi esce 48 se fosse il led che blinka 48 volte non me ne accorgerei).

Strano, quel LED è direttamente connesso all'uscita dell'operazionale come puoi vedere dallo schema:

Dato che quel sensore è normalmente HIGH, e passa LOW quando rileva un ostacolo (entro la sensibilità impostata col trimmerino che vedi onboard), se il tuo dischetto è bianco ed ha una fascia nera, il comportamento sarebbe esattamente l'opposto ossia dovrebbe normalmente "vedere" un ostacolo (il bianco che riflette il segnale IR del LED) quindi essere LOW, e passare HIGH appena arriva la parte scura, per cui nel tuo codice dovresti rimettere il RISING che avevi all'inizio.

Però devi essere certo di questo, ossia che il sensore riesca veramente a "vedere" quando passa la parte nera e che questo riconscimento sia regolare. Per verificarlo devi sperimentare (come dico sempre, bisogna cercare di ingegnarsi...:wink: ) ossia carica questo codice che mostra ogni secondo quanti impulsi ha ricevuto in totale (è lo stesso, in sostanza, senza azzerare la variabile):

#define P_SENSOR 2
volatile int rev=0;
unsigned long tmrLast;

void setup()
{
  Serial.begin(9600);
  pinMode(P_SENSOR,INPUT);
  attachInterrupt(digitalPinToInterrupt(P_SENSOR), contaImpulsi, RISING);
  tmrLast = millis();
}


void loop()
{
  if ( millis() - tmrLast >= 1000 ) {
    noInterrupts();
    Serial.println(rev);
    tmrLast = millis();
    interrupts();
  }
}

void contaImpulsi()
{
  rev++;
}

Quindi:

  1. prendi un foglio dello stesso materiale del dischetto ossia un cartoncino bianco da un lato e dall'altro mettici il nastro nero
  2. prova a posizionare il foglio dal lato bianco davanti al sensore, alla stessa distanza del dischetto e vedi se il contatore dopo qualche primo impulso dovuto al movimento resta fermo o se sale anche mentre stai totalmente fermo. Se non aumenta allora passa al prossimo punto.
    Se invece aumenta senza fare nulla, prova ad abbassare la sensibilità del sensore (col trimmerino) ossia mentre tieni fermo il cartoncino abbassa la sensibilità e fermati quando gli impusli "spuri" spariscono.
    Se non spariscono né girandolo in un verso né nell'altro, allora o il modulo ha qualche problema o il materiale che usi non va bene per questo scopo, e devi passare ad altro.
  3. ora fai la stessa cosa col lato nero: anche qui, dopo un primo "assestamento" iniziale, se resti immobile dovrebbe non contare eventi.

Fammi sapere l'esito.

Ma dato che per me è proprio quel tipo di sensore che non va bene, almeno non utilizzato così, io farei un altro dischetto questa volta con un foro (diciamo del diametro del LED, puoi usare anche un foradocumenti) e dissalderei il LED (quello trasparente per intenderci) per portarlo dal lato opposto, in linea col sensore: in questo modo il sensore quasi certamente potrà "vedere" gli infrarossi solo quando il foro gli passa davanti.

luca1590:
Mettendo un filtro RC con R=10 e C=10 micro farad sembra funzionare meglio apparentemente conta corretto, devo provare con il motore acceso però.

Potrebbe migliorare un poco ma non stiamo mica parlando di un contatto meccanico che necessita di un "antirimbalzo" :wink: Se vedi lo schema c'è in pratica già e c'è poi l'operazionale che fa da trigger. Se "rimbalza" significa che il sensore non è utilizzato correttamente...