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.
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.
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.
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
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í
}
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.
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
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
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.
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.