AVR SPI más lento de lo esperado

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

En teoría, cambiar 5000 bytes por SPI debería tomar 1/4000000 * 8 * 5000 = 10 ms . Pero según el temporizador interno, está tardando poco más de 19 ms . Esto parece una tonelada de gastos generales. ¿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;
}
Conéctelo a un osciloscopio y observe el tamaño de los espacios entre bytes. Para obtener el tipo de transferencia que espera, necesitaría un chip con DMA para administrar la transferencia de datos por usted.
Cambie a Fosc/4 y vea qué sucede.
¿Qué es el dispositivo esclavo? Además, aceptaré la sugerencia de usar un osciloscopio (o un analizador lógico) para verificar los tiempos. No hay reemplazo para una verificación de HW. El código tendrá un impacto muy bajo ya que está sondeando la interfaz. Si la transferencia es lenta, su código no tendrá más remedio que esperar.
La configuración de Fosc/4 provoca un aumento en el tiempo, pero no 2x. No hay ningún dispositivo esclavo conectado. Solo trato de estimar la velocidad cambiando los bits a la nada.

Respuestas (2)

¿En qué está configurada la optimización de su código? Vería el desensamblaje de su código generado, debe recordar que hay algunas instrucciones que deben realizarse para llevar su bucle for e integrar más de 5000 repeticiones que pueden llegar a los 9 milisegundos. Le recomiendo que consulte esta nota de aplicación de atmel llamada Consejos y trucos para optimizar su código C para microcontroladores AVR de 8 bits, y lea la sección de índice de bucle y pruebe sus consejos para ver si puede reducir las instrucciones necesarias para realizar la operación de bucle for.

Gracias. Es la optimización -O3. La nota de la aplicación parece decir que tengo una pequeña ventaja si cambio for (i = 0; i < 5000; i++)a for (i = 5000; i; i--)pero el cambio debe ser menor que la resolución del temporizador porque no tiene efecto.

Debido a que no hay un búfer en el registro de salida SPI, cualquier retraso entre el ciclo de reloj cuando el último bit se desplaza y cuando el siguiente byte se carga 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 se compila en algo como...

  1. Salida 0 a SPDR
  2. Cargar SPSR en un registro
  3. Bit de prueba SPIF en el registro
  4. Si no está configurado, vaya al paso n.º 2
  5. Decrementar el par de registros del contador
  6. Vaya al paso 1 si no ha 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 se sumarán a una brecha relativamente grande entre los bytes SPI transmitidos y reducirán significativamente el rendimiento.

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

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 articulo completo aquí...

http://wp.josh.com/2015/09/29/bare-metal-fast-spi-on-avr/