Frecuencimetro de onda cuadrada muy simple

Buenas noches compañeros,

A continuación les voy a compartir un pequeño código de un frecuencimetro que es capaz de medir la frecuencia de señales digitales.

Este pequeño código nace de la necesidad de medir la frecuencia de salida de un oscilador construido con 555 o cualquier señal digital de 0 a 5 voltios. Hay muchos de nosotros que no disponemos de un osciloscopio o de un multimetro que pueda medir frecuencia y pues quiero compartirles este pequeño aporte.

Sin más acá el código:

float contador = 0;

void setup() {
  Serial.begin(9600);
  attachInterrupt(0, contar, CHANGE);
  Serial.println("Midiendo frecuencia....");
  
}

void loop() {
  
delay(1000);
Serial.print("Frecuencia: ");
Serial.print(contador/1000,2); 
Serial.println(" KHz"); 
contador = 0;
}

void contar(){
contador ++;  
}

Y acá les dejo una captura de pantalla...

Desconozco hasta que frecuencia seria confiable este medidor, fue probado a 3.4 Khz, pero se que hay limitantes de la medición de frecuencias porque el mismo arduino UNO tiene sus tiempos para ejecución de comandos internos el cual va a afectar la medición real metiendo así un error que se va haciendo mas grande conforme vaya aumentando la frecuencia.

Creo que podría mejorar este código para medir señales analógicas también.

Saludos cordiales y que les sea de provecho..

Hola, gracias por compartir el código.

Solo tengo un par de observaciones, la primera es que estas usando Delay y eso podría estar lecturas no muy precisas. La segundo es que estas midiendo con "CHANGE" en los interrupción por lo que la medida te debe estar dando el doble de lo que realmente es la frecuencia,creo que deberías medir con "FALLING" o "RISING" para poder tener una medida de un ciclo completo y no de medio ciclo como parece ser.

Cuando pruebe el código estaré seguro de lo que estoy diciendo, pero en teoría hay un error de precisión.

Saludos

Hola hermano, puede que tengas razón, voy a modificar. Con respecto al CHANGE pues estaría midiendo los cambios de estado y en un ciclo hay 2 cambios por lo que contara 2 veces. TIENES TODA LA RAZON.

En lo que respecta al delay(1000) lo hice así porque no encontré una manera mas simple de tomar las muestras a la variable contador. Si tu tienes una mejor idea te lo agradecería. Pienso que para compensar el retraso generado por los cálculos del programa debo bajarle ese delay a 997 milisegundos. No se, me gustaría que me ayudes a saber cuanto retraso se genera para ser mas certero en el delay.

Aca una imagen, ciertamente bajo la medicion de frecuencia a la mitad. Excelente visionario..! Gracias

Y el nuevo código quedaría así

float contador = 0;

void setup() {
  Serial.begin(9600);
  attachInterrupt(0, contar, FALLING);
  Serial.println("Midiendo frecuencia....");
  Serial.println("");
  
}

void loop() {
  
delay(997);
Serial.print("Frecuencia: ");
Serial.print(contador/1000,2); 
Serial.println(" KHz"); 
contador = 0;
}

void contar(){
contador ++;  
}

Tengo varias objeciones (dudas) respecto al código:

  • El microprocesador de los ATmega carece de FPU (procesador de coma flotante); por lo tanto, realizar operaciones con este tipo de dato, se toma sus ciclos de reloj.
  • También, solo puede procesar 8 bits por ciclo; entonces sugiero que esa variable sea del tipo más pequeño posible. Puede ser unsigned int (para frecuencias menores a 65 KHz), o unsigned long (para frecuencias menores a 4.2 GHz). Si piensas que más bien 65 KHz es demasiado, entonces usa unsigned int. Tomara menos tiempo procesarlo que un float.
  • Mientras ejecuta Serial.print("Frecuencia: ");, el conteo seguirá en pie? Digo, la ISR no dejará de ejecutarse aunque ya haya pasado el delay, o me equivoco?
  • No se supone que toda variable global cuyo valor sea modificado por una ISR, debe tener el prefijo volatile?
  • Creo que delayMicroseconds es más preciso que el mismo delay (sólo 4 microsegundos de error podrían haber)

carlosjq10:
Creo que podría mejorar este código para medir señales analógicas también.

De hecho, había una forma de hacer más rápido un analogRead. Por defecto, esta función se tarda aprox. 220 microsegundos en ejecutarse (dejando así, una frecuencia de muestreo de apenas 4 KHz en el mejor de los casos).
Acelerando las lecturas, se llega hasta entre 40 y 50 KHz (sin no hay nada más que ejecutar).
Sin embargo, dependiendo del código, la frecuencia de muestreo real podría caer hasta los 20 KHz.

carlosjq10:
No se, me gustaría que me ayudes a saber cuanto retraso se genera para ser mas certero en el delay.

Prueba esto:

float contador = 2000.0;
const byte cantidadMuestras = 50;
unsigned long tAnt = 0;
unsigned long tPos = 0;
unsigned long total = 0;


void setup() {
  unsigned long promedio = 0;
  Serial.begin(9600);
  for (byte i = 0; i < cantidadMuestras; i++) { 
    // micros tiene un márgen de error de 4 microsegundos; por lo tanto, vamos a repetir la prueba "cantidadMuestras" veces.
    tAnt = micros();
    // Aquí comienza el código a probar
    Serial.print("Frecuencia: ");
    Serial.print(contador / 1000, 2);
    Serial.println(" KHz");
    // En cada repetición, va a imprimir siempre lo mismo. Eso es normal en este programa de prueba.
    contador = 0;
    // Aquí acaba el código a probar
    tPos = micros();
    total = tPos - tAnt;
    Serial.println(total);
    Serial.println();
    promedio += total;
    contador = 2000.0;
  }
  promedio = promedio / cantidadMuestras;
  // Saca una media aritmética de la duración de cada prueba.
  // De esta forma logramos determinar, con más exactitud, la duración real de la ejecución del código.
  Serial.print("Tiempo que se tardo en ejecutar: ");
  Serial.print(promedio);
  Serial.println(" microsegundos.");
}

void loop() {
  // Nada se hace aquí

}

Excelente Lucario!!!

Y para agregar a lo que dice Lucario, tengo un inyector que oscila a 32Hz (lo mas bajo que puedo con bastante precisión) y el circuito esta registrando 30Hz-29Hz, es decir que tiene un error apreciable de -2Hz, supongo que a frecuencias mas altas sería mas impreciso, pero no tengo como medirlo con exactitud.

También modifiqué y probé el código usando millis() en lugar de delay pero no es apreciable ninguna mejora, tal vez a mayores frecuencias.

Por si te sirve aquí pongo el código


volatile unsigned int contador = 0;

unsigned long previousMillis = 0;
const long interval = 1000;

void setup() {
  Serial.begin(115200);
  attachInterrupt(0, contar, RISING);
  Serial.println("Midiendo frecuencia....");

}

void loop() {

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    Serial.print("Frecuencia: ");
    Serial.print(contador);
    Serial.println(" - Hz");
    contador = 0;
  }

}

void contar() {
  contador ++;
}

Saludos

Lucario eres un taco.....

cargue el código en mi arduino UNO y mira esto.

Esto quiere decir que la ejecucion del código que esta dentro del for es decir los 3 println se tardan en ejecutarse 22345 microsegundos?

Aumente la cantidad de muestras a 100 para tener mas precisión..

Tengo una duda, si yo declaro la variable contador como unsigned int ya perderia la posibilidad de expresar la frecuencia con decimales? por ejemplo 4.30 khz pasaria a mostrarse como 4 khz?

Bueno gente me parece que a uds no les enseñaron a medir correctamente señales.
Y no porque me las sepa todas sino porque hay maneras de medir determinas frecuencias y otras para hacerlo con otro grupo de frecuencias.
Cuando se estudia la medicion de frecuencias y los errores involucrados existe un punto de medición para el cual conviene medir como lo estan haciendo y otro donde conviene medir período.
Ese punto no recuerdo hoy si estaba en 1 o 10khz. Luego lo busco y lo digo con justeza.

Por otro lado, arduino tiene un INPUT CAPTURE pin. Cuando usan un TIMER pueden definirlo de muchos modos. Uno de ellos es para capaturar señales ya sea mediro periodos, o anchos de pulsos o medir frecuencias.
La presición de ese sistema esta dado por el reloj del arduino o sea 65nseg.

Algo mas para decir? Creo que no.
El código es del Administrador Global del foro en inglés Nick Gammon asi que disfrutenlo
Another frequency counter

Muy bien surbyte, ahora acabo de hacer 2 pruebas.

1.- Usando el sketch que venia usando
2.- Usando el sketch por Nick Gammon.

Los resultados son los mismos :o :o :o

Mira esto.

Prueba #1

Codigo usado: Nick Gammon

// Timer and Counter example
// Author: Nick Gammon
// Date: 17th January 2012

// Input: Pin D5

// these are checked for in the main program
volatile unsigned long timerCounts;
volatile boolean counterReady;

// internal to counting routine
unsigned long overflowCount;
unsigned int timerTicks;
unsigned int timerPeriod;

void startCounting (unsigned int ms) 
  {
  counterReady = false;         // time not up yet
  timerPeriod = ms;             // how many 1 ms counts to do
  timerTicks = 0;               // reset interrupt counter
  overflowCount = 0;            // no overflows yet

  // reset Timer 1 and Timer 2
  TCCR1A = 0;             
  TCCR1B = 0;              
  TCCR2A = 0;
  TCCR2B = 0;

  // Timer 1 - counts events on pin D5
  TIMSK1 = bit (TOIE1);   // interrupt on Timer 1 overflow

  // Timer 2 - gives us our 1 ms counting interval
  // 16 MHz clock (62.5 ns per tick) - prescaled by 128
  //  counter increments every 8 µs. 
  // So we count 125 of them, giving exactly 1000 µs (1 ms)
  TCCR2A = bit (WGM21) ;   // CTC mode
  OCR2A  = 124;            // count up to 125  (zero relative!!!!)

  // Timer 2 - interrupt on match (ie. every 1 ms)
  TIMSK2 = bit (OCIE2A);   // enable Timer2 Interrupt

  TCNT1 = 0;      // Both counters to zero
  TCNT2 = 0;     

  // Reset prescalers
  GTCCR = bit (PSRASY);        // reset prescaler now
  // start Timer 2
  TCCR2B =  bit (CS20) | bit (CS22) ;  // prescaler of 128
  // start Timer 1
  // External clock source on T1 pin (D5). Clock on rising edge.
  TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12);
  }  // end of startCounting

ISR (TIMER1_OVF_vect)
  {
  ++overflowCount;               // count number of Counter1 overflows  
  }  // end of TIMER1_OVF_vect


//******************************************************************
//  Timer2 Interrupt Service is invoked by hardware Timer 2 every 1 ms = 1000 Hz
//  16Mhz / 128 / 125 = 1000 Hz

ISR (TIMER2_COMPA_vect) 
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = TCNT1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // see if we have reached timing period
  if (++timerTicks < timerPeriod) 
    return;  // not yet

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 256)
    overflowCopy++;

  // end of gate time, measurement ready

  TCCR1A = 0;    // stop timer 1
  TCCR1B = 0;    

  TCCR2A = 0;    // stop timer 2
  TCCR2B = 0;    

  TIMSK1 = 0;    // disable Timer1 Interrupt
  TIMSK2 = 0;    // disable Timer2 Interrupt
    
  // calculate total count
  timerCounts = (overflowCopy << 16) + timer1CounterValue;  // each overflow is 65536 more
  counterReady = true;              // set global flag for end count period
  }  // end of TIMER2_COMPA_vect

void setup () 
  {
  Serial.begin(115200);       
  Serial.println("Frequency Counter");
  } // end of setup

void loop () 
  {
  // stop Timer 0 interrupts from throwing the count out
  byte oldTCCR0A = TCCR0A;
  byte oldTCCR0B = TCCR0B;
  TCCR0A = 0;    // stop timer 0
  TCCR0B = 0;    
  
  startCounting (500);  // how many ms to count for

  while (!counterReady) 
     { }  // loop until count over

  // adjust counts by counting interval to give frequency in Hz
  float frq = (timerCounts *  1000.0) / timerPeriod;

  Serial.print ("Frequency: ");
  Serial.print ((unsigned long) frq);
  Serial.println (" Hz.");
  
  // restart timer 0
  TCCR0A = oldTCCR0A;
  TCCR0B = oldTCCR0B;
  
  // let serial stuff finish
  delay(200);
  }   // end of loop

Resultados:

Prueba #2

Codigo usado: Mi código con modificaciones por visionario

unsigned int contador = 0;

void setup() {
  Serial.begin(9600);
  attachInterrupt(0, contar, FALLING);
  Serial.println("Midiendo frecuencia....");
  Serial.println("");
  
}

void loop() {
  
delay(1000);
Serial.print("Frecuencia: ");
Serial.print(contador); 
Serial.println(" Hz"); 
contador = 0;
}

void contar(){
contador ++;  
}

Resultados:

Creo que podríamos concluir que no es mucha la diferencia o el error cuando medimos frecuencias bajas. Pienso que al entrar a medir frecuencias en MHZ se vería la diferencia entre ambos códigos.

Atento a sus comentarios...

Sé que ya te dieron mejores soluciones. Sólo venía a responder estas preguntas:

carlosjq10:
Esto quiere decir que la ejecucion del código que esta dentro del for es decir los 3 println se tardan en ejecutarse 22345 microsegundos?

Correcto. Entonces tenemos que se tarda 22 milisegundos; ya tienes cuánto para compensar.

Tengo una duda, si yo declaro la variable contador como unsigned int ya perderia la posibilidad de expresar la frecuencia con decimales? por ejemplo 4.30 khz pasaria a mostrarse como 4 khz?

No necesariamente. Cuando ya es ahora de procesar el dato, el resultado de la división se guarda en una nueva variable float.