SPI и с чем его едят

Рассмотрим пример инициализации SPI1 составе STM32F4. Модуль SPI простой в настройке и управлении. И в основном идентичен во многих сериях микроконтроллеров от STM. Этот код может стать универсальным.

Так как логика управления SPI - это целая маленькая "наука", я ограничусь здесь лишь фрагментами кода, которые дадут вам понимание, как инициализировать модуль, используя ассемблер.

В моём примере, код использовался на отладке, в которой был подключен экранчик по SPI. Я загонял ему большие строки, используя DMA. И это наиболее предпочтительный способ передачи массивов данных. По своей природе, spi довольно медлительный, и нет смысла стоять ждать его, отправляя байт за байтом вручную или по прерываниям.
Натравите на него DMA и радуйтесь жизни!

Итак, рассмотрим SPI1, висящий на ножках 4, 5, 6, 7 порта А. Первым делом, настраивается порт.

    MOV32 R0, GPIOA_BASE

    @ Эти ножки супер-скоростные
    LDR R1, [R0, GPIO_OSPEEDR]
    ORR R1, R1, 3<<8 + 3<<10
    ORR R1, R1, 3<<12 + 3<<14
    STR R1, [R0, GPIO_OSPEEDR]

    @ Ножки будут выполнять альтернативную функцию (это не просто I/O)
    LDR R1, [R0, GPIO_MODER]
    ORR R1, R1, 2<<8 + 2<<10
    ORR R1, R1, 2<<12 + 2<<14
    STR R1, [R0, GPIO_MODER]

    @ Задать номер альтернативной функции для ножек порта A
    LDR R1, [R0, GPIO_AFRL]
    ORR R1, 0x55000000
    ORR R1, 0x00550000
    STR R1, [R0, GPIO_AFRL]


    @ Следующий шаг
    MOV32 R0, RCC_BASE

    @ Затактировать SPI1
    LDR R1, [R0, RCC_APB2ENR]
    ORR R1, R1, RCC_APB2ENR_SPI1EN
    STR R1, [R0, RCC_APB2ENR]


    @ Переходим к настройке SPI1
    MOV32 R0, SPI1_BASE

    @ Разрешить выход slave chip select и передачу при помощи DMA
    MOVLo R1, SPI_CR2_SSOE + SPI_CR2_TXDMAEN
    STR R1, [R0, SPI_CR2]

    @ Запустить SPI1
    MOV R1, SPI_CR1_MSTR + SPI_CR1_BR_1 + SPI_CR1_BR_2 + SPI_CR1_SPE
    STR R1, [R0, SPI_CR1]


Вот и всё. SPI1 готов к передаче и приёму сообщений.
Что бы данные пошли в порт, просто запишите их в регистр данных.

    MOV32 R0, SPI1
    STR R1, [R0, SPI_DR]

Принятые данные будут лежать там же. Заберём их:

    MOV32 R0, SPI1
    LDR R1, [R0, SPI_DR]


Но для работы с массивами данных у нас задекларирован DMA.
Давайте подготовим его.

    MOV32 R0, RCC_BASE

    @ Вначале затактировать DMA2
    LDR R1, [R0, RCC_AHB1ENR]
    ORR R1, R1, RCC_AHB1ENR_DMA2EN
    STR R1, [R0, RCC_AHB1ENR]

    @ Предустановка для DMA2
    @ Прерывания не используются
    MOV32 R0, DMA2_BASE

    @ Сбросить флаги прерываний
    MOV32 R1, 0xffffffff
    STR R1, [R0, DMA_LIFCR]
    STR R1, [R0, DMA_HIFCR]

    @ Адрес регистра SPI1
    @ Куда будут запихиваться данные
    MOV32 R1, SPI1+SPI_DR
    STR R1, [R0, DMA_S3PAR]

    @ Адрес начала массива данных в ОЗУ
    MOV32 R1, ЭКРАННЫЙ_БУФЕР_SPI
    STR R1, [R0, DMA_S3M0AR]

Задвинем массив в SPI используя DMA. Делается это так:

    MOV32 R0, DMA2_BASE

    @ Если флаги не сбросить, dma тупо не будет работать
    MOV32 R1, 0xFFFFFFFF
    STR R1, [R0, DMA_LIFCR]
    STR R1, [R0, DMA_HIFCR]

    @ Загрузить количество байтов для передачи
    @ Максимум 65535 байт
    MOV32 R1, 10000
    STR R1, [R0, DMA_S3NDTR]

    @ Конфигурация DMA и запуск
    MOV32 R1, DMA_SxCR_CHSEL_0 + DMA_SxCR_CHSEL_1 + DMA_SxCR_MINC + DMA_SxCR_DIR_0 + DMA_SxCR_TCIE + DMA_SxCR_EN
    STR R1, [R0, DMA_S3CR]
    @ Всё, данные пошли в SPI1


Правильная логика работы SPI предусматривает так же своевременную проверку флагов TXE, RXNE и BSY. Вы можете написать подпрограммы или макросы, которые будут проверять состояние флагов. А выглядеть они будут примерно так:

    @ Ждём в цикле TXE=1 (Transmit buffer Empty)
    @ Установка этого флага означает, что буфер передачи пустой
    @ И в него можно засунуть следующий байт

    MOV32 R0, SPI1+SPI_SR

    spi1_txe_wait1:
    LDR R1, [R0]
    TST R1, SPI_SR_TXE
    BEQ spi1_txe_wait1

Следующий код ожидает завершения приёма данных
О чём будет сигнализировать установка флага RXNE в единицу:

    MOV32 R0, SPI1+SPI_SR

    spi1_rxne_wait1:
    LDR R1, [R0]
    TST R1, SPI_SR_RXNE
    BEQ spi1_rxne_wait1

И ещё один фрагмент, который ждёт полного завершения всех процессов в SPI.
Для этого дождёмся сброса флага BSY.

    MOV32 R0, SPI1+SPI_SR

    spi1_bsy_wait0:
    LDR R1, [R0]
    TST R1, SPI_SR_BSY
    BNE spi1_bsy_wait0

Вот и всё. Как видите, совсем несложно!

Главное вовремя проверяйте состояние флагов, прежде чем делать что либо с SPI. А где именно их нужно проверять, пошагово и досконально расписано в референсе на камень. Добрые люди даже сделали для вас перевод на русский.

Почитайте перевод
локальная копия