El temporizador PIC16 no funciona... ¿es mi código?

Soy nuevo aquí como OP, pero he estado visitando este sitio durante años. Esta vez, solo mirar otros problemas no me ha ayudado. ¡Por lo tanto, me registro y solo pregunto!

Estoy creando un temporizador de cuenta regresiva con 30 luces que se apagan una tras otra por un tiempo ajustable (para la escuela primaria de mis amigas). Estoy usando pic16f628a y MPLAB con el compilador XC8. El hardware no es un problema, he tenido bastante práctica. El software por otro lado..

El temporizador funciona porque puedo usar los botones para aumentar el tiempo establecido, presionar inicio, todas las luces se encienden y ver cómo las luces cuentan lentamente (o, a veces, rápido). Sin embargo, no puedo arreglar el tiempo. Por ejemplo, el conjunto de 2 minutos dura 3, el conjunto de 4 minutos dura 5. Lo que sea que haya hecho con todas las variables relevantes, no funciona. El tiempo cambia, pero nunca correctamente. Por ejemplo, duplicar el número de interrupciones por luz no duplica (¡ni siquiera casi!) el tiempo y nunca lo hago bien. He calculado y probado decenas de variables y medidas correctivas. La precisión absoluta no es necesaria. Llevo más de 2 semanas con este problema y me estoy desesperando. ¡Por favor ayuda! Aquí está mi (creo que relevante) código:

include <xc.h>
pragma config FOSC = INTOSCIO  // Oscillator Selection bits
pragma config WDTE = OFF       // Watchdog Timer Enable bit
pragma config PWRTE = OFF      // Power-up Timer Enable bit
pragma config MCLRE = ON       // RA5/MCLR/VPP pin function is MCLR
pragma config BOREN = ON       // Brown-out Detect Enable bit
pragma config LVP = OFF        // Low-Voltage Programming Enable bit
pragma config CPD = OFF        // Data EE Memory Code Protection bit 
pragma config CP = OFF         // Flash Program Memory Code Protection bit 
define _XTAL_FREQ 4000000                            

volatile int intr = 0; //number of interrupts

void interrupt TimerOverflow(void) 
{
  if(TMR0IE && TMR0IF)
  {
  TMR0 = 0;  //clear timer 0
  TMR0IF=0;  //clear flag
  intr++;    //count 1 interrupt
  }
}

void buzzer()  // this part just buzzes on start and on end, not included for cleanliness   
void display2(int c) //multiplexing, this function displays the last sections' digits. Working so not included. int c is the number of minutes, as received from display()
void display(int b) //multiplexing, this function displays the first sections. Working so not included. int b is the number of minutes, as received from init() and teller()

void teller(int a) //a is the number of minutes as received from init(), 'teller' is counter in dutch
{
int perlight = a*67 //number of minutes times a variable (now 67) to calculate number of interrupts per light-shut-off
PS0 = 1;        // Prescaler (1:256) is assigned to the timer TMR0
PS1 = 1;
PS2 = 1;
T0CS = 0; //internal clock selector
PSA = 0; //assign prescalar
TMR0IE = 1; //timer0 interrupt active
PEIE = 1; //peripheral interrupt active
GIE = 1; //global interrupt active
TMR0IF = 0; //clear timer0 flag
TMR0 = 0;   // clear timer0
int d = 30;     //turn all lights on starting the count
while(1) //endless loop
{
    if (intr>=perlight) //if number of interrupts is more than 'needed' for one-light-shutoff
    {
        intr = 0; //reset interrupts
        d--; //decrease count (turn one light off)
    }
    display(d); //send count to display
    if(d==0) //if count is done
    {
      buzzer(); //beep 3 times
      __delay_ms(500);
      buzzer();
      __delay_ms(500);
      buzzer();
      break;
    }
}
}

void init()
{
int min = 0; //number of minutes that the clock needs to run
while(1) //endless loop
{
    display(min); //send number of minutes to display function
    if(RA2==0) //if 'add minute' button has been pressed
    {
    __delay_ms(50); //debouncer
    if(RA2==0) 
        {
        min++; //add minute to count
        display(min); //send to display
        }
    } 
   if(RA3==0) //if start is pressed
    {
        __delay_ms(50); //debouncer
        if(RA3==0)
        {
            if(min!=0) //if start is pressed, buzz and start teller()
            {
                buzzer();
                teller(min);
            }
            if(min==0) // if start is pressed without timing set, buzz 3 times
            {
                buzzer();
                __delay_ms(500);
                buzzer();
                __delay_ms(500);
                buzzer();
            }
        }
   }
}

}
void main()
{
TRISB = 0b00000000; //RB as Output PIN
TRISA = 0b00101100; //RA as Input/Output PIN
CMCON = 0b00000111; //disable comparators
while(1)
{
  init(); //start init()
}
}

Realmente espero que alguien pueda ayudar a este total C-amateur. ¡Gracias de antemano!
EDITAR: también he intentado usar timer2 con un contador de interrupciones modificado. Esto fue aún más extraño que antes, con 1 y 2 minutos que terminaron en menos de un segundo, y 3 minutos y más tomando mucho tiempo.
EDIT2 He intentado reemplazar la variable 'perlight' a un número entero (61 (2 minutos), segundo tiempo 122 (4 minutos)). ¡Tiempo perfecto! Pero ahora no es ajustable, así que me imagino que algo anda mal con 'perlight'.

@línea 49 de la pantalla de código actual (d); // enviar el recuento a la pantalla puede estar causando problemas (retrasos). Eliminarlo y publicar los resultados
Hola Triak, gracias por tu respuesta. Implementé y probé su sugerencia, y descubrí que no influye en el tiempo general de este temporizador de cuenta regresiva. El momento es todavía muy poco fiable. EDIT2 en mi publicación es lo que me confunde; de alguna manera no calcula 'perlight' apropiadamente...

Respuestas (2)

Probé su código en MPLab Sim y en hardware real, obteniendo los resultados esperados en ambos casos (4 minutos perlight = a*61y tiempo establecido en '2 minutos'). Quizás su problema se deba a algún código que no nos ha mostrado, o tiene un problema de hardware. Sin embargo, puedo ver algunas cosas que podrían afectar el tiempo: -

  1. Está incrementando la variable de 16 bits intren el ISR y luego comparándola con perlightsu código principal. El 16F628 solo puede comparar 8 bits a la vez, y una interrupción puede ocurrir en cualquier momento , por lo que intrpodría cambiar en medio de la comparación y corromper el resultado. Para evitar esta posibilidad, debe deshabilitar las interrupciones siempre que intrse acceda desde el código principal.

  2. Después de que finaliza la sesión de cronometraje, la interrupción del temporizador continúa y no se reinicia intrantes de comenzar otra sesión de cronometraje. Esto hace que las sesiones de cronometraje posteriores sean de hasta 4 segundos de duración. Al comienzo de cada sesión de cronometraje, debe deshabilitar las interrupciones, restablecer el hardware del temporizador, restablecerlo intra cero y luego volver a habilitar las interrupciones para comenzar a contar.

  3. Estás compensando TMR0en el ISR. Esto no es necesario porque el temporizador vuelve automáticamente a cero cuando se desborda. Sin embargo, cualquier escritura en TMR0también reinicia el prescaler, perdiendo los ciclos contados entre el desbordamiento del temporizador y la escritura en TMR0. Dejar que el temporizador funcione libremente le permite mantener un tiempo constante incluso si cambia la latencia de interrupción.

  4. Con un reloj de 4 MHz y un preescalador de 1:256, no puede obtener un retardo de tiempo preciso de 2 segundos porque 2/0,065536 no es un número entero. Sin embargo, puede acercarse mucho cambiando el preescalador a 128 y usando un multiplicador 'perlight' de 61, lo que equivale a 1,998848 segundos (128*256*61). Para obtener la mejor precisión de sincronización, debe usar un resonador de cristal o cerámica en lugar del oscilador interno (que puede fallar en varios porcentajes).

No he analizado completamente su código, pero sospecho que lo que puede estar sucediendo es que su temporizador se está desbordando más rápido de lo que se está ejecutando su bucle de código. Y, si es así, entonces está desbordando su registro intr, lo que está generando resultados extraños.

Bitsmack, ¡Gracias por tu rápida respuesta! ¿Pero no se supone que el temporizador se desborda cada 256 conteos de timer0? Entonces, ¿una cuenta es 1us, desbordamiento en 256us, con prescaler 0,065s, multiplicado por 67 = 4s?
Solo para estar seguro, reemplacé 67 con 15745 para obtener el tiempo por luz a 4 s. Este movimiento ha hecho que el reloj sea muy lento. También he reemplazado el int 'intr' con un 'intr' largo por si acaso. Aún así, no hay un momento predecible.
@SjorsLiebregts Tienes razón, por supuesto :) He corregido mi publicación. Volveré mañana y miraré de nuevo. ¡Espero que alguien tenga una respuesta para ti antes de eso!