AVR SPI más lento de lo esperado

1

Estoy ejecutando un ATMega88A a 8MHz y tengo el SPI configurado para ejecutarse en Fosc / 2 = 4MHz.

En teoría, desplazar 5000 bytes sobre SPI debería tomar 1/4000000 * 8 * 5000 = 10ms . Pero de acuerdo con el temporizador interno, está tomando solo 19ms . Esto parece una tonelada de sobrecarga. ¿Es esto típico?

Código de muestra:

#include <avr/io.h>

#define set_output(portdir,pin) portdir |= (1<<pin)

void init() {
    // Set MOSI, SCK, SS as Output
    set_output(DDRB, DDB5);
    set_output(DDRB, DDB3);
    set_output(DDRB, DDB2);
    // Enable SPI, Set as Master, Set CPOL & CPHA to 1 (SPI mode 3)
    SPCR = (1 << SPE) | (1 << MSTR) | (1 << CPOL) | (1 << CPHA);
    SPSR = (1 << SPI2X); // Enable SPI clock doubler
    DDRD = 0xff; // Set PORTD as output
    TCCR1B |= (1 << CS12) | (1 << CS10); // Setup Timer with 1024 prescaling
}

int main(void) {
    unsigned int i;
    init();
    TCNT1 = 0; //zero the timer
    for (i = 0; i < 5000; i++) {
        SPDR = 0; // Load data into the SPI data reg
        while (!(SPSR & (1 << SPIF))); //Wait until transmission complete
    }
    PORTD = (unsigned char) TCNT1; // Display the timer on PORTD
    for (;;) {}
    return 0;
}
    
pregunta Adam Griffin

2 respuestas

2

¿Cuál es tu optimización de código configurada para? Me gustaría ver el desensamblaje de su código generado. Debe recordar que hay algunas instrucciones que debe realizar para llevar su bucle for y las más de 5000 repeticiones integradas pueden alcanzar los 9 milisegundos. Le recomiendo que revise esta nota de la aplicación por atmel, llamada Consejos y trucos para optimizar Su código C para microcontroladores AVR de 8 bits, lea la sección de índice de bucle y pruebe sus sugerencias para ver si puede reducir las instrucciones necesarias para realizar la operación de bucle for

    
respondido por el Kvegaoro
0

Debido a que no hay búfer en el registro de salida SPI, cualquier retraso entre el ciclo de reloj cuando se desplaza el último bit y cuando se carga el siguiente byte en el registro se muestra como tiempo de inactividad entre bytes en el flujo de salida SPI.

Hay varios retrasos inherentes en el código anterior (que también se encuentra en la Hoja de datos de Atmel) ...

for (i = 0; i < 5000; i++) {
    SPDR = 0; // Load data into the SPI data reg
    while (!(SPSR & (1 << SPIF))); //Wait until transmission complete
}

... que compila a algo como ...

  1. Fuera de 0 a SPDR
  2. Cargar SPSR en un registro
  3. Bit de prueba SPIF en el registro
  4. Si no está configurado, salta al Paso # 2
  5. Disminuir el par de registro de contador
  6. Salta al paso # 1 si no has terminado

En el mejor de los casos, con una buena optimización, cada uno de esos pasos tomará 1-2 ciclos. En conjunto, estos ciclos agregarán una brecha relativamente grande entre los bytes SPI transmitidos y reducirán significativamente el rendimiento.

Después de encontrar el mismo problema, se me ocurrió una solución que reduce el tiempo de inactividad entre los bytes SPI a solo 1 ciclo. Aquí está el código de ensamblaje ...

LOOP:
                      // Cycles
                      // ------
out SPDR,__zero_reg__ // (transmit byte!)

rjmp .+0              // 2 - twiddle thumbs
rjmp .+0              // 2
rjmp .+0              // 2
rjmp .+0              // 2
rjmp .+0              // 2
nop                   // 1

sbiw len, 1           // 2 - dec counter
brne LOOP             // 2 - loop back until done     
                      // ======
                      // 17

Puedes leer el artículo completo aquí ...

enlace

    
respondido por el bigjosh

Lea otras preguntas en las etiquetas