Lettura rpm con sensore cny70

Ho un sensore ottico cny70. Dovrei usarlo per leggere gli rpm di una ruota di reazione. E' la prima volta che uso un sensore ottico con arduino quindi non saprei da dove iniziare. Esiste qualche link utile o qualche script?

Ciao. Quello è un sensore di distanza ed è difficile vedere come usarlo per misurare i giri, penso che questo altro tipo di sensore sarebbe più adatto: LINK
Saluti.

No @gonpezzi, il CNY70 ha un range massimo di 0.5mm, ma concordo sul fatto che non è la scelta migliore per questo utilizzo.

@manny_99 in pratica è un diodo IR ed un fototransistor nello stesso package, quindi devi usare qualche "resistenza" a contorno (una per la corrente del fotodiodo, ed una per il fototransistor) tipo cosi:

image

1 Like

grazie mille ad entrambi. Purtroppo il sensore mi è stato dato dall'università e sono obbligato ad usarlo. La parte di circuiteria dovrei averla capita. Per quanto riguarda lo script invece, cercando sul web ho trovato qualcosa di utile. Lo script è il seguente:

const int CNY = A0;
int color = 0;

void setup() {
  pinMode(CNY, INPUT);
  Serial.begin(9400);

}

void loop() {
  color = analogRead(CNY);
  Serial.println(color);
  delay(500);

}

da quel che ho capito questo codice è in grado di farmi vedere se il sensore sta passando sopra la parte più "scura" e quando sulla parte più "chiara" per intenderci. Da questo come faccio a trasformare questa informazione in un contagiri in rpm?

Se usi un circuito tipo quello in figura, non serve leggere il valore in analogico perché si tratta di un segnale digitale.

Per quanto riguarda il codice, non parliamo di uno script, ma di firmware perché il software viene compilato e non interpretato (in ambito Arduino, spesso leggerai e sentirai parlare di sketch - schizzo).

Questioni sintattiche a parte, il modo più semplice (e affidabile) per applicazioni di questo tipo è usare uno degli external interrupt messi a disposizione dal microcontrollore della tua scheda (a proposito, di quale scheda si tratta?)
Mi pare di intuire che sei un po' digiuno di programmazione e di come funzionano i microntrollori, o sbaglio?

Ci sono sostanzialmente due strade:

  1. Conti quanti impulsi al secondo ti arrivano dal sensore e fai la conversione;

  2. Leggi il tempo che trascorre tra un fronte di salita dell'impulso e il successivo e calcoli la frequenza.

La prima strada è probabilmente quella più semplice da implementare:

  • Il segnale proveniente dal sensore solleva un interrupt;
  • Nella funzione associata all'interrupt (ISR - Interrupt Service Routine) incrementi un contatore;
  • Una volta al secondo (o più volte, facendo le opportune proporzioni) leggi il valore del contatore e fai la conversione da Pulse Per Seconds in RPM, che dipende essenzialmente da quanti impulsi/360° da il tuo sistema (1 o più per aumentare la precisione) ;
  • Fatte le conversioni, resetti il contatore e ricominci tutto da capo.

sto utilizzando una scheda arduino uno ma poi dovrò passare alla due. Si come dici tu sono molto all'inizio, sono stato un po catapultato in questo mondo senza aver fatto nessuna base prima. I passaggi che mi hai segnalato a grandi linee li ho capiti. Il mio problema è come trasformarli in codice ora. Non avendo fatto base del linguaggio usato da arduino faccio molta fatica nella traduzione in codice.

Il linguaggio è C/C++ con l'aggiunta di alcune funzioni proprie dell'ambiente Arduino/Wiring.
Conoscerlo un minimo è un passaggio imprescindibile se vuoi scrivere del software per una board Arduino.
Non esiste il traduttore automatico :sweat_smile:

L'alternativa è trovare qualcuno che lo scriva al posto tuo (e questo è il posto sbagliato perché contrario alle regole del forum).

Il codice che riporti potrebbe essere riscritto così:

#define CNY_IN D2; // Definisco CNY_IN (in maiuscolo perché è una costante) come (ingresso) D2.
bool stato=LOW; // Dichiaro la variabile booleana stato.

void setup()
{
  pinMode (CNY_IN, INPUT_PULLUP); // Imposto l'ingresso cny_in (D2) come ingresso con resistenza di pullup attiva (quindi non serve quella esterna).
  Serial.begin (9600); // Attivo la porta seriale per comunicare con il computer.
}

void loop()
{
  stato=digitalRead (CNY_IN); // Ogni mezzo secondo scrive lo stato dell'ingresso.
  if (stato==HIGH) {Serial.println("Scuro");}
  else {Serial.println("Chiaro");}
  delay(500);
}

Ora devi sapere approssimativamente a che velocità gira la ruota: se fa migliaia di giri ogni secondo, puoi contare in un secondo quanti giri fa e poi moltiplicare per 60; se, invece, gira molto lentamente, puoi cronometrare con millis() quanti millisecondi (o con microseconds() quanti microsecondi) impiega per fare un giro e, ogni secondo, dividere 60000 o 60000000 per tale valore.

Comunque, per ottenere buoni risultati devi usare gli interrupt e una scheda Arduino Uno compatibile dotata di un vero quarzo (le schede originali usano un risuonatore ceramico sull'ATmega328p, con ampia tolleranza sulla frequenza generata; il quarzo presente è collegato al 16U2, interfaccia USB).

https://www.google.com/search?q=arduino+frequency+counter+interrupt

Per esempio, questo usa gli interrupt ed è molto lineare, semplice da capire:

La dichiarazione
int freqCounter = 0;
deve, però, essere corretta in:
volatile int freqCounter = 0;
perché la variabile viene aggiornata all'interno della routine chiamata dall'interrupt.
Con una variabile int si può arrivare a 65535Hz (quasi 4 milioni RPM). Con una variabile unsigned long si può arrivare al massimo misurabile dall'ATmega328P, che è circa 7MHz.

Il programma originale, che prevede la visualizzazione su LCD, potrebbe essere così aggiustato:

// include the library code:
#include <LiquidCrystal.h>

// Dichiarazione delle connessioni dell'LCD:
LiquidCrystal lcd (6, 7, 10, 11, 12, 13); // RS, EN, D4, D5, D6, D7.  Nota: devi collegare un trimmer al pin del contrasto (Vo), altrimenti non si vede nulla!

volatile unsigned long freqCounter = 0;
long preMillis = 0;

void isr() // Routine chiamata dall'interrupt (Interrupt Service Routine).
{
  freqCounter++;
}

void scrivi_freq()
{
  lcd.setCursor(0, 1); // Posizione iniziale del cursore (colonna 0, riga 1 (inferiore)).
  lcd.print (freq); lcd.print(" Hz"); // Scrive la frequenza, uno spazio e "Hz".
}

void setup()
{
  lcd.begin(16, 2); // Inizializza il display
  lcd.print ("Frequenzimetro");
  scrivi_freq();
  attachInterrupt (0,isr,RISING);  // Attaching the interrupt
}


void loop()
{
  while ((millis()-preMillis)<1000); // Aspetta qui per 1 secondo.
  detachInterrupt(0);           //detaches the interrupt
  scrivi_freq();
  freq = 0;
  preMillis = millis();
  attachInterrupt (0,isr,RISING);  // Attaching the interrupt again
}

molto gentile. Andando avanti con la scrittura di un codice adatto ho scritto;

const int sensorPin = A0;

bool fallen = false;
int lowLevel = 6;
int highLevel = 7;
long lastTime = 0;
long thisTime = 0;

void setup() {
   Serial.begin(4800);
   Serial.print("Starting tachometer...");
  pinMode(sensorPin, INPUT);
  lastTime = micros();

}

void loop() {
  int thisReading = analogRead(sensorPin);
     
     //Serial.print("Reading: ");
     //Serial.println(thisReading);
  if (thisReading > highLevel)
  {
    if (fallen == true)
    {
      thisTime = micros();
      long RPM = 60000000 / (thisTime - lastTime);
      Serial.print("  RPM:  ");
      Serial.println(RPM);
      lastTime = thisTime;
      fallen = false;
    }
  }
  if (thisReading < lowLevel)
  {
    if (fallen == false)
    {
      fallen = true;
    }
  }
}

in teoria questo codice dovrebbe darmi la lettura degli rpm. Quando faccio ruotare la ruota della quale devo calcolare gli rpm a mano sembra che il programma funzioni. Quando invece attacco il motore elettrico sotto la ruota per movimentarla inizia a darmi delle letture sbagliatissime, per esempio mi legge 2000 rpm e un istante dopo 57300 rpm, cosa assolutamente assurda. Da cosa può dipendere ciò?

per intenderci:

questo è un esempio di output che ho

analogRead è lento. Usa digitalRead, mandando livelli adeguati all'ingresso.
Inoltre non puoi mettere i Serial.print all'interno del ciclo di lettura, perché anche quelli sono lenti.
Devi fare la lettura senza nulla che la rallenti, poi con comodo visualizzi il risultato. Meglio ancora sarebbe usare gli interrupt.

@manny_99 tu però non segui i buoni consigli che ti vengono dati :wink:
ovvero uso dell'interrupt e NON utilizzo di analogRead() per leggere un segnale digitale...

// Per usare gli interrupt esterni con Arduino Uno
// hai a disposizione solo i pin 2 e 3
const int sensorPin = 2;

volatile unsigned long pulseCounter;

// ISR function 
void pulseCounting() {
  pulseCounter++;
}

void setup() {
  Serial.begin(4800);   // Perché un baud rate cosi basso?
  Serial.print("Starting tachometer...");
  pinMode(sensorPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(sensorPin), pulseCounting, FALLING);
}

void loop() {
  // Una volta al secondo calcolo gli RPM e stampo a monitor seriale il valore
  static uint32_t getTime;
  if(millis() - getTime > 999) {
    getTime = millis();
    long RPM = pulseCounter * 60;
    pulseCounter = 0;
    Serial.print("  RPM:  ");
    Serial.println(RPM);
  }
}

grazie mille ad entrambi. Il punto non è che non voglia ascoltare i vostri cosnigli che so essere preziosi. Il punto è che sono seguito da un tutor che a quanto pare ne sa meno di me. Mi ha detto che avrei potuto usare un analogRead e per quanto riguarda gl interrupt lo scopro solo ora che esistono anche se non so minimamente come si utilizzino. Ripeto, sono stato messo a programmare con C++ senza una minima spiegazione o introduzione per fare un programma che so essere al momento fuori dalla mia portata e che devo aggiustare nel più breve tempo possibile. Comunque sia vi ringrazio e proverò in questi giorni a mettere in atto ciò che mi avete consigliato

grazie @cotestatnt. Il codice sembra funzionare però l'unico problema che riscontro è che funziona solo quando punto una luce verso il sensore. E' normale questo? Cioè se alterno una zona chiara a una scura senza luce puntata davanti mi continua a dare 0 come lettura di rpm, mentre se faccio la stessa cosa ma con una fonte di luce puntata al sensore il programma sembra darmi una lettura attendibile.

Come ti è stato suggerito all'inizio, questo sensore non è il più adatto allo scopo.

Probabilmente il supporto che stai usando come ruota fonica assorbe troppa luce IR anche sul bianco e quindi il sensore non fa quel che deve. Hai modo di verificare l'uscita con un oscilloscopio?

Anche la distanza è importante: non deve essere troppo appiccicato perché altrimenti mancherebbero gli spazi "ottici" per raccogliere la luce IR di rimbalzo e non deve essere troppo distante perché il range è limitato.

Potresti provare ad aumentare la corrente nel diodo IR.
Se stai usando il circuito proposto nell'altro post, sei intorno ai 17mA con una R da 220ohm, ma il diodo IR secondo il datasheet, ha una If massima assoluta di 50mA. Prova sui 30mA (resistenza da 120 ohm) e vedi se migliora.

ok grazie mille. Io erroneamente agivo sulla resistenza da 10 kohm, non ottenendo infatti nessuna differenza. Riconosco che il sensore non è dei migliori ma purtroppo questo mi hanno offerto :sweat_smile:. Purtroppo non ho neanche un oscilloscopio per verificare quindi l'unica cosa che mi rimane probabilmente è come dici tu la sostituzione della resistenza. Grazie ancora.

il circuito l'ho modificato e ora funziona tutto correttamente, grazie mille. Ora se volessi avere una misura più precisa degli rpm come dovrei agire? Nel programma che avevo progettato precedentemente avevo usato:

hisTime = micros();
      long RPM = 60000000 / (thisTime - lastTime);
      Serial.print("  RPM:  ");
      Serial.println(RPM);
      lastTime = thisTime;
      fallen = false;

questa parte di codice e mi dava una lettura degli rpm all'unità. Ecco come faccio a traslare questa precisione nel programma che mi hai consigliato l'ultima volta? (ovvero quello che faceva uso degli interrupt)

per intenderci io ho scritto:

void loop() {
  static uint32_t getTime;
  if(millis() - getTime > 999) {
    Serial.print(" Pulse counter: ");
    Serial.println(pulseCounter);
    getTime = millis();
    thisTime = micros();
    long RPM = 60000000 / (thisTime - lastTime);
    pulseCounter = 0;
    
    Serial.print(" RPM: ");
    Serial.println(RPM);
    lastTime = thisTime;

il problema è che come lettura massima di rpm mi sta dando 60 anche se la la velocità di rotazione si aggira intorno ai 6000 rpm.