Temporizador PIC16 10hz con timer1 y reloj 1Mhz

Necesito incrementar un cronómetro a 10 hz usando el temporizador 1 en un PIC16F628. El reloj externo es de 1Mhz, suministrado por un oscilador empacado (EPSON sg8002db). Sin prescaler, el valor para configurar el temporizador (creo) debería ser:

tictac del reloj en 1 segundo: 1000000

el temporizador marca en 1 segundo: 250000 (reloj/4)

tictac del temporizador en 1/10 de segundo: 25000 (ticks del temporizador/10)

Entonces: 65536 - 25000 = 40536

pero debo considerar la latencia desde el desbordamiento del temporizador hasta que se restablece el valor del reloj: esta es la cantidad de ciclos que transcurren desde que se produce el desbordamiento hasta que configuro el valor del temporizador.

El código IRQ es:

    irq             movwf   w_temp          ; save state
                    swapf   STATUS, w
                    clrf    STATUS
                    movwf   status_temp
                    movf    PCLATH, w
                    movwf   pclath_temp
                    clrf    PCLATH

                    btfss   PIR1,TMR1IF     ; timer1 IRQ?
                    goto    notimer1
                    bcf     PIR1,TMR1IF     ; yes, clear it

                    movLw   T1SPEED >> 8    ; reset timer1
                    movwf   TMR1H
                    movLw   T1SPEED & 0xff
                    movwf   TMR1L           ; timer1 is off and running again

                    call    timer           ; increment clock

    notimer1        btfss   INTCON, T0IF    ; timer0 IRQ?
                    goto    notimer0
                    bcf     INTCON, T0IF    ; yes, clear it

                    call    led_set         ; update display

                    btfss   PORTA,6         ; button pressed?
                    goto    nobut
                    clrf    digit0          ; yes, reset clock
                    clrf    digit1
                    clrf    digit2
                    clrf    digit3
    nobut
    notimer0        movf    pclath_temp, w  ; restore state
                    movwf   PCLATH
                    swapf   status_temp, w
                    movwf   STATUS
                    swapf   w_temp, f
                    swapf   w_temp, w
                    retfie

Lo que me parece en algún lugar en la región de unos 14 ciclos, entonces LATENCY = 14

65536 - (25000 - LATENCIA) = 40550

pero esto da un reloj que es demasiado lento, perdiendo varios segundos por minuto. Si cambio el valor de LATENCY a ~200 (por ejemplo, configuro timer1 a 40736), está cerca, dentro de 1 segundo por minuto, pero aún no es preciso. De hecho, con LATENCY = 199 es demasiado rápido y con LATENCY = 200 es lento.

No puedo ver dónde se gastan estos ciclos adicionales: establece el valor del reloj a primera hora de la rutina IRQ. No puedo encontrar nada en la hoja de datos sobre la detención del temporizador 1 durante una rutina de interrupción, pero ¿lo es? Si es así, eso sería un fastidio, porque la rutina toma un número variable de ciclos dependiendo de qué dígitos se desbordan.

¿Es necesario elegir 2 valores de recarga diferentes y alternar entre ellos para llegar exactamente a 10 hz?

¿Cuántas 'cosas' estás haciendo en tus rutinas 'temporizador' y 'set_led'?
No mucho, pero tienes razón en que son los que causan el problema. Creo que es porque el ISR del temporizador 0 (frecuencia más alta) a menudo se ejecuta cuando el temporizador 1 se ajusta, lo que provoca la deriva.

Respuestas (2)

En lugar de usar el temporizador 1 de 16 bits, que requiere que vuelva a cargar el valor en el código, use el temporizador 2 de 8 bits, que tiene un registro PR2 preestablecido. Cargue PR2 con 250, con un valor de preescalador de 1:1. Luego interrumpirá cada 1 ms y se recargará automáticamente para que no haya problemas de latencia.

Dentro de su interrupción, simplemente incremente un contador de un solo byte. En su rutina base, use un bucle while y pruebe siempre que el contador sea igual a 100 (es decir, haya transcurrido 1/10 de segundo). Luego reinicie el contador y actualice y reinicie su reloj/led allí en el nivel base, no en su rutina de interrupción.

Perfecto, funciona de maravilla, gracias. Usé prescale=10 y conté hasta 10 antes de incrementar el dígito. Además, tuve que usar 249 para el valor de recarga, que parece ser muy preciso. Creo que el problema con el uso del temporizador 1 es que el ISR del temporizador 0 (frecuencia más alta) a menudo se ejecuta cuando el temporizador 1 se ajusta, lo que provoca la deriva.

en lugar de restablecer el temporizador 1, simplemente actualice la alarma para que sea 0.1 s más en el futuro