Интерфейс SPI в STM32. Часть 2

В предыдущей статье я попробовал очень кратко рассказать о SPI интерфейсе, а сейчас настало время практики.  В микроконтроллерах STM32F407Vxxx существуют аж целых три аппаратных SPI интерфейса. В других менее крутых контроллерах SPI модули не имеют существенных отличий, разве что их там поменьше. Чтоб не запутаться на каких ногах вашего контроллера висит SPI, рекомендую воспользоваться программой MicroXplorer. Если SPI-ноги контроллера уже заняты, то следует напомнить о такой замечательной вещи как remap (позволяет перенести SPI на другие ноги). Посмотреть что и куда ремапится можно так же при помощи этой программы. У моего контроллера все SPI интерфейсы расположены на этих ногах: 

 logo

Для упрощения работы со всей периферией микроконтроллера, (таймеры, UART, SPI итд) компания ST придумала библиотеку под названием stdperiph_lib. Раньше я почему-то старался не использовать  её, но сейчас я осознал, что с её использованием код становится более понятным и читаемым + улучшается переносимость кода с одного STM32 контроллера на другой. Из-за этого, начиная с этой статьи, я буду использовать эту библиотеку для инициализации периферии. Чтоб понять как использовать SPI, крайне желательно ознакомиться со всеми регистрами через которые происходит взаимодействие с ним. Их как обычно достаточно много, но если разобраться, то ничего особо сложного в них нет. По идее библиотека stdperiph_lib как раз и предназначена для того, чтоб избавить программиста от необходимости напрямую взаимодействовать с регистрами настройки периферии, но я считаю что нужно иметь хотя-бы примерное представление о том, что и как она настраивает. 

Регистр SPI_CR1

SPI CR1

BIDIMODE - если этот бит установлен, то прием и передача данных осуществляются по одному проводу (не считая SCK). При этом, как я понял, MOSI вывод мастера подключается к MISO слейва. Экзотический режим на мой взгляд :)

BIDIOE - этот бит используется в однопроводном режиме. Если он установлен - SPI модуль только передает данные. Если сброшен - то принимает.

CRCEN - включает аппаратный модуль расчёт контрольной суммы. 0 - выкл, 1 - вкл. Изменять состояние этого бита можно только когда SPI модуль выключен (бит SPE=0).

CRCNEXT - если этот бит установлен, то после следующей передачи байта данных, будет отправлена контрольная сумма.

DFF - если бит сброшен, то SPI модуль передает/принимает данные по 8 бит, в противном случаее передается/принимается сразу по 16 бит. Изменять состояние этого бита можно только когда SPI модуль выключен (бит SPE=0).

RXONLY - если используется 2-х проводной режим (см бит BIDIMODE) то установка этого бита запрещает передачу, SPI модуль работает только на приём

Перед описанием следующих двух бит, нужно отметить одну интересную особенность вывода NSS (он же SS он же CS). Если SPI модуль настроен в режиме слейва, то он может получать сигнал с ноги NSS или же программно. Если бит SSM сброшен, то сигнал SS будет считываться с ноги NSS, а если он установлен, то состояние ноги NSS игноирируется. В таком случае для управления сигналом SS возлагается на бит SSI. Бит установлен - есть сигнал SS, в противном случае нет. Если же SPI модуль работает в режиме мастера, то ногу NSS нужно подтянуть к питанию или включить программное управление (SSM=1) и установить бит SSI. В противном случае - SPI модуль подумает, что появился новый мастер и сам станет слейвом. Этот момент для меня был не совсем очевиден и я потратил много времени чтоб разобраться. 

LSBFIRST - задает порядок передачи бит:
0 - сначала передается старший бит
1 - сначала передается младший бит

SPE - выключает/выключает SPI модуль

BR2,BR1,BR0 - задают скорость приема/передачи (частоту SCK). Частота тактирования модуля SPI делится на число, которое задается комбинацией этих трех бит.

 

BR2

BR1

BR0

Делитель

0

0

0

2

0

0

1

4

0

1

0

8

0

1

1

16

1

0

0

32

1

0

1

64

1

1

0

128

1

1

1

256

Изменять состояние этих бит можно только когда SPI модуль выключен (бит SPE=0).

MSTR - если бит установлен - SPI модуль является мастером, иначе слейвом.

CPOL - полярность сигнала SCK. (см. табличку из прошлой статьи).

CPHA - фаза сигнала SCK (см. табличку из прошлой статьи).

 

Регистр SPI_CR2

SPI CR2

TXEIE - разрешает прерывание, когда буфер передачи пуст (всё передалось)

RXNEIE - разрешает прерывание, когда буфер заполнен данными и их можно забирать.

ERRIE - разрешает прерывание в случае возникновения ошибки. Их всего три, чтоб разобраться какая возникла, нужно смотреть состояние бит в регистре статуса. Об этом чуть ниже.

FRF - Frame format, не разбирался что это такое.

0 -  SPI Motorola mode

1 - SPI TI mode

По умолчанию там ноль, без надобности не трогать :)

SSOE – Если этот бит выставлен, то SPI модуль (в режиме мастера разумеется) сам управляет выводом NSS. Т.е. как я понял перед началом передачи выставляет ноль на этом выводе, а после завершения – выставляет единицу. Возможно это не правильно, у меня так и не получилось ничего с этим битом сделать.

TXDMAEN - разрешает/запрещает запрос DMA по завершению передачи

RXDMAEN - разрешает/запрещает запрос DMA по завершению приема

Статусный регистр SPI_SR

SPI SR

FRE - Frame error flag, пока не совсем понятно что это за флаг ошибки, он используется когда SPI модуль работает в режиме «TI mode» (бит FRF=1). Еще он относится к интерфейсу I2S, о нем будет отдельная статья. (не путать с I2C)

BSY - если этот бит установлен, значит модуль SPI сейчас занят передачей данных.

OVR - бит выставляется в том случае, если в SPI модуль поступили новые данные и перетерли старые которые не были прочитаны.

MODF - выставляется в том случае если мастер внезапно перестал быть мастером. Такое возможно когда нога мастера NSS настроена как вход и на неё поступил сигнал низкого уровня.

CRCERR - ошибка контрольной суммы

UDR – флаг не используется в режиме SPI

TXE - Передача данных завершилась

RXNE - Приём данных завершен

Регистр SPI_DR

Представляет собой 16-ти битный регистр данных. На самом деле регистров два – один для передачи, а другой для приёма, но работа с ними осуществляется через один регистр SPI_DR. Если мы что-то в него пишем, то запись данных производится в регистр для передачи. Если читаем, то данные считываются из регистра для приёма данных.

Регистр SPI_CRCPR

Сюда записывают некоторое число, которое как-то должно повлиять на расчет контрольной суммы. По умолчанию там записано число 7.

Регистр SPI_TXCRCR

Сюда записывается контрольная сумма, которая посчиталась для передаваемых данных.

Регистр SPI_RXCRCR

Сюда записывается контрольная сумма, которая посчиталась для принятых данных.

Примеры кода

Теперь настало время для кода. Для первых трёх примеров можно использовать плату STM32F4DISCOVERY. Четвертый пример требует для запуска второй контроллер или еще одну отладочную платку STM32VLDISCOVERY. Для начала рассмотрим простой пример, коего будет достаточно в 95% всех случаев. Пример показывает как настроить SPI1 в режиме мастера и отправить данные. В бесконечном цикле происходит передача одного и того же байта (0x93).  


#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"

int main(void) {
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef SPI_InitStructure;
 
  // Тактирование модуля SPI1 и порта А
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
 
  // Настраиваем ноги SPI1 для работы в режиме альтернативной функции
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  //Заполняем структуру с параметрами SPI модуля
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //полный дуплекс
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Полярность и
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // Предделитель SCK
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим - мастер
  SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
  SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1....
 
  // Поскольку сигнал NSS контролируется программно, установим его в единицу
  // Если сбросить его в ноль, то наш SPI модуль подумает, что
  // у нас мультимастерная топология и его лишили полномочий мастера.
  SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);
 
  while(1) {
    SPI_I2S_SendData(SPI1, 0x93); //Передаем байт 0x93 через SPI1
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) //Передатчик занят?
      ; // значит ничего не делаем
  }
}

Следующий пример показывает как данные могут быть приняты тем же модулем SPI который их отправил. Для этого потребуется замкнуть выводы PA7 и PA6. Он стоят рядом, можно просто накинуть перемычку. Если в SPI пришел тот же байт который мы отправили, то загорается зеленый светодиод, в противном случае - красный. 


 
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"
 
#define LED_PORT GPIOD
#define LED_GREEN (1<<12)
#define LED_RED (1<<14)
 

int main(void) {
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef SPI_InitStructure;
 
  //Тактирование порта со светодиодами
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
 
  //Пины со светодиодами настраиваем как выходы
  GPIO_InitStructure.GPIO_Pin = ( LED_GREEN | LED_RED ) ;
  GPIO_Init(LED_PORT, &GPIO_InitStructure);
 
  // Тактирование модуля SPI1 и порта А
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
 
  // Настраиваем ноги SPI1 для работы в режиме альтернативной функции
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  //Заполняем структуру с параметрами SPI модуля
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //полный дуплекс
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Полярность и
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // Предделитель SCK
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим - мастер
  SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
  SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1....
 
  // Поскольку сигнал NSS контролируется программно, установим его в единицу
  // Если сбросить его в ноль, то наш SPI модуль подумает, что
  // у нас мультимастерная топология и его лишили полномочий мастера.
  SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);
 
  uint16_t data = 0;
  while(1) {
    SPI_I2S_SendData(SPI1, 0x93); //Передаем байт 0x93 через SPI1
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) //Передатчик занят?
      ; // значит ничего не делаем
 
    if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == SET) { //Если данные пришли
      data = SPI_I2S_ReceiveData(SPI1); //Читаем принятые данные
        if (data==0x93) { //Пришли правильные данные ?
            GPIO_Write(LED_PORT,LED_GREEN);
          } else {
            GPIO_Write(LED_PORT,LED_RED);
        }
      }
   }
}


Если кроме байт данных нужно еще и отсылать контрольную сумму, то можно сделать это так как написано ниже. В инициализации SPI модуля почти ничего не поменялось:  

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"

int main(void) {
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef SPI_InitStructure;
  // Тактирование модуля SPI1 и порта А
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  // Настраиваем ноги SPI1 для работы в режиме альтернативной функции
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  //Заполняем структуру с параметрами SPI модуля
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //полный дуплекс
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Полярность и
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // Предделитель SCK
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим - мастер
  SPI_InitStructure.SPI_CRCPolynomial = 0x07; // Задаем значение параметра CRCPolynomial
  SPI_CalculateCRC(SPI1,ENABLE); //Включаем расчёт CRC
  SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
  SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1....
  // Поскольку сигнал NSS контролируется программно, установим его в единицу
  // Если сбросить его в ноль, то наш SPI модуль подумает, что
  // у нас мультимастерная топология и его лишили полномочий мастера.
  SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);
  SPI_I2S_SendData(SPI1, 0xAA); 
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
  SPI_I2S_SendData(SPI1, 0xBB); 
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
 
  SPI_TransmitCRC(SPI1); //сразу после последней передачи данных
  // отправить контрольную сумму которая рассчиталась из трех переданых байт
  // 0xAA ,0xBB, 0xCC и некого числа которое называется CRCPolynomial (7 по дефолту) 
  // Алгоритм по которому она считается мне неизвестен, пока не вникал.
 
  SPI_I2S_SendData(SPI1, 0xCC); //Передаем последний байт и контрольную сумму
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
 
  return 0;
 
}

Ну и напоследок пример поинтересней. Первый контроллер шлёт байты данных, второй их принимает и отсылает обратно (во время следующей отправки данных) .  Первый проверяет что вернулось то, что отслылали. Если это так, то загорается зеленый светодиод. В противном случае красный. Чтоб запустить нужно соединить две отладочных платы вот так:
 
 boards
Исходный код для платы STM32VLDISCOVERY
 
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_spi.h"
 
int main(void) {
 
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef SPI_InitStructure;
 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC , &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_6 ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  //Заполняем структуру с параметрами SPI модуля
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //полный дуплекс
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Полярность и
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
  SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; // Управлять состоянием сигнала NSS аппаратно
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // Предделитель SCK
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
  SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; // Режим - слейв
  SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
  SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE); //Включаем прерывание по приему байта
  SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1....
  NVIC_EnableIRQ(SPI1_IRQn); //Разрешаем прерывания от SPI1
 
  while(1) {
    // все работает на прерываниях
  }
}
 
//Обработчик прерываний от SPI1
 
void SPI1_IRQHandler (void) {
  if (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==SET) {
    // Прерывание вызвано приемом байта ?
    uint8_t data = SPI1->DR; //Читаем то что пришло
    GPIOC->ODR ^= (GPIO_Pin_9 | GPIO_Pin_8); //Инвертируем состояние светодиодов
    SPI1->DR = data; //И отправляем обратно то что приняли
  }
}
 
Исходный код для платы STM32F4DISCOVERY 

 
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"
 
#define LED_PORT GPIOD
#define LED_GREEN (1<<12)
#define LED_RED (1<<14)
#define LED_BLUE (1<<15)
 
void delay(void) {
 volatile uint32_t i;
 for (i=1; i != 0x1FFFF; i++)
  ;
}
int main(void) {
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef SPI_InitStructure;
  //Тактирование порта со светодиодами
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  //Пины со светодиодами настраиваем как выходы
  GPIO_InitStructure.GPIO_Pin = ( LED_GREEN | LED_RED | LED_BLUE) ;
  GPIO_Init(LED_PORT, &GPIO_InitStructure);
  // Тактирование модуля SPI1 и порта А
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  // Настраиваем ноги SPI1 для работы в режиме альтернативной функции
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  //Заполняем структуру с параметрами SPI модуля
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //полный дуплекс
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Полярность и
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // Предделитель SCK
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим - мастер
  SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
  SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1....
  // Поскольку сигнал NSS контролируется программно, установим его в единицу
  // Если сбросить его в ноль, то наш SPI модуль подумает, что
  // у нас мультимастерная топология и его лишили полномочий мастера.
  SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);
  uint8_t data = 0;
  while(1) {
    GPIO_ResetBits(GPIOA,GPIO_Pin_4); //Подаем сигнал CS слейву
    SPI_I2S_SendData(SPI1, data); //Передаем байт 0x93 через SPI1
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) //Передатчик занят?
      ; // значит ничего не делаем
    GPIO_SetBits(GPIOA,GPIO_Pin_4); //Снимаем сигнал CS
    if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == SET) { //Если данные пришли
      if ((data-1) == (uint8_t) SPI_I2S_ReceiveData(SPI1)) { //Пришли правильные данные ?
       //Возможно тут есть неочевидный момент. Отправляем data, а ожидаем (data-1).
       // Это связано  с тем, что слейв не может передавать данные пока мастер не
       // инициирует обмен данными. Таким образом если мастер сейчас передает
       // число 123, то оно ему вернется когда он будет передавать следующий байт (124). 
        GPIO_SetBits(LED_PORT,LED_GREEN);
        GPIO_ResetBits(LED_PORT,LED_RED);
      } else {
        GPIO_SetBits(LED_PORT,LED_RED);
        GPIO_ResetBits(LED_PORT,LED_GREEN);
      }
    }
    data++; 
    delay();
    GPIO_ToggleBits(LED_PORT,LED_BLUE); //Подмигнем синим светодиодом
  }
}

Если мои наработки по части SPI окажутся полезными то я буду рад :) Если есть вопросы и замечания -  пишите, постараюсь ответить.