Komunikační protokol SPI v ESP32

19.08.2022 Arduino #arduino #esp32 #spi #bme280

SPI je zkratka pro S erial P eripheral I ninterface a je to synchronní sériový datový protokol používaný mikrokontroléry ke komunikaci s jedním nebo více periferními zařízeními. Například deska ESP32 komunikující se senzorem, který podporuje SPI, nebo s jiným mikrokontrolérem.


V SPI komunikaci vždy existuje řadič (nazývaný master), který řídí periferní zařízení (nazývaná slave). Data lze odesílat a přijímat současně. To znamená, že master může posílat data na slave a slave může odesílat data do masteru současně.

Můžeme mít pouze jeden master, což bude mikrokontrolér (ESP32), ale můžete mít více slave. Slave může být senzor, displej, microSD karta atd. nebo jiný mikrokontrolér. To znamená, že můžeme mít ESP32 připojené k více senzorům, ale stejný senzor nelze připojit k více deskám ESP32 současně.

Rozhraní SPI

Pro SPI komunikaci potřebujeme čtyři linky:

  • MISO : Master In Slave Out
  • MOSI : Master Out Slave In
  • SCK : Sériové hodiny
  • CS / SS : Chip Select (používá se k výběru zařízení, když je na stejné sběrnici SPI použito více periferií)

Na podřízených zařízeních, jako jsou senzory, displeje a další, se můžeme setkat s jinou terminologií:

  • MISO může být označeno jako SDO (Serial Data Out)
  • MOSI může být označeno jako SDI (Serial Data In)

Periferie ESP32 SPI

ESP32 integruje 4 periferie SPI: SPI0, SPI1, SPI2 (běžně označované jako HSPI ) a SPI3 (běžně označované jako VSPI ).

SP0 a SP1 se používají interně ke komunikaci s vestavěnou pamětí flash a neměli byste je používat pro jiné úkoly.

Pro komunikaci s jinými zařízeními můžete použít HSPI a VSPI. HSPI a VSPI mají nezávislé signály sběrnice a každá sběrnice může řídit až tři podřízené jednotky SPI.

Výchozí SPI piny ESP32

Mnoho desek ESP32 je dodáváno s předem přiřazenými výchozími piny SPI. Mapování pinů pro většinu desek je následující:

SPI MOSI MISO SCLK CS
VSPI GPIO 23 GPIO 19 GPIO 18 GPIO 5
HSPI GPIO 13 GPIO 12 GPIO 14 GPIO 15

Upozornění: V závislosti na desce, kterou používáme, se mohou výchozí piny SPI lišit. Ujistěme se tedy, že jsme zkontrolovali pinout pro desku, kterou používáme. Některé desky navíc nemají předem přiřazené piny SPI, takže je musíte nastavit na kód.

Poznámka: obvykle, pokud není specifikováno, deska použije piny VSPI při inicializaci komunikace SPI s výchozím nastavením.

Ať už je vaše deska dodávána s předem přiřazenými kolíky nebo ne, vždy je můžeme nastavit na kód.

Nalezení výchozích pinů SPI vaší desky ESP32

Pokud si nejsme jisti výchozími piny SPI naší desky, můžeme nahrát následující kód, abychom to zjistili.

//Find the default SPI pins for your board
//Make sure you have the right board selected in Tools > Boards
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.print("MOSI: ");
  Serial.println(MOSI);
  Serial.print("MISO: ");
  Serial.println(MISO);
  Serial.print("SCK: ");
  Serial.println(SCK);
  Serial.print("SS: ");
  Serial.println(SS);  
}

void loop() {
  // put your main code here, to run repeatedly:
}

 

Důležité: Musíme se ujistit, že je vybrána odpovídající deska, v Nástroje > Deska , jinak se může stát, že nezískáme správné hodnoty.

Po nahrání kódu otevřeme Serial Monitor, RST svou desku a uvidíme SPI piny.

Použití vlastních pinů ESP32 SPI

Při používání knihoven pro rozhraní s vašimi periferními zařízeními SPI je obvykle jednoduché použít vlastní piny SPI, protože je můžeme předat jako argumenty konstruktoru knihovny.

Například se rychle podívejme na následující příklad, který je propojen se snímačem BME280 pomocí rozhraní knihovny Adafruit_BME280.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#include <SPI.h>
#define BME_SCK 25
#define BME_MISO 32
#define BME_MOSI 26
#define BME_CS 33
#define SEALEVELPRESSURE_HPA (1013.25)

//Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

unsigned long delayTime;

void setup() {
  Serial.begin(9600);
  Serial.println(F("BME280 test"));

  bool status;

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin();  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  Serial.println("-- Default Test --");
  delayTime = 1000;

  Serial.println();
}


void loop() { 
  printValues();
  delay(delayTime);
}

void printValues() {
  Serial.print("Temperature = ");
  Serial.print(bme.readTemperature());
  Serial.println(" *C");

  // Convert temperature to Fahrenheit
  /*Serial.print("Temperature = ");
  Serial.print(1.8 * bme.readTemperature() + 32);
  Serial.println(" *F");*/

  Serial.print("Pressure = ");
  Serial.print(bme.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.print("Approx. Altitude = ");
  Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
  Serial.println(" m");

  Serial.print("Humidity = ");
  Serial.print(bme.readHumidity());
  Serial.println(" %");

  Serial.println();
}

Své vlastní piny SPI můžeme snadno předat konstruktoru knihovny.

Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

 

V takovém případě jsem používal následující piny SPI (ne výchozí) a vše fungovalo podle očekávání:

#define BME_SCK 25
#define BME_MISO 32
#define BME_MOSI 26
#define BME_CS 33

 

Pokud nepoužíváme knihovnu nebo knihovna, kterou používáme, nepřijímá piny v konstruktoru knihovny, možná budeme muset inicializovat sběrnici SPI sami. V takovém případě budeme muset zavolat SPI.begin() v setuo() a předat piny SPI jako argumenty:

SPI.begin(SCK, MISO, MOSI, SS);

 

ESP32 s více zařízeními SPI

Jak je vidět dříve, na ESP32 lze použít dvě různé sběrnice SPI a každá sběrnice může připojit až tři různé periferie. To znamená, že k ESP32 může připojit až šest SPI zařízení. Pokud potřebujeme použít více, můžete použít multiplexer SPI.

Více SPI zařízení (stejná sběrnice, jiný CS pin)

Chceme-li připojit více zařízení SPI, můžeme použít stejnou sběrnici SPI, pokud každá periferie používá jiný pin CS.

 

Chceme-li vybrat periferii, se kterou chceme komunikovat, měli bysme ji nastavit CS na LOW. Představme si například, že máme periferii 1 a periferii 2. Chceme-li číst z periferie 1, ujistěme se, že je pin CS nastaven na LOW (zde zastoupeno jako CS_1):

digitalWrite(CS_1, LOW); // enable CS pin to read from peripheral 1

/*
 use any SPI functions to communicate with peripheral 1
*/

 

Poté ve stejném okamžiku budeme chtít číst z periferie 2. Měli bychom periferii 1 zakázat CS pin nastavením na HIGH a povolit periferní zařízení 2 CS pin nastavením na LOW:

digitalWrite(CS_1, HIGH); // disable CS pin from peripheral 1
digitalWrite(CS_2, LOW);  // enable CS pin to read from peripheral 2

/*
 use any SPI functions to communicate with peripheral 2
*/

 

ESP32 pomocí dvou rozhraní sběrnice SPI (současné použití HSPI a VSPI)

Pro komunikaci s více periferiemi SPI současně můžeme u ESP32 použít dvě sběrnice SPI (HSPI a VSPI). Můžeme použít výchozí piny HSPI a VSPI nebo použít vlastní piny.

Stručně řečeno, abysme mohli používat HSPI a VSPI současně, stačí.

1) Nejprve se ujistit, že jsme do svého kódu zahrnuli knihovnu SPI.

#include <SPI.h>

2) Inicializovali dva objekty SPICclass s různými názvy, jeden na sběrnici HSPI a druhý na sběrnici VSPI. Například:

vspi = new SPIClass(VSPI);
hspi = new SPIClass(HSPI);

3) Zavoláme metodu begin() na tyto objekty.

vspi.begin();
hspi.begin();

Vlastní piny v případě potřeby můžeme předávat metodou begin().

vspi.begin(VSPI_CLK, VSPI_MISO, VSPI_MOSI, VSPI_SS);
hspi.begin(HSPI_CLK, HSPI_MISO, HSPI_MOSI, HSPI_SS);

4) Nakonec je také potřeba nastavit SS piny jako výstupy. Například:

pinMode(VSPI_SS, OUTPUT);
pinMode(HSPI_SS, OUTPUT);

Poté se použijí obvyklé příkazy k interakci se zařízeními SPI, ať už používáme knihovnu senzorů nebo metody knihovny SPI.

Příklad použití více sběrnic SPI najdete v knihovně arduino-esp32 SPI . Viz příklad níže:

/* The ESP32 has four SPi buses, however as of right now only two of
 * them are available to use, HSPI and VSPI. Simply using the SPI API 
 * as illustrated in Arduino examples will use VSPI, leaving HSPI unused.
 * 
 * However if we simply intialise two instance of the SPI class for both
 * of these buses both can be used. However when just using these the Arduino
 * way only will actually be outputting at a time.
 * 
 * Logic analyser capture is in the same folder as this example as
 * "multiple_bus_output.png"
 * 
 * created 30/04/2018 by Alistair Symonds
 */
#include <SPI.h>

// Define ALTERNATE_PINS to use non-standard GPIO pins for SPI bus

#ifdef ALTERNATE_PINS
  #define VSPI_MISO   2
  #define VSPI_MOSI   4
  #define VSPI_SCLK   0
  #define VSPI_SS     33

  #define HSPI_MISO   26
  #define HSPI_MOSI   27
  #define HSPI_SCLK   25
  #define HSPI_SS     32
#else
  #define VSPI_MISO   MISO
  #define VSPI_MOSI   MOSI
  #define VSPI_SCLK   SCK
  #define VSPI_SS     SS

  #define HSPI_MISO   12
  #define HSPI_MOSI   13
  #define HSPI_SCLK   14
  #define HSPI_SS     15
#endif

#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define VSPI FSPI
#endif

static const int spiClk = 1000000; // 1 MHz

//uninitalised pointers to SPI objects
SPIClass * vspi = NULL;
SPIClass * hspi = NULL;

void setup() {
  //initialise two instances of the SPIClass attached to VSPI and HSPI respectively
  vspi = new SPIClass(VSPI);
  hspi = new SPIClass(HSPI);
  
  //clock miso mosi ss

#ifndef ALTERNATE_PINS
  //initialise vspi with default pins
  //SCLK = 18, MISO = 19, MOSI = 23, SS = 5
  vspi->begin();
#else
  //alternatively route through GPIO pins of your choice
  vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); //SCLK, MISO, MOSI, SS
#endif

#ifndef ALTERNATE_PINS
  //initialise hspi with default pins
  //SCLK = 14, MISO = 12, MOSI = 13, SS = 15
  hspi->begin();
#else
  //alternatively route through GPIO pins
  hspi->begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SCLK, MISO, MOSI, SS
#endif

  //set up slave select pins as outputs as the Arduino API
  //doesn't handle automatically pulling SS low
  pinMode(vspi->pinSS(), OUTPUT); //VSPI SS
  pinMode(hspi->pinSS(), OUTPUT); //HSPI SS

}

// the loop function runs over and over again until power down or reset
void loop() {
  //use the SPI buses
  spiCommand(vspi, 0b01010101); // junk data to illustrate usage
  spiCommand(hspi, 0b11001100);
  delay(100);
}

void spiCommand(SPIClass *spi, byte data) {
  //use it as you would the regular arduino SPI API
  spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
  digitalWrite(spi->pinSS(), LOW); //pull SS slow to prep other end for transfer
  spi->transfer(data);
  digitalWrite(spi->pinSS(), HIGH); //pull ss high to signify end of data transfer
  spi->endTransaction();
}

 

Závěr

Tento článek byl rychlý a jednoduchý průvodce, který ukázal, jak používat SPI komunikaci s ESP32 pomocí jádra Arduino – přičemž ESP32 funguje jako řadič (master).

Stručně řečeno, ESP32 má čtyři sběrnice SPI, ale pouze dvě mohou být použity k ovládání periferií, HSPI a VSPI. Většina ESP32 má předem přiřazené HSPI a VSPI GPIO, ale přiřazení pinů můžeme vždy změnit v kódu.

Sběrnice HSPI a VSPI můžeme používat současně k ovládání více periferií SPI, nebo můžeme používat více periferií na stejné sběrnici, pokud je jejich pin CS připojen k jinému GPIO.

Podrobnější informace o ovladači SPI Master na ESP32 najdete v oficiální dokumentaci espressif.

Nezabývali jsme se nastavením ESP32 jako SPI slave, ale můžete se podívat na tyto příklady.

Literatura

ESP32 SPI Communication: Set Pins, Multiple SPI Bus Interfaces, and Peripherals (Arduino IDE). Random Nerd Tutorials [online]. USA: RNT, 2022 [cit. 2022-08-19]. Dostupné z: https://randomnerdtutorials.com/esp32-spi-communication-arduino/