contagiri

Salve a tutti,

Sono nuovo del forum e prego perdonare qualche omissione.
Sono un Maker, posseggo delle infarinature elettronico/informatiche e
smanetto un pò con arduino ma a 60 anni non è poi cosi' facile.

Ho bisogno di costruire un contagiri per il motore della mia fresa cnc diy.

Premetto che il mio livello di programmazione è molto basso, e pertanto ho cercato degli sketch in rete.

Utilizzo arduino nano, sensore IR e display I2c

Dopo averne provati alcuni, uno "sembra" funzionare

Il problema sta che anche in assenza di segnali o anche con un solo input il display mostra valori non plausibili, a caso, a volte positivi o negativi. qualcuno sa dirmi dove sta il problema? Tutto qusto avviene a presindere dal tipo di alimentazione applicata ad Arduino.

Ecco i link allo sketch e un breve video di ciò che accade

https://create.arduino.cc/editor/napalmax/6dc0e1b4-e370-4aa9-82c4-cc01ed3ecdc8/preview

Grazie 1000!

Buongiorno, :slight_smile:
essendo il tuo primo post, nel rispetto del regolamento della sezione Italiana del forum (… punto 13, primo capoverso), ti chiedo cortesemente di presentarti IN QUESTO THREAD (spiegando bene quali conoscenze hai di elettronica e di programmazione … possibilmente evitando di scrivere solo una riga di saluto) e di leggere con molta attenzione tutto il su citato REGOLAMENTO … Grazie. :slight_smile:

Guglielmo

P.S.: Ti ricordo che, purtroppo, fino a quando non sarà fatta la presentazione nell’apposito thread, nessuno ti potrà rispondere, quindi ti consiglio di farla al più presto. :wink:

Penso che il problema venga da qui:

volatile float time = 0;
volatile float time_last = 0;

time e time_last devono essere delle variabili unsigned long e non float.

Comunque questo sketch lo trovo strano! Dove lo hai trovato?

Oltre all’errore nel definire le due variabili c’è anche un loop infinito (while(1)) all’interno della funzione loop(), questo certamente non è corretto, anche leggere il valore della variabile time fuori dall’interrupt non garantisce di leggere tutti i dati, anzi ne elimina parecchi!

Secondo me se dici che “quasi” fa ciò che deve, si potrebbe provare con qualche modifica:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define MAX_SAMPLES 5

//LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 29 chars and 4 line display
unsigned long time = 0;
unsigned long time_last = 0;
unsigned long rpm_array[MAX_SAMPLES];
int i = 0;

void setup() {
  //Digital Pin 2 Set As An Interrupt
  attachInterrupt(0, fan_interrupt, FALLING);

  // set up the LCD's number of columns and rows:
  lcd.init();                      // initialize the lcd
  lcd.init();
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(1, 0);
  lcd.print("Rpm Spindle Motor");

  lcd.setCursor(1, 2);
  lcd.print("RPM");
  lcd.setCursor(1, 3);
  lcd.print("Power By Napalmax!");

  // azzero l'array
  for (int j=0; j<MAX_SAMPLES; j++)
    rpm_array[j] = 0;
}

//Main Loop To Calculate RPM and Update LCD Display
void loop() { 
  //Update The RPM
  unsigned long rpm = 0;
  // sommo tutti i valori nell'array
  for (int j=0; j<MAX_SAMPLES; j++)
    rpm += rpm_array[j];
  // calcolo la media 
  rpm /= MAX_SAMPLES;
  
  //Slow Down The LCD Display Updates
  delay(400);
  //Clear The Bottom Row
  lcd.setCursor(7, 2);
  lcd.print("                ");

  //Update The Rpm Count
  lcd.setCursor(5, 2);
  lcd.print(rpm);

  ////lcd.setCursor(4, 1);
  ////lcd.print(time);
}

//Capture The IR Break-Beam Interrupt
void fan_interrupt()
{
  // calcolo il tempo passato sall'ultimo interrupt
  time = (micros() - time_last);
  time_last = micros();
  // salvo il dato per la media 
  rpm_array[i] = 60 * (1000000 / (time * 7));
  // incremento l'indice
  i++;
  // controllo che l'indice non oltrepassi la fine dell'array
  if (i >= MAX_SAMPLES)
    i = 0;
}

Ho preso per buoni i conti fatti (60*un milione ecc.) anche se mi sembra strano quel 7, dato che conti il tempo sul cambio di stato da HIGH a LOW del sensore ipotizzo che lo fai su una ruota dentata con 7 denti (o non ho capito nulla forse :-)), questo ti farà avere tempi molto molto brevi quando l’albero ruoterà ad alta velocità, forse il tempo sarà così breve che l’errore di lettura sarà maggiore del tempo stesso e questo ti darà valori estremamente errati, ti consiglio, se fattibile di leggere solo uno o due cambi di stato per ogni giro dell’albero.
C’è anche un altro problema che puoi valutare se risolvere o meno, ad albero fermo il tempo sarà letto in modo errato per le prime MAX_SAMPLES - 1 volte dopo che viene rimesso in rotazione perché non ci saranno stati cambi di stato per molto tempo, un altro problema, ma più teorico che pratico, è la possibilità di overflow della variabile dato che vengono sommati molti valori dello stesso tipo, ma questo è un caso molto raro.

Non ho provato lo sketch ma credo non ci siano errori di battitura

Grazie infinite per l'interessamento,

ora sono fuori casa, nonostante il divieto, per emergenza familiare (non corona fortunatamente)

ma appena rientro proverò le modifiche consigliate e vi aggiorno.

Grazie

Per prima cosa vi faccio gli auguri

Forse per avere risultati più corretti potresti aumentare la dimensione dell'array (basta che cambi il valore in
#define MAX_SAMPLES 5 con uno più alto, magari anche 15, 20 e
spostare i calcoli fuori dall' ISR quindi in void fan_interrupt() metti solo

  // calcolo il tempo passato sall'ultimo interrupt
  rpm_array[i] = (micros() - time_last);
  time_last = rpm_array[i];
  // incremento l'indice
  i++;
  // controllo che l'indice non oltrepassi la fine dell'array
  if (i >= MAX_SAMPLES)
    i = 0;

e poi in loop() dopo
** // calcolo la media**
** rpm /= MAX_SAMPLES;**

aggiungi

  rpm = 60 * (1000000 / (rpm * 7));

D

buongiorno a tutti,

grazie degli auguri e dell'interessamento, fortunatamente tutto si è risolto per il meglio.

ho avuto modo di applicare le modifiche ed i consigli da voi dati:

Purtroppo il risultato non cambia, sempre valori enormi (anche nell'ordine di milioni) e che nulla hanno a che fare con la rotazione del motore.

L'unica stringa che fa cambiare qualcosa è questa:

// salvo il dato per la media
rpm_array = 60 * (1000000 / (time * 4000));
aumentando il valore "time *" fino a 4000 si ottiene un valore relativamente corretto alla velocità di circa
2 giri/sec. Se aumenta la velocità il valore rimane più o meno fermo. Quando si ferma il motore rimane bloccato quel valore
Forse non avevo specificato che la lettura avviene 1 volta per giro.
Non ho bisogno di una lettura precisa "al giro" potrebbe essere sufficiente anche l'aggiornamento di 500 in 500 giri, tutti gli altri valori potrebbero tranquillamente essere scartati (non so se questo possa semplificare la situazione). Il n. giri del mandrino, (max 15000 rpm) mi serve per calcolare le velocità di taglio opportune.
a seguire link al video su ciò che succede ora.
https://drive.google.com/open?id=1N4v9ZJLmIPrYHugB1nwK0ZEZDUcjpKJ5
Grazie 1000

Ciao,
hai modificato il tipo delle variabili in unsigned long?
A questo punto credo che ci siano dei problemi di lettura, controlla molto bene che il sensore rilevi un solo impulso a giro, forse è un problema della videocamera ma ad inizio video sembra che il led lampeggi due volte quando passa lo scotch.
Quella formula è sbagliata se leggi solo un tic a giro, dato che misuri il tempo in microsecondi per passare da tempo a rpm bisogna fare:

rpm_array = 60000000ul / time;

Prova a far stampare anche time magari su seriale dato che mi sembra di capire che il tuo LCD non è proprio performante dato che richiede un aggiornamento ogni 400millisecondi massimo

Ma il problema credo sia più legato al sensore che non legge correttamente.

Ho appena provato lo sketch che allego e funziona molto bene, ho testato simulando il sensore con un arduino che genera l’onda quadra tramite la funzione tone(), ho testato da 100 a 15000 RPM e funziona molto stabilmente, per curiosità mi sono spinto fino a 25kHz (1666666RPM)
Non ho l’LCD la credo funzioni comunque

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define MAX_SAMPLES 30

LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 29 chars and 4 line display
unsigned long time_delta = 0;
unsigned long time_last = 0;
unsigned long rpm_array[MAX_SAMPLES];
int i = 0;

void setup() {
  //Digital Pin 2 Set As An Interrupt
  attachInterrupt(digitalPinToInterrupt(2), fan_interrupt, FALLING);
  Serial.begin(115200);

  // set up the LCD's number of columns and rows:
  lcd.init();                      // initialize the lcd
  lcd.init();
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(1, 0);
  lcd.print("Rpm Spindle Motor");

  lcd.setCursor(1, 2);
  lcd.print("RPM");
  lcd.setCursor(1, 3);
  lcd.print("Power By Napalmax!");

  // azzero l'array
  for (int j=0; j<MAX_SAMPLES; j++)
    rpm_array[j] = 0;
}

//Main Loop To Calculate RPM and Update LCD Display
void loop() { 
  //Update The RPM
  unsigned long rpm = 0;
  // sommo tutti i valori nell'array
  for (int j=0; j<MAX_SAMPLES; j++)
    rpm += rpm_array[j];
  // calcolo la media 
  rpm /= MAX_SAMPLES;
  rpm = 60000000ul/rpm;
  
  //Slow Down The LCD Display Updates
  delay(400);
  Serial.println(rpm);
  Serial.println(rpm_array[i]);
  Serial.println();
  
  //Clear The Bottom Row
  lcd.setCursor(7, 2);
  lcd.print("                ");

  //Update The Rpm Count
  lcd.setCursor(5, 2);
  lcd.print(rpm);

  //lcd.setCursor(4, 1);
  //lcd.print(time);

}

//Capture The IR Break-Beam Interrupt
void fan_interrupt()
{
  // calcolo il tempo passato sall'ultimo interrupt
  time_delta = (micros() - time_last);
  time_last = micros();
  // salvo il dato per la media 
  rpm_array[i] = time_delta;
  // incremento l'indice
  i++;
  // controllo che l'indice non oltrepassi la fine dell'array
  if (i >= MAX_SAMPLES)
    i = 0;
}

forse il problema era dovuto ad un metodo desueto di settare l’interrupt

Rieccomi!

Grazie per lo sketch fornito e devo dire che funziona bene.

Si rende perfettamente conto che i giri aumentano e diminuiscono ma.......

ne misura troppi.

Ho ridotto il più possibile la velocità del motore fino a poter contare i giri con cronometro.

E' una misura che lascia il tempo che trova ma rende l'idea che a fronte di circa 120 rpm "impulsi" ne

segna intorno 450/500: Anche il serial monitor dà gli stessi valori visualizzati dal display.

ho modificato il "delay" fino a 2-3 secondi, la lettura è ovviamente più lenta ma meno fluttuante. Rimane

comunque esagerata.

Ho collegato poi un microswitch dando manualmente impulsi per x tempo al posto del sensore e

comunque il valore misurato è errato.

A questo punto è chiaro che lo sketch è a posto ma qualche disturbo inficia la lettura. Ora sto

realizzando un filtro per il segnale in ingresso, proverò un altro tipo di sensore

(ricavato da vecchia stampante) con una ruota fonica con un solo foro che ho stampato in 3d. Come

ultima spiaggia ordinerò un optoisolatore.

Da bravo ignorante, non ho capito una cosa dello sketch:

l'interrupt misurato è HIGHT o LOW? perchè se misurasse HIGHT sarebbe giustificato l'errore di

misurazione

Ho notato inoltre una cosa: quando il segnale cambia di stato da HIGHT a LOW la tensione sul pin 2 da

5v passa a 2,4. dovrebbe andare a 0? E' sufficiente per Arduino questo cambio di stato?

Detto tutto questo, cosa ne pensate? le prove che ho fatto sono plausibili?

Ho scritto una baraonda di cose, ma spero di essere stato chiaro.

Grazie, saluti

Puoi decidere quando far attivare la procedura di interrupt settando correttamente la chiamata, attualmente viene gestito il fronte di discesa(FALLING)
LOW per attivare l'interrupt ogni volta che il pin è basso,
CHANGE per attivare l'interrupt ogni volta che il pin cambia valore
RISING per attivare l'interrupt quando il pin passa da basso ad alto
FALLING per attivare l'interrupt quando il pin passa da alto a basso
https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

A memoria mi sembra di ricordare che il micro controllore consideri LOW una tensione minore di 1.5V e HIGH superiore a 3V quindi saresti proprio nella zona di incertezza, controlla bene i collegamenti e prova anche a regolare tramite il trimmer sulla schedina del sensore la sensibilità, al limite potresti anche provare ad aggiungere una resistenza di pull DOWN tra il pin e massa (credo che un valore tra 3.3KOhm e 10KOhm possa andare bene) così da scongiurare il più possibile la lettura nella zona di incertezza (la prova che hai fatto con un interruttore non è attendibile per il fatto che collegando direttamente l'interruttore i rimbalzi della meccanica interna non producono un segnale pulito e quindi l'attivazione dell'interrupt per ogni pressione risulta multipla ed inficia la lettura).
Secondo me con la resistenza di pull down dovresti risolvere sempre che la schedina non sia difettosa (hai lo schema elettrico?)

EDIT: ho riguardato il video e non riesco a capire bene come hai alimentato il tutto, l'arduino ha la massa in comune con il sensore? non si vedono bene i collegamenti ma mi pare di no... se è così il problema è quello!

Eureka!!!!!

Salve a tutti,

Problema risolto!!!

Ho ricablato tutto in maniera definitiva (tutto saldato) ed ho costruito una shield con un filtro

sulla linea del pin2 ora finalmente funziona tutto, e la lettura è coerente. ho sostituito

anche il segno di lettura sul motore con una linea ben marcata senza sbavature. Un po' qua ed un po' là

si creavano disturbi. ecco una foto:

A disposizione per condividere lo chema elettrico del filtro

A questo punto ancora una piccola domanda: E' possibile fare in modo che a motore fermi il display

indichi 0? pensavo ad un ciclo "if" - "else". Sarebbe solo la ciliegina sulla torta (non indispensabile)

Ad ogno modo non so come ringraziarVi.

Non so come ma mi farebbe piacere offrire una birra (magari non proprio a tutti...)

Massimo

Mi fa piacere che hai risolto, per visualizzare 0 a motore fermo basterà aggiungere una variabile globale da settare ad ogni interrupt (setti ad esempio a True una variabile booleana in fan_interrupt()) e resettare ad ogni ciclo del loop (in loop() setti la stessa variabile a False) se dopo un certo tempo la variabile è ancora resettata (vale False) significa che il sensore non ha letto nulla quindi il motore è fermo, così com'è scritto lo sketch non serve altro che aggiungere appena dopo rpm = 60000000ul/rpm;:

  if (interrupt_occurred) {
    interrupt_occurred = False;
  else {
    rpm = 0;
  }

in fan_interrupt() basta settare la variabile

  interrupt_occurred = True;

e definire la variabile interrupt_occurred globale, booleana e settarla a False così all'accensione verrà azzerata anche rpm
Non serve altro perché nel loop c'è già un' attesa di 400 millisecondi (che equivalgono a 150rpm, se vuoi considerare motore fermo ad una velocità inferiore potresti aumentare l'attesa, magari a 1000 che equivale ad un giro al secondo, 60rpm)

dinodf:
... definire la variabile interrupt_occurred globale, booleana ...

... aggiungerei solo che, oltre ciò, essendo usata in una ISR, ci vuole anche la parolina magica "volatile": :slight_smile:

volatile bool interrupt_occurred = false;

Guglielmo

P.S.: TUTTE le variabili globali, modificate in una ISR, dovrebbero essere dichiarate anche "volatile"