PIC16 Temporizador0 rareza

3

Al usar un PIC16F886, estoy tratando de generar interrupciones cada 100 milisegundos usando TMR0 sincronizado desde el oscilador interno, pero tengo un comportamiento realmente extraño.

Este es un circuito alimentado por batería, así que estoy usando el oscilador interno de 125KHz, seleccionado a través de:

OSCCON = 0b00010000; // select 125 KHz Int. Osc. = IRCF<2:0>=001

Luego asigno el prescaler a TMR0 y establezco un valor de precaler de 1: 2:

T0CS = 0;   // TMR0 Clock Source: Internal instruction cycle clock (FOSC/4)
PSA = 0;    // Prescaler is assigned to TMR0
PS2 = 0;
PS1 = 0;    // > TMR0 Rate: 1:2;
PS0 = 0; 

Así que ahora, de acuerdo con mis cálculos, cada 'tic' debería tomar ((1/125 000) / 4) / 2 = 1.0 × 10^-6 segundos. Si precargo el temporizador con 155, se necesitarán 100 'tics' para desbordar, generando una interrupción cada 100uS.

Mi rutina de servicio de interrupción consiste en:

if(T0IE)
{
    ticks++;
    if (ticks >= 999){
        ticks = 0;

        PORTB = ~PORTB;
    }

    TMR0 = 155; 
    return;     
}

Y funciona, pero el momento no es exactamente el correcto.

Cuando lo simulo usando la SIM MPLAB, toma alrededor de 85 ms y en el hardware real parece que toma más de 100 ms.

El listado completo de códigos se puede encontrar aquí: enlace

Es muy posible que esté calculando mal algo, por lo que cualquier puntero / corrección sería muy apreciado.

    
pregunta Hamza

5 respuestas

5

Creo que tus cálculos están mezclando la frecuencia y los períodos de tiempo.

1/125000 Hz equivale a un período de 8 us. FOSC / 4 divide la frecuencia más abajo (o multiplica el período). No se puede multiplicar una frecuencia sin circuitos especiales. El preescalador también divide la frecuencia, o multiplica el período. Entonces, en lugar de dividir el 8 por 4 * 2, debes multiplicarlo por 8: 8 * 4 * 2 = un período de 64, o 64 veces lo que pensabas que era.

Desafortunadamente, 1000 no se divide uniformemente por 64, por lo que no puede obtener una interrupción exacta de 1 ms. En su lugar, probablemente debería utilizar una frecuencia de reloj de 1 MHz (OSCCON = 0b01000000) durante un período de 1 us. Entonces 8 * 1 es igual a 8 que nosotros, y puede usar un reloj preestablecido de 256-125 = 134 o 0x86 y obtener una interrupción cada 1 ms. Cuente cada 100 de esos para su tiempo de 100 ms.

Como alternativa, si el consumo de energía es un problema, puede configurar la frecuencia del reloj a 250 KHz (solo el doble de lo que estaba usando), y obtener una interrupción cada 4 ms. Luego cuente 25 de esos por tiempo de 100 ms.

    
respondido por el tcrosley
2

Tienen razón, cada tick T0 es 64 nosotros. En 100 ms, habrá 1.562,5 t0 ticks. Cada desbordamiento T0 toma 256 tics, por lo que habrá 6 desbordamientos. Luego, T0 tick número 26.5 corresponde a su centésimo milisegundo.

if (T0IE&&T0IF)
{
    // clear interrupt flag
    T0IF=0;
    ticks++;
    if (ticks == 6)
    {
        // Prepare fractional overflow of 26 ticks
        TMR0 = (256 - 26);
    }
    else if (ticks == 7)
    {   
        ticks = 0;
        PORTB = ~PORTB;
    }   

    // don't need return
}
    
respondido por el ajs410
1

Consigo lo mismo con las matemáticas como tcrosley: no puedes dividir el período entre 4 para un divisor de reloj. Multiplicas el período (frecuencia dividida == período multiplicado). Además, ¿necesita borrar el indicador de interrupción en la rutina de servicio? No te veo haciéndolo si lo haces.

    
respondido por el AngryEE
1

Acabo de notar otro problema que no había notado antes: no estás comprobando ni borrando TMR0IF dentro de tu rutina de interrupción. La forma en que funciona el PIC, cada vez que está a punto de ejecutar una instrucción, se establece GIE y se establece cualquier indicador de interrupción periférica junto con su habilitación correspondiente (por ejemplo, TMR0IF y TMR0IE), borrará GIE y llamará a la rutina de interrupción. La eliminación de GIE permite que se ejecuten las instrucciones dentro de la rutina de interrupción (de lo contrario, cada vez que el sistema estuviera a punto de ejecutar la segunda instrucción de la rutina de interrupción, se generaría otra llamada a la primera instrucción). Volviendo de la interrupción reajusta GIE. Si en ese momento todavía hay un indicador de interrupción periférica que se establece junto con su habilitación correspondiente, el sistema volverá a borrar GIE y generará una llamada a la rutina de interrupción.

Hay dos estrategias mediante las cuales una rutina de interrupción puede resolver este problema:

  1. Si hay más de una interrupción que puede estar habilitada, la rutina de interrupción debe verificar el indicador de uno de ellos y, si el indicador está establecido, borre ese indicador y maneje la condición que representa. Después de que se haya encontrado que la primera bandera está clara o que se ha manejado su condición, verifique la bandera para la segunda interrupción y, si está establecida, borre esa bandera y maneje su condición. Repita para todas las interrupciones que está utilizando. Si se habilita una interrupción inesperada, el código de la línea principal nunca se ejecutará, pero las interrupciones se comportarán con normalidad (siempre que no exista una condición de interrupción esperada, la rutina de interrupción verificará continuamente todas las condiciones de interrupción, el retorno y el reinicio, hasta que surja una condición de interrupción esperada). ).
  2. Si solo hay una interrupción que se habilitará alguna vez, se puede omitir la verificación de condición y simplemente borrar la bandera incondicionalmente cuando ocurra la condición. Tenga en cuenta que si alguna otra interrupción se habilita de alguna manera, el código puede ejecutar erróneamente la rutina de interrupción "lo más rápido posible", sin tener en cuenta la frecuencia con la que se debe ejecutar el código. Si tal ejecución errónea representara un peligro para la seguridad, uno debe protegerse contra él. Sin embargo, en muchos casos, el hecho de que el código de la línea principal no pueda ejecutarse será un problema suficiente (con la esperanza de que se produzca un reinicio de vigilancia eventual) de que realmente no importa lo que haga la rutina de interrupción.

Intente cambiar la primera línea de su código de interrupción a "if (T0IF & & & T0IE)", y agregue a la cláusula condicional "T0IE = 0". Eso debería hacer que su frecuencia de interrupción se acerque más a ser correcta. Sin embargo, no será del todo correcto, a menos que uses algo como el código en mi otra respuesta.

    
respondido por el supercat
0

Para obtener una sincronización precisa con el temporizador 0, la relación de división debe ser inferior a aproximadamente 259, o bien debe ser una potencia de dos. Debido a la desafortunada decisión de Microchip de no permitir que se escriba TMR0 sin borrar el prescalar, es imposible obtener tiempos precisos al ajustar TMR0 con el prescalar habilitado.

Para lograr tiempos precisos con intervalos de temporizador "cortos", solo es necesario agregar

  TMR0 += (259-interval);

Siempre que el código genere una instrucción "ADDWF _TMR0, f" (como debería hacerlo con cualquier compilador típico), debe proporcionar una tasa de temporizador precisa de ciclos de reloj de "intervalo".

Si uno necesita una acción de interrupción que se realice a un ritmo más lento que una vez cada 259 ciclos, no es una potencia de dos, y si la acción de interrupción puede llevar más de 256 ciclos, agregue un poco de lenguaje ensamblador antes de El manejador de interrupciones estándar puede ser 'solo el ticket'. Para lograr la mejor eficiencia, este enfoque requiere una variable no almacenada para mantener el contador de ejecución. Antes del controlador de interrupciones, inserte:

; Example assumes code should run once every thousand cycles
  bcf    _INTCON,2  ; TMR0IF
  decfsz _intCounter,f  ; Must be in unbanked RAM!
   retfie
  ... Proveed to normal interrupt handler.  Then in C code:
  TMR0 += (3+24); /* Three cycles for RTCC 'loss'; advance 24 cycles;
                     so we execute main interrupt once every 1000 cycles
                     rather than once every 1024 */
  GIE = 1;  /* Interrupts are okay now */
  ... Do main interrupt code; at end of interrupt:
  intCounter += 4;
  /* Note that if the timer tick interrupt triggers more than three times while
     running the main interrupt routine, intCounter will be zero or will be greater
     than four.  This code ignores that possibility. */

Tenga en cuenta que este código permite interrupciones anidadas, pero las interrupciones "rápidas" no alteran las banderas, ni alteran ningún registro que no sea _intCounter. En consecuencia, no hay necesidad de guardar / restaurar registros, y no hay problemas de reingreso si se producen las interrupciones "rápidas" del temporizador mientras se ejecuta la rutina principal del temporizador. Los problemas solo ocurren si se producen cuatro o más interrupciones de temporizador mientras el código principal de temporizador está funcionando.

    
respondido por el supercat

Lea otras preguntas en las etiquetas