Autor Tema: Problema con el uso de interrupción TMR0  (Leído 2159 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado kobukguille

  • PIC10
  • *
  • Mensajes: 6
Problema con el uso de interrupción TMR0
« en: 27 de Agosto de 2015, 16:36:37 »
Buenas,

Tengo un problema, o un error de concepto y lo que calculo luego no lo reproduce el PIC.
Veamos: quiero que se produzca una interrupción cada 1ms para realizar un cronometro de milésimas de segundo.

Usando la fórmula del TMR0= 4·(1/Fosc)·(255-PrecargaTMR0)·Prescaler
Substituyo usando prescaler 4, cristal externo de 4Mhz y como decía 1ms para la interrupción, y obtengo una precarga para el TMR0 de 5.

Hasta aquí genial. Tengo mis dudas de si en la formula hay que restar a 255 o a 256, creo que es 255 ya que són 256 valores (de 0 a 255) no?
Pero vaya... el error no viene de aquí diría yo, seria una variación mínima. El problema es que cuando lo compilo y lo pongo en el PIC se demora...

Ahí va el código:

Código: [Seleccionar]
#include<16F84A.h>
#fuses XT,NOWDT,NOPROTECT //Fuses
#use delay(clock=4000000)
#use standard_io(B)
#use fixed_io(a_outputs=PIN_A0,PIN_A1,PIN_A2)    //A0,A1,A2 como salidas en porta

char i=0,j=0,k=0;   //variables globales

///LLAMADA FUNCION INTERRUPCION
#INT_TIMER0
void interrupcion()
{
   if(i>9){      //¿se ha mostrado por 1º 7seg digito 9?
        i=0;      //SI -> i=0 (muestra digito 0) (*)
        j++;      //incremento indice j
     
        if(j>9){      //¿se ha mostrado por 1º 7seg digito 9?
             j=0;      //SI -> j=0 (muestra digito 0) (*)
             k++;      //incremento indice k
         
         if(k>9){    //¿se ha mostrado por 2º 7seg digito 9?
                k=0;}   //SI -> j=0 (muestra digito 0)
            }
         }
      else{         //(*) NO -> incrementa i
           
          i++;}
     
   set_timer0(36);      //reset TMR0
}

///PROGRAMA
void main(void)
{
   int tab7seg[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x67};   //7seg hex 0-9
   set_tris_b(0x00);                         //portb como salida
   enable_interrupts(INT_TIMER0);            //interrupcion TIMER0 activada
   setup_counters(RTCC_INTERNAL,RTCC_DIV_4); //configuracion interrupcion TMR0
   set_timer0(36);                            //5 carga TMR0 + 31 ciclos que demoran las instrucciones de la rutina
   enable_interrupts(GLOBAL);                //activadas interrupciones

   do{                           //bucle...
      output_b(tab7seg[ i ]);   //muestra por portb digito 7 segmentos
     output_low(PIN_A2);      //3º 7seg off
     output_low(PIN_A1);      //2º 7seg off           
      output_high(PIN_A0);       //1º 7seg on
      delay_ms(10);           
      output_b(tab7seg[ j ]);   //muestra por portb digito 7 segmentos
      output_low(PIN_A2);      //3º 7seg off
     output_high(PIN_A1);       //2º 7seg on           
      output_low(PIN_A0);      //1º 7seg off
      delay_ms(10);           
      output_b(tab7seg[ k ]);   //muestra por portb digito 7 segmentos
     output_high(PIN_A2);       //3º 7seg on
     output_low(PIN_A1);      //2º 7seg off           
      output_low(PIN_A0);      //1º 7seg off
      delay_ms(10);           
      }while(TRUE);             //...infinito         
}

Buscando y leyendo he entendido que las instrucciones dentro de la interrupción demoran la temporización, así que he mirado cuantos ciclos ocupaba y lo he compensado sumando 31 a la precarga. Queda 5+31=36
Esto es una chapuza, no acaba de ser exacto, pues hay instrucciones que no se si duran 1 o 2 ciclos. Así que a la vista y comparando con el crono de mi reloj parece que funciona bien, pero me gustaría más exactitud.
Para ello he pensado que si la interrupción tarda mucho menos en producirse, más rápido se ejecutaran las instrucciones no? pero sigo queriendo milisegundos. De esta manera creo una variable que controlará los milisegundos. Temporizo la interrupción para 10us y cada vez que entre a la interrupción la incremento hasta llegar al milisegundo (10usx100) y entonces ejecuto las instrucciones.

Usando la fórmula del TMR0= 4·(1/Fosc)·(255-PrecargaTMR0)·Prescaler
Substituyo usando prescaler 2, cristal externo de 4Mhz y como decía 10us para la interrupción, y obtengo una precarga para el TMR0 de 250.

Ahí va de nuevo el código:

Código: [Seleccionar]
#include<16F84A.h>
#fuses XT,NOWDT,NOPROTECT //Fuses
#use delay(clock=4000000)
#use standard_io(B)
#use fixed_io(a_outputs=PIN_A0,PIN_A1,PIN_A2)    //A0,A1,A2 como salidas en porta

char i=0,j=0,k=0,x=0;   //variables globales

///LLAMADA FUNCION INTERRUPCION
#INT_TIMER0
void interrupt()
{
set_timer0(250);      //reset TMR0       
if(x==100){ //ajuste fino 1ms  si interrupcion desborda cada 10us
      x=0;
      if(i>9){      //¿se ha mostrado por 1º 7seg digito 9?
        i=0;      //SI -> i=0 (muestra digito 0) (*)
        j++;      //incremento indice j
        if(j>9){      //¿se ha mostrado por 2º 7seg digito 9?
             j=0;      //SI -> j=0 (muestra digito 0) (*)
             k++;      //incremento indice k
             if(k>9){    //¿se ha mostrado por 3º 7seg digito 9?
                k=0;}   //SI -> j=0 (muestra digito 0)
        }
       }
       else{         //(*) NO -> incrementa i
         i++;}
      }
else{
x++;   
}
}

///PROGRAMA
void main()
{
   int tab7seg[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x67};   //7seg hex 0-9
   set_tris_b(0x00);                         //portb como salida
   enable_interrupts(INT_TIMER0);            //interrupcion TIMER0 activada
   setup_counters(RTCC_INTERNAL,RTCC_DIV_2); //configuracion interrupcion TMR0
   set_timer0(250);                            //5 carga TMR0
   enable_interrupts(GLOBAL);                //activadas interrupciones

   while(true){                           //bucle...
      output_b(tab7seg[ i ]);   //muestra por portb digito 7 segmentos
      output_low(PIN_A2);      //3º 7seg off
      output_high(PIN_A1);      //2º 7seg off           
      output_low(PIN_A0);       //1º 7seg on
      delay_ms(1);           
      output_b(tab7seg[ j ]);   //muestra por portb digito 7 segmentos
      output_low(PIN_A2);      //3º 7seg off
      output_low(PIN_A1);       //2º 7seg on           
      output_high(PIN_A0);      //1º 7seg off
      delay_ms(1);           
      output_b(tab7seg[ k ]);   //muestra por portb digito 7 segmentos
      output_high(PIN_A2);       //3º 7seg on
      output_low(PIN_A1);      //2º 7seg off           
      output_low(PIN_A0);      //1º 7seg off
      delay_ms(1);
      }             //...infinito         
}

Y aquí es dónde llevo encallado des de hace ya demasiadas horas...  :5]
Si os fijáis he tenido que variar de 10 a 1 el delay de visualización del 7seg para ver los tres a la vez y aun así no se ve tan bien como antes.
Y ahora tarda casi siete segundos y medio en contar 999ms. Ahora que yo suponía que debía de ser más exacto es desastrosamente inexacto...

Está claro que debo de tener un error de concepto de como funciona o de como programarlo pero no se cual es por más vueltas que le doy...
Como puedo hacerlo?

Mil gracias por vuestra atención, espero que me podáis echar una mano  :oops:

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re: Problema con el uso de interrupción TMR0
« Respuesta #1 en: 27 de Agosto de 2015, 17:12:55 »
Citar
Usando la fórmula del TMR0= 4·(1/Fosc)·(255-PrecargaTMR0)·Prescaler

es 256 asi que deberias cargar 6.

La interrupcion deberia tener instrucciones para que valga la pena obviamente, el tema es que no debe tardar demasiado. Para obtener un valor lo mas cerca a lo que queres deberias de cargar el timer apenas entras de esa forma no influye las demas instrucciones, Aun asi si quisieras algo realmente exacto tenes que contar las instrucciones tal cual hiciste ya que el 16F84 no posee las "herramientas" para lograrlo tal como un modulo CCP.

El programa es simple y deberia funcionar

Desconectado kobukguille

  • PIC10
  • *
  • Mensajes: 6
Re: Problema con el uso de interrupción TMR0
« Respuesta #2 en: 27 de Agosto de 2015, 18:42:00 »
Ostras!
Gracias KILLERJC!!!!
Realmente cuando te obcecas en algo dejas de ver más allá...
Citar
Para obtener un valor lo mas cerca a lo que queres deberias de cargar el timer apenas entras de esa forma no influye las demas instrucciones.
En el segundo código si que lo había cambiado y recargaba el timer nada más entrar, pero en el primero no... lo hacia al final :(

No me liare con el CCP pero para acabar de precisarlo... crees que debería sumar 2 a la precarga del timer para compensar las dos instrucciones que supone la misma precarga? En la inicial del main no, en la de la interrupción solamente me refiero. Los seis que ya tenia según la fórmula que corregiste + dos por las instrucciones:

Citar
....................    set_timer0(8);      //reset TMR0
*
0036:  MOVLW  06
0037:  MOVWF  01
....................    if(i>9){      //¿se ha mostrado por 1º 7seg digito 9?

De todas formas ahora me parece que ya está bastante ajustado para lo que quiero, GRACIAS!!!!
« Última modificación: 27 de Agosto de 2015, 18:47:14 por kobukguille »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re: Problema con el uso de interrupción TMR0
« Respuesta #3 en: 27 de Agosto de 2015, 18:56:20 »
es muy complicado saber cuantas instrucciones te va a poner antes el compilador en C.

Si quisiera estimar y hacerlo en ASM deberia pensar en unas 4 instrucciones tal ves 6 al entrar a la interrupcion, para guardar STATUS,W y no se si guarda mas cosas en el stack de C (SP y FP). Y luego las 2 del MOVLW/MOVWF, creo que facil unas 10 instrucciones, aun asi no vas a estar demasiado seguro nunca de la cantidad de instrucciones que te pone dentro el Compilador C. Y si fuera poco el Timer tambien tiene un delay de unos ciclos para la carga al TMR0

El PIC16F84A no posee modulo CCP (Capture/Compare/PWM) asi que no te vas a tener que preocupar por eso :)

Si necesitas tiempos demasiados precisos no se si lo haria con ese micro, y tal ves me buscaria otro con mayor velocidad y algun modulo (como el CCP) que te ayude con esa tarea.
Pero si no te interesa que pasen 6 instrucciones ( 6 microsegundos, 0.000006s a 4Mhz )  entonces no vale la pena complicarse la vida.
« Última modificación: 27 de Agosto de 2015, 22:37:17 por KILLERJC »

Desconectado kobukguille

  • PIC10
  • *
  • Mensajes: 6
Re: Problema con el uso de interrupción TMR0
« Respuesta #4 en: 28 de Agosto de 2015, 00:09:33 »
Gracias de nuevo  :)

Pues lo estoy simulando en proteus con un 18F2550, que si no voy mal encaminado puedo hacer uso de CCP, por si luego también me atrevo a monitorizar los resultados en el PC vía USB y guardarlos en una hoja de resultados de excel.

Me recomiendas algún lugar dónde explique con claridad el uso del CCP para poder empezar a realizar alguna prueba de código?

Muy agradecido  :-/


Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re: Problema con el uso de interrupción TMR0
« Respuesta #5 en: 28 de Agosto de 2015, 02:25:41 »
Citar
Me recomiendas algún lugar dónde explique con claridad el uso del CCP para poder empezar a realizar alguna prueba de código?

El datasheet xD, si miras los graficos y entendes como funciona un timer vas a entender como funciona.

El modulo CCP ( Capture / Compare / PWM ) usa del timer ( algunos del timer 1 otros del timer 2 )

En modo captura, detecta el flanco ascendente/descendente/ambos y cuando se produce guarda el valor del Timer en los registros del CCP
En modo compare, justamente compara el valor de lo que hay en el registro del CCP y el valor del Timer, cuando se produce una coincidencia puede ocurrir varias cosas segun como se configure:
- Se pone a 1 una salida
- Se pone a 0 una salida
- Se resetea el timer a 0 ( este es el que te interesa )

De esa forma si tenes que contar 5000 pulsos, pones 5000 en el modulo del CCP y el timer a 0 por unica vez, cuando llega a 5000 el timer y se produce la coincidencia. Pone nuevamente el timer a 0 por hardware ( asi cuenta de vuelta otros 5000) y activa la interrupcion.

El modo PWM es parecido, nomas que ahora se compara el timer con 2 registros, 1 que es el duty, y que cuando ocurre una coincidencia pone a 0 una salida y el otro es el periodo, que cuando coincide resetea todo y pone a 1 la salida, de esa forma tenes un PWM en un pin.


 

anything