Tacómetro con Arduino

Cómo andan? Ando necesitando diseñar un tacómetro con Arduino el cual pueda medir velocidad menores a 100rpm con una resolución de 1rpm. El tema es que estuve haciendo uno pero la resolución es malísima y no sé por qué. Paso al código:

Código:
int ledPin = 13; // IR LED conectado al pin 13
volatile byte rpmcount;
unsigned int rpm;
unsigned long timeold;

void rpm_fun()
{
rpmcount++;
}

void setup()
{
//Interrupt 0 esta en el pin 2 del arduino, es donde esta conectado el detector de IR
//La interrupcion se activa por FALLING, cuando va de Alto a bajo
attachInterrupt(0, rpm_fun, FALLING);
pinMode(ledPin, OUTPUT);//se activa el led IR
digitalWrite(ledPin, HIGH);
rpmcount = 0;
rpm = 0;
timeold = 0;
}

void loop()
{
delay(1000);//se actualiza el RPM cada segundo
detachInterrupt(0);//no entrar en las interrupciones durante el calculo de RPM
rpm = 30*1000/(millis() – timeold)*rpmcount;
timeold = millis();
rpmcount = 0;
attachInterrupt(0, rpm_fun, FALLING);//Reinicia la funcion de Interrupcion
}

Estoy simulando el proyecto con un motor paso a paso en cuyo eje hay instalado un encoder de 10 ranuras que son detectadas con un sensor de ranura y lo qué hago es, mediante una interrupción, contar la cantidad de ranuras detectas en 1 segundo... aplico fórmula y obtengo el resultado en un display. La cosa es que el error es de como unas 10rpm por cada ranura y no lo puedo corregir.

¿El método para baja velocidad está bien aplicado o debería implementar otro?

Muchas gracias!!!!

p.d.: adjunto una foto del motor con el encoder
Hola, puedo ver en tu código que utilizas la función delay(1000). Eso quiere decir que el microcontrolador permanecerá bloqueado durante un segundo cada ciclo, sin hacer "nada", es decir que el microcontrolador permanece ocioso la mayor parte del tiempo, en su lugar deberías utilizar la función millis, esa es mi recomendación.
 
Si usa tacómetro seguro que hace falta Millis.

¿Puedes mostrar códigos?


Saludos
 
Última edición:
Cordial saludo, estoy tratando de realizar la lectura "RPM" cambiando el número de ranuras del disco que lee el control óptico (creería que si mas ranuras mejor resolución).
Cuando lo hago con 1 ranura (1pulso) funciona perfecto, pero al hacerlo con 2 ranuras (2 pulsos ) la lectura varía demasiado, el disco ranurado tiene originalmente 8 ranuras (8 pulsos) pero así mas se enloquece la lectura. ¿Qué podría estar pasando, influye la velocidad del puerto serie para leer rpm ? La coloco a 9600 y a 115200 y trabaja igual, también cambia el retardo y tampoco cambia. Gracias de antemano por las sugerencias.

El código de este ejemplo es original de Autor: Nick Gammon

Código:
// Frequency timer
    // Author: Nick Gammon
    // Date: 10th February 2012
   
   

   
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    // variables para lectura RPM
    volatile boolean first;
    volatile boolean triggered;
    volatile unsigned long overflowCount;
    volatile unsigned long startTime;
    volatile unsigned long finishTime;
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
   


  //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

  // here on rising edge
  void isr ()
  {
  unsigned int counter = TCNT1;  // quickly save it // guardalo rapidamente

  // wait until we noticed last one
  if (triggered) // si es true sale de la funcion - si es false sigue
    return;

  if (first)// si es true toma la lectura actual y lo pone a falso para que no vuelva a entrar y sale de la funcion  / si es false sigue
  {
    startTime = (overflowCount << 16) + counter; //guarda el primer valor
    first = false;
    return;
  }
  finishTime = (overflowCount << 16) + counter; // guarda el ultimo valor
  triggered = true; // lo pone a true para garantizar que (triguered) no vuelva a entrar a la interrupcion y que entre al loop
  detachInterrupt(0);   //detiene las interrupciones
}
// end of isr

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
  overflowCount++;           //cada que TIMER1 llegue a 65535 overflowCount++ aumenta 1
}  // end of TIMER1_OVF_vect

void prepareForInterrupts ()
{
  // get ready for next time  -  prepárate para la próxima vez
  EIFR = _BV (INTF0);  // clear flag for interrupt 0
  first = true;                                     // habilita la primera lectura
  triggered = false;  // re-arm for next time      //permite que entre a la interrupcion
  attachInterrupt(0, isr,RISING);               // habilita la interrupcion - lee sensor hall de 1 a 0 (pull up)
}
// end of prepareForInterrupts


void setup ()
{
  Serial.begin(115200);
  //Serial.println("Frequency Counter");
  //pinMode(MOSFET, OUTPUT); // Asigna el pin del led como salida
 //pinMode(led, OUTPUT);
  //pinMode(encendido, INPUT);

  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;  // timer1 sin preescaler 0 - 65536
  // Timer 1 - interrupt on overflow
  TIMSK1 = _BV (TOIE1);   // enable Timer1 Interrupt // habilita interrupcion
  // zero it
  TCNT1 = 0;
  // start Timer 1
  TCCR1B =  _BV (CS10);  //  coloca a 1 CS10
  // set up for interrupts
  prepareForInterrupts ();
}

      // end of setup

      void loop ()
      {
      //Serial.println ("in loop ");

    //xxxxxx DISPARO DEL SENSOR HALL xxxxx
    if (!triggered) // no pasa hasta que no sea true, y sigue preguntando hasta que haga la segunda lectura
    return;
    unsigned long elapsedTime = finishTime - startTime;
    float freq = 4.0 / ((float (elapsedTime) * 62.5e-9));  // each tick is 62.5 nS
 
    // Serial.print ("Took: ");     //tics
    // Serial.print (elapsedTime);  // tiempor transcurrido: finishTime - startTime;
    // Serial.print (" counts. ");
   //  Serial.print ("Frequency: ");
   Serial.println (freq /2* 60, 0);
    Serial.println (freq* 60);
   // Serial.println (freq);
    // Serial.println (" Hz. ");
    // so we can read it  //para que podamos leerlo
     delay (500);

  prepareForInterrupts ();  


}
// end of loop
// **************
 
Última edición por un moderador:
cordial saludo, estoy tratando de realizar la lectura "RPM" cambiando el numero de ranuras del disco que lee el control optico (creeria que si mas ranuras mejor resolucion).
cuando lo hago con 1 ranura(1pulso) funciona perfecto pero al hacerlo con 2 ranuras (2 pulsos ) la lectura varía demasiado, el disco ranurado tiene originalmente 8 ranuras (8pulsos) pero asi mas se enloquece la lectura.
¿ Circuito del sistema de detección ?

Los "Delay´s" NO son de mi simpatía
 
cuando lo hago con 1 ranura(1pulso) funciona perfecto pero al hacerlo con 2 ranuras (2 pulsos ) la lectura varía demasiado, el disco ranurado tiene originalmente 8 ranuras (8pulsos) pero asi mas se enloquece la lectura.
¿A qué te refieres con hacerlo con 2 ranuras?

Por lo que pude entender en el código, se inicia la captura con el timer e interrupciones, hasta ahí todo bien, una interrupción se detona por el Pin y otra por el Timer, esta solo para contar los desbordes, pero la captura la toma el pin. El timer se dispara cuando la señal pasa a L (por ejemplo, el sensor abierto en un encoder óptico) y comienza a contar, si se desborda cuenta el desborde, el timer no se detiene, cuando se dispara la interrupción por flanco ascendente se detiene el contador y las interrupciones, después se realizan los cálculos, se envía los datos y se pasa el delay, luego se habilita la interrupción nuevamente y el ciclo comienza.

No veo en ninguna parte del código razón para necesitar 2 ranuras, básicamente de forma periódica haces un muestreo de una sola ranura.
 
Última edición por un moderador:
Gracias por las respuestas,
estoy utilizando un encoder óptico y lo paso por un "schmititt trigger" para eliminar falsas lecturas, mi pregunta es que originalmente el disco que pasa por el optoacoplador tiene 8 ranuras, tape 7 y trabaja bien este codigo con la lectura de una sola, destapé otra ranura que se encuentra lineal, es decir que al dar una vuelta completa el rotor debería contar 2 pulsos y ahí es donde la lectura se enloquece.
Por eso mi pregunta, no debería tener mas resolución la lectura al tener mas ranuras?
Y en el código no se que cambios hacer para que lea mas de una ranura.
La idea medir entre 500 / 5000 RPM.

sensor.jpg

 
Última edición por un moderador:
no deberia tener mas resolucion la lectura al tener mas ranuras? y en el codigo no se que cambios hacer para que lea mas de una ranura.
No, hay dos formas en la que se miden las revoluciones, la mala es en un intervalo de tiempo contar el número de pulsos. La buena es contar el periodo del pulso, al saber el ancho de la ranura lo que mides es la velocidad angular, al obtener este entonces obtienes con mucha más precisión las revoluciones del motor, aunque el disco solo tenga una ranura, la precisión dependerá más de la velocidad de captura del temporizador y la resolución de este y el ejemplo está usando el reloj principal a 16MHz para eso así que para el Arduino Uno es el límite que podrá leer, ahora que si cambias a otro con contador de 64bits y digamos 80MHz pues podrías subir la resolución.
 
Perfecto "Nuyel", iniciando el tema haces referencia a contar ranuras y paredes pero con el mismo ancho, pero, si las ranuras y las paredes tienen longitudes diferentes, que se haria?
"Nuyel"
Cual seria la formula? bueno, el ancho del puso suponiendo que entre abierto y cerrado tiene la misma longitud de arco, entonces deducimos que para 1RPM la duración del pulso seria 1s/(10ranuras+10paredes)= 50ms para 1RPM, ahora para X RPM seria cosa de dividir 50ms entre al ancho capturado, asi si se captura un pulso de 25ms entonces 50ms/25ms=2 RPM si se captura uno de 5ms seria 50/5=10 RPM, si se captura uno de 200ms 50/200=0,25 RPM, fácil :D, al menos en teoría


He cambiado el codigo y funciona muy bien pero como antes con un solo tick por vuelta.
que deberia modificar en el codigo para medir 2,4,8 ticks?
Código:
float tempo1;
float tempo2;
float rpm;
boolean flag;

void setup(){
 Serial.begin(115200) ;
 pinMode(2,INPUT);
 attachInterrupt(0,TICK,FALLING);
 tempo2 = 0;
}

void loop(){

 if(flag){
 tempo1 = tempo2;
 tempo2 = micros() ;
 rpm = 60000000.0/(tempo2 - tempo1);
// Serial.print("Speed = ");
  Serial.println(rpm);
  Serial.println(rpm/2);
 //Serial.println(" RPM");
 flag= false;
  }
 
}

void TICK(){
flag = true;
}
 
Última edición:
En el caso de una ranura arbitraría aplicarías el 360/ángulo de apertura hasta la posición del sensor
así digamos tu ranura tiene una apertura de 30º tendríamos 12, llamemos este valor como C y es nuestro factor de escalado para una revolución, es decir, el tiempo que tomaría en dar la vuelta completa considerando la ventana que se tiene para medirlo, sabiendo eso y el tiempo del sensor (T) podemos determinar la frecuencia como f=1/C*T

Si quieres precisión recomiendo mantener el ancho de pulso, el ejemplo anterior estaba bien, pero si quieres intentarlo con tu propio código puedes usar la función pulseIn(pin, value) esta lee un pulso (ya sea HIGH o LOW) y te retorna el ancho del mismo en microsegundos, el mínimo según es 10us pero sobra para el margen que quieres. Tu no mides los "tick" porque esos no provienen del sensor sino del reloj interno, no estas contando pulsos sino su longitud, si quieres puedes hacer más mediciones, pero realmente no se ocupa que sean tan recurrentes ya que por la misma inercia del motor la velocidad no variará considerablemente de una a otra ranura.
 
Gracias por todas las respuestas.
Logré ponerlo a trabajar con precisión con las 8 aspas del encoder y así no tener que tapar los orificios sobrantes con pega.
Además le coloqué un promedio de 5 lecturas, lo que me estabilizó más los datos.
Adjunto el código.
C++:
    float tiempo1;
    float tiempo2;
    float rpm;
    volatile unsigned pulso=0; // numero de aspas(ranuras=8)
    int conta1=0;// aux para promedio
    float conta2=0; // aux para promedio

    void setup(){
     Serial.begin(115200) ;
     pinMode(2,INPUT);
     attachInterrupt(0,TICK,RISING);
     tiempo2 = 0;
    }

    void loop()
    {
       if(pulso==8) //8 aspas
       {
       detachInterrupt(0);
       pulso=0;
       tiempo1 = tiempo2;
       tiempo2 = micros() ;
       rpm = 60000000.0/(tiempo2 - tiempo1);//60 segundos por 1 millon
       // promedio*****************
       conta1++;
       conta2=rpm+conta2;
          if (conta1==5)
          { 
          Serial.println(conta2/5);
          //Serial.println(conta2/5+0.5);// ajuste para coincidir con mi tacometro digital
          conta1=0;
          conta2=0;
          }
       // fin  promedio*****************
       attachInterrupt(0,TICK,RISING);
        }
    }

    void TICK()
    {
        pulso++;
    }
 
Última edición por un moderador:
Cordial saludo. He realizado una modificación en el programa.
L
e implementé una lectura analógica con un potenciómetro, pero al momento de tratar de imprimirlo por el puerto serial, solo imprime uno de los dos.

C++:
Serial.println( lectura);

Serial.println(conta2/5);

Si comento uno imprime el otro, pero no los dos.

¿Qué podría estar pasando?
C++:
    float tiempo1;
    float tiempo2;
    float rpm;
    volatile unsigned pulso=0;
    int conta1=0;// aux para promedio
    volatile float conta2=0; // aux para promedio

    int led=13;

    int MOSFET = 11;  // pin salida pwm
    int Sensor = A0; //potenciometro
    volatile int lectura=0;  // guarda el valor del potenciometro;
 
    void setup(){
     Serial.begin(57600) ;
     pinMode(13,led);
     pinMode(2,INPUT);
     pinMode(MOSFET, OUTPUT);
 
     attachInterrupt(0,TICK,RISING);
     tiempo2 = 0;
    }
 
    void loop()
    {

       lectura = analogRead(Sensor);
       lectura = map(lectura, 0, 1023, 0, 100);
       Serial.println( lectura);
 
       if(pulso==8) //8 aspas
       {
       detachInterrupt(0);
       pulso=0;
       tiempo1 = tiempo2;
       tiempo2 = micros() ;
       rpm = 60000000.0/(tiempo2 - tiempo1);//60 segundos por 1 millon
 
       // promedio*****************
       conta1++;
       conta2=rpm+conta2;

          if (conta1==5)
          {
          Serial.println(conta2/5);
          conta1=0;
          conta2=0;
          }
       // fin  promedio*****************
      attachInterrupt(0,TICK,RISING);
      }

   
    }
 
    void TICK()
    {
    pulso++;
    }
 
Última edición por un moderador:
Atrás
Arriba