[Home] [Donate!] [Контакты]

Использование SPI при работе с микроконтроллерами STM32F100xx

Рассмотрим основные вопросы практического применения интерфейса SPI при работе с микроконтроллерами STM, такие как: используемые выводы микроконтроллера; базовое конфигурирование SPI; включение, управление и отключение SPI; обработка прерываний от SPI; использование DMA и т.д. Подробная информация справочного характера содержится в статье "SPI в микроконтроллерах STM32", где изложены основные сведения из руководства по STM32F100xx. Здесь больше внимания уделим программной реализации взаимодействия с SPI.

Оглавление
SPI в микроконтроллерах STM32. Основы
SPI в STM32. Работа в ведомом и ведущем режимах
SPI в STM32. Управление передачей данных
Регистры SPI
Использование SPI при работе с микроконтроллерами STM32F100xx
Введение
Используемые выводы
SPI в архитектуре микроконтроллера
Базовое конфигурирование
Использование SPI в STM32F100xx. Примеры

Введение

Микроконтроллеры STM32 имеют поддержку интерфейса SPI - они включают в себя одно или несколько периферийных устройств, обеспечивающих аппаратную реализацию интерфейса. Так, в разных микроконтроллерах серии STM32F10x имеется от 1 до 3 устройств SPI, а в серии STM32F4xx количество SPI доходит до 6.

Если говорить о серии STM32F100xx, то относящиеся к ней микроконтроллеры имеют одно или два устройства SPI (одно устройство SPI имеют микроконтроллеры с объёмом Flash памяти 16 и 32 Кбайт; два SPI - имеющие Flash объёмом 64 и 128 Кбайт). Поддерживаемая скорость передачи данных - до 12 Мбит/с.

Используемые выводы

Типичный вариант подключения предполагает наличие четырёх сигнальных линий: MOSI, MISO, SCK, NSS (рис. %img:c1). В некоторых конфигурациях линия NSS, предназначенная для выбора ведомого устройства, не используется, и тогда оказываются задействованы только три линии. Иногда данные передаются только в одном направлении, и тогда используется только одна линия данных (вторая линия исключается, выводы, к которым она подключается, освобождаются).

Типичный вариант подключения устройства по интерфейсу SPI.
Рис. %img:c1

Если к ведущему устройству подключено несколько ведомых, в общем случае, для управления каждым из них нужен независимый сигнал NSS. А так как аппаратная поддержка нескольких выводов NSS для ведущего устройства не предусмотрена, то в подобных ситуациях следует программно реализовать требуемое количество выходов NSS на основе обычных портов ввода-вывода общего назначения (GPIO). Программная реализация NSS часто оказывается выгодной даже в простейшем случае, когда интерфейс SPI соединяет всего два устройства. Во-первых, в качестве выхода NSS получаем возможность использовать любой удобный вывод GPIO микроконтроллера, а не аппаратно предопределённый; во-вторых, снимаются имеющиеся функциональные ограничения на сигнал NSS, связанные с особенностями аппаратной реализации интерфейса.

Иногда используется полудуплексный режим работы, при котором данные передаются по одной двунаправленной линии, подключаемой к выводу MOSI ведущего устройства и MISO ведомого устройства (подробнее смотрите "SPI в STM32. Работа в ведомом и ведущем режимах: Конфигурирование SPI для полудуплексной и симплексной связи").

Какие выводы микроконтроллера используются для нужд SPI, мы можем узнать в спецификации на микроконтроллер. Например, в случае серии STM32F100xx имеет место следующее соответствие выводов:

Таблица %tbl:pinouts. Схема расположения выводов
Вывод SPI Вывод MCU
(основная функция)
Другие
альтернативные
функции вывода
FT Номер вывода Remap
LQFP100 LQFP64 TFBGA64 LQFP48
1 2 3 4 5 6 7 8 9
SPI1_NSS PA4 ADC1_IN4 / USART2_CK / DAC1_OUT - 29 20 H3 14 PA15 (JTDI)
SPI1_SCK PA5 ADC1_IN5 / DAC2_OUT - 30 21 F4 15 PB3 (JTDO)
SPI1_MISO PA6 ADC1_IN6 / TIM3_CH1 - 31 22 G4 16 PB4 (NJTRST)
SPI1_MOSI PA7 ADC1_IN7 / TIM3_CH2 - 32 23 H4 17 PB5
SPI2_NSS PB12 I2C2_SMBA / TIM1_BKIN / USART3_CK + 51 33 H8 25 x
SPI2_SCK PB13 TIM1_CH1N / USART3_CTS + 52 34 G8 26 x
SPI2_MISO PB14 TIM1_CH2N / USART3_RTS + 53 35 F8 27 x
SPI2_MOSI PB15 TIM1_CH3N / TIM15_CH1N + 54 36 F7 28 x

В таблице для каждого вывода интерфейса SPI (первый столбец) указывается соответствующий ему вывод микроконтроллера (второй столбец таблицы, вывод указан по имени); перечень других, кроме SPI, альтернативных функций, привязанных к данному выводу микроконтроллера (столбец 3); поддержка выводом микроконтроллера сигналов с уровнем 5 В (столбец 4, вывод с поддержкой сигналов с уровнем 5 В отмечен символом  + ); номер вывода микроконтроллера (столбцы 5, 6, 7, 8 - номер вывода зависит от типа корпуса). В последнем столбце указан вывод микроконтроллера, на который имеется возможность переназначить данный вывод SPI.

Как видим из таблицы, SPI1 использует выводы микроконтроллера совместно с аналого-цифровым преобразователем ADC1. Однако это не проблема, так как ADC имеет много входных каналов. Несколько хуже то, что данные выводы не имеют поддержки сигналов с уровнем 5 В. Ещё хуже то, что PA4 и PA5 выводы используются как выходы цифро-аналоговых преобразователей DAC1_OUT и DAC2_OUT, а выводы DAC не могут быть переназначены на другие выводы микроконтроллера. И если от использования PA4 (сигнал NSS интерфейса SPI) мы ещё можем отказаться, то PA5 (сигнал SCK интерфейса SPI) принципиально необходим. Поэтому, если нам нужен DAC (или выводы, используемые SPI1, задействованы для выполнения иных функций), то для использования SPI1, потребуется переназначать выводы SPI1 на другие выводы микроконтроллера (возможные варианты замены указаны в последнем столбце таблицы). Однако и тут имеется проблема: выводы PA15, PB3, PB4, на которые можно переназначить выводы NSS, SCK, MISO устройства SPI1, используются интерфейсом JTAG. И если мы выполним переназначение, то не будем иметь возможности проводить отладку по JTAG (возможность отладки по Serial wire сохраняется).

Устройство SPI2 использует выводы микроконтроллера, имеющие поддержку сигналов с уровнем 5 В, что хорошо. Но нет возможности переназначения на другие выводы микроконтроллера, что менее хорошо. Тем более что имеет место совместное использование выводов с таймером TIM1 - довольно важным периферийным устройством микроконтроллера. Например, вывод SPI2_SCK подключён к выводу PB13 микроконтроллера, который также используется таймером (TIM1_CH1N). Для SPI2_SCK не предусматривается переназначения, поэтому, если нам нужен SPI2, но и сигнал TIM1_CH1N также требуется, мы должны переназначить TIM1_CH1N на другой вывод микроконтроллера. В документации для TIM1_CH1N указаны два варианта переназначения: PA7 и PE8. PE8 доступен только для устройств в 100-выводных корпусах. PA7 имеется во всех микроконтроллерах, но, ирония заключается в том, что PA7 также используется устройством SPI1.

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

SPI в архитектуре микроконтроллера

Интерфейс SPI в микроконтроллерах STM32 реализован как встроенное периферийное устройство. Периферийные устройства подключаются к шине APB, через которую устройство взаимодействует с ядром микроконтроллера. Также с шины устройство получает тактовый сигнал.

SPI может генерировать запросы на прерывание (при получении данных, после передачи порции данных, в случае возникновения ошибки).

Имеется поддержка прямого доступа к памяти (DMA), упрощающая передачу больших блоков данных и позволяющая достичь максимальной скорости передачи при минимальной вычислительной нагрузке на процессор.

Шины

Как показано на рис. %img:sys_arch1, устройство SPI1 подключено к шине APB2, а SPI2 подключено к шине APB1. Если в микроконтроллере имеется SPI3 (в старших сериях STM32F10x), то это периферийное устройство подключено к шине APB1. Данная информация нам потребуется уже в самом начале работы - для включения тактирования используемых устройств. Ведь прежде чем мы сможем обращаться к регистрам некоторого устройства, должны включить для него тактовый сигнал. Также, то, на какой шине находится устройство, имеет значение, если мы производим тонкое конфигурирование тактовых сигналов микроконтроллера, устанавливая на разных шинах разную частоту тактового сигнала (может потребоваться, например, для установки минимальной частоты на каждой шине, чтобы снизить потребляемый микроконтроллером ток).

Системная архитектура микроконтроллеров STM32F100.
Рис. %img:sys_arch1

Прерывания

Устройства SPI микроконтроллера могут генерировать прерывания, каждое из устройств имеет своё отдельное прерывание (и свой выделенный вектор прерывания). Имена обработчиков прерываний для SPI1 и SPI2 соответственно имеют вид SPI1_IRQHandler, SPI2_IRQHandler. Номера прерываний в CMSIS соответственно SPI1_IRQn и SPI2_IRQn (номера понадобятся для настройки контроллера вложенных векторных прерываний NVIC - чтобы задать уровень приоритета и разрешить обработку прерываний).

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

DMA

SPI1 использует каналы 2 и 3 устройства DMA1, SPI2 использует каналы 4 и 5 DMA1 (рис. %img:dma_map).

Распределение линий запросов DMA по каналам DMA.
Рис. %img:dma_map
(*) Запросы DMA от TIM1_CH1 и TIM1_CH2 направляются на каналы 2 и 3 DMA только в том случае, если бит TIM1_DMA_REMAP в регистре AFIO_MAPR2 сброшен (подробнее об этом в разделе AFIO руководства по STM32F100xx).

Распределение запросов на прямой доступ к памяти от разных периферийных устройств (в том числе, от устройств SPI) по каналам DMA1 можно посмотреть также в размещённой ниже таблице. По таблице, кроме того, можно легко выявить возможные конфликты между разными периферийными устройствами, при одновременном использовании ими DMA.

ПУ Канал 1 Канал 2 Канал 3 Канал 4 Канал 5 Канал 6 Канал 7
ADC1 ADC1            
SPI   SPI1_RX SPI1_TX SPI2_RX SPI2_TX    
USART   USART3_TX USART3_RX USART1_TX USART1_RX USART2_RX USART2_TX
I2C        I2C2_TX I2C2_RX I2C1_TX I2C1_RX
TIM1   TIM1_CH1 TIM1_CH2 TIM1_CH4
TIM1_TRIG
TIM1_COM
TIM1_UP TIM1_CH3
TIM1_CH2
TIM1_CH1
 
TIM2 TIM2_CH3 TIM2_UP     TIM2_CH1   TIM2_CH2
TIM2_CH4
TIM3   TIM3_CH3 TIM3_CH4
TIM3_UP
    TIM3_CH1
TIM3_TRIG
 
TIM4 TIM4_CH1     TIM4_CH2 TIM4_CH3   TIM4_UP
TIM6/
DAC Ch1
    TIM6_UP/
DAC_Channel1
       
TIM7/
DAC Ch2
      TIM7_UP/
DAC_Channel2
     
TIM15         TIM15_CH1
TIM15_UP
TIM15_TRIG
TIM15_COM
   
TIM16           TIM16_CH1
TIM16_UP
 
TIM17             TIM17_CH1
TIM17_UP

Базовое конфигурирование

Рассмотрим пример кода, выполняющего подготовку SPI к работе: включение тактирования устройства SPI; включение тактирования портов GPIO, к которым принадлежат выводы SPI; конфигурирование используемых выводов (выводы, являющиеся выходами, должны быть настроены как выходы для выполнения альтернативной функции, как правило, в случае SPI выход должен быть типа push-pull; входы, очевидно, должны быть настроены как входы); конфигурирование SPI и включение SPI.

Прежде, чем станет возможна любая работа с регистрами SPI, должен быть включен тактовый сигнал этого устройства. С учётом того, что SPI1 находится на шине APB2, в SPI2 - на APB1, то включается тактовый сигнал для них следующим образом:

RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;

Каждое из устройств SPI использует до 4 выводов микроконтроллера. В нашем случае (STM32F100xx) используются: для SPI1 - выводы PA4..PA7, а для SPI2 - выводы PB12..PB15. Соответственно, для работы с SPI1 следует включить тактирование GPIOA, а для работы с SPI2 - тактирование GPIOB:

RCC->APB2ENR |=
    RCC_APB2ENR_IOPAEN |   // Включаем тактовый сигнал для GPIOA.
    RCC_APB2ENR_IOPBEN;    // Включаем тактовый сигнал для GPIOB.

Если для передачи данных будет задействован механизм прямого доступа к памяти, необходимо также включить тактовый сигнал для DMA1 (DMA1 находится на шине AHB):

RCC->AHBENR |= RCC_AHBENR_DMA1EN;

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

Например, для настройки NVIC, разрешающей обработку прерываний от SPI2, выполняем следующие действия:

// Задаём приоритет (используемая здесь идиома задаёт низший приоритет).
NVIC_SetPriority(SPI2_IRQn, (1 << __NVIC_PRIO_BITS) - 1);

// Разрешаем обработку этого прерывания.
NVIC_EnableIRQ(SPI2_IRQn);

Настройка для разрешения обработки прерываний от SPI1 совершенно аналогична. Генерация прерываний на уровне устройства SPI разрешается в процессе конфигурирования устройства SPI (об этом немного позже).

Если мы разрешаем обработку прерывания, то должны позаботиться о написании обработчика прерывания. Обработчик прерываний от SPIx должен иметь имя SPIx_IRQHandler. Внутри обработчика следует установить причину возникновения прерывания, т.е. проверить, какие установлены флаги из числа тех, при установке которых разрешена генерация прерывания. Затем - выполнить действия, в результате которых флаги будут сброшены. Так, флаг SPI_SR_TXE сбрасывается при записи в регистр DR, а флаг SPI_SR_RXNE - при чтении из регистра DR.

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

Очевидно, что флаги, при установке которых не разрешена генерация прерывания (настройками SPI), не требуется сбрасывать и их можно не проверять.

extern "C" void SPI2_IRQHandler()
{
    if(SPI2->SR & SPI_SR_TXE)
    {
        SPI2->DR = <передаваемые данные>;
    }

    if(SPI2->SR & SPI_SR_RXNE)
    {
        <приёмный буфер> = SPI2->DR;
    }
}

В данном примере предполагается, что настройками SPI разрешены только прерывания при установке флагов SPI_SR_TXE, SPI_SR_RXNE, т.е. в процессе конфигурирования SPI выполнен код

SPI2->CR2 =
        SPI2->CR2 & ~ 0xE7 | // Сбрасываем все значимые биты регистра CR2.
        SPI_CR2_RXNEIE |     // Разрешены прерывания при установке флага RXNE.
        SPI_CR2_TXEIE;       // Разрешены прерывания при установке флага TXE.

Прерывания при установке флагов ошибок (MODF, OVR, CRCERR) здесь не разрешены, соответственно эти флаги в обработчике не контролируются.

Мы рассмотрели основные вопросы, касающиеся подготовки к работе с SPI, теперь рассмотрим несколько конкретных примеров, демонстрирующих непосредственно работу с SPI:
Использование SPI в STM32F100xx. Примеры

hamper, 2020-11-01
  Рейтинг@Mail.ru