Интерфейс SPI наряду с интерфейсом I2C занимает одно из важнейших мест для обмена данными между компонентами электронных устройств. Ранее на нашем сайте мы рассматривали использование интерфейса SPI в плате Arduino – в данной статье мы демонстрировали возможность связи между двумя платами Arduino по протоколу SPI. В этой же статье мы заменим одну из плат Arduino в отмеченном проекте на плату STM32F103C8, которая также известна под названием STM32 Blue Pill ("синяя таблетка"). Таким образом, мы рассмотрим использование интерфейса SPI в плате STM32F103C8 (Blue Pill) и ее связь по данному интерфейсу с платой Arduino.
Если сравнивать интерфейс SPI в платах STM32F103C8 Blue Pill и Arduino Uno мы увидим, что в плате Arduino он реализуется с помощью микроконтроллера ATMEGA328, а в плате STM32F103C8 – с помощью микроконтроллера ARM Cortex M3. Плата Arduino Uno имеет только одну шину SPI, а плате STM32 их две.
В данном проекте мы будем использовать плату STM32F103C8 в качестве ведущего устройства (Master), а плату Arduino – в качестве ведомого устройства (slave). К обоим платам будут подключены потенциометры для управления передаваемыми по интерфейсу SPI значениями и ЖК дисплеи 16х2 для отображения принимаемых значений.
Контакты SPI в плате STM32F103C8
Распиновка платы STM32F103C8 приведена на следующем рисунке.
Контакты SPI в плате STM32F103C8 представлены в следующей таблице.
SPI (линия 1) | Контакт в STM32F103C8 | SPI (линия 2) | Контакт в STM32F103C8 |
MOSI1 | PA7 или PB5 | MOSI2 | PB15 |
MISO1 | PA6 или PB4 | PB14 | PB14 |
SCK1 | PA5 или PB3 | SCK2 | PB13 |
SS1 | PA4 или PA15 | SS2 | PB12 |
Как видите, две шины SPI открывают больше возможностей (по сравнению с платой Arduino).
Контакты SPI в плате Arduino Uno
Контакты SPI в плате Arduino Uno представлены в следующей таблице.
Линия SPI | Контакт Arduino Uno |
MOSI | 11 или ICSP-4 |
MISO | 12 или ICSP-1 |
SCK | 13 или ICSP-3 |
SS | 10 |
Здесь, как видите контакты SPI также сгруппированы в одном месте.
Необходимые компоненты
- Плата разработки STM32F103C8 (STM32 Blue Pill) (купить на AliExpress).
- Плата Arduino Uno (купить на AliExpress).
- ЖК дисплей 16x2 – 2 шт. (купить на AliExpress).
- Потенциометр 10 кОм – 4 шт. (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Схема проекта
Схема для связи между платами STM32F103C8 и Arduino Uno по интерфейсу SPI представлена на следующем рисунке.
Схема соединений по интерфейсу SPI между платами STM32F103C8 и Arduino Uno приведена в следующей таблице.
Контакт SPI | STM32F103C8 | Arduino Uno |
MOSI | PA7 | 11 |
MISO | PA6 | 12 |
SCK | PA5 | 13 |
SS1 | PA4 | 10 |
Примечание: не забывайте соединить контакты GND (общий провод) плат STM32F103C8 и Arduino Uno.
Схема соединений между ЖК дисплеями 16х2 и платами STM32F103C8 и Arduino Uno приведена в следующей таблице.
ЖК дисплей | STM32F103C8 | Arduino Uno |
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 |
Внешний вид собранной конструкции проекта приведен на следующем рисунке.
Объяснение программ для работы с интерфейсом SPI
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Программа для платы STM32 в данном случае будет очень похожа на программу для платы Arduino – будет использоваться та же самая библиотека <SPI.h>. Программировать плату STM32F103C8 мы будем с помощью Arduino IDE через USB порт, без использования внешнего FTDI программатора.
В нашем проекте плата STM32F103C8 будет использоваться в качестве ведущей (Master), а плата Arduino Uno – в качестве ведомой (Slave). К каждой из плат подключены ЖК дисплеи 16х2 для отображения на них поступающей по интерфейсу SPI информации. Также к платам подключены потенциометры для управления передаваемыми значениями (от 0 до 255).
Потенциометр к плате STM32F103C8 подключен к ее контакту PA0 (0 до 3.3V). Значение на выходе АЦП данного контакта будет лежать в диапазоне от 0 до 4096, в дальнейшем мы преобразуем это значение в диапазон от 0 до 255 поскольку за один раз через интерфейс SPI мы можем передать только один байт (8 бит) информации.
На стороне ведомой Arduino потенциометр подключен к ее контакту A0. Значение на выходе АЦП данного контакта будет лежать в диапазоне от 0 до 1023, в дальнейшем мы преобразуем это значение в диапазон от 0 до 255 по указанной выше причине.
В нашем проекте у нас будет две программы: одна для платы STM32, которая будет выступать ведущим устройством, и одна для платы Arduino, которая будет выступать ведомым устройством.
Объяснение программы для платы STM32 (ведущей)
Первым делом в программе подключим библиотеки для работы с интерфейсом SPI и ЖК дисплеем 16х2, также укажем контакты платы, к которым подключен дисплей и создадим объект для работы с ним. Более подробно о подключении ЖК дисплея 16х2 к плате STM32 Blue Pill можно прочитать в этой статье.
1 2 3 4 |
#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); |
Далее в функции void setup() инициализируем последовательную связь со скоростью 9600 бод.
1 |
Serial.begin(9600); |
Затем начнем связь по протоколу SPI.
1 |
SPI.begin(); |
Затем установим делитель частоты синхронизации (Clock divider) для связи по интерфейсу SPI. Мы установили этот делитель равным 16.
1 |
SPI.setClockDivider(SPI_CLOCK_DIV16); |
Далее подадим на контакт SS уровень HIGH до тех пор пока мы не начнем передачу данных на ведомую Arduino.
1 |
digitalWrite(SS,HIGH); |
Затем в функции void loop() подадим на контакт SS (slave select – выбор ведомого) уровень LOW – это необходимо чтобы начать передачу данных от ведущего к ведомому.
1 |
digitalWrite(SS, LOW); |
После этого считаем аналоговое значение с контакта PA0 платы STM32F10C8.
1 |
int pot = analogRead(PA0); |
Затем преобразуем это значение в диапазон 0-255 чтобы его можно было передавать с помощью одного байта информации.
1 |
byte MasterSend = map(pot,0,4096,0,255); |
Далее у нас идет такой важный шаг, как передача значения переменной Mastersend к ведомой Arduino и прием значения от ведомой Arduino в переменную Mastereceive.
1 |
Mastereceive = SPI.transfer(Mastersend); |
Далее отобразим принятые значения от ведомой Arduino на экране ЖК дисплея с задержкой 500 мс и затем продолжим в непрерывном режиме принимать значения и отображать их на экране дисплея.
1 2 3 4 5 6 7 |
Serial.println("Slave Arduino to Master STM32"); Serial.println(MasterReceive lcd.setCursor(0,0); lcd.print("Master: STM32"); lcd.setCursor(0,1); lcd.print("SalveVal:"); lcd.print(MasterReceive delay(500); digitalWrite(SS, HIGH); |
Примечание: мы используем функцию serial.println() для отображения результатов в окне монитора последовательной связи Arduino IDE.
Объяснение программы для платы Arduino (ведомой)
Здесь в начале программы, также и для платы STM32, подключим библиотеки для работы с интерфейсом SPI и ЖК дисплеем, также укажем контакты платы Arduino, к которым подключен дисплей и создадим объект для работы с дисплеем.
1 2 3 |
#include<SPI.h> #include<LiquidCrystal.h> LiquidCrystal lcd(2, 3, 4, 5, 6, 7); // Define LCD Module Pins (RS,EN,D4,D5,D6,D7) |
Далее в функции void setup() инициализируем последовательную связь со скоростью 9600 бод.
1 |
Serial.begin(9600); |
Затем зададим режим работы для контакта MISO на вывод данных (OUTPUT) – это необходимо чтобы передавать данные ведущему устройству (Master).
1 |
pinMode(MISO,OUTPUT); |
После этого переведем интерфейс SPI в режим ведомого (Slave Mode) используя управляющий регистр SPI (SPI Control Register).
1 |
SPCR |= _BV(SPE); |
Далее включим обработку прерываний в интерфейсе SPI. Если мы получим данные от ведущего (master), то произойдет вызов процедуры обработки прерывания (Interrupt Service Routine) и принятое значение будет извлечено из регистра SPDR (SPI data Register – регистр данных SPI).
1 |
SPI.attachInterrupt(); |
Затем в функции обработки прерывания (Interrupt Routine function) мы принятое значение из регистра SPDR будем сохранять в переменной Slavereceived.
1 2 3 4 5 |
ISR (SPI_STC_vect) { Slavereceived = SPDR; received = true; } |
Далее в функции void loop() мы будем считывать аналоговое значение с контакта A0 платы Arduino и сохранять его в переменной pot.
1 |
int pot = analogRead(A0); |
Затем преобразуем это значение в диапазон 0-255 чтобы его можно было передавать с помощью одного байта.
1 |
Slavesend = map(pot,0,1023,0,255); |
Далее нам необходимо передать это преобразованное значение ведущей STM32F10C8, поэтому мы поместим это значение в регистр SPDR. Данный регистр используется для передачи и приема значений.
1 |
SPDR = Slavesend; |
Затем отобразим принятое от ведущей STM32F103C8 значение на экране ЖК дисплея с задержкой 500 мс и затем в непрерывном режиме будем осуществлять прием данных и их отображение на экране ЖК дсиплея.
1 2 3 4 5 6 7 8 |
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); lcd.print(SlaveReceived); delay(500); |
Для тестирования работы проекта вращайте потенциометры проекта, в результате этого вы должны увидеть изменяющиеся значения на экранах ЖК дисплеев на противоположных сторонах.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы (скетча)
Код для ведущей STM32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
//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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
//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(); } |
Добрый день! Контакты SPI в плате STM32F103C8 представлены в следующей таблице. А как скеч решает к каким именно контактом обратится?
Может кому пригодится, синяя плата, 128 кб, не работает SPI2.
Решение:
SPI.SPI_CPOL = SPI_CPOL_High;//Low для оригинальных STM32, а для китайских High !
//Name.SPI_CPOL-высоким или низким уровнем будет тактовый сигнал:
//SPI_CPOL_Low-в ожидании на выходе у MASTER будет низкий уровень, тактовый сигнал высокий.
//SPI_CPOL_High-тут наоборот,в ожидании на выходе у MASTER будет высокий уровень, тактовый сигнал низкий.
Да, спасибо за полезный совет
А все это может кто реализовать в Proteus))?
Иван, к сожалению, вряд ли пока у нас на сайте найдется такой человек
так это не сложно. сделай сам. код весь тут есть или можешь посмотреть в первоисточнике https://circuitdigest.com/microcontroller-projects/stm32-spi-communication-tutorial. Компилируешь код, собираешь в протеусе схемку и код подсовываешь каждому из процессоров. Посмотри как это делается, думаю за час разберешься.
Прямо вот напрямую соединили STM32 на 3.3 вольта уровни, с Arduino на 5в уровни, и ничего не сдохло... странно, странно...
Леонид, ну чудеса случаются. )) У нас на сайте есть статья где мы соединяли 5в Ардуино с 3.3 вольта Raspberry Pi и тоже ничего не сдохло
да не, никаких чудес. просто ты спер этот текст с чужого сайта, а ссылочку не поставил.
Не спер, а вручную перевел. Различайте понятия, Артем. И ссылок на их сайт у меня на сайте стоит достаточно много, я думаю они не очень в обиде если именно с этой страницы на их сайт ссылки не стояло. И первоисточник статьи всегда посмотреть под их видео, которое я всегда оставляю. А если бы хотел усиленно скрыть что оригинал статьи находится на другом сайте, то не оставлял бы. И деньги за просмотры своих видео на моем сайте получают они, а не я, поэтому в некотором плане им даже хорошо, что кто то их статьи переводит на русский язык.
Есть общепринятый порядок определения первоисточника в переводной стате. И это не: "Опубликовано 04.04.2022 автором admin-new". Оформляйте переводы как положено и тогда вопросов возникать не будет. Помимо этого есть еще и копирайт, автор первоисточника должен подтвердить согласие на перевод и опубликование своего материала.
Андрей, хочется верить в то, что вы в жизни жестко соблюдаете все правила, которые есть в современном мире