AVR SPI медленнее, чем ожидалось

Я использую ATMega88A на частоте 8 МГц, а SPI настроен на работу на частоте Fosc/2 = 4 МГц.

Теоретически передача 5000 байт по SPI должна занимать 1/4000000 * 8 * 5000 = 10 мс . Но, согласно внутреннему таймеру, это занимает чуть более 19 мс . Это похоже на тонну накладных расходов. Это типично?

Образец кода:

#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;
}
Подключи его к осциллографу и посмотри размер промежутков между байтами. Чтобы получить тип передачи, который вы ожидаете, потребуется чип с DMA для управления передачей данных для вас.
Переключитесь на Fosc/4 и посмотрите, что произойдет.
Что такое ведомое устройство? Кроме того, я предложу использовать осциллограф (или логический анализатор) для проверки таймингов. Нет никакой замены для проверки HW. Код будет иметь очень небольшое влияние, поскольку вы опрашиваете интерфейс. Если передача идет медленно, у вашего кода не будет другого выбора, кроме как ждать.
Установка Fosc/4 вызывает увеличение времени, но не в 2 раза. Ведомое устройство не подключено. Просто пытаюсь оценить скорость, превращая биты в ничто.

Ответы (2)

На что настроена ваша оптимизация кода?? Я бы посмотрел на дизассемблирование вашего сгенерированного кода, вы должны помнить, что есть некоторые инструкции, которые необходимо выполнить, чтобы выполнить цикл for и интегрировать более 5000 повторений, возможно, до 9 миллисекунд. Я рекомендую вам ознакомиться с этой заметкой по применению от atmel под названием «Советы и рекомендации по оптимизации вашего кода C для 8-битных микроконтроллеров AVR», а также прочитать раздел указателя циклов и попробовать их советы, чтобы увидеть, можете ли вы уменьшить количество инструкций, необходимых для выполнения операции цикла for.

Спасибо. Это оптимизация -O3. В примечании к приложению, кажется, говорится, что у меня есть небольшое преимущество, если я перейду for (i = 0; i < 5000; i++)на for (i = 5000; i; i--), но изменение должно быть меньше, чем разрешение таймера, потому что оно не имеет никакого эффекта.

Поскольку в выходном регистре SPI нет буфера, любая задержка между тактовым циклом, когда последний бит смещается, и когда следующий байт загружается в регистр, проявляется как время простоя между байтами в выходном потоке SPI.

В приведенном выше коде (который также есть в таблице данных Atmel) есть несколько внутренних задержек...

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

... который компилируется во что-то вроде...

  1. Выход 0 в SPDR
  2. Загрузить SPSR в регистр
  3. Тестовый бит SPIF в регистре
  4. Если не установлено, перейдите к шагу № 2.
  5. Уменьшить пару регистров счетчика
  6. Перейти к шагу № 1, если вы еще не закончили

В лучшем случае при хорошей оптимизации каждый из этих шагов займет 1-2 такта. В совокупности эти циклы приводят к относительно большому разрыву между передаваемыми байтами SPI и значительно снижают пропускную способность.

Столкнувшись с той же проблемой, я придумал решение, которое сокращает время простоя между байтами SPI до 1 цикла. Вот ассемблерный код...

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

Полностью статью можно прочитать здесь...

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