Estoy tratando de optimizar mis interrupciones RX y TX para cumplir con el tiempo máximo de ejecución de 25 ciclos mientras las interrupciones están deshabilitadas.
Hasta ahora, he descubierto que el código está lo suficientemente optimizado, pero empujar y abrir una serie de registros entre la carga y la descarga __SREG__
excede 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 forma de poder colocarlo __SREG__
en la ubicación más segura (incluya tantos empujones como sea posible en el área inconsciente) fue asm en línea.
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__" ::); // -
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 puede ver, hay un registro codificado que empujó mi compilador, por cierto, está funcionando, pero no estoy seguro de qué tan portátil es.
El único registro que puedo obtener con el especificador "=r" es r24
y está sucediendo incluso para mask y rx0_first_byte
.
Entonces, ¿cómo puedo decirle al compilador que empuje/salte estos 5 registros, incluso si se colocarán en otro lugar?
¿ Cuál es la posibilidad de que el compilador use r19
y r26
en lugar de r18
y r25
?
No quiero reescribir todo el ISR en ensamblador.
EDITAR: 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 */
);
}
ACTUALIZAR:
Después de algunas pruebas, descubrí que las interrupciones se deshabilitan antes de ingresar al controlador ISR, no después de la descarga, __SREG__
como se me sugirió anteriormente.
La única forma es globalizar los registros como sugiere ndim, o usar el siguiente código:
ISR(RX0_INTERRUPT, ISR_NAKED)
{
asm volatile("\n\t" /* 4 ISR entry */
"push r0 \n\t" /* 2 */
"in r0, __SREG__ \n\t" /* 1 */
"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 */
/* read byte from UDR register */
"lds r25, %M[uart_data] \n\t" /* 2 */
#ifdef USART_UNSAFE_RX_INTERRUPT // enable interrupt after satisfying UDR register
"sei \n\t" /* 1 */
#endif
/* load globals */
"lds r24, (rx0_last_byte) \n\t" /* 2 */
"lds r18, (rx0_first_byte) \n\t" /* 2 */
/* tmp_rx_last_byte = (rx0_last_byte + 1) & RX0_BUFFER_MASK */
"subi r24, 0xFF \n\t" /* 1 */
"andi r24, %M[mask] \n\t" /* 1 */
/* if(rx0_first_byte != tmp_rx_last_byte) */
"cp r18, r24 \n\t" /* 1 */
"breq .+14 \n\t" /* 1/2 */
/* rx0_buffer[tmp_rx_last_byte] = tmp */
"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 */
/* rx0_last_byte = tmp_rx_last_byte */
"sts (rx0_last_byte), r24 \n\t" /* 2 */
#ifdef USART_UNSAFE_RX_INTERRUPT
"cli \n\t" /* 1 */
#endif
"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 */
"out __SREG__ , r0 \n\t" /* 1 */
"pop r0 \n\t" /* 2 */
"reti \n\t" /* 4 ISR return */
: /* output operands */
: /* input operands */
[uart_data] "M" (_SFR_MEM_ADDR(UDR0_REGISTER)),
[mask] "M" (RX0_BUFFER_MASK)
/* no clobbers */
);
}
Ahorrando algunos empujones/pops usando variables de registro global, poniendo todas las instrucciones en una asm()
declaración, llegaría 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 */
);
}
Esto todavía termina tomando 42 ciclos.
Reorganizar un poco el código del búfer de anillo podría reducir aún más la cantidad de código en el ISR que se escribe en el búfer de anillo (a costa de hacer que la función de lectura del búfer sea más compleja).
He puesto un ejemplo completo con un sistema de compilación y estructuras de soporte en https://github.com/ndim/avr-uart-example/
r24
.Algunas cosas útiles que encontré al hacer un AVR ISR corto y rápido para https://github.com/ndim/freemcan/tree/master/firmware fueron:
Haga que su sistema de compilación genere volcados en lenguaje ensamblador de su código generado durante cada reconstrucción y observe los cambios en el código generado cada vez que cambie la fuente. Realmente ayuda a ver lo que realmente sucede. (Yo uso avr-objdump -h -S firmware.elf > firmware.lss
.)
Si necesita un ISR realmente rápido, puede guardar algunos ciclos para empujar/quitar registros indicando avr-gcc
que compile todo el código C sin usar algunos registros (p. ej -ffixed-r13
.), y luego use esos registros como variables globales en el ISR sin empujar/quitar. Esto también le ahorra los ciclos adicionales para el acceso a la memoria. Los punteros de cabeza y cola para el búfer de anillo son candidatos en su caso.
No puedo recordar de antemano si el avr-gcc
ISR generado siempre empuja/abre todos los registros, o solo los que realmente usa. Si empuja/hace estallar más de lo absolutamente necesario, es posible que tenga que escribir el ISR en ensamblaje después de todo.
Luego, aún puede tomar las instrucciones en lenguaje ensamblador generadas, colocarlas en un .S
archivo fuente ensamblador y optimizarlo a mano aún más.
En mi caso de uso, sin embargo, resultó que el ISR no era tan crítico después de todo.
Por cierto, usaría los parámetros asm en línea para permitir que gcc seleccione los registros en lugar de codificarlos. Consulte http://www.nongnu.org/avr-libc/user-manual/inline_asm.html
r0
y r1
(+ ponerlo a cero) y deshabilita las interrupciones después del primer impulso, mientras tanto, quiero hacer impulsos primero y luego deshabilitar las interrupciones para que quepan en 25 ciclos mientras las interrupciones están deshabilitadas. Pensé en volver a escribir ISR en ensamblador, pero necesito muchos conceptos básicos de avr-asm. El asm en línea es incluso peor que el normal: cómo llegar a las variables globales y cómo pasar los registros usados por los parámetros del asm en línea (puedo obtenerlo r24
y este manual no es lo suficientemente útil)
David
jnk0le
Wouter van Ooijen
Arsenal
jnk0le
Wouter van Ooijen
Wouter van Ooijen
jnk0le
david tweed
Lundin
register
palabras clave y vea si logra producir un código más eficiente.jnk0le
Golaž