Obtener periodo/frecuencia

Tal como dice el titulo, quiero medir el periodo/frecuencia de una señal cuadrada usando un 89s52. En si la idea de como hacerlo la tengo, pero no por mas que lo intente, la medicion es mala. El rango de la señal sera de 10Hz en adelante, hasta lo que soporte el uC, en mi caso para la aplicacion que estoy tratando de hacer, hasta 50 kHz me alcanza.

Mi idea original fue:

- Uso interrupcion externa (osea c/vez que llega un evento se produce una interrupcion) y la configuro para que se active con flanco descendente.

- Uso un timer para medir el tiempo entre flanco y flanco, de esa forma debiera obtener el periodo/frecuencia.

- Por ultimo envio el resultado por puerto serie a una Pc.

Haciendo esto, siempre mis mediciones fueron malas, tanto en proteus, como en la practica.

Entonces trate de verlo del punto de vista de la frecuencia:

- Configuro un timer para que salte una interrupcion c/100 mSeg.

- Uso interrupcion externa, la configuro para que se active con flanco descendente y cada vez que salte una interrupcion sumo un contador.

- Al terminar de producirse los 100mSeg, paro de contar los pulsos y multiplico el contador por 10 (x pulsos en 100 mSeg => 10*x pulsos en 1 Seg).

- Envio el resultado por puerto serie.

Nuevamente, la medicion es incorrecta en el proteus, no llegue a probarlo en la practica.

Mi duda entonces es (independientemente del uC):

- ¿Son correctos los procedimientos?
- Los codigos estan hechos en C y compilados en keil, ¿habra diferencia en la ejecucion en codigo ASM?
 
A que llamas "mala medicion" ?
- A que la lectura es inestable.
- A que la lectura es constante pero nada que ver con la realidad.
- La dos anteriores.
 
A que llamas "mala medicion" ?
- A que la lectura es inestable.
- A que la lectura es constante pero nada que ver con la realidad.
- La dos anteriores.

Al principio me pasaba que la medicion no era repetible. Eso logre solucionarlo y obtuve mediciones repetibles pero no reflejaba el verdadero periodo/frecuencia.

Igual, peleando un poco pude encontrarle la vuelta:

- Para el 1er procedimiento (lectura de tiempo), tuve que leer un flanco descendente de mas, osea flanco 1 -> flanco 2 -> cuento el tiempo -> flanco 3 - dejo de contar

De esta forma, consigo resultados repetibles que si reflejan el periodo real.

- Para el 2do procedimiento, me di cuenta que meti la pata en la configuracion del timer :)

Compare los resultados de ambos procedimientos y llegue a la conclusion que para frecuencias:

- Menores a 1,5kHz: es mas exacto el 1er procedimiento
- Mayores a 1,5kHz: es mas exacto el 2do procedimiento

Esto nada mas lo comprobe con el proteus, pero voy a probarlo en la practica a ver que pasa.
 
Los procedimientos que estas usando tienen limitaciones.
Si medis periodo, la mayor precision la vas a tener a la minima frecuencia de capura. Si medis frecuencia, la mayor precision la vas a tener a maxima frecuencia de captura --> A frecuencias intermedias vas a tener poca precision con los dos.


Hay otra forma de medir con la que tenes precision constante en todo el rango.

- Se programa el timer de manera que pida interrupciones periodicas al doble de la frecuencia con que se quiere transferir las muestras.
Siendo que la frecuencia minima son 10Hz, valores razonables son entre 300ms y 1s --> Interrupciones cada 150ms a 500ms (bauticemos este valor del timer N0).

- Tal como estabas haciendo, programas una entrada (la señal) para que genere una interrupcion por flanco que incrementara un contador (bauticemoslo N2).


El ciclo de medicion es asi:

- Con la interrupcion del timer deshabilitada te quedas esperando la interrupcion externa.

- Al ocurrir la interrupcion haces lo siguiente: Reseteas el timer, le habilitas la interrupcion y pones a 0 el contador N1.
De esta manera sincronizas la ventana de muestreo con la señal. Tal como habias hecho.

- Con cada interrupcion externa que venga ahora solamente incrementas N2.

- Despues que el timer pida la interrupcion, en la siguiente interrupcion externa terminara el proceso.

- Llega la interrupcion externa --> Incrementas N2, lees la cuenta actual del timer (la guardas en N1) y cancelas las interrupciones.

- Ahora el micro solo tiene que hacer fo*N2/(N0+N1) si queres frecuencia (fo:frecuencia del timer) o To*(N0+N1)/N2 (To:periodo del timer).
Ojo, N0 y N1 tienen que estar corregidos porque el contador del timer es descendente.

O bien mandar directamente a la PC N1 y N2 y el calculo lo haces ahi.

- Despues... vuelta al principio.



Parece complicado, pero no lo es. Ademas de acuerdo a los contadores y opciones del micro N2 se hace solo. Incluso puede implementarse con contadores externos (para frecuencia de señal y clock altas) y que el micro los lea al final.
 
Ok.

Estoy tratando de hacer el procedimiento, pero me estoy haciendo un lio terrible en la cuenta final con los floats y unsigned int.

Si quiero el periodo deberia hacer esto:

Periodo= To*(N0+N1)/N2

N0 es el valor de cuenta del timer inicial? y N1 el valor de la cuenta del timer al final, despues de la interrupcion del timer y de la señal externa?

EDITADO

De esa forma no me da bien el resultado, sin embargo asi si:

Periodo= (N0+N1)/N2
 
Última edición:
Estoy tratando de hacer el procedimiento, pero me estoy haciendo un lio terrible en la cuenta final con los floats y unsigned int.
Despreocupate por el momento de eso y trabaja con los valores crudos. Despues que todo ande bien recien ocupate de eso.

Si quiero el periodo deberia hacer esto:
Periodo= To*(N0+N1)/N2
N0 es el valor de cuenta del timer inicial? y N1 el valor de la cuenta del timer al final, despues de la interrupcion del timer y de la señal externa?
Si, pero tenes que tener la precaucion de corregir N1 porque el contador es descendente.
Por ejemplor, si N0 (constante) es la cuenta inicial del timer y estaba programado para recargar este valor --> tenes que hacer N1 = N0-N1 o directamente To*(2*N0-N1)

Cuando esto se hace con contadores externos, son dos los dos ascendentes y la interrupcion del clock se genera en el overflow, despues se lee el contador y la cuenta total 2^16 + N1 (si era de 16 bits)




Me habia olvidado. Si la frecuencia de la señal es muy baja, te va a llegar la nueva interrupcion del timer sin que la señal haya cambiado --> Se manda un codigo de "frecuencia demasiado baja" y se larga de nuevo el ciclo.

Como este problema tambien lo vas a tener durante la espera del flanco de sincronizacion, te conviene que el timer este activo en esta fase y si genera interrupcion significa timeout.
Me comi este caso, en el mensaje anterior te dije que la interrupcion este deshabilitada :oops:


EDITO:

No entiendo lo que pusiste, (N0+N1)/N2 es adimensional, si queres el periodo tenes que multiplicarlo por un factor de escala que en este caso es el periodo del timer (en us,ms,s lo que sea). Salvo que hayas elegido un periodo multiplo de 10 que tenes lectura casi directa To = 10ms,100ms etc
 
Última edición:
No entiendo lo que pusiste, (N0+N1)/N2 es adimensional, si queres el periodo tenes que multiplicarlo por un factor de escala que en este caso es el periodo del timer (en us,ms,s lo que sea). Salvo que hayas elegido un periodo multiplo de 10 que tenes lectura casi directa To = 10ms,100ms etc

Por eso me suena raro, las unidades no dan ni a patada.

Vamos a tratar de reorganizarnos, yo tengo esto:

- Timer con autorrecarga, que lo hago funcionar en forma ascendente, con una capacidad de 16 bits (osea 2^16 cuentas)

- Para simplificar, en vez de hacerlo a 150 mSeg, lo hago a 143 mSeg aprox. (q son 2*2^16 cuentas)

- Uso un cristal de 11.0592 MHz, y como el 8051 es una lenteja, c/instruccion equivale a 12clks, osea 1 cuenta de mi timer me lleva 1/(11.0592 MHz/12)=1,085...uSeg

Entonces en mi configuracion inicial el valor del timer sera 0 y debera llegar a la cuenta 2*2^16 (tengo una interrupcion en el medio).

Entonces lo resultados que obtengo (siempre usando proteus):

Con 1kHz:

Pulsos=N2= 0x008F=143 cuentas(lo cual en principio estaria bien, en 143mSeg obtengo 143 pulsos, 1pulso/mSeg)

Tiempo despues de la ultima I.Ext=N1=0x25=37 cuentas

Tiempo del timer= T0= 143 mSeg

N0=2*2^16 cuentas

Si:

Periodo= To*(N0+N1)/N2

Periodo=143mSeg *(2*2^16 cuentas+37 cuentas)/(143 cuentas)

Periodo=.143 Seg*916.84 =131.109 seg

Si te fijas el valor (2*2^16 cuentas+37 cuentas)/(143 cuentas)=916.84, da aproximadamente el valor de la frecuencia (no el del periodo como puse arriba, me equivoque).

Estoy seguro que los valores obtenidos por el uC son correctos, pero fallo en la ultima cuenta, es posible que mal interprete esa formula.
 
El problema esta aca, me equivoque yo donde puse:

Periodo= To*(N0+N1)/N2

To no es el periodo del timer sino el del clock (1.085us). Desde el momento que To*(N0+N1) representa el tiempo total transcurrido. Sorry :oops:
 
Ahi si va como jamon :) .

En vez de sacar el periodo saco la frecuencia (bajo el mundo ideal de proteus):

10: 10 y 9 Hz
1K: 1006 Hz
10k: 10017 Hz

El problema se me presenta con frecuencia de 20, 30 kHz en adelante. Supongo que debe haber un overflow de variables asqueroso :LOL: .

Aca esta el codigo asi nomas:

Código:
#include <reg52.h>
#define Cuenta_T2 131072 // 2*2^16 cuentas
#define F_clock 921600

unsigned char estado=0,estado_serie=0,mult_t2;
unsigned int pulsos,frec;

void reset_timer2(void)
{
	TR2=0;
	TL2=0x00; 
	TH2=0x00;
	mult_t2=2;
}

void timer2(void) interrupt 5	 
{
	mult_t2--;	 // Tener en cuenta mult_t2 en la cuenta 
	if(!mult_t2)  // fo=7 Hz o To=143 mSeg
		{
		estado=3;
		}
	TF2=0;
	return;
}

void ext0(void) interrupt 0											
{
	switch(estado)
		{
		case 1:{TR2=1; IE=0xA1; estado=2; break;}
		case 2:{pulsos++; break;}
		case 3:{
				TR2=0; 
				IE=0x90; 
				pulsos++; 
				frec=(unsigned int)(F_clock/(((Cuenta_T2+(unsigned int)(TH2<<8)+(unsigned int)TL2)/pulsos))); //Aca tiene q ir la cuenta final 
				estado_serie=1;
				estado=0; 
				SBUF=(unsigned char)(frec>>8);}
		}		
	return;
	
}


void serie(void) interrupt 4
{
	unsigned char aux=0;
	 if(RI)
	 	{
			aux=SBUF;
			if(aux=='a')
				{estado=1; pulsos=0; frec=0; reset_timer2(); IE=0x81;} // Habilito Ext0 solamente
			RI=0;
		}

	 if(TI)
	 	{
		
		switch(estado_serie)
			{
			case 1:{SBUF=(unsigned char)(frec&0x00ff); estado_serie=0; break;}
			}	
		  
		 TI=0;
		}
	 return;
}  


void main (void)
{
	TR1=0;
	TL1=0xff; // 57600 Baudios
	TH1=0xff;
	reset_timer2();
	TMOD=0x21; // Timer 1 de 8 bit con autorecarga, Timer 0 de 16 bit sin autorecarga
	PCON=PCON|0x80;	// Smod=1
	SCON=0x50;
	TR1=1;
	IT0=1;
	TR2=0;
	RCAP2H=0x00; RCAP2L=0x00;	// Autorrecarga del T2
 	T2CON=0x00; // Timer 2, 16 bit, auto recarga 
	IE=0x90;  // I. Puerto Serie 
	
	while(1);
}

Faltaria tener en cuenta lo que me dijiste y tampoco tuve en cuenta que el tiempo despues de la ultima interrupcion externa supere el timer, pero se supone que no deberia ser tan grande el tiempo.

Entonces mandando 'a' por el puerto serie te devuelve en hexa la frecuencia.

Por otro lado, habia hecho el codigo con los metodos que mencione antes, pero combinados, teniendo en cuenta que mas o menos 1,5kHz tenia que cambiar de procedimiento para una mejor lectura. Pude comprobar tanto en proteus como en la practica funca bastante bien, los errores estan en el mismo orden que con tu codigo, lo malo es que como patron uso un 555 :eek:.

Despues voy a probar con este codigo como funciona en la practica, lo bueno es que es bastante mas corto que el otro, ya que no tengo que combinar los procedimientos.
 
Última edición:
Si la frecuencia minima a medir es 10Hz, no entiendo porque usas para la frecuencia un entero en Hz :confused: --> Asi te queda un error espantoso a baja frecuencia (10% a 10Hz, 1% a 100Hz etc). Cuando la precision "natural" midiendo asi es muy superior en todo el rango.

La ultima cuenta tenes que hacerla en punto flotante o por lo menos que agrandar la longitud de frec para que represente centesimas o milesimas de Hertz.

Ademas, cuando hacias el cociente de enteros entre parentesis porque te lo truncaba a entero (estas metiendo un error espantoso) --> hace:
frec = (F_clock*pulsos)/(Cuenta_T2+(unsigned int)(TH2<<8)+(unsigned int)TL2)

con frec de una longitud tal que F_clock*pulsos no te de overflow. O sino punto flotante.
 
Seguro, estoy recontra truncando el resultado, pero me resulta mas sencillo trabajar con int para despues mandar los datos por el puerto serie asi de una y ver rapidamente el resultado, sino tendria que trabajar con uniones y flotantes, para despues usar algun conversor a IEE 754 y ver cual es el valor decimal, asi y todo probe esto ultimo para ver que tan exacto es este metodo, y a un 1kHz daba 1000,...; osea muy bien.

En realidad lo que me interesa es obtener el periodo en funcion de las cuentas del timer, osea:

Cuentas del timer del periodo=(N0+N1)/N2

Pero no se porque, independientemente de la cuenta, el programa me falla para altas frecuencias en el proteus, osea se queda ahi sin hacer nada, hoy voy a probar este codigo en la practica y despues te comento.
 
Seguro, estoy recontra truncando el resultado, pero me resulta mas sencillo trabajar con int para despues mandar los datos por el puerto serie asi de una y ver rapidamente el resultado, sino tendria que trabajar con uniones y flotantes, para despues usar algun conversor a IEE 754 y ver cual es el valor decimal,
Por eso te sugeri al principio mandar a la PC el contenido de los contadores (N1 y N2, lo demas no hace falta porque son constantes) y hacer la operacion al momento de presentar el numero.

Tenes que tener en cuenta que con los datos crudos (N1 y N2) tenes una resolucion de 17bits, y por eso, cuandos haces las operaciones con enteros tenes que tener cuidado en el orden que las haces porque si no, con los truncamientos vas a terminar con una resolucion pedorra de 3 o 4 bits.
 
Te comento, probe el codigo en la practica y para frecuencias menores a 10kHz y un poco mas va todo bien, pero al aumentar ponele 20 kHz en adelante el programa se trula tal como sucedia en el proteus.

Dandole vueltas y vueltas al codigo, probe cambiando las prioridades en las interrupciones, en un principio las tenia 1ero externa y luego el timer, invirtiendo las prioridades logre que no se trule mas el uC.

Pero ahora se presenta otro problema, la medicion es mala en altas frecuencias (seguro porque se morfa flancos por el cambio de prioridad), asi que por el momento estoy trabado con esto, voy a ver si cambiando la estructura del codigo (en vez de usar maquinas de estado), lo planteo de otra forma mas tradicional consigo algo.
 
Te comento que al final lamentablemente no pude alcanzar los 50kHz que me habia propuesto, pero hasta los 46kHz va bien con un desvio de 20 Hz (mas que bien, para lo que necesito); supongo que ya estoy jugando justo en el limite del uC (con el cristal que estoy usando) y mas que la programacion la estoy realizando en C.

El codigo final fue este, en este caso use los flotantes para comprobar la exactitud de la medida, y tal como dijiste el plan es hacer esa cuenta en la Pc en vez de hacerla en el uC:

Código:
#include <reg52.h>
#define Cuenta_T2 131072 // 143 mSeg cuentas
#define F_clock 921600

unsigned char estado=0,estado_serie=0,mult_t2=0;
unsigned int pulsos;
bit eflag=0;	 

union partir{
			float frec;
			unsigned char byte[4];
			}datos;

void reset_timer2(void)
{
	TR2=0;
	TL2=0x00; 
	TH2=0x00;
	mult_t2=2;
}

void timer2(void) interrupt 5	 
{
	mult_t2--;
	if(!mult_t2)
		estado=3;
	TF2=0;
	return;
}

void ext0(void) interrupt 0											
{
	eflag=1;
	return;
	
}


void serie(void) interrupt 4
{
	unsigned char aux3=0;
	 if(RI)
	 	{
			aux3=SBUF;
			if(aux3=='a')
				{estado=1; pulsos=0; datos.frec=0; reset_timer2(); eflag=0; IE=0x81;} // Habilito Ext0 solamente
			RI=0;
		}

	 if(TI)
	 	{
		
		switch(estado_serie)
			{
			case 1:{SBUF=datos.byte[1]; estado_serie=2; break;}
			case 2:{SBUF=datos.byte[2]; estado_serie=3; break;}
			case 3:{SBUF=datos.byte[3]; estado_serie=0;}
			}	
		  
		 TI=0;
		}
	 return;
}  


void main (void)
{
	TR1=0;
	TL1=0xff; // 57600 Baudios
	TH1=0xff;
	reset_timer2();
	TMOD=0x21; // Timer 1 de 8 bit con autorecarga, Timer 0 de 16 bit sin autorecarga
	PCON=PCON|0x80;	// Smod=1
	SCON=0x50;
	TCON|=0x01;
	TR1=1;
	TR2=0;
	RCAP2H=0x00; RCAP2L=0x00;	// Autorrecarga del T2
 	T2CON=0x00; // Timer 2, 16 bit, auto recarga 
	IE=0x90;  // I. Puerto Serie 
	
	while(1)
		{
			switch(estado)
				{
				case 1:{if(eflag){TR2=1; IE=0xA1; estado=2; eflag=0;} break;}
				case 2:{if(eflag){pulsos++; eflag=0;} break;}
				case 3:{if(eflag){ TR2=0; IE=0x90; estado=4; eflag=0;} break;}
				case 4:{ 
						pulsos++;
						datos.frec=((float)F_clock/(((float)(Cuenta_T2+(unsigned int)(TH2<<8)+(unsigned int)TL2)/(float)pulsos)));
						estado_serie=1;
						estado=0; 
						SBUF=datos.byte[0];
						}
				}
		}
}

La verdad te agradezco la ayuda, realmente me fue muy util el procedimiento que me pasaste (y).
 
Atrás
Arriba