Midiendo un pulso. 1ª Parte. Tiempo en Alto con INTEXT A.- Conceptos involucrados: Los pulsos corren alocados entre dos estados conocidos: Alto y Bajo. Es lo que conocemos como un Tren de Pulsos . El bajo (Low) cuando la tensión aplicada al Pin está cerca de GND y el otro, el Alto (High), cuando se parece a VCC. A todos los efectos para nuestras aplicaciones vamos a considerarlos como GND y VCC respectivamente. respectivamente. Cuando el estado pasa de Bajo a Alto decimos que nos ha llegado un Flanco de Subida (Raising Edge). Cuando por el contrario el estado pasa de Alto a bajo entonces decimos que nos llega un Flanco de Bajada (Falling Edge). Un Pulso (Pulse) es lo que ocurre entre uno y otro Flancos de Subida sucesivos (o de Bajada). El Tiempo T que transcurre ente dos flancos sucesivos de subida (o de bajada) es lo que conocemos como Periodo del Pulso (Period), o sea: lo que dura un pulso completo. Cada T segundos (ó milisegundos, ó microsegundos) tenemos un nuevo pulso completo. Como vemos, cada pulso tiene un Tiempo en Alto (High Time) y un Tiempo en estado Bajo (Low Time). La suma de los dos es el Periodo T del pulso. Al Tiempo que permanece un pulso en Alto que es lo que vamos a llamar Ancho del Pulso (Pulse Width) y es el tiempo que pretendemos medir. W en el esquema inferior.
B.- Técnica a Aplicar: Para medir el ancho W del pulso que permanece en Alto vamos a utilizar dos recursos de los que disponen la inmensa mayoria de los PIC's, tanto los de la serie 18F como los 16F: El TIMER1 y la Interrupción Externa por RB0 . * El TIMER1 es un contador de 16 bits que se incrementa cada 4 ciclos de Reloj (FOSC /4). A este tiempo le vamos a llamar ll amar Tick de TIMER1 . Cuando el TIMER1 alcanza el valor 0xFFFF continúa de nuevo por 0x0000 , y así hasta el infinito y mas allá. (Cuando pasa de 0xFFFF a 0x0000 genera una Interrupción por Desbordamiento de Timer pero que no vamos a utilizar en este ejemplo, así que no vamos a entrar en cómo funciona). Si el cristal de cuarzo con el calzamos nuestro PIC es de 20 Mhz, por ejemplo, entonces cada 4 * 1/20.000.000 = 0,0000002 segundos (0.2 microsegundos) se produce un Tick de TIMER1.
De igual forma una vuelta completa del TIMER1, desde 0x0000 hasta 0xFFFF (65536 pasos), ocupa un tiempo total de 0,0000002 * 65.536 = 0,0131072 segundos (13,1072 milisegundos) * La Interrupción Externa por RB0 , conocida como Int_Ext consiste en una petición de interrupción que se produce al recibir por RB0 un flanco de subida o bajada determinado, que es configurable por nuestro firmware. Podemos así fijar un tipo de flanco a detectar y escribir un cierto código para ejecutarlo cuando ese tipo de flanco, subida o bajada, es detectado. Es lo que conocemos como Petición de Servicio por Interrupción (Interrupt Service Request ó ISR) En este código ISR escrito para tratar la interrupción externa por RB0 podemos mantener o cambiar el flanco a detectar , cambiándolo del de Subida al de Bajada o viceversa y leer o escribir el valor de TIMER1 que en ese momento tiene el contador. * Lo que vamos a realizar es:
1.- Configuraremos la Int_Ext por RB0 para detectar inicialmente el Flanco de Subida. 2.- Al llegarnos ll egarnos este Flanco de Subida guardaremos el valor en ese momento de TIMER1 y ... 3.- Cambiaremos la configuración de la Int_Ext por RB0 para detectar el siguiente Flanco de Bajada. 4.- Cuando nos llegue el Flanco de Bajada guardaremos de nuevo el valor de TIMER1 y ... 5.- Volveremos a configurar la Int_Ext por RB0 para detectar de nuevo un Flanco de Subida. Y ... 6.- Con estos dos valores de TIMER1 tendremos, expresados en Tick's de TIMER1, restando el segundo del primero, el tiempo ti empo que ha permanecido en alto el Pulso. Multiplicando dicho número de Tick's de TIMER1 por el tiempo que dura cada Tick (dependiente del cristal que usemos) tendremos el Tiempo W que estamos buscando. C.- Implementación en C: Para implementar nuestro Código en C vamos a centrarnos en los puntos que hemos descrito en la sección anterior. Para configurar inicialmente el flanco de subida a detectar utilizaremos: Código GeSHi (c): 1.
ext_int_edge( ext_int_edge (0,L_TO_H) ,L_TO_H);
// Configuro captura
de 1er flanco de subida 2.
flagToggleFlanco flagToggleFlanco = 0;
// inicializo el
Flag para cambiar de flanco 3.
enable_interrupts( enable_interrupts (int_ext) int_ext);
interrupciones necesarias 4. 5.
enable_interrupts( enable_interrupts (global) global);
// Habilito las
Hemos añadido la variable int1 flagToggleFlanco debido a que una vez establecido el flanco a detectar no tenemos oportunidad de saber qué flanco es el que estamos esperando, así si flagToggleFlanco tiene un valor de 0 es que estamos esperando el de Subida y si por el contrario flagToggleFlanco tiene un valor de 1 entonces es el de bajada el que esperamos. Con esta configuración inicial podemos ya escribir nuestra rutina ISR: Código GeSHi (c): 1. #int_ext 2. void handle_ext_int(){ 3. 4. if(flagToggleFlanco==0){
// He recibido
Flanco de Subida 5. 6.
t1=get_timer1();
// Guardo en t1 el
valor de TMR1 al Flanco de Subida 7.
ext_int_edge(0,H_TO_L);
// Configuro para
capturar siguiente flanco de Bajada 8.
flagToggleFlanco=1;
// Indico que el
siguiente flanco será de Bajada 9. 10.
} else {
// He recibido
Flanco de Bajada 11. 12.
t2=get_timer1();
// Guardo en t2 el
valor de TMR1 al Flanco de Bajada 13.
ext_int_edge(0,L_TO_H);
// Configuro para
capturar siguiente flanco de subida 14.
flagToggleFlanco=0;
// Indico que el
siguiente flanco será de Subida 15. 16.
set_timer1(0); if(flagHayDatos==0){
// Reinicio TMR1 // Si los datos
anteriores han sido procesados ... 17.
flagHayDatos=1;
// Indico que ya
hay nuevos datos de flancos para calcular 18. 19. 20. 21.
} } }
Fijaos que en la rutina ISR anterior no realizamos ningún cálculo . Aplicamos aqui el principio fundamental de que " dentro de una rutina de interrupción haz lo debas y abandónala lo antes que puedas ". Asi que si tenemos valores válidos de T1 y T2 entonces solo hacemos que flagHayDatos valga 1 y ya trataremos estos valores dentro de main(). Código GeSHi (c): 1.
if(flagHayDatos==1){
hay datos de flancos ...
// Detecto que ya
2.
if(t2 > t1){
// Compruebo que
estoy en la misma vuelta de TMR1 3.
tt = t2 - t1;
// Calculo en Tick's
de TMR1 el tiempo entre flancos 4.
st = uSxTick * tt;
// Calculo en uS el
flagHayTransmitir=1;
// Indico que tengo
tiempo. 5.
nuevo valor para transmitir 6. 7.
} flagHayDatos=0;
// Indico que ya han
sido procesados los datos. 8. 9.
}
Cuando flagHayDatos es 1 podemos proceder a calcular los Ticks transcurridos entre un flanco y otro. Fijaos que preguntamos si T2 es mayor que T1 . Esto es debido a una limitación que nos impone el no tener en cuenta vueltas completas de TIMER1 para realizar nuestros cálculos: T2 y T1 deben estar dentro de una misma vuelta de TIMER2, o sea que el pulso en alto debe ser menor que 13,1072 milisegundos. Notad que cuando detectamos el segundo flanco volvemos a poner TIMER1 a cero para recomenzar siempre cerca del inicio de la cuenta de TIMER1 y evitar asi su desborde. Para computar tiempos de pulso mayores 13,1072 milisegundos solo habría que computar tambien cuantas veces se desborada completo TIMER1 y realizar los cálculos aritméticos correspondientes. Al igual que hacíamos en la Rutina ISR en que no calculábamos sino que solo indicábamos que podíamos calcular, aquí una vez realizados los calculos de Tick´s y su equivalencia en segundos no los transmitimos, sólo indicamos que hay datos para transmitir mediante poner a 1 flagHayTransmitir. Para transmitir el último valor computado vamos a implementar una recepción vía serie RS232 que al recibir el comando ' T' nos envía el último valor correcto registrado: Código GeSHi (c): 1.
if(Command!=0x00){
// Si he recibido un
comando vía Serie ... 2.
if(Command=='T'){
// Si el comando es
'T' (Transmite) ... 3.
if(flagHayTransmitir==1){
// Si hay algo
pendiente de transmitir ... 4.
printf("Ticks %Lu - %Lu = %Lu = %3.1fuS \r\n",t2,t1,tt,st); 5. flagHayTransmitir=0; // Indico que ya he
transmitido lo pendiente. 6. 7. 8.
} } Command=0x00;
procesado el comando. 9.
}
// Indico que ya he
10.
Con todo esto el programa completo para nuestro Detector de Tiempo en Alto de un Pulso queda así: Código GeSHi (c): 1. ////////////////////////////////////////////////////////////////
//////////////////// 2. // 3. // Midiendo_un_pulso_1_Width_High.c 4. // 5. // SERIE: "Técnicas en C" para el Foro TODOPIC 6. // 7. // (c) 10.2006 by RedPic 8. // 9. // Propósito: Medir el tiempo que permanece un pulso en alto 10. // 11. // Condiciones de Test: Inyección por RB0 de una señal de 2 Khz
(0.5 ms de periodo) 12. // 13. // Técnica Empleada: Detectar mediante la Interrupción Externa por RB0 14. // un flanco de subida de un pulso, guardar el estado 15. // de TMR1, detectar a continuación el siguiente 16. // flanco de bajada, guardar el nuevo estado de TMR1, 17. // realizar las substracción de ambos para obtener el 18. // tiempo que permanece en alto y transmitir mediante 19. // el puerto RS232 a petición. 20. // 21. /////////////////////////////////////////////////////////////// ///////////////////// 22. 23. 24.
#include <18f4550.h> #fuses HS,MCLR,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOPBADEN,NOLVP,NOCPD,NO DEBUG,NOWRT,NOVREGEN 25. #use delay(clock=20000000) 26. 27. #use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7) 28. 29. /////////////////////////////////////////////////////////////// 30. 31. 32. 33. 34. 35.
///////////////////// // // Defines y Constantes // /////////////////////////////////////////////////////////////// /////////////////////
#define LED PIN_E0 del Led 36. #define FLASH Output_Toggle(LED) funcion Flash de monitor
// Defino el Pin // Defino la
37. 38.
float const uSxTick = 0.2;
// Microsegundos
por Tick de TMR1 a 20 Mhz 39. 40.
/////////////////////////////////////////////////////////////// ///////////////////// 41. // 42. // Variables en RAM 43. // 44. /////////////////////////////////////////////////////////////// ///////////////////// 45. 46.
char
cRec=0x00;
// Último caracter
recibido via serie 47.
char
Command=0x00;
// Comando a
procesar 48.
int1
flagToggleFlanco=0;
// Flag para
cambiar de flanco 49.
int16 t1=0x00,t2=0x00,tt=0x00;
// Variables para
guardar estados de ... 50.
float st=0.0;
// TMR1 en cada
flanco y hacer la resta 51.
int1
flagHayDatos=0;
// Flag para
indicar que ya hay datos de .. // dos flancos (de
52.
subida y bajada) 53.
int1
flagHayTransmitir=0;
// Flag para
indicar que hay datos para ... // Transmitir al
54.
PC. 55. 56.
/////////////////////////////////////////////////////////////// ///////////////////// 57. // 58. // Interrupción por Recepción Serie RS232 59. // 60. /////////////////////////////////////////////////////////////// ///////////////////// 61. 62. 63. 64. 65.
#int_rda void handle_rda_int(){ if(kbhit()){
// Si hay algo pdte
de recibir ... 66.
cRec=getc();
// lo recibo sobre
cRec ... 67.
if(cRec!=0x00){
// Si es distinto
de \0 ... 68.
Command=ToUpper(cRec);
// cargo cRec sobre
Command para procesarlo 69.
}
// pasándolo a
Mayúsculas para no confundir. 70. 71. 72. 73.
} }
/////////////////////////////////////////////////////////////// ///////////////////// 74. // 75. // Interrupción por Externa por Cambio de Flanco en RB0 76. //
77.
/////////////////////////////////////////////////////////////// /////////////////////
78. 79. 80. 81. 82.
#int_ext void handle_ext_int(){ if(flagToggleFlanco==0){
// He recibido
Flanco de Subida 83. 84.
t1=get_timer1();
// Guardo en t1 el
valor de TMR1 al Flanco de Subida 85.
ext_int_edge(0,H_TO_L);
// Configuro para
capturar siguiente flanco de Bajada 86.
flagToggleFlanco=1;
// Indico que el
siguiente flanco será de Bajada 87. 88.
} else {
// He recibido
Flanco de Bajada 89. 90.
t2=get_timer1();
// Guardo en t2 el
valor de TMR1 al Flanco de Bajada 91.
ext_int_edge(0,L_TO_H);
// Configuro para
capturar siguiente flanco de subida 92.
flagToggleFlanco=0;
// Indico que el
siguiente flanco será de Subida 93. 94.
set_timer1(0); if(flagHayDatos==0){
// Reinicio TMR1 // Si los datos
anteriores han sido procesados ... 95.
flagHayDatos=1;
// Indico que ya
hay nuevos datos de flancos para calcular 96. 97. 98.
} } FLASH;
// Reproduzco la
entrada mediante un LEd en E0; 99. } 100. 101. void main() { 102. 103. ////////////////////////////////////////// INICIALIZACIONES
GENERALES 104. 105.
delay_ms(333);
// Espero a que
todo se estabilice e ... 106.
disable_interrupts(global);
// Inicializo el
Micro y ... 107.
disable_interrupts(int_timer1);
todo lo no necesario ... 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121.
disable_interrupts(int_rda); disable_interrupts(int_ext); disable_interrupts(int_ext1); disable_interrupts(int_ext2); setup_adc_ports(NO_ANALOGS); setup_adc(ADC_OFF); setup_spi(FALSE); setup_psp(PSP_DISABLED); setup_counters(RTCC_INTERNAL,RTCC_DIV_2); setup_timer_0(RTCC_OFF); setup_timer_1(T1_INTERNAL | T1_DIV_BY_1); setup_timer_2(T2_DISABLED,0,1); setup_timer_3(T3_DISABLED); setup_comparator(NC_NC_NC_NC);
// deshabilitando
122. 123. 124. 125. 126.
setup_vref(FALSE); port_b_pullups(FALSE); delay_ms(333);
/////////////////////////////////////////// INICIALIZACIONES PERTINENTES A LA APLICACION
127. 128.
set_tris_c(0b10000000);
// Habilito como
entrada RC7 para canal RS232 129. 130.
ext_int_edge(0,L_TO_H);
// Configuro
captura de 1er flanco de subida 131.
flagToggleFlanco = 0;
// inicializo el
Flag para cambiar de flanco 132. 133.
enable_interrupts(int_rda);
// Habilito las
interrupciones necesarias 134. 135. 136. 137. 138. 139. 140. 141. 142.
enable_interrupts(int_ext); enable_interrupts(global); printf("\r\nMidiendo un pulso : Width High\r\n"); printf("By Redpic para Foro TODOPIC\r\n\n"); do { if(flagHayDatos==1){
// Detecto que ya
hay datos de flancos ... 143.
if(t2 > t1){
// Compruebo que
estoy en la misma vuelta de TMR1 144.
tt = t2 - t1;
// Calculo en
Tick's de TMR1 el tiempo entre flancos 145.
st = uSxTick * tt;
// Calculo en uS el
flagHayTransmitir=1;
// Indico que tengo
tiempo. 146.
nuevo valor para transmitir 147. 148.
} flagHayDatos=0;
// Indico que ya
han sido procesados los datos. 149. 150.
} if(Command!=0x00){
// Si he recibido
un comando vía Serie ... 151.
if(Command=='T'){
// Si el comando es
'T' (Transmite) ... 152.
if(flagHayTransmitir==1){
// Si hay algo
pendiente de transmitir ... 153. 154.
printf("Ticks %Lu - %Lu = %Lu = %3.1fuS \r\n",t2,t1,tt,st); 155. 156. flagHayTransmitir=0; // Indico que ya he
transmitido lo pendiente. 157. 158. 159.
} } Command=0x00;
procesado el comando. 160. 161. 162. } 163.
} } while (TRUE);
// Indico que ya he
Seguimos dándole vueltas al Tema de Capturar el ancho de un pulso ... En Midiendo un pulso. 1ª Parte. Tiempo en Alto con INTEXT hemos utilizado la técnica de recoger el valor de TMR1 cuando ocurre la interrupción externa por RB0, sea en el flanco de caída, sea en el de subida. Con los valores de ambos flancos y simplemente restando el uno del otro tenemos el valor del ancho del pulso. Hay otra manera de hacer exactamente lo mismo de una forma absolutamente similar: Utilizando el módulo CCP del PIC funcionando en modo Capture.
A.- Conceptos involucrados: Los conceptos son exactamente los mismos que los descritos en la parte anterior por lo que os ruego que consultéis si os es necesario: Midiendo un pulso. 1ª Parte
B.- Técnica a Aplicar: El módulo hardware CCP del PIC en configuración Capture realiza de forma automática (por hardware) lo que implementamos en nuestro anterior Técnica en C mediante la interrupción externa por RB0. Cuando activamos el módulo CCP, le configuramos el flanco que deseamos que lo dispere, subida o bajada, automáticamente cada vez que se nos presente dicho flanco en el pin correspondiente se copia el valor de TMR1, de 16 bits, en la pareja de registros CCPRL y CCPRH. Cada vez que llega un flanco tenemos en CCPR el valor en ese momento de TMR1. Si además habilitamos la Interrupción CCP del PIC se producirá además una Petición de Servicio para esta interrupción cada vez que nuestro esperado flanco se presente en el pin correspondiente. En esta rutina de interrupción podremos así cambiar el modelo de flanco a utilizar para el siguiente disparo del CCP, cambiándolo ahora al flanco de bajada, y así en el siguiente disparo tendremos en CCPR el nuevo valor de TIMER1 con lo restando éste del anterior tendremos el ancho de pulso en High que deseamos. Como véis es absolutamente identico al método anterior pero sin tener que recoger "a mano" el valor de TMR1.
C.- Implementación en C: Nota Importante: En mi ejemplo utilizo el 18F4550 de la RRBOARD2 usando del módulo CCP2, de los dos que tiene este PIC, configurándolo además para que CCP2 en lugar de estar en RC1 como originalmente está configurado y que estoy usando para otras cosas, se multiplexe por RB3 que lo tengo libre. Esto último se consigue en el 18F4550 usando el fuse CCP2B3. Para configurar inicialmente el flanco de subida a detectar utilizaremos:
Código GeSHi (c): 1.
setup_ccp2(CCP_CAPTURE_RE);
// Configuro captura de 1er
flanco de subida 2.
flagToggleFlanco = 0;
// inicializo el Flag para
cambiar de flanco 3. 4. 5.
enable_interrupts(int_ccp2); enable_interrupts(global);
Nuestra rutina ISR para CCP quedaría como sigue: Código GeSHi (c): 1. #int_ccp2 2. void handle_ccp2_int(){ 3. if(flagToggleFlanco==0){
// He recibido
Flanco de Subida 4.
t1=CCP_2;
// Guardo la captura del
CCP2 al Flanco de Subida 5.
// Configuro para
setup_ccp2(CCP_CAPTURE_FE);
capturar siguiente flanco de Bajada 6.
// Indico que el
flagToggleFlanco=1;
siguiente flanco será de Bajada 7. 8.
// He recibido
} else {
Flanco de Bajada 9. 10.
t2=CCP_2;
// Guardo en t2 la nueva
captura de CCP2 al Flanco de Bajada 11.
setup_ccp2(CCP_CAPTURE_RE);
// Configuro
para capturar siguiente flanco de subida 12.
flagToggleFlanco=0;
// Indico que el
siguiente flanco será de Subida 13.
if(flagHayDatos==0){
// Si los datos
anteriores han sido procesados ... 14.
flagHayDatos=1;
// Indico que ya
hay nuevos datos de flancos para calcular 15. 16. 17.
} }
El resto de la implementación en C de esta Técnica es identica a la mostrada en Midiendo un pulso. 1ª Parte. Tiempo en High con Int_Ext
D.- Ejemplo funcionando:
La señal inyectada podéis verla en :
Bueno, y esto es todo por hoy amigos.
Y por fin le damos una vuelta de tuerca al Tema de Capturar el ancho de un pulso ... Por indicación del amigo filth ampliamos lo que hemos visto en Midiendo un pulso. 1ª Parte. Tiempo en Alto con INTEXT y en Midiendo un pulso. 2ª Parte. Tiempo en Alto con INTCCP. En ambas hemos utilizado la técnica de recoger el valor de TMR1 cuando ocurre o la interrupción externa por RB0 o la interrupcion CCP, sea en el flanco de caída, sea en el de subida. Con los valores de ambos flancos y simplemente restando el uno del otro tenemos el valor del ancho del pulso. Hay otra manera de hacer exactamente lo mismo utilizando una tercera fuente de interrupción de los PIC's: Utilizando la intetrrupción por cambio de estado de RB04:7
A.- Conceptos involucrados: Los conceptos son exactamente los mismos que los descritos en las parte anteriores por lo que os ruego que consultéis si os es necesario: Midiendo un pulso. 1ª Parte y/o 2ª Parte.
B.- Técnica a Aplicar: La interrupción por Cambio de Estado de los pines 4 a 7 del PORTB es una interrupción muy socorrida aunque algo falta de precisición. No dispone como en nuestros anteriores ejemplos INTEXT e INTCCP de la posibilidad de pre-programar el flanco que deseamos que dispare la Petición de Servicio de Interrupción ( ISR). Aunque esta interrupción, a la que a partir de aquí llamaremos INTRB, afecta en principio a los bits desde el 4 hasta el 7 del PORTB, solo lo hará efectivamente a aquellos que esten configurados como de entrada , que hayamos programado con el TRIS a 1. Al dispararse la interrupción solo sabemos que uno o mas de estos pines ha cambiado de estado . Nada mas, ni nada menos. Por ello es imprescindible al usar INTRB que vayamos recogiendo sobre una variable estática de un byte los cambios sucesivos que se van produciendo para poder comparar en el momento de recibir la interrupción el estado actual del PORTB con el inmediatamente anterior. Así si el bit correspondiente al pin que estamos utilizando para nuestra medición del pulso es igual en el estado actual que en el anterior no tenemos cambio en el pulso y además no ha sido éste pin el responsable de disparar la interrupción. Si el estado anterior era bajo y el actual es alto estamos ante el flanco de subida de nuestro pulso y además es él quien ha producido el disparo de la interrupción. Si por el contrario el estado anterior del pin era alto y ahora es bajo entonces acabamos de detectar el flanco de bajada de nuestro pulso, y por supuesto también ha
sido él el responsable de disparar la interrupción INTRB. Con esta información realizaremos exactamente igual que en los ejemplos anteriores: guardamos el valor de TMR1 tras el flanco de subida y tras el de bajada, realizamos la correspondiente resta entre ambos valores y transmitimos los resultados.
C.- Implementación en C: Para configurar inicialmente nuestra interrupción INTRB debemos hacer que nuestro pin, en este caso el 4, sea de entrada, y habilitaremos las interrupciones necesarias con: Código GeSHi (c): 1.
set_tris_b(0b00010000);
// Habilito como
entrada RB7 para interrupción RB 2. 3. 4.
enable_interrupts(int_rb); enable_interrupts(global);
Nuestra rutina ISR para INTRB quedaría como sigue: Código GeSHi (c): 1. int estado_portb_anterior=0; 2. int estado_portb_actual=0; 3. 4. ... 5. 6. #int_rb 7. void handle_rb_int(){ 8. 9. estado_portb_actual=input_b(); 10. if ((!bit_test(estado_portb_anterior,4))&&(bit_test(estado_portb_ac tual,4))) 11. { 12. hay_flanco_de_bajada=0; 13. } 14. 15. if(hay_flanco_de_bajada!=0){ // He recibido
Flanco de Subida 16.
t1=get_timer1();
// Guardo en t1 el
valor de TMR1 al Flanco de Subida 17.
} else {
// He recibido
Flanco de Bajada 18.
t2=get_timer1();
// Guardo en t2 el
valor de TMR1 al Flanco de Bajada 19. 20.
set_timer1(0); if(flagHayDatos==0){
// Reinicio TMR1 // Si los datos
anteriores han sido procesados ... 21.
flagHayDatos=1;
hay nuevos datos de flancos para calcular 22. 23.
} }
// Indico que ya
24. 25.
}
El resto de la implementación en C de esta Técnica es identica a la mostrada en Midiendo un pulso. 1ª Parte. Tiempo en Alto con INTEXT y Midiendo un pulso. 2ª Parte. Tiempo en Alto con INTCCP Bueno, y esto es todo por hoy amigos.
Midiendo un pulso. 4ª Parte. El pulso completo. El Periodo y la Frecuencia A.- Conceptos involucrados: Si lo creéis oportuno podéis antes darle un repaso a la misma sección de Conceptos de la parte 1ª de esta Técnica en C : Tiempo en Alto. Los coceptos son los mismos y aquí solo vamos a hacer algunas consideraciones sobre los mismos. Decíamos allí que el Tiempo T que transcurre ente dos flancos sucesivos de subida (o de bajada) es lo que conocemos como Periodo del Pulso (Period), o sea: lo que dura un pulso completo. Cada T segundos (ó milisegundos, ó microsegundos) tenemos un nuevo pulso completo. Como vemos, cada pulso tiene un Tiempo en Alto (High Time) y un Tiempo en estado Bajo (Low Time). La suma de los dos es el Periodo T del pulso.
Como vemos en la imagen superior un pulso completo comienza con un flanco de subida en T1, sigue con uno de bajada en T2 y concluye en el nuevo flanco de subida T3. Entre T1 y T2 permanece el pulso en Alto, Wh, mientras que entre T2 y T3 lo hace en bajo, Wl. El tiempo transcurrido entre T1 y T3 es el Periodo del Pulso. Si este periodo lo expresamos en Segundos entonces su inverso, 1 / Periodo, es la Frecuencia del tren de pulsos. Estos dos valores son los quie vamos a calcular con nuestro PIC: el Periodo T y la frecuencia F.
B.- Técnica a Aplicar: Para medir el periodo T del pulso vamos a utilizar los dos mi smos recursos que en la 1ª parte de este artículo, y de los que disponen la gran mayoria de los PIC's, tanto los de la serie 18F como los 16F: El TIMER1 y la Interrupción Externa por RB0 . Para ver los detalles de estos recursos dadle también un vistazo a la primera parte de esta Técnica en C 1ª Parte. Tiempo en High. (Quito este trozo para no sobrepasar los límites de un Post que nos impone el Foro
Aunque para calcular el Periodo del pulso y/o su Frecuencia no es necesario tomar el Tiempo T2 intermedio del Flanco de Bajada, ya que solo nos son necesarios T1 y T3,
)
los dos flancos de Subida sucesivos, vamos a calcularlos t odos ya que así tendremos también cuánto tiempo permanece tanto en alto como en bajo y tendremos de esta forma el tanto por ciento de la primera fase sobre la segunda del pulso, o sea: El Duty Cicle. * Lo que vamos a realizar es:
1.- Configuraremos la Int_Ext por RB0 para detectar inicialmente el Flanco de Subida. 2.- Al llegarnos este Flanco de Subida guardaremos el valor en ese momento de TIMER1 y ... 3.- Cambiaremos la configuración de la Int_Ext por RB0 para detectar el siguiente Flanco de Bajada. 4.- Cuando nos llegue el Flanco de Bajada guardaremos de nuevo el valor de TIMER1 y ... 5.- Volveremos a configurar la Int_Ext por RB0 para detectar de nuevo un Flanco de Subida. Y ... 6.- Al llegarnos este nuevo Flanco de Subida guardaremos también el valor en ese momento de TIMER1 y ... 7.- Con estos tres valores de TIMER1 tendremos, expresados en Tick's de TIMER1, restando el tercero del primero, el tiempo que ha durado el Pulso. Multiplicando dicho número de Tick's de TIMER1 por el tiempo que dura cada Tick (dependiente del cristal que usemos) tendremos el Perido T que estamos buscando. Con 1 / T obtendríamos la frecuencia del Pulso. C.- Implementación en C: Para implementar nuestro Código en C vamos a centrarnos en los puntos que hemos descrito en la sección anterior. Para configurar inicialmente el flanco de subida a detectar utilizaremos igual quen la primera parte: Código GeSHi (c): 1.
ext_int_edge(0,L_TO_H);
// Configuro captura
de 1er flanco de subida 2.
flagToggleFlanco = 0;
// inicializo el
Flag para cambiar de flanco 3.
enable_interrupts(int_ext);
// Habilito las
interrupciones necesarias 4. 5.
enable_interrupts(global);
Hemos añadido la variable int1 flagToggleFlanco debido a que una vez establecido el flanco a detectar no tenemos oportunidad de saber qué flanco es el que estamos esperando, así si flagToggleFlanco tiene un valor de 0 es que estamos esperando el de Subida y si por el contrario flagToggleFlanco tiene un valor de 1 entonces es el de bajada el que esperamos. Con esta configuración inicial podemos ya escribir nuestra rutina ISR:
Código GeSHi (c): 1. #int_ext 2. void handle_ext_int(){ 3. 4. ++numFlancoQueLlega;
// Cuento flanco que
nos llega 5.
if(flagToggleFlanco==0){
// He recibido
Flanco de Subida 6. 7. 8.
if(numFlancoQueLlega==1){ set_timer1(0); t1=get_timer1();
// Reinicio TMR1 // Guardo en t1 el
valor de TMR1 al primer Flanco de Subida 9. 10. 11.
} if(numFlancoQueLlega==3){ t3=get_timer1();
// Guardo en t1 el
valor de TMR1 al primer Flanco de Subida 12.
if(flagHayDatos==0){
// Si los datos
anteriores han sido procesados ... 13.
flagHayDatos=1;
// Indico que ya
hay nuevos datos de flancos para calcular 14. 15. 16.
} } ext_int_edge(0,H_TO_L);
// Configuro para
capturar siguiente flanco de Bajada 17.
flagToggleFlanco=1;
// Indico que el
siguiente flanco será de Bajada 18.
} else {
// He recibido
Flanco de Bajada 19.
t2=get_timer1();
// Guardo en t2 el
valor de TMR1 al Flanco de Bajada 20.
ext_int_edge(0,L_TO_H);
// Configuro para
capturar siguiente flanco de subida 21.
flagToggleFlanco=0;
// Indico que el
siguiente flanco será de Subida 22. 23.
} FLASH;
// Reproduzco la
entrada mediante un LEd en E0; 24. 25. 26. 27. 28.
if(numFlancoQueLlega==3){ numFlancoQueLlega=0; } }
Fijaos que en la rutina ISR anterior no realizamos ningún cálculo . Aplicamos aqui el principio fundamental de que " dentro de una rutina de interrupción haz lo debas y abandónala lo antes que puedas ". Asi que si tenemos valores válidos de T1, T2 y T3 entonces solo hacemos que flagHayDatos valga 1 y ya trataremos estos valores dentro de main(). Con numFlancoQueLlega sabremos qué flanco de Subida es T1 y cúal es T3 ya que solo cuando obtengamos T3 daremos por concluida nuestra captura. Código GeSHi (c):
1.
if(flagHayDatos==1){
// Detecto que ya
hay datos de flancos ... 2.
if((t3>t2)&&(t2>t1)){
// Compruebo que
estoy en la misma vuelta de TMR1 3.
tth = t2 - t1;
// Calculo en Tick's
de TMR1 el tiempo en Alto 4.
ttl = t3 - t2;
// Calculo en Tick's
de TMR1 el tiempo en Bajo 5.
tt
= tth + ttl;
// Calculo en Tick's
de TMR1 el Periodo del Pulso 6.
sth = uSxTick * tth;
// Calculo en uS el
tiempo en Alto. 7.
stl = uSxTick * ttl;
// Calculo en uS el
tiempo en Bajo. 8.
st
= uSxTick * tt;
// Calculo en uS el
tiempo el Periodo. 9.
f
= 1 / (st / 1000000);
// Calculo la
Frecuencia 10.
flagHayTransmitir=1;
// Indico que tengo
nuevo valor para transmitir 11. 12.
} flagHayDatos=0;
// Indico que ya
han sido procesados los datos. 13. 14.
}
Cuando flagHayDatos es 1 podemos proceder a calcular los Ticks transcurridos entre un flanco y otro. Fijaos que preguntamos si T3 es mayor que T2 y que éste es a su vez mayor que T1 . Esto es debido a una limitación que nos impone el no tener en cuenta vueltas completas de TIMER1 para realizar nuestros cálculos: T3, T2 y T1 deben estar dentro de una misma vuelta de TIMER2, o sea que el pulso debe ser menor que 13,1072 milisegundos. Notad que cuando detectamos el primer flanco volvemos a poner TIMER1 a cero para recomenzar siempre cerca del inicio de la cuenta de TIMER1 y evitar asi su desborde. Para computar tiempos de pulso mayores 13,1072 milisegundos solo habría que computar tambien cuantas veces se desborada completo TIMER1 y realizar los cálculos aritméticos correspondientes. Al igual que hacíamos en la Rutina ISR en que no calculábamos sino que solo indicábamos que podíamos calcular, aquí una vez realizados los calculos de Tick´s y su equivalencia en segundos no los transmitimos, sólo indicamos que hay datos para transmitir mediante poner a 1 flagHayTransmitir. Para transmitir el último valor computado vamos a implementar una recepción vía serie RS232 que al recibir el comando ' T' nos envía el último valor correcto registrado: Código GeSHi (c): 1.
if(Command!=0x00){
comando vía Serie ...
// Si he recibido un
2.
if(Command=='T'){
// Si el comando es
'T' (Transmite) ... 3.
if(flagHayTransmitir==1){
// Si hay algo
pendiente de transmitir ... 4.
printf("Ticks ....... H %4Lu + L %4Lu = %4Lu\r\n",tth,ttl,tt); 5. printf("uSegundos ... H %3.1f + L %3.1f = %3.1f F = %4.2f Hz\r\n\n",sth,stl,st,f); 6. flagHayTransmitir=0; // Indico que ya he
transmitido lo pendiente. 7. 8. 9.
} } Command=0x00;
// Indico que ya he
procesado el comando. 10. 11.
}
Con todo esto el programa completo para nuestro Detector de Periodo y Frecuencia de un Pulso queda así: Código GeSHi (c): 1. ////////////////////////////////////////////////////////////////
//////////////////// 2. // 3. // Midiendo_un_pulso_2_Complete.c 4. // 5. // SERIE: "Técnicas en C" para el Foro TODOPIC 6. // 7. // (c) 10.2006 by RedPic 8. // 9. // Propósito: Medir los tiempos en alto y bajo que permanece un pulso : Periodo y Frecuencia 10. // 11. // Condiciones de Test: Inyección por RB0 de una señal de 2 Khz (0.5 ms de periodo) 12. // 13. // Técnica Empleada: Detectar mediante la Interrupción Externa por RB0 14. // un flanco de subida de un pulso, guardar el estado 15. // de TMR1, detectar a continuación el siguiente 16. // flanco de bajada, guardar el nuevo estado de TMR1, 17. // detectar a continuación un nuevo flanco de subida, 18. // guardar nuevamente el estado de TMR1 y realizar la 19. // correspondiente substracción de ellos para obtener 20. // el tiempo que permanece en alto y bajo y transmitir 21. // los resultados mediante el puerto RS232 a petición. 22. // 23. /////////////////////////////////////////////////////////////// /////////////////////
24. 25. 26. 27.
#include <18f4550.h> #fuses HS,MCLR,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOPBADEN,NOLVP,NOCPD,NO DEBUG,NOWRT,NOVREGEN 28. #use delay(clock=20000000) 29. 30. #use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7) 31. 32. 33. /////////////////////////////////////////////////////////////// 34. 35. 36. 37.
///////////////////// // // Defines y Constantes // /////////////////////////////////////////////////////////////// /////////////////////
38. 39.
#define LED PIN_E0 del Led 40. #define FLASH Output_Toggle(LED) funcion Flash de monitor 41. 42. float const uSxTick = 0.2;
// Defino el Pin // Defino la
// Microsegundos
por Tick de TMR1 a 20 Mhz 43. 44. 45.
/////////////////////////////////////////////////////////////// ///////////////////// 46. // 47. // Variables en RAM 48. // 49. /////////////////////////////////////////////////////////////// ///////////////////// 50. 51.
char
cRec=0x00;
// Último caracter
recibido via serie 52.
char
Command=0x00;
// Comando a
procesar 53.
int8
numFlancoQueLlega=0;
// Número de Flanco
que llega 54.
int1
flagToggleFlanco=0;
// Flag para
cambiar de flanco 55.
int16 t1=0x00,t2=0x00,t3=0x00;
// Variables para
guardar estados de ... 56. 57.
int16 tth=0x00,ttl=0x00,tt=0x00; float sth=0.0,stl=0.0,st=0.0,f=0.00;
// Timers y pulsos. // Para hacer las
restas oportunas en uS 58.
int1
flagHayDatos=0;
// Flag para
indicar que ya hay datos de .. // dos flancos (de
59.
subida y bajada) 60.
int1
flagHayTransmitir=0;
// Flag para
indicar que hay datos para ... // Transmitir al
61.
PC. 62. 63.
/////////////////////////////////////////////////////////////// ///////////////////// 64. //
65. 66. 67.
// Interrupción por Recepción Serie RS232 // /////////////////////////////////////////////////////////////// /////////////////////
68. 69. 70. 71. 72. 73.
#int_rda void handle_rda_int(){ if(kbhit()){
// Si hay algo pdte
de recibir ... 74.
cRec=getc();
// lo recibo sobre
cRec ... 75.
if(cRec!=0x00){
// Si es distinto
de \0 ... 76.
Command=ToUpper(cRec);
// cargo cRec sobre
Command para procesarlo 77.
// pasándolo a
}
Mayúsculas para no confundir. 78. 79. 80. 81.
} }
/////////////////////////////////////////////////////////////// ///////////////////// 82. // 83. // Interrupción por Externa por Cambio de Flanco en RB0 84. // 85. /////////////////////////////////////////////////////////////// ///////////////////// 86. 87. 88. 89. 90. 91.
#int_ext void handle_ext_int(){ ++numFlancoQueLlega;
// Cuento flanco
que nos llega 92. 93.
if(flagToggleFlanco==0){
// He recibido
Flanco de Subida 94. 95. 96. 97.
if(numFlancoQueLlega==1){ set_timer1(0); t1=get_timer1();
// Reinicio TMR1 // Guardo en t1 el
valor de TMR1 al primer Flanco de Subida 98. 99. 100.
} if(numFlancoQueLlega==3){ t3=get_timer1();
// Guardo en t1 el
valor de TMR1 al primer Flanco de Subida 101.
if(flagHayDatos==0){
// Si los datos
anteriores han sido procesados ... 102.
flagHayDatos=1;
// Indico que ya
hay nuevos datos de flancos para calcular 103. 104. 105.
} } ext_int_edge(0,H_TO_L);
// Configuro para
capturar siguiente flanco de Bajada 106.
flagToggleFlanco=1;
// Indico que el
siguiente flanco será de Bajada 107. 108.
} else {
Flanco de Bajada
// He recibido
109. 110.
t2=get_timer1();
// Guardo en t2 el
valor de TMR1 al Flanco de Bajada 111.
ext_int_edge(0,L_TO_H);
// Configuro para
capturar siguiente flanco de subida 112.
flagToggleFlanco=0;
// Indico que el
siguiente flanco será de Subida 113. 114. 115.
} FLASH;
// Reproduzco la
entrada mediante un LEd en E0; 116. 117. if(numFlancoQueLlega==3){ 118. numFlancoQueLlega=0; 119. } 120. 121. } 122. void main() { 123. 124. 125. ////////////////////////////////////////// INICIALIZACIONES
GENERALES 126. 127.
delay_ms(333);
// Espero a que
todo se estabilice e ... 128.
disable_interrupts(global);
// Inicializo el
Micro y ... 129.
disable_interrupts(int_timer1);
// deshabilitando
todo lo no necesario ... 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148.
disable_interrupts(int_rda); disable_interrupts(int_ext); disable_interrupts(int_ext1); disable_interrupts(int_ext2); setup_adc_ports(NO_ANALOGS); setup_adc(ADC_OFF); setup_spi(FALSE); setup_psp(PSP_DISABLED); setup_counters(RTCC_INTERNAL,RTCC_DIV_2); setup_timer_0(RTCC_OFF); setup_timer_1(T1_INTERNAL | T1_DIV_BY_1); setup_timer_2(T2_DISABLED,0,1); setup_timer_3(T3_DISABLED); setup_comparator(NC_NC_NC_NC); setup_vref(FALSE); port_b_pullups(FALSE); delay_ms(333);
/////////////////////////////////////////// INICIALIZACIONES PERTINENTES A LA APLICACION
149. 150.
set_tris_c(0b10000000);
// Habilito como
entrada RC7 para canal RS232 151. 152.
ext_int_edge(0,L_TO_H);
// Configuro
captura de 1er flanco de subida 153.
flagToggleFlanco = 0;
// inicializo el
Flag para cambiar de flanco 154. 155.
enable_interrupts(int_rda);
interrupciones necesarias 156.
enable_interrupts(int_ext);
// Habilito las
157. 158. 159. 160. 161. 162. 163. 164.
enable_interrupts(global); printf("\r\nMidiendo un pulso : Periodo\r\n"); printf("By Redpic para Foro TODOPIC\r\n\n"); do { if(flagHayDatos==1){
// Detecto que ya
hay datos de flancos ... 165.
if((t3>t2)&&(t2>t1)){
// Compruebo que
estoy en la misma vuelta de TMR1 166.
tth = t2 - t1;
// Calculo en
Tick's de TMR1 el tiempo en Alto 167.
ttl = t3 - t2;
// Calculo en
Tick's de TMR1 el tiempo en Bajo 168.
tt
= tth + ttl;
// Calculo en
Tick's de TMR1 el Periodo del Pulso 169.
sth = uSxTick * tth;
// Calculo en uS el
stl = uSxTick * ttl;
// Calculo en uS el
st
= uSxTick * tt;
// Calculo en uS el
f
= 1 / (st / 1000000);
// Calculo la
tiempo. 170.
tiempo. 171.
tiempo. 172.
Frecuencia 173. 174.
flagHayTransmitir=1;
// Indico que tengo
nuevo valor para transmitir 175. 176.
} flagHayDatos=0;
// Indico que ya
han sido procesados los datos. 177. 178.
} if(Command!=0x00){
// Si he recibido
un comando vía Serie ... 179.
if(Command=='T'){
// Si el comando es
'T' (Transmite) ... 180.
if(flagHayTransmitir==1){
// Si hay algo
pendiente de transmitir ... 181. 182. printf("Ticks ....... H %4Lu + L %4Lu = %4Lu\r\n",tth,ttl,tt); 183. printf("uSegundos ... H %3.1f + L %3.1f = %3.1f F = %4.2f Hz\r\n\n",sth,stl,st,f); 184. 185. flagHayTransmitir=0; // Indico que ya he
transmitido lo pendiente. 186. 187. 188.
} } Command=0x00;
procesado el comando. 189. 190. 191. 192. 193. } 194.
}
} while (TRUE);
D.- Ejemplo funcionando:
// Indico que ya he