Necesito medir la frecuencia de onda cuadrada que puede variar entre 0 y 1MHz, y tiene una resolución de 0.25Hz.
Todavía no he decidido qué controlador, pero lo más probable es que sea uno de los 20 pin de Attiny.
Normalmente, cómo mediría las señales de baja frecuencia sería usando dos temporizadores, uno configurado en modo de captura de temporizador para interrumpir, por ejemplo, los bordes ascendentes de la señal externa y otro temporizador configurado para interrumpir cada segundo, por lo tanto, los antiguos temporizadores registran el valor después de 1 segundo sería igual a la frecuencia de la señal.
Sin embargo, este método obviamente no funcionará para capturar señales que oscilan entre 0 y 1MHz con una resolución de 0.25Hz para esto necesitaría un contador de 22 bits (los micros AFAIK de 8 bits solo tienen contadores de 8/16 bits).
Una idea que tuve fue dividir la señal antes de aplicarla al micro, pero esto no sería práctico ya que la señal tendría que dividirse entre 61, por lo tanto, la frecuencia solo se podría actualizar cada 61 segundos, donde me gustaría que fuera cada pocos segundos. .
¿Hay algún otro método que permita actualizar la frecuencia, por ejemplo, cada 4 segundos?
Actualizar:
La solución más simple es usar una interrupción externa o una captura de temporizador para interrumpir en el borde ascendente de la señal y hacer que isr
incremente una variable de tipo long int
. Lea la variable cada 4 segundos (para permitir frecuencias de hasta 0.25Hz para medir).
Actualización 2:
Como señaló JustJeff, una MCU de 8 bits no podrá mantenerse al día con una señal de 1MHz, por lo que se descarta la interrupción en cada flanco ascendente y el incremento de un long int
...
Elegí el método sugerido por timororr. Una vez que llegue a implementarlo, volveré a publicar y compartiré los resultados. Gracias a todos por sus sugerencias.
Informe de progreso:
Ivé comenzó a probar algunas de las ideas presentadas aquí. Primero probé el código de vicatcu. Hubo un problema obvio de que TCNT1 no se borró después de que se calculó la frecuencia, no es un gran problema ...
Luego, al depurar el código, noté que aproximadamente cada 2 a 7 veces se calculaba la frecuencia, el conteo de desbordamiento del temporizador 1 (el temporizador configurado para contar eventos externos) sería corto en dos. Puse esto a la latencia de Timer 0 ISR y decidí mover el bloque de declaración if del ISR al principal (ver fragmento a continuación) y simplemente establecer una bandera en el ISR. Algunas depuraciones mostraron que la primera medición estaría bien, pero con cada lectura subsiguiente, el recuento de desbordamiento del temporizador 1 se terminaría en 2. Lo que no puedo explicar: hubiera esperado que no estuviera debajo ...
int main()
{
while(1)
{
if(global_task_timer_ms > 0 && (T0_overflow == 1))
{
global_task_timer_ms--;
T0_overflow = 0;
}
.....
}
}
Luego decidí que intentaría implementar la sugerencia de timrorrs. Para generar el intervalo necesario (de aproximadamente 15 ms entre cada interrupción timer_isr), tendría que conectar en cascada los dos temporizadores de 8 bits, ya que el único temporizador de 16 bits en Atmega16 se está utilizando para capturar los bordes ascendentes de la señal externa.
Pensé que esta solución funcionaría y sería mucho más eficiente, ya que la mayor parte de la sobrecarga se traslada a los temporizadores y solo queda un corto ISR para que la CPU lo maneje. Sin embargo, no fue tan preciso como esperaba, las mediciones se movieron hacia adelante y hacia atrás en aproximadamente 70Hz, lo que no me importaría en frecuencias altas, pero definitivamente no es aceptable en frecuencias más bajas. No pasé mucho tiempo analizando el problema, pero supongo que la disposición en cascada del temporizador no es tan precisa, ya que he implementado una disposición similar a la sugerencia de los temporizadores en un controlador 8051 mucho más lento que tenía 2 temporizadores de 16 bits y los resultados fueron bastante precisos.
Ahora he vuelto a la sugerencia de vicatcu, pero he movido el cálculo de frecuencia al temporizador 0 isr (ver fragmento a continuación ), este código ha producido mediciones consistentes y razonablemente precisas. Con un poco de calibración, la precisión debe ser aproximadamente de +/- 10Hz.
ISR(TIMER0_OVF_vect)
{
TCNT0 = TIMER0_PRELOAD; //Reload timer for 1KHz overflow rate
if(task_timer_ms > 0)
{
task_timer_ms--;
}
else
{
frequency_hz = 1.0 * TCNT1;
TCNT1 = 0;
frequency_hz += global_num_overflows * 65536.0;
global_num_overflows = 0;
frequency_hz /= (TASK_PERIOD_MS / 1000.0);
task_timer_ms = TASK_PERIOD_MS;
}
}
Si alguien tiene alguna otra sugerencia, estoy abierto a ellas, aunque prefiero no tener que usar rangos ... Tampoco tengo la intención de obtener una resolución del 0.25%, no parece tener mucho sentido el nivel de precisión que tengo en este momento. .
Respuestas:
Si es posible, sugeriría seleccionar un microcontrolador que admita una operación de contador utilizando las entradas del temporizador; en lugar de incrementar manualmente un contador dentro de un ISR (que a altas frecuencias rápidamente termina saturando la actividad del microcontrolador), permite que el hardware maneje el conteo. En este punto, su código simplemente se convierte en una cuestión de esperar su interrupción periódica y luego calcular la frecuencia.
Para ampliar el rango y hacer que el contador de frecuencia sea más generalizado (eliminando la necesidad de múltiples rangos a expensas de un poco más de trabajo para la MCU), puede usar la siguiente técnica.
Seleccione una frecuencia de interrupción periódica que permita la precisión de la medición a la frecuencia de entrada más alta; esto debe tener en cuenta el tamaño de su contador (debe seleccionar el período del temporizador de modo que el contador del temporizador no se desborde a la frecuencia de entrada máxima). Para este ejemplo, supondré que el valor del contador de entrada se puede leer desde la variable "timer_input_ctr".
Incluya una variable para contar las interrupciones periódicas (debe inicializarse a 0 al inicio); para este ejemplo me referiré a esta variable como "isr_count". El período de interrupción está contenido en la constante "isr_period".
Su interrupción periódica debe implementarse como (pseudocódigo C):
Obviamente, este ejemplo aproximado se basa en algunas matemáticas de coma flotante que pueden no ser compatibles con los microcontroladores de gama baja, existen técnicas para superar esto, pero están fuera del alcance de esta respuesta.
fuente
Es posible que desee considerar tener dos (o más) rangos. Los problemas con la captura de frecuencias muy bajas son algo diferentes de los problemas con las más altas. Como ya ha notado, en el extremo superior de su rango tiene problemas de desbordamiento de contador.
Pero considere en el extremo inferior de su rango, su precisión sufrirá al no tener suficientes recuentos en el registro. No estoy seguro de si realmente desea discriminar entre 0.25Hz y 0.5Hz, pero si lo hace, entonces tendrá que contar durante cuatro segundos para hacerlo.
Además, especificar una resolución plana de 0.25Hz, estrictamente interpretada, significa que podrá discernir 500,000.00Hz de 500,000.25Hz, que es un grado de precisión bastante alto.
Por esas razones, el diseño para distintos rangos podría aliviar el problema del tamaño del contador. Si saca números al azar por ejemplo, para el extremo inferior, digamos de 0 a 100Hz, cuente durante intervalos de 10 segundos, y obtendrá una resolución de 0.1Hz, y el contador solo necesita subir a 1000, ni siquiera 10 bits. Luego, de 100Hz a 10kHz, cuente por intervalos de 1 segundo; solo obtiene una resolución de 1Hz, pero su contador solo necesita ejecutar hasta 10,000 aún más pequeños que 16 bits. El rango superior de 10kHz a 1MHz podría contar por solo 0.01 segundos, y el recuento máximo solo sería de 10,000 y aunque su resolución sería de 100Hz, esto sería una precisión razonable.
fuente
Puede mezclar un contador de hardware y un software contando los desbordamientos del contador de hardware en un ISR.
Contar cada borde de la señal en un ISR será demasiado lento para una señal de 1 MHz. Creo que podría hacerlo hasta unos 50 kHz de esa manera.
fuente
En lugar de hacer un contador de 1 segundo, conviértalo en un contador de 0.1 segundos y multiplique el conteo por 10.
Si solo se trata de almacenar el número del contador, ¿no puede usar un código adicional para realizar un seguimiento de cuándo el contador está a punto de desbordarse y escribir en otra ubicación de memoria para mantener el recuento?
fuente
¿No puede usar simplemente la captura de entrada de un temporizador de 16 bits y las interrupciones de desbordamiento (más una variable) para realizar la medición? Así es como lo haría con el ATTiny24A con AVR-GCC (no probado y potencialmente defectuoso, por supuesto):
... en cualquier caso, se compila :)
EDITAR Miré la salida del archivo lss de mi código, y el código generado tiene demasiadas instrucciones para no tropezarse con 1MHz con un reloj de 8MHz ... ¡incluso el simple incremento de una línea en el TIM1_OVF_vect genera 19 instrucciones! Entonces, para manejar eventos de 1MHz, definitivamente necesitaría optimizar, probablemente registrar, asignar algunas cosas (probablemente num_overflows y capture_value_ticks), usar el ensamblador en línea (robar las cosas importantes del archivo lss) y mover el procesamiento fuera de las interrupciones hacia el principal bucle siempre que sea posible.
fuente
Publicar este código como una alternativa según la sugerencia de @ timrorr a mi publicación anterior. Esto se compila para el ATTiny24A usando el estándar de lenguaje c99, pero en realidad no lo he probado de ninguna manera más allá de eso.
Este es un pequeño uso agradable de las capacidades de hardware del Timer1 y libera una tonelada de ciclos de procesamiento en comparación con mi publicación original.
fuente
Usando preescaladores, incluso se puede lograr la medición de GHz. Este es un medidor de frecuencia simple de 40MHz con ATMEL AVR AT90S2313: http://www.myplace.nu/avr/countermeasures/index.htm
Aquí hay otros proyectos similares:
http://www.ikalogic.com/freq_meter_2.php
http://www.saturn.dti.ne.jp/~khr3887/lfcd_e.html
http://www.circuitlake.com/rs232-frequency-meter-and-pulse-generator.html
http://www.ulrichradig.de/home/index.php/avr/frequenzcounter
http://www.triplespark.net/elec/analysis/FreqCnt/
http://www.cappels.org/dproj/30MHzfmeter/30MhzFmtr.html
http://www.qsl.net/pa3ckr/bascom%20and%20avr/rfcounter/index.html
http://www.sump.org/projects/counter
http://digilander.libero.it/alfred73/eprojects.htm#1300%20Mhz%20Frequencymeter%20with%20prescaler
fuente