5 votos

AVR: Cómo optimizar ciclo contado ISR a código portable, con asm inline

Estoy tratando de optimizar mi RX y TX interrupciones para cumplir con el tiempo de ejecución máximo de 25 ciclos, mientras que las interrupciones están deshabilitadas.

Hasta el momento, he encontrado que el código está optimizado lo suficiente, pero empujando y haciendo estallar serie de registros entre la carga y la descarga __SREG__ supera el límite de tiempo.

 272:   80 91 24 01     lds r24, 0x0124
 276:   8f 5f           subi    r24, 0xFF   ; 255
 278:   8f 71           andi    r24, 0x1F   ; 31
 27a:   90 91 c6 00     lds r25, 0x00C6
 27e:   20 91 25 01     lds r18, 0x0125
 282:   28 17           cp  r18, r24
 284:   39 f0           breq    .+14        ; 0x294 <__vector_18+0x30>
 286:   e8 2f           mov r30, r24
 288:   f0 e0           ldi r31, 0x00   ; 0
 28a:   ea 5d           subi    r30, 0xDA   ; 218
 28c:   fe 4f           sbci    r31, 0xFE   ; 254
 28e:   90 83           st  Z, r25
 290:   80 93 24 01     sts 0x0124, r24

La única manera de ser capaz de colocar __SREG__ en lugar más seguro (incluyen tanto empuja como sea posible en la zona inconsciente) fue inline asm.

Aquí está mi código actual:

ISR(RX0_INTERRUPT, ISR_NAKED)
{
    //push
    asm volatile("push r31" ::); // table pointer
    asm volatile("push r30" ::); // table pointer
    asm volatile("push r25" ::); // received character
    asm volatile("push r18" ::); // once compared to r24 -> rx0_first_byte

    asm volatile("push r24" ::); // most stuff is executed in r24
    asm volatile("in r24,__SREG__" ::); // possible 4 cycle gain if used r0 
    asm volatile("push r24" ::); // but one byte more on stack

    register uint8_t tmp_rx_last_byte = (rx0_last_byte + 1) & RX0_BUFFER_MASK;
    register uint8_t tmp = UDR0_REGISTER;

    if(rx0_first_byte != tmp_rx_last_byte)
    {
        rx0_buffer[tmp_rx_last_byte] = tmp;
        rx0_last_byte = tmp_rx_last_byte;
    }

    //pop
    asm volatile("pop r24" ::);
    asm volatile("out __SREG__,r24" ::);
    asm volatile("pop r24" ::);

    asm volatile("pop r18" ::);
    asm volatile("pop r25" ::);
    asm volatile("pop r30" ::);
    asm volatile("pop r31" ::);

    reti();
}

Como usted puede ver, hay un registro codificado empuja a que mi compilador utilizado, por cierto, se está trabajando en todos, pero no estoy seguro de cómo portátil es.

El único registro que me puede pasar "=r" specificator es r24 y ocurre incluso la máscara y rx0_first_byte.

Entonces, ¿cómo puedo decirle al compilador a push/pop estos 5 los registros, incluso si van a ser colocados en otros lugares?

¿Cuál es la posibilidad de que el compilador utilizará r19 y r26 en lugar de r18 y r25?

No quiero volver a escribir toda la ISR en ensamblador.

EDIT: gracias por todas las sugerencias, finalmente reescribí ISR en asm

    ISR(RX0_INTERRUPT, ISR_NAKED)
{
    asm volatile("\n\t"                      /* 5 ISR entry */
    "push  r31 \n\t"                         /* 2 */
    "push  r30 \n\t"                         /* 2 */
    "push  r25 \n\t"                         /* 2 */
    "push  r24 \n\t"                         /* 2 */
    "push  r18 \n\t"                         /* 2 */
    "in    r18, __SREG__ \n\t"               /* 1 */
    "push  r18 \n\t"                         /* 2 */

    /* read byte from UDR register */
    "lds   r25, %M[uart_data] \n\t"          /* 2 */

    /* load globals */
    "lds   r24, (rx0_last_byte) \n\t"        /* 2 */
    "lds   r18, (rx0_first_byte) \n\t"       /* 2 */

    /* add 1 & mask */
    "subi  r24, 0xFF \n\t" //???                  /* 1 */
    "andi  r24, %M[mask] \n\t"            /* 1 */

    /* if head == tail */
    "cp    r18, r24 \n\t"                    /* 1 */
    "breq  L_%= \n\t"                        /* 1/2 */

    "mov   r30, r24 \n\t"                    /* 1 */
    "ldi   r31, 0x00 \n\t"                   /* 1 */
    "subi  r30, lo8(-(rx0_buffer))\n\t"      /* 1 */
    "sbci  r31, hi8(-(rx0_buffer))\n\t"      /* 1 */
    "st    Z, r25 \n\t"                      /* 2 */
    "sts   (rx0_last_byte), r24 \n\t"        /* 2 */

"L_%=:\t"
    "pop   r18 \n\t"                         /* 2 */
    "out   __SREG__ , r18 \n\t"              /* 1 */
    "pop   r18 \n\t"                         /* 2 */
    "pop   r24 \n\t"                         /* 2 */
    "pop   r25 \n\t"                         /* 2 */
    "pop   r30 \n\t"                         /* 2 */
    "pop   r31 \n\t"                         /* 2 */
    "reti \n\t"                              /* 5 ISR return */

    : /* output operands */

    : /* input operands */
    [uart_data] "M"    (_SFR_MEM_ADDR(UDR0_REGISTER)),
    [mask]      "M"    (RX0_BUFFER_MASK)

    /* no clobbers */
    );

}

1voto

rksprst Puntos 195

Ahorro de un par de empujones/pops mediante registro global de las variables, poniendo todas las instrucciones en un asm() declaración, me gustaría llegar a algo como

#define RB_WIDTH 5
#define RB_SIZE (1<<(RB_WIDTH))
#define RB_MASK ((RB_SIZE)-1)

register uint8_t rb_head      asm("r13");
register uint8_t rb_tail      asm("r14");
register uint8_t rb_sreg_save asm("r15");

volatile uint8_t rb_buf[RB_SIZE];

ISR(USART0_RX_vect, ISR_NAKED)                 /* CLOCK CYCLES */
{
  asm("\n\t"                                   /* 5 ISR entry */
      "push  r24\n\t"                          /* 2 */
      "push  r25\n\t"                          /* 2 */
      "push  r30\n\t"                          /* 2 */
      "push  r31\n\t"                          /* 2 */
      "in    %r[sreg_save], __SREG__\n\t"      /* 1 */
      "\n\t"

      /* read byte from UART */
      "lds   r25, %M[uart_data]\n\t"           /* 2 */

      /* next_tail := (cur_tail + 1) & MASK; */
      "ldi   r24, 1\n\t"                       /* 1 */
      "add   r24, %r[tail]\n\t"                /* 1 */
      "andi  r24, %a[mask]\n\t"                /* 1 */

      /* if next_tail == cur_head */
      "cp    r24, %r[head]\n\t"                /* 1 */
      "breq  L_%=\n\t"                         /* 1/2 */

      /* rb_buf[next_tail] := byte */
      "mov   r30, r24\n\t"                     /* 1 */
      "ldi   r31, 0\n\t"                       /* 1 */
      "subi  r30, lo8(-(rb_buf))\n\t"          /* 1 */
      "sbci  r31, hi8(-(rb_buf))\n\t"          /* 1 */
      "st    Z, r25\n\t"                       /* 2 */

      /* rb_tail := next_tail */
      "mov   %r[tail], r24\n\t"                /* 1 */

      "\n"
"L_%=:\t"
      "out   __SREG__, %r[sreg_save]\n\t"      /* 1 */
      "pop   r31\n\t"                          /* 2 */
      "pop   r30\n\t"                          /* 2 */
      "pop   r25\n\t"                          /* 2 */
      "pop   r24\n\t"                          /* 2 */
      "reti\n\t"                               /* 5 ISR return */
      : /* output operands */
        [tail]      "+r"   (rb_tail)    /* both input+output */
      : /* input operands */
        [uart_data] "M"    (_SFR_MEM_ADDR(UDR0)),
        [mask]      "M"    (RB_MASK),
        [head]      "r"    (rb_head),
        [sreg_save] "r"    (rb_sreg_save)
        /* no clobbers */
      );
}

Reorganizar el búfer de código un poco, se podría reducir la cantidad de código en el ISR escrito en el búfer aún más simple (en el costo de la realización de la función de lectura del búfer más complejo).

Me han puesto un ejemplo completo con sistema de construcción y estructuras de apoyo en https://github.com/ndim/avr-uart-example/

1voto

rksprst Puntos 195

Un par de cosas útiles que he encontrado mientras que hace un AVR ISR corto y rápido para https://github.com/ndim/freemcan/tree/master/firmware fueron:

  • Tener su sistema de compilación de generar lenguaje ensamblador volcados de su código generado durante cada reconstruir y observar los cambios en el código generado cada vez que usted cambie la fuente. Esto realmente ayuda a ver lo que realmente sucede. (Yo uso avr-objdump -h -S firmware.elf > firmware.lss.)

  • Si usted necesita un rápido ISR, usted puede ahorrar algo de ciclos para empujar/popping registros contando avr-gcc a compilar todo el código C sin el uso de algunos de los registros (por ejemplo, -ffixed-r13) y, a continuación, utilice los registros de la ela variables globales en el ISR, sin empujar/popping. Esto también le ahorra la extra de los ciclos de acceso a memoria. La cabeza y la cola de punteros para el búfer de anillo son los candidatos en su caso.

  • No puedo recordar con la mano si el avr-gcc generados ISR siempre empuja/pops todos los registros, o sólo aquellas que realmente se utiliza. Si empuja/pops más de lo estrictamente necesario, puede que tenga que escribir el ISR en la asamblea, después de todo.

  • Usted puede entonces tomar el ensamblado generado las instrucciones en el idioma, poner en un .S archivo de origen de ensamblado a mano y optimizar aún más.

En mi caso, sin embargo, resultó que la ISR no era que el tiempo es fundamental, después de todo.

Por CIERTO, me gustaría utilizar el código ensamblador en línea de los parámetros de dejar gcc seleccionar los registros en lugar de escribir. Ver http://www.nongnu.org/avr-libc/user-manual/inline_asm.html

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X