PIC16 Timer0 rareza

Usando un PIC16F886, estoy tratando de generar interrupciones cada 100 milisegundos usando TMR0 cronometrado desde el oscilador interno, pero estoy obteniendo un comportamiento realmente extraño.

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

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

Luego asigno el preescalador a TMR0 y establezco un valor de preescalador 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, según mis cálculos, cada 'tick' debería tardar ((1/125 000) / 4) / 2 = 1.0 × 10^-6unos segundos. Si precargo el temporizador con 155, tardará 100 'ticks' en desbordarse, 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 MPLAB SIM, toma alrededor de 85mS y en hardware real parece tomar más de 100mS.

La lista completa de códigos se puede encontrar aquí: http://pastebin.ca/1928766

Es muy posible que esté calculando mal algo, por lo que cualquier sugerencia o corrección sería muy apreciada.

Respuestas (5)

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

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

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

(He olvidado por completo que publiqué esto aquí, ¡lo siento!) Gracias por su respuesta, poco después de hacer la pregunta me di cuenta del error tonto :)

Tienen razón, cada T0 tick es 64 us. En 100 ms, habrá 1.562,5 T0 ticks. Cada desbordamiento de T0 requiere 256 tics, por lo que habrá 6 desbordamientos. Entonces T0 tick número 26.5 corresponde a su milisegundo 100th.

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
}

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

Acabo de notar otro problema que no había notado antes: no está verificando ni borrando TMR0IF dentro de su 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 bandera 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. Borrar 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, generaría otra llamada a la primera instrucción). Regresar de la interrupción reinicia GIE. Si en ese momento todavía hay un flag de interrupción de periférico activado junto con su correspondiente habilitación, el sistema volverá a borrar GIE y generará una llamada a la rutina de interrupción.

Hay dos estrategias a través de las cuales una rutina de interrupción puede resolver este problema:

  1. Si hay más de una interrupción que puede habilitarse, la rutina de interrupción debe verificar la bandera de una de ellas y, si la bandera está activada, borrar esa bandera y manejar la condición que representa. Después de que se encuentre que el primer indicador está despejado o se haya manejado su condición, verifique el indicador de la segunda interrupción y, si está establecido, borre ese indicador 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 normalmente (siempre que no exista una condición de interrupción esperada, la rutina de interrupción verificará continuamente todas las condiciones de interrupción, regresará y reiniciará, hasta que surja una condición de interrupción esperada). ).
  2. Si solo hay una interrupción que alguna vez se habilitará, uno puede omitir la verificación de condición y simplemente borrar incondicionalmente la bandera cuando ocurra la condición. Tenga en cuenta que si alguna otra interrupción de alguna manera se habilita, el código puede ejecutar erróneamente la rutina de interrupción 'lo más rápido posible', sin importar la frecuencia con la que realmente se debe ejecutar el código. Si tal ejecución errónea representa un peligro para la seguridad, uno debe evitarlo. En muchos casos, sin embargo, el hecho de que el código de la línea principal no se pueda ejecutar será un problema suficiente (con suerte, que producirá un reinicio eventual del perro guardián) que realmente no importará 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 use algo como el código en mi otra respuesta.

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

Para lograr tiempos precisos con intervalos de tiempo 'cortos', solo se necesita agregar

  TMR0 += (intervalo 259);

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

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

; El ejemplo asume que el código debe ejecutarse una vez cada mil ciclos
  bcf_INTCON,2; TMR0IF
  decfsz _intContador,f ; ¡Debe estar en RAM no bancarizada!
   recuperar
  ... Probado para el manejador de interrupciones normal. Luego en código C:
  TMR0 += (3+24); /* Tres ciclos para RTCC 'pérdida'; avance 24 ciclos;
                     entonces ejecutamos la interrupción principal una vez cada 1000 ciclos
                     en lugar de una vez cada 1024 */
  GIE = ​​1; /* Las interrupciones están bien ahora */
  ... Haz el código de interrupción principal; al final de la interrupción:
  contadorint += 4;
  /* Tenga en cuenta que si la interrupción del tictac del temporizador se activa más de tres veces mientras
     ejecutando la rutina de interrupción principal, intCounter será cero o será mayor
     que cuatro. Este código ignora esa posibilidad. */

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 las interrupciones "rápidas" del tic del temporizador ocurren mientras se ejecuta la rutina principal del tic del temporizador. Los problemas solo ocurren si ocurren cuatro o más interrupciones de tic de temporizador mientras se ejecuta el código de tic de temporizador principal.