El temporizador en PIC18 no se acerca al tiempo deseado para el período de interrupción

0

Intenté configurar Timer0 en un Microchip PIC18F46K22 para que dispare una interrupción y un LED. Sin embargo, se necesitan al menos 42 µs para que el LED se encienda.

A una tasa de incremento de Fosc / 4 (= 8 MHz / 4 = 2 MHz), una precarga de TMR0 de 0xFF y una pérdida de dos ciclos al escribir en TMR0 espero una interrupción cada 1.5 µs, al menos en papel . Incluso si tomo en cuenta algunos ciclos de instrucción para mis operaciones de LED, no puedo entender por qué toma 42 µs. ¿Qué está mal aquí?

Aquí está mi código:

volatile unsigned char ledStatus;


void greenLEDon(void)
{
    LATCbits.LATC5 = 0;
}

void greenLEDoff(void)
{
    LATCbits.LATC5 = 1;
}


void interrupt isr(void)
{
    if(TMR0IE && TMR0IF)
    {
        if(ledStatus == 0)
        {
            ledStatus = 1;
            greenLEDon();
        }
        else
        {
            ledStatus = 0;
            greenLEDoff();
        }

        TMR0IF = 0;            // Clear interrupt flag
        TMR0 = 0xFF;           // preset for timer register
    }
}


void setup(void)
{
    OSCCON = 0b01100010;    // 8 MHz, internal

    // General outputs
    TRISCbits.TRISC5 = 0;   // RC5 (green LED)

    INTCON = 0b01000000;    // PEIE on (Peripheral Interrupt Enable)
    INTCONbits.GIE = 1;     // General Interrupt Enable


    // Timer0 Registers
    T0CONbits.T0CS = 0;   // TMR0 Clock Source Select bit: 0 = Internal Clock (CLKO)
    T0CONbits.T0SE = 0;   // TMR0 Source Edge Select bit: 0 = low/high
    T0CONbits.PSA  = 1;   // Prescaler Assignment bit: 0 = No Prescaler is assigned to the Timer0
    TMR0 = 0xFF;           // preset for timer register
    T0CONbits.TMR0ON = 1;
    T0CONbits.T08BIT = 1;
    INTCONbits.TMR0IE = 1;
}


void main(void)
{
    setup();

    while (1)
    {
    }
}

    
pregunta Norbert

4 respuestas

4

Detente y piensa en lo que significa configurar el temporizador 0 en FFh. Eso significa que se desbordará y establecerá la condición de interrupción en el siguiente reloj. El reloj se detiene durante un par de ciclos cuando se escribe TMR0, por lo que esto ocurrirá 3 ciclos (si recuerdo bien) después de la escritura. ¿Cómo puedes esperar que el mecanismo de interrupción responda tan rápido? El retorno de la interrupción (RETFIE) toma 2 ciclos solo, y la entrada en la rutina de interrupción al menos otros 2 ciclos (es esencialmente una llamada), luego está el código para guardar y restaurar el contexto. Todo esto es una sobrecarga, incluso si el código de su aplicación no hace nada.

Esperar que un PIC 18 se interrumpa cada 3 ciclos es ridículo , ya que incluso una mirada superficial a la hoja de datos debería haber sido evidente. Usar un compilador encima de eso solo hace que la frecuencia de interrupción mínima que realmente se puede manejar es peor.

Si desea configurar una interrupción periódica, comience con el temporizador 2, no con el temporizador 0. Ese temporizador tiene un registro de período para exactamente este propósito.

Incluso si está atascado con el temporizador 0 para crear una interrupción periódica, debe agregar un valor en TMR0, no configurarlo en un valor fijo. De esa manera, no hay error de acumulación y no importa el número de ciclos desde el desbordamiento del temporizador 0 a la instrucción que lo escribe en un nuevo valor.

    
respondido por el Olin Lathrop
1

Debe intentar echar un vistazo al código equivalente en lenguaje ensamblador producido por su código C en la compilación. El código real producido le dará una mejor idea de por qué ciertas cosas toman un número dado de ciclos. Con la tasa de interrupción rápida que está intentando, la tasa de procesamiento real que alcanza es la suma de:

  1. La duración del desbordamiento del temporizador si el tiempo de salida de ISR de regreso a main () es más corto que la duración programada del temporizador. Esto es probablemente cerca de cero ciclos en tu ejemplo.
  2. El tiempo de interrumpir al menos una instrucción de búsqueda en main ().
  3. La hora en que la MCU responde a la interrupción y busca el vector de interrupción.
  4. El tiempo total de ejecución del ISR como la siguiente interrupción no puede ocurrir hasta que finalice la actual.

Podría hacerlo más rápido simplificando el código en la rutina de interrupción. Intente intentar XOR el bit de puerto en el ISR en lugar de usar la variable de copia y las subrutinas adicionales.

Código como:

LATCbits.LATC5 ^= 1;

O posiblemente incluso más rápido:

LATC ^= 0x20;

Bienvenido al mundo de la programación en el dominio integrado en hardware de bajo rendimiento donde su CPU no se ejecuta a 3.27GHz.

    
respondido por el Michael Karas
0

Veo una cosa que podría causar resultados inesperados aquí: configuras TMR0 a 0xff en el ISR. No entiendo por qué haces eso. Desea que el temporizador se incremente en sí mismo, no necesita escribirlo usted mismo. De hecho, ni siquiera es necesario establecer TMR0 en 0xff en setup() , realmente no importa cuál sea el valor en el inicio.

Ahora, vamos a calcular esto de nuevo. Estás usando un reloj de 8MHz, eso es 2MIPS. El preescalador se establece en 1: 2, lo que significa que su TMR0 incrementa 1M veces por segundo. Esto significa que su ISR se llamará 1M / 256 veces por segundo, si elimina el TMR0 = 0xff del ISR. Esto es 256us o 3906 veces por segundo.

No debe realizar cálculos para llamar al ISR o al LED. Esto se debe a que mientras haces esto, el temporizador sigue funcionando. Cuando sales del ISR, el temporizador ya no es 0. Por lo tanto, el ISR se llama exactamente a la tasa que acabamos de calcular.

    
respondido por el Keelan
0

Además de lo anterior, el manejador de interrupciones hace copias de una variedad de registros para que se puedan restaurar al regresar. Como mínimo, la dirección de retorno debe guardarse en la pila, incluso si está dispuesto a deshacerse del valor de cada registro. Consulte la sección 9.12 en la hoja de datos para obtener una mejor descripción.

Para reducir los errores de este tipo, haga el reloj más rápido. Con su cristal de 8Mhz, puede usar el PLL 4x para marcar a 32MHz. Con un cristal más rápido, el PLL permite que la unidad de control de temperatura funcione a 64MHz. Su error no desaparecerá, pero disminuirá en un factor de 8.

Otro enfoque sería moverse a la línea dsPIC33, que parece realmente optimizada para interrupciones de baja latencia, y puede cronometrar a 80MHz para una tasa de 40MIPS (¡el ciclo de instrucción en esos uC es 2 tics de reloj!). Por supuesto, esto podría tener implicaciones para su cadena de herramientas.

Por último, si es realmente crítico, podría considerar usar chips lógicos para realizar su tarea, lo que no parece tan complejo. Puede mantener la uC, pero solo use la lógica de hardware externa a la uC para hacer las cosas realmente rápidas. FPGA sería otra posibilidad realmente rápida, pero con problemas definidos en la cadena de herramientas.

    
respondido por el Scott Seidman

Lea otras preguntas en las etiquetas