Ускорьте код для последовательных протоколов, используя битбэнг

Мне интересно, есть ли какие-либо особенно быстрые способы чтения или записи последовательного протокола (такого как SPI) с использованием вызовов GPIO (бит-бэнгинга) на микроконтроллере. То, что быстрее всего, может быть несколько специфичным для архитектуры, но меньшее количество операций, вероятно, будет быстрее в любой архитектуре. Справедливо предположить, что чтение / запись порта и любые побитовые целочисленные операции выполняются за один такт процессора; сравнивать и прыгать можно за несколько часов.

В качестве простого примера, представьте себе, как записать байт с использованием обычного SPI, учитывая порт (регистр) с битовыми масками для последовательных тактовых импульсов, мисо (вход), выводов mosi (выход). Для вывода 1 бита на заднем фронте часов port |= CLOCK|OUTPUT; port &= ~CLOCK; и запись байта будет состоять из выполнения этого для каждого бита в этом байте (с выводом 0 или 1) в цикле.

Что-то вроде этого:

uint8_t data;
// for each bit, msb first
for (i = 7; i >= 0; i--)
{
    if ((data >> i) & 0x01)
    {
        // set output pin to 1
        port |= OUTPUT;
    }
    else
    {
        // set output pin to 0
        port &= ~OUTPUT;
    }
    // strobe clock
    port |= CLOCK;
    port &= ~CLOCK;
}

Некоторые оптимизации этого очевидны, например, развернуть циклы.

Некоторые оптимизации могут быть не столь очевидны: возможно ли переключение в зависимости от записываемого байта (или только его части), и поэтому избегайте сдвига и маскирования каждого бита? Как сделать эту ветку бесплатной? Как воспользоваться прогонами нулей или единиц? (В этом случае нет необходимости менять выход, только часы) Как насчет изменения выхода и часов за одну операцию?

Пример, в котором используются некоторые из этих приемов (когда возможно, измените выходные данные и часы в одной операции, разверните циклы, но не разветвите): https://github.com/FastLED/FastLED/blob/master/fastspi_bitbang.h

1 ответ

SPI синхронизирует данные через каждое другое ребро. Включение фронта часов с вашими данными, вероятно, быстрее (то же самое ИЛИ, но сохраняет одно назначение)

port = CLOCK
for (i = 7; i >= 0; i--)
{
    if ((data >> i) & 0x01)
    {
        // set output pin to 1
        port |= OUTPUT | CLOCK;
    }
    else
    {
        // set output pin to 0
        port &= OUTPUT;
        port |= CLOCK;
    }
    // strobe clock
    port &= ~CLOCK;
}
Другие вопросы по тегам