TODOPIC
Microcontroladores PIC => Todo en microcontroladores PIC => Mensaje iniciado por: kobukguille 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:
#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:
#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:
-
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
-
Ostras!
Gracias KILLERJC!!!!
Realmente cuando te obcecas en algo dejas de ver más allá...
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:
.................... 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!!!!
-
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.
-
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 :-/
-
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.