Newsgroup: it.hobby.elettronica.digitale
Titolo: Contato giri con interrupt

Allora ho un albero su cui è montato un disco con una finestra e 2 
fotocellule che leggono questo disco al pari di un encoder, voglio 
realizzare un conta giri con arduino che possa leggere i giri in un 
senso incrementando e nel senso opposto decrementando il valore, ci sono 
riuscito perfettamente senza usare gli interrupt il problema è che 
funziona finchè i il numero dei giri è basso, se aumenta la velocità non 
ci sta più dietro, ho quindi trovato questo esempio per fare la stessa 
cosa usando gli interrupt:
 
https://electronoobs.com/eng_arduino_tut125.php
 
(l'esempio è circa a metà pagina)
 
Allora il problema di perdere i passi me lo risolve effettivamente, il 
problema è che in avanti conta sempre 2 passi alla volta perchè rileva 
il cambio di stato del piedino che da 0 va a 1 e incrementa 1, poi da 1 
torna a zero e incrementa ancora di 1 e li ho risolto poi dividendo per 
2 il valore del contatore prima di stamparlo a display, il secondo 
problema invece è che non riesco in nessun modo a farlo contare a 
rovescia in nessunissimo modo, quando l'albero gira a rovescia fa 
un'incremento di 1 seguito subito da un decremento di 1, quindi vedi la 
cifra che avanza e torna indietro restando sempre allo stesso valore.
Non sono riuscito a risolvere in nessun modo oppure se mi conta 
all'indietro dopo non conta in avanti.
 
Non voglio usare logiche cablate, in realtà ho già un contatore a 
logiche (che ho usato per confrontare i conteggi del mio circuito ed 
essere sicuro che contasse bene) ma mi serve l'arduino perchè voglio 
implementare altre funzioni come un segnale acustico al raggiungimento 
di un certo numero di passi contati che posso impostare arbitrariamente 
di volta in volta o la lettura di switch e altre cose.
 
A un certo punto durante le mie prove ero riuscito a farlo funziona in 
qualche modo impostando counter come float e nel senso in avanti 
incrementavo di 0,5 mentre all'indietro decrementavo di 1,5, funzionava 
bene finchè da zero non andavo nei numeri negativi a quel punto 
funzionava lo stesso con il numero del display che in certi casi vedevi 
che avnazava e tornava indietro, sempre segnalando corretto... ma mi 
pare che float non supportsa i numeri negativi quindi sicuramente stava 
facendo errori e in generale la soluzione mi sembra un workaround orribile.

 torna a zero e incrementa ancora di 1
 
Non conosco molto bene Arduino (l'ho usato solo per fare qualche giochino)
ma googlando mi pare di capire che per generare un interrupt su uno solo
dei due fronti (che è quello che serve a te) basta usare la funzione
attachInterrupt():
 
https://www.arduino.cc/reference/tr/language/functions/external-interrupts/attachinterrupt/
 
 
Un altro metodo è impostare direttamente il registro EICRA (che immagino
sia quello che fa internamente attachInterrupt) ma visto che hai a
disposizione delle funzioni di alto livello che fanno tutto il lavoro
sporco non vedo perché complicarsi la vita.
 
-- 
Fletto i muscoli e sono nel vuoto.

 rovescia in nessunissimo modo, quando l'albero gira a rovescia fa 
 
 
non sono stato li a pensarci più di tanto, di conseguenza non è detto 
che funzioni, ma prova ad usare una variabile temporanea.
 
Visto che dici che la cifra è sempre uguale :
 
"""
 > un'incremento di 1 seguito subito da un decremento di 1, quindi vedi la
 > cifra che avanza e torna indietro restando sempre allo stesso valore.
 > Non sono riuscito a risolvere in nessun modo oppure se mi conta
 > all'indietro dopo non conta in avanti.
"""
 
la confronti con l'ultima memorizzata, se risulta uguale, decrementi 
invece di incrementare memorizzando da qualche parte il senso di rotazione.
Non sarà comunque preciso, avrai sicuramente altri problemi soprattutto 
con il senso di rotazione.
 
 

Il 14/11/22 13:47, dalai lamah ha scritto:

allora i 2 sensori coperti sono entrambe 0, poi se giro in un senso avrò 
una sequenza 10/11 ... mentre se giro nel senso opposto avrò una 
sequenza 01/11, quindi devo attivare l'interrupt su entrambe i pin, sto 
usando un 328p e infatti viene detto che i pin di interrupt usabili sono 
2 e 3, infatti non capisco perchè quel codice funziona anche se male, 
collegato ai pin 8 e 9...

 sequenza 01/11, quindi devo attivare l'interrupt su entrambe i pin
 
Certo che devi attivarlo su entrambi i pin, ma devi attivarlo solo su un
fronte su ciascun pin. Se ad esempio lo attivi sul fronte di salita, avrai
un interrupt ogni volta che uno dei due pin passerà da 0 a 1, ma non quando
i pin passeranno da 1 a 0.


 incrementando e nel senso opposto decrementando il valore
 
E' passato un po' di tempo dalla tua domanda... Comunque io per leggere 
l'encoder manuale a scatti uso sempre questa funzione, che leggo 
continuamente in un loop brevissimo in cui faccio poche altre cose, tra 
cui aggiornare il valore sul display, quando faccio delle impostazioni:
 
void encoder()
{
//            PD 76543210
// S=3-PIND&B00000011; Gli I/O 1 e 0 sono PD1 e PD0, perciò non devo 
scorrere a destra.
// S=3-(PIND>>3)&B00000011; Serviva per l'encoder su PD4 e PD3.
// S=3-((PIND>>4)&B00000011); // Gli I/O 5 e 4 erano PD5 e PD4, perciò 
dovevo scorrere a destra di 4 bit.
// S=3-((PIND>>2)&B00000011); // Uso PD3 e PD2, quindi scorro a destra 
di 2 bit per farli diventare 1 e 0.
  S=3-((PINC>>1)&B00000011); // Uso PC2 e PC1 (A2 e A1), quindi scorro a 
destra di 1 bit per farli diventare 1 e 0.
// Il valore binario di S rappresenta A e B. Il centrale dell'encoder è 
a massa, quindi faccio complemento a 3 (11).
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.
E=0;
if (S!=So && S==0) X=0;
if (X==0)
   {
   if (So==1&&S==2)
     {E=1; X=1;
     // Bip();
     }
   if (So==3&&S==2)
     {E=-1; X=1;
     // Bip();
     }
   if (S==0)
     {E=0; X=0;}
   So=S;
   }
}
 
Poiché l'encoder viene ruotato manualmente e il loop è un while() 
brevissimo in attesa della pressione del pulsante, funziona benissimo 
senza dover usare gli interrupt; nel tuo caso, però, puoi usarne la 
logica insieme agli interrupt.
 
I pin su cui possono essere usati gli interrupt hardware sono il 2 e il 
3, che corrispondono a INT0 e INT1. Sugli altri puoi usare solo gli 
interrupt "pin change", in cui viene rilevata una variazione su uno 
qualsiasi dei pin della porta e devi andare a controllare tu su quale 
pin è avvenuta.

 3, che corrispondono a INT0 e INT1.
In realtà sono gli I/O 2 e 3, non i pin fisici del microcontrollore.

Il 12/01/23 18:25, LAB ha scritto:

Il software di quel contaspire l'ho già terminato da tempo, sono 
riuscito a girare attorno ai bug del codice di interrupt e a farlo 
contare giusto in qualche maniera. Anche se magari avrei preferito non 
fare workaround.
 
Fare un main loop molto corto nno ci riuscivo perchè dovevo aggiornare 
un display a segmenti e monitorare 4 pulsanti, di fatti se andavo in 
polling funzionava a bassi giri ma se aumentano gli RPM dell'albero 
oltre un certo punto perdeva tantissimi contaggi per la strada.
 
Se vuoi posso farti vedere il mio intero programma.

Non è questione di bug... Bisogna usare bene gli interrupt e fare
 bene i conti!
Pubblica il programma.
Comunque c'è anche il forum ufficiale di Arduino, con diverse
 persone molto preparate e disponibili.
 
 
-- 

Il 12/01/23 22:23, LAB ha scritto:

https://paste.ofcode.org/ruAiJKxvU6stVqsKHNx8dc
 
 
Il codice è qua, non ho mai trovato il modo di far contare quel codice 
+1 in avanti e -1 indietro, mi contava sempre a comunque tipo uno in 
avanti e +1 e poi -1 (restando fermo) se andavo indietro. Alla fine ho 
risolto girandoci attorno con la matematica, magari non è bello ma 
funziona e non mi interessa andarci attorno ancora.
 
L'unica cosa intipatica ma che non so se è dovuta al programma fatto 
male o a un limite intrinseco dell'ancoder è che se mi fermo con 
l'albero nel punto della finestra sulle fotocellule e mi metto a 
spippolare sul rocchetto per attaccare fili o mettere isolanti, facendo 
piccoli movimenti in avanti e indietro capitano conteggi spuri per cui 
ho implementato la funzione di pausa... quando mi fermo per fare cose 
per evitare conteggi spuri premo un pulsante per freezare il conteggio, 
ma mi devo ricordare di ripremerlo per riavviarlo.

 ho implementato la funzione di pausa...
Non so come funziona l'encoder che usi. Quello manuale a scatti genera 
una sequenza 0-1-2-3-0 oppure 0-3-2-1-0 per ogni scatto in avanti o 
indietro. Come puoi vedere nel codice che ho pubblicato, per 
l'incremento (E=1) vado a rilevare il passaggio da 1 a 2:
   if (So==1 && S==2)  // da 1 a 2.
mentre per il decremento (E=-1) vado a rilevare il passaggio da 3 a 2:
   if (So==3 && S==2) // da 3 a 2.
(le variabili con la 'o' sono lo stato precedente).
Essendo due condizioni diverse, è impossibile che si verifichino 
instabilità.

Il 13/01/23 15:38, LAB ha scritto:

L'encoder di questa macchina ha 2 fotocellule consecutive con in mezzo 
un disco con una tacca, quando passa la tacca la sequenza di bit è
 
00 - 01 - 11 - 10 - 00
 
oppure
 
00 - 10 - 11 - 01 - 00 per il verso opposto

 00 - 10 - 11 - 01 - 00 per il verso opposto
E' in codice Gray, uguale a quelli a scatti che uso per le impostazioni.
Chiamando i due bit A e B e facendo AB XOR A (bit a bit, senza riporti):
 
AB  AB ___ A
00: 00 XOR 0 = 00 = 0;
01: 01 XOR 0 = 01 = 1;
11: 11 XOR 1 = 10 = 2;
10: 10 XOR 1 = 11 = 3;
00: 00 XOR 0 = 00 = 0.
 
Oppure:
00: 00 XOR 0 = 00 = 0;
10: 10 XOR 1 = 11 = 3;
11: 11 XOR 1 = 10 = 2;
01: 01 XOR 0 = 01 = 1;
00: 00 XOR 0 = 00 = 0;


Se si verifica un passaggio da 1 a 2, significa che fa parte della prima 
sequenza: +1;
se, invece, si verifica un passaggio da 3 a 2, significa che fa parte 
della seconda sequenza: -1.

N.B.: Ho evitato con cura i passaggi che coinvolgono la posizione di 
riposo (decimale 0) prendendo il successivo, che avviene solo dopo che 
il movimento ha superato 1/4 dello scatto.

 il movimento ha superato 1/4 dello scatto.
ok ma a livello del codice che devo modificare ?

Nella ISR calcoli il valore decimale di S (0...3) e vedi se è
 verificata una delle due condizioni di S e di S_prec (che chiamai
 So) che ti ho detto prima: se è vera una delle due, incrementi o
 decrementi il contatore.
 
 
-- 

Il 13/01/23 22:49, LAB ha scritto:

l'unica parte di quel codice che ho copiato è la procedura di interrupt 
perchè proprio non la capisco. Io credo che parta un'interrupt ad ogni 
cambio di stato dei 2 pin che monitorano le 2 fotocellule e credo che 
parte un'int quando si passa da 00 a 01 e non aspetti che arrivi l'11 
per capire la direzione del conteggio, quindi da 01 a 11 sono già 2 
interrupt che partono e per quello io credo che conteggiasse a cavolo di 
cane quindi non ho semplicemente non ho idea di come modificare quel codice.

Il 14/01/23 10:05, Gremlin ha scritto:

Tra l'altro un codice praticamente uguale usato nel loop normale 
funzionava (anche se perdeva i passi se aumentavo i giri) mentre usato 
come procedura di interrupt incrementava di 2 in avanti e faceva +1 poi 
-1 andando indietro per questo ho fatto il tric di incrementare +5 uno e 
-15 l'altro, poi con le divisioni lo forzo a conteggiare giusto, mi sono 
rotto la testa ore cercando di capire come farlo andare alla fine mi 
sono arreso a questa soluzione che all'atto pratico funziona. Se mi 
scrivi il pezzo di codice come dovrebbe essere scritto per funzionare 
magari lo capisco e posso provare se funziona se no da solo non ci 
arrivo proprio

Ancora non ho acceso il computer. Dal telefonino ti dico solo che
 l'interrupt parte ogni volta che accade la condizione impostata,
 che può essere una transizione a livello alto o a livello basso o
 entrambe. Tu devi rilevarle entrambe, andare subito a vedere che
 cosa è accaduto e interpretarlo in base allo stato decimale
 precedente. 
 
 
-- 

Se era 0 e adesso è 1, lo ignori.
Se era 1 e adesso è 2, incrementi.
Se era 3 e adesso è 2, decrementi.
Se era 3 o 1 e adesso è 0, azzeri.
 
-- 

Il 14/01/23 11:45, LAB ha scritto:

io credo che quel codice avvi linterrupt ad ogni cambio di stato di uno 
dei 2 pin e basta

Con un encoder a scatti scrive il valore di contatore sulla seriale:
 
volatile int16_t contatore=0; // Fino a oltre +/-32000. Ho previsto 
anche i valori negativi.
volatile uint8_t s=0; // Valore decimale dei bit.
volatile uint8_t sp=0; // Stato precedente di s.
volatile uint8_t x=0;
int16_t contatorep=0; // Valore precedente.
 
void setup()
{
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Start");
// Pin Change Interrupt Control Register (PCICR): PORTD=PCIE2; 
PORTC=PCIE1; PORTB=PCIE0;
PCICR |= 0b00000001; // PORT B.
// PORT B: PMSK0; D9 e D8 sono i bit 1 e 0:
PCMSK0 |= 0b00000011;
}
 
 
void loop()
{
if(contatore != contatorep)
   {
   Serial.println (contatore);
   contatorep = contatore;
   }
}
 
 
ISR (PCINT0_vect)
{
//            bit AB
s=((PINB^0xFF) & 0b00000011); // Inverto i bit, perché l'encoder chiude 
a massa, e considero solo D9 e D8. S è composto dai bit A e B.
s^=s>>1; // Faccio AB XOR A, ottenendo un valore decimale fra 0 e 3.
if (s!=sp && s==0) x=0;
if (x==0)
   {
   if (sp==1 && s==2)
     {
     contatore+=1;
     x=1;
     }
   else if (sp==3 && s==2)
     {
     contatore-=1;
     x=1;
     }
   else if (!s) x=0;
   sp=s;
   }
}

Altro metodo equivalente per impostare i registri:
 
// 
https://dronebotworkshop.com/interrupts/
 
 
volatile int16_t contatore=0; // Fino a oltre +/-32000. Ho previsto 
anche i valori negativi.
volatile uint8_t s=0; // Valore decimale dei bit.
volatile uint8_t sp=0; // Stato precedente di s.
volatile uint8_t x=0;
int16_t contatorep=0; // Valore precedente.
 
void setup()
{
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Start");
// Pin Change Interrupt Control Register (PCICR): PORTD=PCIE2; 
PORTC=PCIE1; PORTB=PCIE0;
// PCICR |= 0b00000001; // PORT B.
PCICR |= 1<<PCIE0;
// PORT B: PMSK0; D9 e D8 sono i bit 1 e 0:
// PCMSK0 |= 0b00000011;
PCMSK0 |= 1<<PCINT1 | 1<<PCINT0;


https://i.postimg.cc/9QmqZWPC/20230114-141844.jpg
 
 
https://i.postimg.cc/9Ft7D47L/20230114-141914.jpg
 

 
https://i.postimg.cc/9Ft7D47L/20230114-141914.jpg
 
 
quando ho tempo lo provo

Il 14/1/23 14:32, Gremlin ha scritto:

Io ho usato un encoder che chiude verso massa, infatti ho attivato i 
pullup e ho dovuto scrivere:
s=((PINB^0xFF) & 0b00000011);
dove ^0xFF fa XOR con 0xFF, cioè 0b11111111, invertendo ogni bit.
 
Se chiude verso Vcc, devi scrivere pinMode (8, INPUT); e pinMode (9, 
INPUT);, mettere le resistenze di pulldown e poi:
s=(PINB & 0b00000011);
senza inversione.

Il 14/01/23 14:42, LAB ha scritto:

allora ho provato il codice, funziona
 
https://paste.ofcode.org/mpLUgHjRM4SHEQgv6YMm7e
 
 
Mi resta da vedere se gli capitano ancora conteggi spuri ad cazzum 
quando per qualche motivo mi soffermo nel punto dove c'è la finestra del 
disco proprio sopra le fotocellule.
 
Intanto grazie.

Le variabili usate solo nella ISR, però, dovrebbero avere
 l'attributo volatile per evitare danni da parte del compilatore
 che ottimizza il codice.
 
 
-- 

Sulla volatilità:
https://www.html.it/pag/72236/la-parola-chiave-volatile/
 
 
 
-- 

Leggendolo bene, anche contatore deve essere volatile, altrimenti
 il compilatore, se impostato a un livello alto di ottimizzazione,
 potrebbe semplicemente sostituire a contatore il suo valore
 iniziale, che è zero o quello letto dalla EEPROM! La ISR viene
 chiamata solo da eventi esterni, non generati dal programma,
 quindi per il compilatore la ISR potrebbe non essere mai eseguita
 e potrebbe considerarla un refuso, un blocco dimenticato lì dal
 programmatore.
 
Considera che il C fa pochissimi controlli, anche per lasciare la
 massima libertà al programmatore. Un classico sono gli array, per
 i quali non controlla se un elemento a cui viene assegnato un
 valore ne fa parte o sta fuori! È facile, quindi, per esempio,
 assegnare un valore a pippo[12] dopo aver dimensionato pippo per
 12 elementi che vanno da 0 a 11 e riscontrare comportamento
 imprevisti e incomprensibili o caratteri strani sull'LCD.
 
 
-- 

 
C++ ma su arduino non si usa il C ?
Scusa io ho imparato a programmare autodidatta l'ansi C standard, 
credevo che questi uint16 blabla fossero solo modi diversi per definire 
la stessa cosa, il programma funziona...

Il 15/01/23 18:53, LAB ha scritto:

ho rimesso i suffissi volatile, ho visto che il programma compilato è 
uscito un centinaio di byte in più rispetto non mettere volatile, ma a 
parte questo esegue perfettamente allo stesso modo.

 la stessa cosa, il programma funziona...
 
Io ho cominciato con il C da Arduino... :)
 
Arduino usa il C++, che comprende il C. Quando scrivi un programma si 
può fare ricorso anche alle caratteristiche del C++, ma normalmente ci 
si limita al C, anche perché, purtroppo, si vedono tante cose copiate 
qua e là e pochissimi pubblicano codice scritto bene e in maniera 
"colta"! Questo è lo svantaggio della divulgazione a tutti di qualcosa 
che, in realtà, sarebbe alquanto complessa :(
 
Anch'io usavo int, unsigned int, long, byte, char, ma poi ho ritenuto 
molto più semplice e chiaro usare int_8t, uint_8t, ...16t, ...32t, senza 
stare a pensare se byte accetta i numeri negativi o no... (No! Li 
accetta char... Come!... Caratteri negativi???...). Inoltre una 
variabile che si chiama char fa pensare che contenga caratteri...
 
Inoltre, int_8t, uint_16t e simili sono indipendenti dal 
microcontrollore: una 8t è sempre a 8bit, una 16t è sempre a 16 e una 
32t è sempre a 32, mentre una long su un microcontrollore superiore 
all'ATmega328p di Arduino Uno può anche essere a 64 bit!
 
volatile è un qualificatore che può essere usato sia con int, byte, ecc. 
che con le varie int_ e uint_, equivalenti a int, byte, ecc.

Il 15/01/23 20:08, LAB ha scritto:

ma alla fine int e unit sono l'abbreviazione di int e unsigned int...
 
Io ho imparato autodidatta su una scheda Z80, poi anche con turbo c su 
DOS, l'ansi C standard usa char int long e varianti unsigned. Questi 
nuovi suffissi sono modernità, la mia è abitudine dalle basi da cui ho 
imparato... mi vengono più naturali... cmq non c'è nessuna differenza da 
un carattere a un qualsiasi valore a 8bit, di fatti le lettere della 
tastiera sono comprese dentro la tabella ascii che va da 0 a 255...

 che con le varie int_ e uint_, equivalenti a int, byte, ecc.
 
volatile long me l'ha accettato cmq

 complessa 🙁
 
non sono d'accordo che una cosa debba essere riservata a pochi eletti, 
chi è bravo farà bel codice, gli altri potrebbero imparare vedendo o 
usando il codice di altri. E cmq tanti potrebbero trovare sufficiente 
per i loro scopi fare così. Io di certo non sono un programmatore visto 
che per lavoro riparo amplificatori a valvole, sarebbe come dire che non 
diritto di fare le mie cazzatine per hobby perchè non sono bravo come 
altri... Io scrivo il mio codice fin dove arrivo, dove non arrivo cerco 
aiuto o copio pezzetti di codice di esempio che trovo in giro e ho fatto 
tante cosine che mi sono utili, da un wattmetro audio, questo contaspire 
per la bobinatrice che fa cose un più di quello originale della 
macchina, il sistema che mi gestisce temperature e illuminazione in un 
terrario con una tartaruga di terra che ho a casa e che non trovavo 
niente di fatto specifico per questa razza...

 usando il codice di altri.
Il problema sta proprio nel fatto che "gli altri" copiano a caso, 
aggiustano come possono e poi pubblicano in rete, diffondendo cose 
sbagliate! Non dico che debba essere riservato a pochi eletti: d'altra 
parte, ai tempi dei PIC non osavo nemmeno avvicinarmici, ma se tanti 
diffondono cose fatte male il livello si abbassa enormemente e trovare 
informazioni per fare qualcosa che funzioni bene diventa molto 
difficile. Andando a chiedere nel forum, poi, ci si rende conto di aver 
preso abitudini sbagliate.

Il 17/01/23 11:27, LAB ha scritto:

O si torna negli anni 80 quando non c'era internet e nessuno poteva fare 
niente se non gli ingegneri che dovevano spendersi milioni per i sistemi 
e i software o si resta nel 2023 dove ci sono anche gli hobbysti, e a me 
sembra che con l'opensource alla fine sulle cose serie se si organizzano 
gruppi si possano fare grandi cose, vedi Linux. Una via di mezzo non può 
esistere e cmq io preferisco adesso che gli anni 80 dove accendevo il 
commodore 64 e immaginavo di fare grandi cose ma non trovano nessuna 
informazione su come provare a fare quello che immaginavo e alla fine 
tutto quello che potevo fare era ficcare un dischetto e far partire un 
giochino del cazzo di cui mi stancavo dopo 3 volte.

 e i software o si resta nel 2023 dove ci sono anche gli hobbysti
 
Anch'io uso Arduino come hobbista, ma mi piace fare le cose bene e, 
nonostante Internet, trovo muri appena mi discosto un po' dai soliti 
giochetti...
 
Prova solo a cercare informazioni sull'uso dei contatori, per esempio 
per fare un frequenzimetro: a fatica si trova qualcuno che non ha usato 
pulseIn() e nessuno spiega bene come funziona.
Si trovano anche tantissimi programmi fatti con i delay() in mezzo e poi 
se uno prova ad aggiungere qualcosa diventa matto perché non si rende 
conto che i delay() bloccano tutto!

Il 19/01/23 10:40, LAB ha scritto:

Io ho fatto un frequenzimetro per un modulo per convertire le radio 
d'epoca OM in FM.
 
Delay lo uso, lo so che blocca il programma, dipende se mi interessa la 
cosa o no a volte ho usato millis