controlador de interrupción de desbordamiento del temporizador avr-gcc en ensamblaje en línea

Mi proyecto actual consiste en producir formas de onda PWM de 200 KHz utilizando el temporizador 1 en modo PWM rápido. Me gustaría incrementar un contador de 16 bits cada vez que se desborda el temporizador (cada 5 μS)

volatile uint16_t count;
ISR(TIMER1_OVF_vect)
{
    ++count;
}

El microcontrolador es un ATmega8 que funciona a 16 MHz, lo que deja solo 80 ciclos para dar servicio a la interrupción e incrementar la variable antes de que se dispare la siguiente interrupción. Mirando el código compilado...

00000890 <__vector_8>:
 890:   1f 92           push    r1
 892:   0f 92           push    r0
 894:   0f b6           in  r0, 0x3f    ; 63
 896:   0f 92           push    r0
 898:   11 24           eor r1, r1
 89a:   8f 93           push    r24
 89c:   9f 93           push    r25
 89e:   80 91 c9 00     lds r24, 0x00C9
 8a2:   90 91 ca 00     lds r25, 0x00CA
 8a6:   01 96           adiw    r24, 0x01   ; 1
 8a8:   90 93 ca 00     sts 0x00CA, r25
 8ac:   80 93 c9 00     sts 0x00C9, r24
 8b0:   9f 91           pop r25
 8b2:   8f 91           pop r24
 8b4:   0f 90           pop r0
 8b6:   0f be           out 0x3f, r0    ; 63
 8b8:   0f 90           pop r0
 8ba:   1f 90           pop r1
 8bc:   18 95           reti

... Descubrí que la rutina de servicio de interrupción generada podría optimizarse aún más fácilmente. Esta es la primera vez que intento incluir el ensamblado en línea en un programa C, y descubrí que aprender a hacerlo es innecesariamente frustrante y requiere comprender una sintaxis bastante esotérica. Me gustaría saber cómo acceder uint8_t countdentro del ensamblaje en línea (ya que la variable se asigna de forma estática a diferencia de todas las respuestas que he visto en la web). ¿Está bien el código o me he perdido algo más?

ISR(TIMER1_OVF_vect, ISR_NAKED)
{
    asm volatile("push    r24"                                      "\n\t"
                 "in      r24, __SREG__"                            "\n\t"
                 "push    r24"                                      "\n\t"
                 "push    r25"                                      "\n\t"
                 "lds     r24, %A0"                                 "\n\t"
                 "lds     r25, %B0"                                 "\n\t"
                 "adiw    r24, 1"                                   "\n\t"
                 "sts     %B0, r25"                                 "\n\t"
                 "sts     %A0, r24"                                 "\n\t"
                 "pop     r25"                                      "\n\t"
                 "pop     r24"                                      "\n\t"
                 "out     __SREG__, r24"                            "\n\t"
                 "pop     r24"                                      "\n\t"
                 "reti"                                             "\n\t"
                 : "=r" (count)   /*this does*/
                 : "0" (count));  /*not work*/
}

Como nota al margen, ¿hay alguna manera de hacer que el compilador reserve un par de registros específicamente para uint8_t count, ya que eso permitiría reducir la longitud de ISR en al menos 6 instrucciones (al eliminar las instrucciones lds y sts, un push to stack y un pop de la pila)?

¿Compiló el código original con la optimización habilitada en GCC?
@David Originalmente se compiló con el nivel de optimización establecido en O2. Desde entonces, he intentado compilar con optimizaciones establecidas en O, O1, O2, O3 y Os. Aparentemente, este código ISR siempre se compila en el mismo resultado, independientemente del nivel de optimización. El resto de la salida compilada muestra grandes diferencias.
Eso es interesante. Me preguntaría si hay muy buenas razones por las que GCC no optimiza esto más. El hecho de que r0/r1 se guarden y restauren es interesante y podría ser una característica de la arquitectura más que una falta de optimización. No sé lo suficiente sobre AVR sin leer más.

Respuestas (2)

Encontré tu publicación cuando estaba buscando la optimización de la rutina ISR. Finalmente, tengo una solución que tú (y yo) queríamos obtener.

Uso Atmel Studio 6.1 (GCC 3.4.2.1002)

ISR(TIM0_OVF_vect,ISR_NAKED)
{
    asm volatile(
        "push   r24"            "\n"
        "in     r24, __SREG__"  "\n"
        "push   r24"            "\n"
        "push   r25"            "\n"

        "lds    r24, %A[_ts]"   "\n"
        "lds    r25, %B[_ts]"   "\n"
        "adiw   r24,1"          "\n"
        "sts    %B[_ts], r25"   "\n"
        "sts    %A[_ts], r24"   "\n"

        "pop    r25"            "\n"
        "pop    r24"            "\n"
        "out    __SREG__,r24"   "\n"
        "pop    r24"            "\n"
        "reti"                  "\n"
        : 
        : [_ts] "m" (ts)
        : "r24", "r25"
    );
}

Aquí tsse declara como volatile unsigned int ts = 0;
utilizo operandos con nombre ( [_ts] "m" (ts)) en lugar de predeterminados%0

El resultado es:


    0000005a <__vector_11>:
    5a: 8f 93           push    r24
    5c: 8f b7           in  r24, 0x3f   ; 63
    5e: 8f 93           push    r24
    60: 9f 93           push    r25
    62: 80 91 60 00     lds r24, 0x0060
    66: 90 91 61 00     lds r25, 0x0061
    6a: 01 96           adiw    r24, 0x01   ; 1
    6c: 90 93 61 00     sts 0x0061, r25
    70: 80 93 60 00     sts 0x0060, r24
    74: 9f 91           pop r25
    76: 8f 91           pop r24
    78: 8f bf           out 0x3f, r24   ; 63
    7a: 8f 91           pop r24
    7c: 18 95           reti

Deje que el compilador haga el trabajo duro por usted.

asm volatile("adiw %0,1\n\t"
             : "=w" (count)
             : "0" (count)
            );
reti();

Además, registerpero el compilador solo lo toma como una sugerencia.

De acuerdo, eso funciona, pero tendría que hacer una copia de seguridad de los valores de r23, r24 y SREG para apilarlos en una declaración de asm separada de antemano (ya que los registros podrían modificarse) y luego restaurarlos en una tercera declaración de asm. Y que yo sepa, no tengo forma de saber si el valor de countse almacena realmente en los registros r23 y r24 para incrementar o en otro lugar.
Los argumentos segundo y tercero deben indicarle al compilador que necesita empujar y abrir los registros. ¿Estás encontrando que este no es el caso?
Sí, el compilador podría encargarse de hacer una copia de seguridad de esos registros si solo escribiera un ISR() normal sin ISR_NAKED. Sin embargo, eso no sería mejor que el ISR(TIMER1_OVF_vect){ ++count; }rendimiento del código C original, ya que ambos compilan exactamente el mismo código ensamblador.