Перевод статьи How to Use SPI Communication in STM32 Microcontroller (STM32F103C8)Pramoth Thangavel, Circuit Digest.

В прошлых материалах мы разбирали связь по SPI и I2C между двумя платами Arduino. В этом руководстве мы заменим одну из Arduino платой Blue Pill (STM32F103C8) и наладим обмен с Arduino по шине SPI. В этом примере STM32F103C8 будет мастером, Arduino UNO — ведомым; к каждой отдельно подключён LCD-дисплей 16×2. Также к STM32 (PA0) и Arduino (A0) подключены потенциометры — они задают передаваемые значения (0–255) от мастера к ведомому и обратно.

SPI в STM32F103C8#

Если сравнить SPI у Arduino и STM32F103C8 Blue Pill: у STM32 две шины SPI, а у Arduino Uno — одна. В Arduino Uno стоит микроконтроллер ATMEGA328, а в STM32F103C8 — ARM Cortex-M3, за счёт чего он быстрее.

Пины SPI у STM32F103C8#

SPI Line1Пин STM32F103C8
MOSI1PA7 или PB5
MISO1PA6 или PB4
SCK1PA5 или PB3
SS1PA4 или PA15
SPI Line2
MOSI2PB15
MISO2PB14
SCK2PB13
SS2PB12
Распиновка STM32F103C8 с пинами SPI

Пины STM32F103C8.

Пины SPI у Arduino#

SPI LineПин Arduino
MOSI11 или ICSP-4
MISO12 или ICSP-1
SCK13 или ICSP-3
SS10
Пины SPI на Arduino UNO

Пины SPI на Arduino UNO.

Компоненты#

  • STM32F103C8;
  • Arduino;
  • LCD 16×2 — 2 шт.;
  • потенциометр 10 кОм — 4 шт.;
  • макетная плата;
  • соединительные провода.

Схема и подключения#

В таблице ниже — соединения для SPI-связи STM32 с Arduino.

Пин SPISTM32F103C8Arduino
MOSIPA711
MISOPA612
SCKPA513
SS1PA410

А это — подключение двух LCD (16×2) к STM32F103C8 и Arduino по отдельности.

Пин LCDSTM32F103C8Arduino
VSSGNDGND
VDD+5V+5V
V0средний пин потенциометра (контраст)средний пин потенциометра (контраст)
RSPB02
RWGNDGND
EPB13
D4PB104
D5PB115
D6PC136
D7PC147
A+5V+5V
KGNDGND

Важно: не забудьте соединить GND у Arduino и STM32F103C8.

Схема SPI-связи между STM32 и Arduino с двумя LCD

Схема SPI-связи STM32 ↔ Arduino.

Собранная схема SPI-связи STM32 и Arduino на макетной плате

Собранная схема на макетной плате.

Программирование SPI на STM32#

Программирование похоже на код Arduino — используется та же библиотека <SPI.h>. STM32F103C8 можно прошивать через USB без FTDI-программатора.

Аналоговый вход снимается на пине PA0 STM32 (0–3,3 В) вращением потенциометра. Значение переводится в цифровое (0–4096), а затем масштабируется к 0–255 — ведь по SPI за раз можно передать только 8 бит (байт). Аналогично на стороне ведомого: на пине A0 Arduino (0–5 В) значение переводится в 0–1023 и масштабируется к 0–255.

Разбор кода мастера (STM32)#

  1. Подключаем библиотеку SPI и LCD, задаём пины дисплея.
#include<SPI.h>
#include<LiquidCrystal.h>
const int rs = PB0, en = PB1, d4 = PB10 , d5 = PB11 , d6 = PC13, d7 = PC14;
LiquidCrystal lcd(rs,en,d4,d5,d6,d7);
  1. В setup(): запускаем Serial на 9600, начинаем SPI, задаём делитель тактовой частоты (16) и выставляем SS в HIGH (передачи ведомому пока нет).
Serial.begin(9600);
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV16);
digitalWrite(SS,HIGH);
  1. В loop(): перед передачей опускаем SS в LOW, читаем аналоговое значение с PA0 и переводим его в байт (0–255).
digitalWrite(SS, LOW);
int pot = analogRead(PA0);
byte MasterSend = map(pot,0,4096,0,255);

Ключевой шаг: отправляем значение ведомому и одновременно получаем значение от него.

Mastereceive = SPI.transfer(Mastersend);

Затем выводим полученное от ведомого значение с задержкой 500 мс.

Разбор кода ведомого (Arduino)#

  1. Подключаем библиотеки SPI и LCD.

  2. В setup(): запускаем Serial, задаём MISO как выход (данные к мастеру идут через MISO ведомого), включаем SPI в режиме ведомого через регистр управления и разрешаем прерывание.

pinMode(MISO,OUTPUT);
SPCR |= _BV(SPE);
SPI.attachInterrupt();

При получении данных от мастера вызывается обработчик прерывания, и значение берётся из регистра SPDR.

ISR (SPI_STC_vect)
{
  Slavereceived = SPDR;
  received = true;
}
  1. В loop(): читаем потенциометр на A0, масштабируем к 0–255 и кладём значение в регистр SPDR для передачи мастеру.
SPDR = Slavesend;

Вращая потенциометр на одной стороне, вы видите изменяющиеся значения на LCD другой стороны. Так и работает SPI-связь на STM32 — теперь к плате можно подключить любой SPI-датчик.

Проверка SPI-связи: значения на двух LCD-дисплеях

Проверка: значения на двух LCD меняются при вращении потенциометров.

Полный код#

Мастер (STM32)#

//SPI Master code for STM32F103C8
//SPI Communication between STM32 & Arduino
//Circuit Digest 
#include<SPI.h>                                                               // Including Library for using SPI Communication
#define SS PA4                                                              
#include<LiquidCrystal.h>                                                     // Including LCD display library
const int rs = PB0, en = PB1, d4 = PB10 , d5 = PB11 , d6 = PC13, d7 = PC14;   // Declaring pin names and pin numbers of lcd
LiquidCrystal lcd(rs,en,d4,d5,d6,d7);                                         // Setting lcd and its paramaters
void setup (void)
{
  lcd.begin(16,2);                                                            // Setting lcd as 16x2 mode
  lcd.setCursor(0,0);                                                         // Setting cursor at first row and first column
  lcd.print("CIRCUIT DIGEST");                                                // Puts CIRCUIT DIGEST in LCD
  delay(3000);                                                                // Delays for 3 seconds
  lcd.clear();                                                                // Clears lcd display
  
  Serial.begin(9600);                                                         // Starts Serial Communication at Baud Rate 9600
  pinMode(SS,OUTPUT);                                                         // Puts SS as Output
  SPI.begin();                                                                // Begins the SPI commnuication
  SPI.setClockDivider(SPI_CLOCK_DIV16);                                       // Sets clock for SPI communication at 16 (72/16=4.5Mhz)
  digitalWrite(SS,HIGH);                                                      // Setting SlaveSelect as HIGH (So master doesnt connnect with slave)
}
void loop(void)
{ 
  
  byte MasterSend,MasterReceive;
  
  int pot = analogRead(PA0);                                                   // Analog read the input pot value at pin PA0
  
  MasterSend = map(pot,0,4096,0,255);                                          // Used to convert pot value in terms of 0 to 255 from 0 to 4096
  
  digitalWrite(SS, LOW);                                                       // Starts communication with Slave connected to master
  
  MasterReceive=SPI.transfer(MasterSend);                                      // Send the mastersend value to slave also receives value from slave
  Serial.println("Slave Arduino to Master STM32");                             // Used in Serial Monitor
  Serial.println(MasterReceive);                                               // Puts value Received im Serail Monitor
  lcd.setCursor(0,0);                                                          
  lcd.print("Master: STM32");
  lcd.setCursor(0,1);                                                    
  lcd.print("SalveVal:");                                                         
  lcd.print(MasterReceive);                                                    // Puts the received value from slave arduino      
  delay(500);
  digitalWrite(SS, HIGH);                                                      // Again make SS line HIGH so that it doesnt communicate with Slave
  lcd.clear();
}

Ведомый (Arduino)#

//SPI Slave Code for Arduino
//SPI Communication between STM32F103C8 & Arduino
//Circuit Digest

#include<SPI.h>                           // Including Library for using SPI Communication
#include<LiquidCrystal.h>                 // Including LCD display library
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);      // Define LCD Module Pins (RS,EN,D4,D5,D6,D7)
volatile boolean received;
volatile byte SlaveReceived,Slavesend;
void setup()
{ 
  lcd.begin(16,2);                        // Initilize LCD display
  lcd.setCursor(0,0);                     // Sets Cursor at first line of Display 
  lcd.print("CIRCUIT DIGEST");            // Prints CIRCUIT DIGEST in LCD 
  delay(3000);                            // Delay for 3 seconds
  lcd.clear();                            // Clears LCD display
  
  Serial.begin(9600);                     // Starts Serial Communication at Baud Rate 9600
  
  pinMode(MISO,OUTPUT);                   // Sets MISO as OUTPUT (Have to Send data to Master IN (STM32F103C8)
  SPCR |= _BV(SPE);                       // Turn on SPI in Slave Mode
  received = false;
  SPI.attachInterrupt();                  // Interuupt ON is set for SPI commnucation
}
ISR (SPI_STC_vect)                        // Inerrrput routine function 
{
  SlaveReceived = SPDR;                   // Value received from master STM32F103C8 is stored in variable slavereceived
  received = true;                        // Sets received as True 
}
void loop()
{ 
  int pot = analogRead(A0);               // Analog read the input pot value from analog pin A0
  Slavesend = map(pot,0,1023,0,255);      // Converts the value pot (0-1023) to (0-255) for sending to master stm32 
                                  
  SPDR = Slavesend;                       // Sends the salvesend value to master STM32F103C8 via SPDR 
  lcd.setCursor(0,0);                     
  lcd.print("Slave: Arduino");
  lcd.setCursor(0,1);                                                     
  lcd.print("MasterVal:");    
  Serial.println("Master STM32 to Slave Arduino");   
  Serial.println(SlaveReceived);                   // Puts the received value from Master STM32F103C8 at Serial Monitor                          
  lcd.print(SlaveReceived);                        // Puts the received value from Master STM32F103C8 at LCD display
  delay(500);
  lcd.clear();
}