SPI-связь на STM32 (STM32F103C8): обмен данными с Arduino

Перевод статьи 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 |
|---|---|
| MOSI1 | PA7 или PB5 |
| MISO1 | PA6 или PB4 |
| SCK1 | PA5 или PB3 |
| SS1 | PA4 или PA15 |
| SPI Line2 | |
|---|---|
| MOSI2 | PB15 |
| MISO2 | PB14 |
| SCK2 | PB13 |
| SS2 | PB12 |

Пины STM32F103C8.
Пины SPI у Arduino#
| SPI Line | Пин Arduino |
|---|---|
| MOSI | 11 или ICSP-4 |
| MISO | 12 или ICSP-1 |
| SCK | 13 или ICSP-3 |
| SS | 10 |

Пины SPI на Arduino UNO.
Компоненты#
- STM32F103C8;
- Arduino;
- LCD 16×2 — 2 шт.;
- потенциометр 10 кОм — 4 шт.;
- макетная плата;
- соединительные провода.
Схема и подключения#
В таблице ниже — соединения для SPI-связи STM32 с Arduino.
| Пин SPI | STM32F103C8 | Arduino |
|---|---|---|
| MOSI | PA7 | 11 |
| MISO | PA6 | 12 |
| SCK | PA5 | 13 |
| SS1 | PA4 | 10 |
А это — подключение двух LCD (16×2) к STM32F103C8 и Arduino по отдельности.
| Пин LCD | STM32F103C8 | Arduino |
|---|---|---|
| VSS | GND | GND |
| VDD | +5V | +5V |
| V0 | средний пин потенциометра (контраст) | средний пин потенциометра (контраст) |
| RS | PB0 | 2 |
| RW | GND | GND |
| E | PB1 | 3 |
| D4 | PB10 | 4 |
| D5 | PB11 | 5 |
| D6 | PC13 | 6 |
| D7 | PC14 | 7 |
| A | +5V | +5V |
| K | GND | GND |
Важно: не забудьте соединить GND у Arduino и STM32F103C8.

Схема 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)#
- Подключаем библиотеку 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);
- В
setup(): запускаем Serial на 9600, начинаем SPI, задаём делитель тактовой частоты (16) и выставляем SS в HIGH (передачи ведомому пока нет).
Serial.begin(9600);
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV16);
digitalWrite(SS,HIGH);
- В
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)#
Подключаем библиотеки SPI и LCD.
В
setup(): запускаем Serial, задаём MISO как выход (данные к мастеру идут через MISO ведомого), включаем SPI в режиме ведомого через регистр управления и разрешаем прерывание.
pinMode(MISO,OUTPUT);
SPCR |= _BV(SPE);
SPI.attachInterrupt();
При получении данных от мастера вызывается обработчик прерывания, и значение берётся из регистра SPDR.
ISR (SPI_STC_vect)
{
Slavereceived = SPDR;
received = true;
}
- В
loop(): читаем потенциометр на A0, масштабируем к 0–255 и кладём значение в регистр SPDR для передачи мастеру.
SPDR = Slavesend;
Вращая потенциометр на одной стороне, вы видите изменяющиеся значения на LCD другой стороны. Так и работает SPI-связь на STM32 — теперь к плате можно подключить любой SPI-датчик.

Проверка: значения на двух 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();
}