¿Cómo usar el Timer 0 correctamente?

Hola que tal, queria saber si alguien me podria poner algun ejemplo basico y facil explicado sobre usar el TMR0 como temporizador, ya que hay muchisimos ejemplos, pero la verdad se me hace lío entenderlo.
Se que alguien puede tener una forma sencilla de explicarlo de tal forma que pueda mi cabeza dura entender.

Por ejemplo lo que yo hago ( por supuesto sin usar el TMR0 )
Un estúpido ejemplo de 1 segundo, muy tonto, pero para que se entienda seria el siguiente:

Código:
          pic 16f84a .... bla bla bla bla !!!!
leoboton
            btfsc     PORTA,0          ; leo si se presiona
            goto      leoboton          ; NO vuelvo a leer
            call        retardo_20ms   ; miro rebotes
            btfsc     PORTA,0
            goto      leoboton
            call        rutina_1_segundo
            goto      leoboton
 
rutina_1_segundo
            Por supuesto aca la rutina que tarda un segundo
            que no es necesario ya que no es lo que me interesa.
            return

Con este ejemplo anterior todo bien, pero no puedo hacer nada mas durante ese segundo.
Ni siquiera podría, cancelar la temporización.

Otro ejemplo que yo es el que uso, seria algo parecido, con la diferencia de que para llegar al segundo, cuenta de a 10ms.
Seria algo asi:

Código:
          pic 16f84a .... bla bla bla bla !!!!
Cblock   0x0C
Contador                                 ; Agrego un estúpido contador
Endc
 
leoboton
            btfsc     PORTA,0          ; leo si se presiona
            goto      leoboton          ; NO vuelvo a leer
            call        retardo_20ms   ; miro rebotes
            btfsc     PORTA,0
            goto      leoboton
            call        rutina_10_ms
            goto      leoboton
 
rutina_10_ms
            movl      d\'100\'
            movwf   Contador          ; Contador = 100
cuenta
            ( aca la rutina de 10ms ) ;
            btfsc     PORTA,0           ; Esta presionado ?
            goto      $+3
            clrf       Contador
            return                          ; retorno a leoboton
            decfsz   Contador,f        ; Contador - 1
            goto      cuenta
            clrf        Contador
            return                          ; retorno a leoboton

Este es el metodo que uso, logico quito la exactitud de los tiempos que pierdo en testear boton y todo eso.
Uso este tipo de rutinas para todo aun para contadores de horas, logre hacer contadores de 6 horas con 5 segundos de diferencia en 1 hora ( que es demasiado ), ademas he hecho temporizadores de 1000 horas y no logro exactidud.

A ver si me pueden dar un ejemplo simple por favor que sea facil de entender.

He visto algunos ejemplos que no se si se adaptan a lo que quiero he visto algo asi pero para el 16f876A y yo quisiera algo con el 16f84a:


Código:
              pic16f876a
Cblock     0x0C
Contador
EndC
        org       0x00         ;Vector de RESET
        goto       Inicio
        org       0x04         ;Vector de interrupción
        goto       Interrupcion
        org       0x05
 
Interrupcion       
        bcf       INTCON,T0IF    ;Repone flag del TMR0
        decfsz  Contador,F    ;Decrementa el contador. Ha habido 50 interrupciones ??
               goto        Seguir        ;No, no han pasado los 500 mS
Con_si_0       
        movlw     .50
               movwf     Contador       ;Repone el contador nuevamente para contar 50 interrupciones
               movlw    b\'10000000\'
        xorwf            PORTB,F    ;RB7 cambia de estado
Seguir        
        movlw     ~.39
               movwf     TMR0          ;Repone el TMR0 con 39
               retfie                    ;Retorno de interrupción
 
Inicio               
        clrf             PORTB        ;Borra los latch de salida
        bsf            STATUS,RP0    ;Selecciona banco 1
        clrf            TRISB                ;Puerta B se configura como salida
        movlw    0x06
        movwf    ADCON1            ;Puerta A digital
        movlw    b\'00111111\'
        movwf    TRISA        ;Puerta A se configura como entrada
        movlw    b\'00000111\'
        movwf    OPTION_REG    ;Preescaler de 256 para el TMR0        
        bcf            STATUS,RP0    ;Selecciona banco 0
 
;====================================================================================
;El TMR0 se carga con 39. Con un preescaler de 256 y a una frecuencia de 4MHz se obtiene una interrupción
;cada 10mS. Se habilita la interrupción del TMR0.
;====================================================================================
 
        movlw    ~.39
        movwf    TMR0                ;Carga el TMR0 con 39
        movlw    .50
        movwf    Contador            ;Nº de veces a repetir la interrupción
        movlw    b\'10100000\'
        movwf    INTCON        ;Activa la interrupción del TMR0
 
;================================================================================
;Este es el cuerpo principal del programa. Consiste en leer constantemente el estado de RA0 y RA1 para visualizar
;sobre RB0 y RB1 sin que cambie el estado actual de RB7
;================================================================================
 
Loop        
        btfsc        PORTA,0         ;Testea el estado de RA0
               goto        RA0_ES_1
               bcf        PORTB,0        ;Desactiva RB0
               goto        TEST_RB1
RA0_ES_1       
        bsf        PORTB,0        ;Activa RB0
TEST_RB1       
        btfsc        PORTA,1        ;Testea el estado de RA1
               goto        RA1_ES_1
               bcf        PORTB,1        ;Desactiva RB1
               goto        Loop
RA1_ES_1      bsf        PORTB,1        ;Activa RB1
        goto        Loop
 
        end            ;Fin del programa fuente

Segun el programa anterior si a Contador le agrego 100 contaria un segundo, lo que no entiendo como manejarlo con un boton ( osea con un boton hacer la interrupcion y cancelarla, y de mientras poder hacer otras cosas por ejemplo navegar por un menu en un lcd etc .....
Desde ya muchas gracias a quien me pueda explicar con sus propias palabras que hay en el codigo anterior ya que no entiendo nadita de nada, y ya he leido muchos post y la hoja de datos y no lo llego a entender claramente como usarlo .... Gracias a todos !!! ...
 
Última edición por un moderador:
quisiera ayudarte pero no te entiendo.
que es lo que quieres hacer con el boton?
dices que con el boton quieres hacer una interrupcion(interrunpir que cosa?) y cancelarla( cancelar que?)

podrias poner que es lo que quieres hacer concretamente?
ya consultaste el libro microcontrolador pic16f84a de fernando remiro?
 
quisiera ayudarte pero no te entiendo.
que es lo que quieres hacer con el boton?
dices que con el boton quieres hacer una interrupcion(interrunpir que cosa?) y cancelarla( cancelar que?)

podrias poner que es lo que quieres hacer concretamente?
ya consultaste el libro microcontrolador pic16f84a de fernando remiro?

Gracias por contestar, lo que quisiera es controlar el temporizador del TMR0 con un boton,
Ejemplo ( Un boton que inicie el TMR0 y un contador aparezca en un LCD o display 7 Seg y yo lo cancele cuando quiera, y lo resetee y lo inicie cuando quiera ) osea, darle la orden de cuando activarlo y cuando cancelarlo o si llega a ese tiempo que se corte solo y espère la orden de nuevo, osea todo eso, lo se hacer mediante el CP pero no entiendo como usar el TMR0 ya mire muchos tutoriales y apuntes pero no me ha quedado entendible, y quizá halla alguien que lo pueda explicar sencillamente que lo pueda entender !!!
 
Última edición por un moderador:
ok ya en el libro microcontrolador pic16f84a de fernando remiro viene unos ejemplos en capitulo 15 Timer0

para activar o desactivar el TMR0 hay que manipular el T0IE del resgistro INTCON
T0IE=0 Interrupcion deshabilitada detiene el temporizador
T0IE=1 Interrupcion habilitada reaunuda el temporizador
y para activar o desactivar el timer0 con el boton usa el puerto B0 configurado como interrupcion y cada vez que lo presiones genera una interrupcion activara o desactivara el timer0 poniendo a uno o cero el registro T0IE

Checa el libro microcontrolador pic16f84a de fernando remiro capitulo 15(tmr0) 17(interrupcion ITN externa) 18(interrupcion por desboradmiento del timer 0 hay un ejemplo de temporizador digital))
 
Última edición:
ok ya en el libro microcontrolador pic16f84a de fernando remiro viene unos ejemplos en capitulo 15 Timer0

para activar o desactivar el TMR0 hay que manipular el T0IE del resgistro INTCON
T0IE=0 Interrupcion deshabilitada detiene el temporizador
T0IE=1 Interrupcion habilitada reaunuda el temporizador
y para activar o desactivar el timer0 con el boton usa el puerto B0 configurado como interrupcion y cada vez que lo presiones genera una interrupcion activara o desactivara el timer0 poniendo a uno o cero el registro T0IE

Checa el libro microcontrolador pic16f84a de fernando remiro capitulo 15(tmr0) 17(interrupcion ITN externa) 18(interrupcion por desboradmiento del timer 0)


Gracias creo que tengo ese libro, y de ahi salen las rutinas:

Del libro "MICROCONTROLADOR PIC16F84. DESARROLLO DE PROYECTOS"
E. Palacios, F. Remiro y L. López.
Editorial Ra-Ma. www.ra-ma.es

voy a buscarlo, muchas gracias !!!
 
Última edición por un moderador:
Una forma de parar y arrancar el TIMER0 sería pasar el modo de funcionamiento, de temporizador a contador (bit 5 de OPTION). Si en RA4/T0CK no recibe ninguna entrada, pues entonces el TMR0 se para.

La forma de reiniciarlo es colocando el ~.39 en el TMR0 y el 50 en Contador. La interrupción la sigues necesitando, porque en ella es donde se lleva la cuenta de Contador y donde se reinician los valores.

Puedes también pensar en el TIMER0 de 1 segundo como si fuera un reloj, y dejarlo permanentemente en marcha, y el resto del programa, mirar si ha pasado el tiempo que queramos (1 segundo, 10 minutos, lo que sea, por medio de otros contadores). Por ejemplo:
PHP:
tiempo_final = ahora() + 10;   // vemos el tiempo que será dentro de 10 segundos
while (ahora() < tiempo_final) {
    // aquí esperamos o hacemos algo
}
O también:
PHP:
while(1) {                  // bucle infinito del programa

    // aquí hacemos cualquier tarea
    if( ... ) {             // si sucede algo de lo que tenemos que tomar nota
        evento1 = ahora();  // tomamos nota del momento
    }

    ...;

    // Ver si han pasado quince minutos desde el evento especial
    if ( evento1 && (ahora() > evento1 + 900) ) {
        ...;
    }
}
Un detalle sobre el ejemplo publicado. No es 39 el valor que se ha almacenado en TMR0 como valor inicial, sino ~39, es decir, 39 negado. Al ser un byte, sería 256 - 39 = 217.

La explicación es que el cálculo de los 10 ms se hace así:

[LATEX]T_{out} = \frac{4 \cdot (256 - TMR0) \cdot Preescalado}{f_{osc}} = \frac{1}{f_{out}}[/LATEX]​

donde, en este caso, 4 es el número de ciclos que se ejecutan por instrucción, [LATEX]f_{osc}[/LATEX] es la frecuencia de oscilación (4 Mhz), Preescalado es 256 y TMR0 es 217 (256 - 39). Eso nos da [LATEX]T_{out}[/LATEX], que es el tiempo del periodo. La frecuencia, obviamente, es [LATEX]f_{out}[/LATEX].

Entonces, tenemos

[LATEX]T_{out} = \frac{4 \cdot (256 - 217) \cdot 256}{4 \cdot 10^{6}} = 0.009984 s[/LATEX]​

Junto con el Contador, que es la variable que se decrementa en la interrupción, tenemos múltiplos de ese lapso (en el ejemplo, 500 ms).

La razón de elegir 39 viene a partir de los 10 ms:

[LATEX]T_{out} = \frac{4 \cdot (256 - TMR0) \cdot 256}{4 \cdot 10^{6}} = 0.010 s[/LATEX]

[LATEX]256 - TMR0 = \frac{4 \cdot 10^{6} \cdot 0.010}{4 \cdot 256} = 256 - 39.0625 \simeq -39[/LATEX]​

La elección del valor inicial TMR0, del Preescalado y de la variable Contador depende del lapso que necesitemos. Lo normal es elegir el mínimo común para que el TIMER0 se puede usar por distintas partes del programa.

Lo que se suele hacer es ajustar primero el Preescalado, y luego calcular el Contador, afinando el TMR0.

Ejemplo: si queremos una frecuencia de ejecución de 2 hz (500 ms), se hace así:

[LATEX]Contador = \frac{f_{osc}}{4 \cdot Preescalado \cdot (256 - TMR0) \cdot 2 hz } \\ = \frac{4 \cdot 10^{6}}{4\cdot256\cdot(256-0)\cdot2} \\ = 7.629394531 \simeq 8 \ (error \ 4.85 \ \%)[/LATEX]​

Como ves, sale un número distinto del programa, debido a que hemos usado TMR0 = 0. Si TMR0 fuera ~39, saldría

[LATEX]Contador = \frac{f_{osc}}{4 \cdot Preescalado \cdot (256 - TMR0) \cdot 2 hz } \\ = \frac{4 \cdot 10^{6}}{4\cdot256\cdot(256-217)\cdot2} \\ = 50.080128205 \simeq 50 \ (error \ 0.16 \ \%)[/LATEX]​

Tenemos un error mucho más bajo, a costa de tener que inicializar TMR0 en cada interrupción.

Tienes unas fórmulas muy bonitas y más explicado en este enlace.
 
Última edición por un moderador:
Muchas gracias por tu extensa explicación, la verdad te pasaste... Un genio !!! :)
Sobre todo, no sabia esto:
Un detalle sobre el ejemplo publicado. No es 39 el valor que se ha almacenado en TMR0 como valor inicial, sino ~39, es decir, 39 negado. Al ser un byte, sería 256 - 39 = 217.
También he entendido perfectamente lo que planteas ( aunque en lenguaje C no es mi fuerte ) pero al programar en VB6 se me hace familiar.
Si no me equiboco lo que muestras en C es lo que yo haria en ASM asi:
PHP:
Mientras está el TMR0 trabajando leo el TMR0
             movf     TMR0,w   ; extraigo la cuenta actual del TMR0 y la acumulo en W
             movwf   PORTB     ; Lo actual lo saco y leo por el PORTB.
Lógico que podría pasarlo a BCD para poder graficarlo y expresarlo en 1 display o LCD etc.

Pero lo que no me quedó muy claro es como decirle ( 'ALTO TMR0 !!' Corta la cuenta ahi y quedate sin hacer nada.) Y cuando yo quiera decirle ( 'ARRIBA TMR0' a trabajar ) y que vuelva a contar.
Algo que me confundió es lo que has escrito al principio:
Una forma de parar y arrancar el TIMER0 sería pasar el modo de funcionamiento, de temporizador a contador (bit 5 de OPTION). Si en RA4/T0CK no recibe ninguna entrada, pues entonces el TMR0 se para.
No entiendo mucho, pero si uso la forma que mensionas ( Contador por RA4 ) ya no tengo un temporizador ? solo un contador de pulsos ?.
Quizá me quieras decir que un contador me sirve igual :confused:

Yo pude ir sacando un poco de info de todos lados y me arme un txt con lo basico para entender los registros 'OPTION y INTCON.'
PHP:
####################################
                ' TMR0 '
####################################
'Configuracion TMR0'
 
Para controlar el TMR0 se utilizan los registros OPTION_REG y INTCON
El registro INTCON esta ubicado en la direccion 0BH del banco 0 y duplicado en la direccion 8Bh del banco 1.
Contiene 8 bits de programacion:
 
============================================================
======================= 'I N T C O N' ========================
============================================================
GIE    EEIE    T0IE    INTE    RBIE    T0IF    INTF    RBIF
BIT7    BIT6    BIT5    BIT4    BIT3    BIT2    BIT1    BIT0
============================================================
___________________________________________
GIE    'Selecciona todas las interrupciones'
 
    GIE = 0 ( // Cancela Interrupciones )
    GIE = 1 ( // Habilita Interrupciones )
_____________________________
EEIE    'Interrupciones EEPROM'
 
    EEIE = 0 ( // Permite Interrupciones fin de escritura EEPROM )
    EEIE = 1 ( // Deshabilita Instrucciones EEPROM )
_____________________________
T0IE    'Habilitación de la interrupción del temporizador por desbordamiento'
 
    T0IE = 0 ( // Ignora la interrupción del flag por desbordamiento)
    T0IE = 1 ( // Autoriza Interrupciones deltempo por desbordamiento )
_____________________________
INTE    'Habilitación de la entrada de interrupción externa (Interrupt Enable) por patilla RB0/INT.'
 
    INTE = 0 ( // Deshabilita e ignora el flag por interrupción externa )
    INTE = 1 ( // Habilita Interrupciones Externas de RB0/INT )
_____________________________
RBIE    'Habilitación de las interrupciones del puerto B (RB Interrupt Enable).'
 
    RBIE = 0 ( // Interrupción del puerto B deshabilitada. )
    RBIE = 1 ( // Autoriza las interrupciones provocadas por un cambio de estado de las líneas RB4 a RB7 del puerto B. )
____________________________________
T0IF    'Flag de interrupcion, indica desbordamiento del TMR0'
 
    TOIF = 0 ( // TMR0 NO SE HA DESBORDADO )
    TOIF = 1 ( // TMR0 SE HA DESBORDADO ( DEBE BORRARSE POR SOFTWARE ))
_____________________________
INTF    'Bit de interrupción de la Entrada de Interrupción INT (patilla RB0/INT).'
 
    INTF = 0 ( // No hay interrupción externa. )
    INTF = 1 (  // La entrada de interrupción se ha activado (patilla RBO/INT del puerto B). Se borra por software )
_____________________________
RBIF    'Bit de interrupción del puerto B.'
 
    RBIF = 0 ( // Ninguna línea de RB4 a RB7 del puerto B ha cambiado. )
    RBIF = 1 ( // Cambio de estado en una de las líneas de RB4 a RB7 del puerto B. Se borra por software.)
 
 
============================================================
======================= 'O P T I O N' ========================
============================================================
/RBPU      INTEDG    T0CS    T0SE    PSA    PS2    PS1    PS0
BIT7      BIT6    BIT5    BIT4    BIT3    BIT2    BIT1    BIT0
============================================================
 
______________________________________________
/RBPU      '((RB Pull Up). Conexión de las resistencias de polarización del Puerto B.' 
      'Se conectan todas cuando el puerto B actua como entrada. )'
 
    /RBPU = 0 ( // Las resistencias se activan de forma individual. )
    /RBPU = 1 ( // Todas las resistencias son desconectadas. )
______________________________________________
INTEDG    '( Selecciona el tipo de flanco para la interrupción externa.' 
          'Este bit indica el tipo de flanco de la señal externa' 
          'que ha de provocar una interrupción en la patilla RB0/INT. )'
 
    INTEDG = 0 ( // La interrupción es producida por el flanco descendente o de bajada. )
    INTEDG = 1 ( // La interrupción es producida por el flanco ascendente o de subida. )
_______________________________________
T0CS     'SELECCIONA LA FUENTE DE LA SEÑAL'
 
    T0CS = 0 ( // PULSOS DE RELOJ INTERNO Fosc/4 ************* TMR0 COMO TEMPORIZADOR )
    T0CS = 1 ( // PULSOS INTRODUCIDOS A TRAVEZ DE RA4/T0CKI ** TMR0 COMO CONTADOR )
__________________________________
T0SE     'SELECCIONA SEÑAL DE FLANCO'
 
    T0SE = 0 ( // TMR0 INCREMENTA EN CADA FLANCO ASCENDENTE EN SEÑAL RA4/TOCKI )
    T0SE = 1 ( // TMR0 INCREMENTA EN CADA FLANCO DESCENDENTE EN SEÑAL RA4/TOCKI )
______________________________________________
PSA    '( SELECTOR DE DIRECTIVA DEL PRESCALER )'
 
    PSA = 0 ( // PRESCALER SE ASIGNA A TMR0 )
    PSA = 1 ( // PRESCALER SE ASIGNA A WATCHDOG )
 
______________________________________________
PS2-PS0   '( PRESCALER DE DIVISIÓN DE FRECUENCIA )'
 
============================================
========== 'T A B L A   V A L O R' ===========
============================================
PS2 PS1 PS0    DIV TMR0    DIV WATCHDOG
 0   0   0        1:2                1:1
 0   0   1        1:4                1:2
 0   1   0        1:8                1:4
 0   1   1        1:16               1:8
 1   0   0        1:32               1:16
 1   0   1        1:64               1:32
 1   1   0        1:128              1:64
 1   1   1        1:256              1:128

Aun hay cosas que no entiendo, no se cuando usar TMR0 o WATCHDOG ni como.
No siempre se usan los registros OPTION y INTCON.
Un ejemplo que vi de contador que lee interrupciones externas por RA4 solo usa:
PHP:
    movlw    b'00111000'        ; TMR0 como contador por flanco descendente de 
    movwf    OPTION_REG    ; RA4/T0CKI. Prescaler asignado al Watchdog.
Con eso solo, ya configuró el contador de pulsos externo :eek:
Desde ya muchisimas gracias por tu tiempo, tu experiencia y tus ganas de ayudar !!!
 
Última edición por un moderador:
Si no me equivoco lo que muestras en C es lo que yo haría en ASM así:
PHP:
Mientras está el TMR0 trabajando leo el TMR0
             movf     TMR0,w   ; extraigo la cuenta actual del TMR0 y la acumulo en W
             movwf   PORTB     ; Lo actual lo saco y leo por el PORTB.
Lógico que podría pasarlo a BCD para poder mostrarlo y expresarlo en un display o LCD etc.

Bueno... no "exactamente".

El TIMER0 -en modo temporizador- se incremente en 1 cada vez que se ejecuta una instrucción (si no hay preescalado), lo cual sucede cada 4 ciclos de reloj, así que

[LATEX]4 \frac{1}{4000000} = 1\cdot10^{-6}[/LATEX]​

Así que, si planeas sacar los datos por el PORTB, ves que cambian cada 1 µs (si no hay preescalado). Sospecho que no es eso lo que quieres ver. Necesitas un valor más fácil, como por ejemplo, una décima de segundo, que ya es visible, en un pantalla de 7 segmentos.

Pero lo que no me quedó muy claro es como decirle ( 'ALTO TMR0 !!' Corta la cuenta ahí y quédate sin hacer nada.) Y cuando yo quiera decirle ( 'ARRIBA TMR0' a trabajar ) y que vuelva a contar.
Algo que me confundió es lo que has escrito al principio:

No entiendo mucho, pero si uso la forma que mencionas ( Contador por RA4 ) ya no tengo un temporizador ? solo un contador de pulsos ?.
Quizá me quieras decir que un contador me sirve igual :confused:
A ver... es fácil de entender: el TIMER0 tiene dos modos de funcionamiento: temporizador y contador. En modo temporizador, se incrementa en 1 (si no hay preescalado) cada 4 ciclos del reloj del sistema. Siempre. Y en modo contador, lo que hace es contar los pulsos que le llegan desde la patilla RA4.

Entonces... si estamos en modo temporizador, está incrementándose, y cuando pase de 0xFF a 0x00, saltará una interrupción (si antes has habilitado las interrupciones en el INTCON). En cambio, si lo pasas a modo contador, empezará a incrementarse por cada pulso que le llegue desde RA4 (según preescalado). Bueno, ¿qué pasa si por RA4 NO hay ningún pulso? Pues que... el TIMER0 no cuenta nada... así que en efecto, lo que hemos hecho es pararlo. Si luego lo pasas a modo temporizador, seguirá la cuenta por donde iba.

En situaciones normales, no vamos a hacer eso.

Si queremos que el temporizador no cuente, en realidad lo que haremos será desactivar el bit 5 de INTCON (T0IE). De esa manera la interrupción del TIMER0 ya no se produce. El TIMER0 seguirá contando, pero no hay interrupción, y por lo tanto, la variable Contador no se actualiza. Para ponerlo en marcha, reactivar T0IE.

En situaciones más normales, tampoco vamos a hacer eso.

Simplemente, ignoraremos lo que diga el TIMER0. Si lo necesitamos, reiniciamos los parámetros y reactivamos la interrupción.


Yo pude ir sacando un poco de información de todos lados y me arme un txt con lo básico para entender los registros 'OPTION y INTCON.'
Toda esa información la tienes en la hoja de datos del procesador PIC16F84A, sección 5, páginas 19 y 20, y ampliado en el documento DS31011A, que pertenece a su vez al documento PICMicro Mid-Range MCU Family Reference Manual. Usa un visor de PDF que te muestre el índice de contenidos, para ir rápidamente de una sección a otra.

Aun hay cosas que no entiendo, no se cuando usar TMR0 o WATCHDOG ni como.
Se suele pasar de TIMER0 a WATCHDOG cuando queremos usar TIMER0 con un preescalado 1:1. Y si no sabes lo que es WATCHDOG, no necesitas saber nada más, de momento.

No siempre se usan los registros OPTION y INTCON.
Un ejemplo que vi de contador que lee interrupciones externas por RA4 solo usa:
PHP:
    movlw    b'00111000'        ; TMR0 como contador por flanco descendente de 
    movwf    OPTION_REG    ; RA4/T0CKI. Prescaler asignado al Watchdog.
Con eso solo, ya configuró el contador de pulsos externo :eek:
Desde ya muchísimas gracias por tu tiempo, tu experiencia y tus ganas de ayudar !!!
Claro, está configurando el TIMER0 para contar pulsos externos por la patilla RA4/T0CKI, y con un preescalado de 1:1. Vamos: contar pulsos externos.

Hay otras formas de uso, como la de no usar interrupciones, y simplemente ver si el temporizador ha pasado:
PHP:
wait
    MOVF    TMR0,W        ; leer valor de TMR0
    BTFSS    STATUS,Z    ; ver si es 0, y si es así, romper el bucle
    GOTO    wait        ; si no es cero, seguir esperando
Esta técnica es la que se llama "polling": el programa se queda esperando a que el temporizador llegue a 0, para continuar. Es un modo de hacer pequeñas esperas, de unos milisegundos. (Cuidado: si la frecuencia de reloj es muy alta, el temporizador podría incrementarse mientras se está ejecutando el GOTO, por lo que podríamos perder el control del primer 0, y entonces estaríamos esperando más tiempo del deseado. Ver página 11-10 del documento DS31011A).
 
Última edición por un moderador:
Gracias nuevamente por toda tu info, para mi es muy grata y voy tomando nota. también gracias por esos enlaces ....

Si tienes razón, con respecto a esto:
PHP:
Mientras está el TMR0 trabajando leo el TMR0
             movf     TMR0,w   ; extraigo la cuenta actual del TMR0 y la acumulo en W
             movwf   PORTB     ; Lo actual lo saco y leo por el PORTB.
Eso estaba en los ejemplos de Editorial Ra-ma sobre el timer0, usando el contador de pulsos, y mostrando en un LCD el conteo de los pulsos en RA4.

Aunque creo yo, por lo que me explicas que usandolo a modo temporizador, podria hacer que el PORTB marque un incremento cada tantos desbordamientos, tanto en un LCD como en un display usando por ejemplo el contador del principio
( Contador = 100 ) y el ( TMR0 = ~39 ) con un ( preescalado 256 ) = 1 segundo.
Y para pararlo como tu me explicas poniendo:
PHP:
para pararlo
         'bcf'      INTCON,T0IE   ; //para o ignora el TMR0
 
para seguir      
         'bsf'      INTCON,T0IE   ;//Reanuda o comienza de nuevo
                   ;// Aunque como tu explicas el TMR0 sigue contando y me preocupa q
                   ;// reanudaria en cualquier lugar perdiendo la exactitud del conteo
                   ;// Aunque se me ocurre que podria ir guardandola en otro registro.
Eso es lo que he entendido, ( disculpa si soy cabeza dura :cry: )
Bueno tengo mas dudas en mi cabeza, pero para ir cerrandolas voy a exponerte algo en concreto que es lo que quiero hacer:

Hice un contador de 6Horas, visualizo la seleccion en un display 7 seg
PHP:
Mi primer contador:
Lleva 2 pulsadores 
      'Pulsador1' // incrementa llega a 6 y vuelve a 1 
      'Pulsador2' // Inicia --- Solo corta por reset o quitando alimentación.
Cuando presiono 'Pulsador2' va a la rutina ( segun el numero visualizado ).
 
      // el primero contador que hice si bien era muy preciso pero a la hora 
      // de pararlo, tenia que, o hacer un reset con un pulsador mas 
      // o ( quitar la alimentación ) :D

Ya el segundo pulsador, gracias a la gente del foro, pude agregarle selector de minutos y segundos por medio de A,B, y C.
Y pude hacer que cada 10ms checkee el Pulsador2
y siga con la rutina de la hora seleccionada con el cual quedaría:
PHP:
Mi Segundo contador:
Lleva 2 pulsadores 
      'Pulsador1' // incrementa llega a 6 y vuelve a 1 
      'Pulsador2' // Inicia / Corta ----- Ahora corta con el mismo pulsador.
     // Pero lógico, al chequear 'Pulsador2' cada 10ms me robaba mucho tiempo.
     // Ya la rutina no era casi exacta, y tube que ir modificando numeritos, 
     // pero la precisión de los tiempos ya no son casi exactos como antes.
Por eso la intensión mía seria, desatender la cuenta del TMR0 sin interferir en sus registros de almacenamiento y atender solo un bucle checkeando el pulsador2, algo asi:
PHP:
bucle      btfsc     'Pulsador2'         // pulsador de corte presionado ?
             goto      'bucle'              // Sigue sin hacer nada
             goto      'cortar TMR0'     // Con esto cancela la cuenta del TMR0
Con eso no modificaria los tiempos fijos de las horas.

__________________________________________________________________________

Ahora la pregunta del millón sería:

Puedo usar ( modo Contador ) el pulsador RA4 para que haga lo que hago con Pulsador2 ?
Osea, supongamos que presiono el pulsador de RA4 .. va a la rutina seleccionada, inicia y cuando termina queda en reposo esperando que vuelva a presionar RA4.

Hasta ahi todo bien, pero que tal si quiero cancelarlo en la mitad del proceso.
Que hace RA4 si lo vuelvo a precionar ( se para o se resetea ? :confused: )

Para terminar con esta duda, me sirve la opcion de contador por RA4, para mi proposito o descarto la opcion de contador y voy por la opcion de temporizador ?

__________________________________________________________________________

Antes que se me pase, hay algo que me llamó la atención y lo expresaste en 2 ocaciones y mi cabeza no lo supo entender cuando dices:
Una forma de parar y arrancar el TIMER0 sería pasar el modo de funcionamiento, de temporizador a contador (bit 5 de OPTION). Si en RA4/T0CK no recibe ninguna entrada, pues entonces el TMR0 se para.
Bueno, ¿qué pasa si por RA4 NO hay ningún pulso? Pues que... el TIMER0 no cuenta nada... así que en efecto, lo que hemos hecho es pararlo. Si luego lo pasas a modo temporizador, seguirá la cuenta por donde iba.
Ahi te rerfieres a que se puede jugar o interactuar con ambas configuraciones en el mismo procedimiento ?, osea, pasar de uno a otro en cualquier momento sin afectar el proceso que llevamos contando ?
Se puede hacer eso ? :confused:

__________________________________________________________________________

Gracias y nuevamente disculpa si parezco muy repetitivo, ya que mucha información de golpe me ( desborda la cabeza pasando de 0xff a 0x00 ) :D
 
Última edición por un moderador:
¿Puedo usar ( modo Contador ) el pulsador RA4 para que haga lo que hago con Pulsador2 ? O sea, supongamos que presiono el pulsador de RA4... va a la rutina seleccionada, inicia y cuando termina queda en reposo esperando que vuelva a presionar RA4.
Bueno... sí... pero te estás complicando un poco la vida.

Si quieres que una pulsación genere una interrupción, puedes asociar el pulsador a la patilla RB0/INT, que genera una interrupción cuando hay un cambio de flanco. Ver pág. 29 de la hoja de datos.

Incluso puedes usar los pines RB4 a RB7 para el mismo propósito. Ver la misma página anterior.

Hasta ahí todo bien, pero ¿qué tal si quiero cancelarlo en la mitad del proceso? ¿Qué hace RA4 si lo vuelvo a presionar ( se para o se resetea ?
RA4 es un pin. Si haces un cambio de flanco, y el TIMER0 está configurado como contador, se incrementará TMR0 (ajustado según el preescalado, claro). Y se provocará una interrupción si TMR0 pasa de 0xFF a 0x00, igual que antes.

Para terminar con esta duda, ¿me sirve la opción de contador por RA4, para mi propósito o descarto la opción de contador y voy por la opción de temporizador?
Depende de lo que quieras que pase. ¿Quieres que el TIMER0 cuenta cambios externos, pues modo contador. ¿Quieres que TIMER0 se incremente según f_osc?, pues modo temporizador.

Antes que se me pase, hay algo que me llamó la atención y lo expresaste en 2 ocasiones y mi cabeza no lo supo entender cuando dices: Ahí te refieres a que se puede jugar o interactuar con ambas configuraciones en el mismo procedimiento ?, osea, pasar de uno a otro en cualquier momento sin afectar el proceso que llevamos contando ? Se puede hacer eso ?
Se pueden hacer los cambios, pero... la documentación dice que suceden cosas cuando hacemos cambios en el preescalado (se reinicia el contador interno del preescalado), o pasamos de modo contador a temporizador (se producen dos ciclos de retraso, para sincronización).

Mejor que hagas pruebas.
 
Gracias Joaquin, la verdad pude lograr lo que quería.
Al final, probé lo que habias mensionado el principio:
Una forma de parar y arrancar el TIMER0 sería pasar el modo de funcionamiento, de temporizador a contador (bit 5 de OPTION). Si en RA4/T0CK no recibe ninguna entrada, pues entonces el TMR0 se para.
Al no haber leído atentamente los ejemplos que me has mandado, lo pase por alto, pero al final pude entenderlo.
Solo con poner:
PHP:
TMR0 = ~.39
Contador = 100    // 1 segundo
Contador2 = 60    // 60 x 1 = 1 Minuto

Al iniciar:
'bsf	OPTION_REG,T0CS'        // Al inicio para comenzar parado.

Luego en un pulsador puse:
'bcf	OPTION_REG,T0CS'        // Con eso comienza el TMR0.

Luego en el segundo estado del pulsador:
'bsf	OPTION_REG,T0CS'        // Pausa el conteo.
Con eso funciona perfecto, aunque no se porque motivo, por mas retraso que le puse al pulsador ( por rebotes ), aun así hay veces que debo presionarlo varias veces para que responda.
Lo que se me ocurrió es probar algo así:
PHP:
		bcf	'OPTION_REG,T0CS'      // ENCIENDE Poniendo a 0 ( T0CS )
		btfsc	'OPTION_REG,T0CS'    // PASÓ A 0 ( T0CS ) ???
		goto	$-2                   // NO, VUELVE A INTENTAR.
        goto  continuar               // SI, ES 0, ENTONCES CONTINUAR
No sé porqué motivo tube que llegar a hacer eso, pero de esa forma me toma los pulsos correctamente.
Bueno muchisimas gracias por tu tiempo, experiencia y paciencia, ya que has podido llegar a mi objetivo .... Saludos y Dios te bendiga Joaquin !!!
 
Gracias, Joaquin, lo he logrado....
Pero me ha ocurrido algo muy extraño ( por lo menos para mi ).

Tengo entendido que el TMR0 es independiente a lo que haga el resto del programa, por ejemplo
el chequeo de un pulsador mientras el TMR0 trabaja y sigue su curso.

Al igual que el ejemplo anterior, le puse:

TMR0 = ~.39
Contador = 100

Logrando 1 segundo.
Quedando asi:
PHP:
cblock  0x0C
Contador
segundo
endC
		org	0x04		     // ;Vector de interrupción
		goto	Interrupcion
		org	0x05

Interrupcion   	
		bcf	   INTCON,T0IF	//;Repone flag del TMR0
		decfsz 	Contador,F	//;Decrementa contador. Ha habido 100 interrupciones?
        goto 	Seguir	      //;No, no han pasado los 1000 mS = 1 SEGUNDO
Con_si_0
	    incf	  segundo,f      //  ; INCREMENTA SEGUNDOS
	    movlw 	.100
        movwf 	Contador   	//;Repone contador para contar 100 interrupciones
Seguir    	
		movlw 	~.39		
        movwf 	TMR0      	//;Repone el TMR0 con 39
        retfie			      //;Retorno de interrupción

// =================================================================
' Ejemplo resumido, sin antirebotes ni muchos detalles'
' Se trata de chequear si SEGUNDO llega a 20'

'pulsador1'
             PRESIONADO ?
             NO, goto  pulsador1
             SI, ENCIENDE
             goto  pulsador2
'pulsador2'
             PRESIONADO ?
             SI, goto  cortar     // Si se presiona, interrumpe la Rutina
             NO, 'segundo = 20' ?   // Ejemplo:  Mira si segundo llegó a 20
             SI, goto  cortar     // Terminó la cuenta programada 
             NO, goto  pulsador2  // Aun no, sigue contando
'cortar'
             CORTA
             resetea  segundo
             goto  pulsador1

Hasta ahí todo bien, pero para hacer mas exactos los tiempos y usar menos programa.
Decidí poner la llamada a una rutina de tiempo de 1 MINUTO dentro del TMR0 quedando algo asi.
PHP:
cblock  0x0C
Contador
MINU     // Rutina que cuenta de minutos deseados a contar.
endC
		org	0x04		     // ;Vector de interrupción
		goto	Interrupcion
		org	0x05

Interrupcion   	
		bcf	   INTCON,T0IF	//;Repone flag del TMR0
		decfsz 	Contador,F	//;Decrementa contador. Ha habido 100 interrupciones?
        goto 	Seguir	      //;No, no han pasado los 1000 mS = 1 SEGUNDO
Con_si_0
		'call	minuto'		//; LLAMA RUTINA DE MINUTO
		'incf	MINU,f'		//; REGISTRO QUE INCREMENTA MINUTOS A CONTAR.
	    movlw 	.100
        movwf 	Contador   	//;Repone contador para contar 100 interrupciones
Seguir    	
		movlw 	~.39		
        movwf 	TMR0      	//;Repone el TMR0 con 39
        retfie			      //;Retorno de interrupción

// =================================================================
' Ejemplo resumido, sin antirebotes ni muchos detalles'
' Se trata de chequear si MINU llega a 20'

'pulsador1'
             PRESIONADO ?
             NO, goto  pulsador1
             SI, ENCIENDE
             goto  pulsador2
'pulsador2'
             PRESIONADO ?
             SI, goto  cortar     // Si se presiona, interrumpe la Rutina
             NO, 'MINU = 20' ?   // Ejemplo:  Mira si MINU llegó a 20
             SI, goto  cortar     // Terminó la cuenta programada 
             NO, goto  pulsador2  // Aun no, sigue contando
'cortar'
             CORTA
             resetea  MINU
             goto  pulsador1

Con eso llamo a una rutina de 1 minuto dentro de la interrupción del TMR0, y funciona perfecto !!!.
En realidad le he puesto 59 segundos ya que el TMR0 ya tiene 1 segundo, quedando justo 1 minuto.
Peroooo ..... no se porque motivo, cuando inicia con el pulsador1, ya no me toma lo que haga pulsador2, aun asi termine el conteo, corta correctamente, pero quiero volver a iniciar y pareciera que no se reseteó el MINU .....

Sigo pensando con que:
Tengo entendido que el TMR0 es independiente a lo que haga el resto del programa, por ejemplo:
el chequeo de un pulsador mientras el TMR0 trabaja y sigue su curso.
.

Quizá puedas explicarme, porqué el comportamiento del TMR0 de ese modo tan raro.
Y si puedo evitarlo, ya que de la forma anterior funciona correctamente, y de esta última forma funciona correctamente, pero en su funcionamiento ( encendido ) no me chequea los botones.
Gracias nuevamente !!!
 
Por lo que veo, si el programa entra en pulsador2, sin que esté pulsado el botón, no sale de ahí salvo si MINU == 20 o si se vuelve a pulsar el botón, así que ahí es donde tienes el problema, ya que no regresa a pulsador1.
 
Por lo que veo, si el programa entra en pulsador2, sin que esté pulsado el botón, no sale de ahí salvo si MINU == 20 o si se vuelve a pulsar el botón, así que ahí es donde tienes el problema, ya que no regresa a pulsador1.
Claro, exacto, solo que en ese estado, queda corriendo el TMR0, llamando la rutina de 1 minuto 20 veces ( el 20 es ejemplo ) Lo uso unos ( 5 minutos maximo para pruebas ) .... pero al precionar el pulsador2 ( para pararlo ) no me responde nada( supuestamente como si estubiese detenido en la rutina de minuto ) ... pero es raro porque la rutina de minuto la llamé del TMR0, y no deberia afectar al resto del programa, en ese caso leyendo el pulsador2.

Lo extraño que con el primer programa de segundo funciona todo correcto, la unica diferencia que lo puse para que llame el TMR0 una rutina de MINUTO.

Salvo se me ocurre integrar la rutina dentro de la interrupción antes del RETFIE ( osea sin usar un call ) ... aunque pareceria lo mismo !!, es raro que el primer ejemplo funciona bien y queda leyendo el pulsador y el segundo ( pareciera que queda pegado el programa en la rutina de MINUTO ) uniendo el TMR0 al programa ....
 
No sabemos qué hace la rutina minuto, y cuántas instrucciones tiene.

Ten en cuenta que se está llamando desde una interrupción, así que se supone que debe ocupar muy poco tiempo, para que la llamada a la interrupción termine cuanto antes.

La pila de un 16f84A solo puede almacenar 8 niveles de profundidad.
 
No sabemos qué hace la rutina minuto, y cuántas instrucciones tiene.

Ten en cuenta que se está llamando desde una interrupción, así que se supone que debe ocupar muy poco tiempo, para que la llamada a la interrupción termine cuanto antes.

La pila de un 16f84A solo puede almacenar 8 niveles de profundidad.
Gracias Joaquin:
Si, la rutina minuto es una rutina cerrada calculada con un generador:
Código:
Estan declarados los registros de retardos:
En realidad son 59 segundos, ya que el TMR0 primero cuenta 1 segundo.
cblock  0x0c
d1
d2
d3
endc

minuto
			;58999995 cycles  = 59 segundos
	movlw	0x1A
	movwf	d1
	movlw	0x9D
	movwf	d2
	movlw	0x81
	movwf	d3
minuto_0
	decfsz	d1, f
	goto	$+2
	decfsz	d2, f
	goto	$+2
	decfsz	d3, f
	goto	minuto_0

			;1 cycle
	nop
        return
Ya he intentado colocar toda la rutina dentro de la interrupcion del TMR0 y si bien me toma dentro del checkeo pulsador2 el incremento ( cada minuto incrementa MINU ), y lo saca por el puertoB ( en un display 7 seg ), pero al presionar pulsador2 no hace nada .... como si trabajara simultaneamente el TMR0 ( en la rutina 1 minuto ) y el resto pero en conjunto y no por separado.

Lo que se me ocurre de ultimo recurso, es ingresar el chequéo de pulsador dentro de la interrupción, pero ( que lo lea cada 10ms ) me robaría unos cuantos ms en cada minuto, que tendria que calcular y restar, pero es la ultima que queda por hacer ...sino coloco un pulsador de reset y se acabó el problema.

Lastima porque tengo ya el programa echo aunque sin el TMR0, solo lo tengo que iniciar, luego cada 10ms chequea el boton2 ( por si cancelo ) y sigue una rutina hasta llegar al tiempo señalado.

Pero quería evitar eso, con el TMR0, ya que se comenta que trabaja individual al resto del programa, pero veo que se me comporta como integrado al programa, y no me sirve.

Yo lo que queria era algo simple: pulsar boton1, iniciar TMR0, mientras cuenta Chequear boton2, ( si se presiona se cancela ), ( si sigue se corta solo al llegar al final ). Pero parece que con períodos largos de tiempos se cuelga o algo asi ...

Bueno gracias por todo Joaquin, estoy defraudado con ese TMR0, pero aprendí mucho !!! seguiré investigando !!!
 
Pero... ¿te das cuenta de lo que estás haciendo?

Estas "esperando" un minuto en una rutina que se ejecuta cada segundo...

Es como meter un elefante dentro de un carro. No puedes hacerlo.

Una rutina de servicio de interrupción debe ejecutarse en menos tiempo que el tiempo que tarda en ejecutarse la siguiente llamada de interrupción.

Entonces, si tenemos una rutina de interrupción que va a ser llamada cada 1 s, debe terminar antes de 1s, para que el control vuelva al programa principal (estos microcontroladores son monotareas). Cuando decíamos que el temporizador trabajaba por separado, nos referíamos solo al proceso de incrementar TMR0, no a la rutina de interrupción. Cuando el TMR0 pasa de 0xFF a 0x00, se produce una interrupción: el programa principal se detiene, para atender a la rutina de interrupción. Y cuando ésta termina, regresa al punto donde estaba ejecutándose el programa principal.

Lo que tienes que hacer es lo siguiente: como ya tienes una rutina que se llama cada segundo, podemos decir que ya tienes un segundero (de un reloj). Solo tienes que agregar código a la rutina de interrupción para que, cada segundo, incremente una variable llamada segundos.

Luego, en el programa principal, dentro del bucle, solo tienes que comprobar si la variable segundos vale 60 o más, y si es así, ya sabes que ha pasado un minuto, y actúas en consecuencia (mostrarlo fuera, por ejemplo). Pones el contador de segundos a 0 -o mejor, restas la diferencia de 60-, y regresas al bucle principal.

Lo que no puedes es parar toda la máquina durante un minuto entero. Bueno, sí que puedes -según el caso-, pero NO dentro de una interrupción que se ejecuta cada segundo.

Mañana miro a ver si preparo algo de código. Ahora es muy tarde para mi.

Saludos.
 
Última edición por un moderador:
Es como meter un elefante dentro de un carro. No puedes hacerlo.
Ja ja ja ja ja !!!! :LOL:
Ahora con lo que me comentas me van quedando claro algunos errores y el porqué.

Claro yo pensaba que el TMR0 era algo mágico, como tener 2 micro a la vez :confused:haciendo cada uno su trabajo sin importarle el uno del otro.

Ahora me aclaras, al diferenciar una rutina y una interrupción en cada desborde.

Y yo que tenía una rutinita como esa de 1 minuto, ( pero de 1 hora ) :confused: hubiese pasado lo mismo que me pasa con la de 1 minuto solo que multiplicado !!!
___________________________________________________
Yo pensaba, que mientras trabaja el TMR0, yo podría usar en el programa, por ejemplo:
que chequee un pulsador, con su retardo ( unos 20ms )
+ incremento del dato actual.
+ comparación del dato actual con el dato establecido de corte
+ muestreo por PuertoB del cambio de dato ... etc.

Algo así:
Código:
boton
	  incf	 minuto,f          ;INCREMENTO MINUTO ACTUAL
	  movf   minuto,w          ;LO MUEVO AL ACUMULADOR
	  sublw  d'10'		   ;LE RESTO EL DATO ESTABLECIDO AL ACUMULADOR ( 10 )
	  btfsc  STATUS,Z          ;MINUTO = 10 ? ( STATUS,Z = 0) ?
	  goto   cancelar          ;SI, CANCELA TMR0
          movf   minuto,w          ;NO, LO MUEVO AL ACUMULADOR DE NUEVO
          call   tabla             ;LLAMO LA TABLA QUE REPRESENTA EL NUMERO A DISPLAY
          movwf  PORTB             ;SACO EL VALOR POR PORTB Y MUESTRO EN DISPLAY    
          
          btfsc  pulsador          ;PRECIONADO ?
          goto   boton             ;NO, VUELVE
          call   retardo_20ms      ;SI, LLAMO RETARDO X REBOTES 
          btfsc  pulsador          ;VOLVEMOS A PREGUNTAR PRESIONADO ?
          goto   boton             ;NO, VUELVE
          goto   cancelar          ;CANCELA TMR0
cancelar
          clrf  minuto
          bsf	STATUS,RP0	   ;Selecciona banco 1
	  bsf	OPTION_REG,T0CS    ;Cambia de Interno a Externo por RA4 APAGA TMR0
	  bcf	STATUS,RP0	   ;Selecciona banco 0
Creo que con lo que me comentas, al hacer todo el control de la subrutina boton se me estaría sumando al tiempo de desbordamiento del TMR0.
Quedando alterado en ( 1 segundo + 20ms del retardo + los microsegundos por linea + los microsegundos x 2 ( en los call, return, goto ) ... sumaria mas de 20ms por incremento creo !!!
Eso es lo que yo entiendo :unsure:
___________________________________________________
Pero bueno:
Muy amable y gracias ... y veo que aprendí algo nuevo!!! ya que entendía cualquier cosa !! mañana volveré al ataque nuevamente a ver que me sale !!! que descanses !!! :)
 
Última edición:
No importa los milisegundos de espera o los microsegundos de ejecución de las instrucciones. Eso es muy poco comparado con los mil misegundos que van a pasar hasta la próxima interrupción.

Si la rutina de interrupción ocupa un par de milisegundos, aún quedan 998 milisegundos para el programa principal, antes de que se repita la interrupción.
 
Atrás
Arriba