This is a simple guide about SPI communication protocol with the ESP32 using Arduino IDE. We’ll take a look at the ESP32 SPI pins, how to connect SPI devices, define custom SPI pins, how to use multiple SPI devices, and much more.
Table of Contents:
- Introducing ESP32 SPI Communication Protocol
- ESP32 SPI Peripherals
- ESP32 SPI Pins
- Using Custom ESP32 SPI Pins
- ESP32 with Multiple SPI Devices
This tutorial focus on programming the ESP32 using the Arduino core, so before proceeding, you should have the ESP32 add-on installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven’t already.
Alternatively, you can also use VS Code with the PlatformIO extension to program your boards using the Arduino core:
Introducing ESP32 SPI Communication Protocol
SPI stands for Serial Peripheral Interface, and it is a synchronous serial data protocol used by microcontrollers to communicate with one or more peripherals. For example, your ESP32 board communicating with a sensor that supports SPI or with another microcontroller.
In an SPI communication, there is always a controller (also called master) that controls the peripheral devices (also called slaves). Data can be sent and received simultaneously. This means that the master can send data to a slave, and a slave can send data to the master at the same time.
You can have only one master, which will be a microcontroller (the ESP32), but you can have multiple slaves. A slave can be a sensor, a display, a microSD card, etc., or another microcontroller. This means you can have an ESP32 connected to multiple sensors, but the same sensor can’t be connected to multiple ESP32 boards simultaneously.
SPI Interface
For SPI communication you need four lines:
- MISO: Master In Slave Out
- MOSI: Master Out Slave In
- SCK: Serial Clock
- CS /SS: Chip Select (used to select the device when multiple peripherals are used on the same SPI bus)
On a slave-only device, like sensors, displays, and others, you may find a different terminology:
- MISO may be labeled as SDO (Serial Data Out)
- MOSI may be labeled as SDI (Serial Data In)
ESP32 SPI Peripherals
The ESP32 integrates 4 SPI peripherals: SPI0, SPI1, SPI2 (commonly referred to as HSPI), and SPI3 (commonly referred to as VSPI).
SP0 and SP1 are used internally to communicate with the built-in flash memory, and you should not use them for other tasks.
You can use HSPI and VSPI to communicate with other devices. HSPI and VSPI have independent bus signals, and each bus can drive up to three SPI slaves.
ESP32 Default SPI Pins
Many ESP32 boards come with default SPI pins pre-assigned. The pin mapping for most boards is as follows (it is different for an ESP32-S3):
SPI | MOSI | MISO | SCLK | CS |
VSPI | GPIO 23 | GPIO 19 | GPIO 18 | GPIO 5 |
HSPI | GPIO 13 | GPIO 12 | GPIO 14 | GPIO 15 |
Warning: depending on the board you’re using, the default SPI pins might be different. So, make sure you check the pinout for the board you’re using. Additionally, some boards don’t have pre-assigned SPI pins, so you need to set them on code.
Note: usually, when not specified, the board will use the VSPI pins when initializing an SPI communication with the default settings.
Whether your board comes with pre-assigned pins or not, you can always set them on code.
Finding your ESP32 Board’s Default SPI Pins
If you’re not sure about your board’s default SPI pins, you can upload the following code to find out.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-spi-communication-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
//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:
}
Important: make sure you select the board you’re using in Tools > Board, otherwise, you may not get the right pins.
After uploading the code, open the Serial Monitor, RST your board and you’ll see the SPI pins.
Using Custom ESP32 SPI Pins
When using libraries to interface with your SPI peripherals, it’s usually simple to use custom SPI pins because you can pass them as arguments to the library constructor.
For example, take a quick look at the following example that interfaces with a BME280 sensor using the Adafruit_BME280 library.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-spi-communication-arduino/
Based on the Adafruit_BME280_Library example: https://github.com/adafruit/Adafruit_BME280_Library/blob/master/examples/bme280test/bme280test.ino
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#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();
}
You can easily pass your custom SPI pins to the library constructor.
Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
In that case, I was using the following SPI pins (not default) and everything worked as expected:
#define BME_SCK 25
#define BME_MISO 32
#define BME_MOSI 26
#define BME_CS 33
If you’re not using a library, or the library you’re using doesn’t accept the pins in the library constructor, you may need to initialize the SPI bus yourself. In that case, you would need to call the SPI.begin() method on the setup() and pass the SPI pins as arguments:
SPI.begin(SCK, MISO, MOSI, SS);
You can see an example of this scenario in this tutorial, in which we initialize an SPI LoRa transceiver that is connected to custom SPI pins. Or this example showing how to use custom SPI pins with a microSD card module.
ESP32 with Multiple SPI Devices
As we’ve seen previously, you can use two different SPI buses on the ESP32 and each bus can connect up to three different peripherals. This means that we can connect up to six SPI devices to the ESP32. If you need to use more, you can use an SPI multiplexer.
Multiple SPI Devices (same bus, different CS pin)
To connect multiple SPI devices, you can use the same SPI bus as long as each peripheral uses a different CS pin.
To select the peripheral you want to communicate with, you should set its CS pin to LOW. For example, imagine you have peripheral 1 and peripheral 2. To read from peripheral 1, make sure its CS pin is set to LOW (here represented as CS_1):
digitalWrite(CS_1, LOW); // enable CS pin to read from peripheral 1
/*
use any SPI functions to communicate with peripheral 1
*/
Then, at same point, you’ll want to read from peripheral 2. You should disable peripheral 1 CS pin by setting it to HIGH, and enable peripheral 2 CS pin by setting it to 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 Using Two SPI Bus Interfaces (Use HSPI and VSPI simultaneously)
To communicate with multiple SPI peripherals simultaneously, you can use the ESP32 two SPI buses (HSPI and VSPI). You can use the default HSPI and VSPI pins or use custom pins.
Briefly, to use HSPI and VSPI simultaneously, you just need to.
1) First, make sure you include the SPI library in your code.
#include <SPI.h>
2) Initialize two SPIClass objects with different names, one on the HSPI bus and another on the VSPI bus. For example:
vspi = new SPIClass(VSPI);
hspi = new SPIClass(HSPI);
3) Call the begin() method on those objects.
vspi.begin();
hspi.begin();
You can pass custom pins to the begin() method if needed.
vspi.begin(VSPI_CLK, VSPI_MISO, VSPI_MOSI, VSPI_SS);
hspi.begin(HSPI_CLK, HSPI_MISO, HSPI_MOSI, HSPI_SS);
4) Finally, you also need to set the SS pins as outputs. For example:
pinMode(VSPI_SS, OUTPUT);
pinMode(HSPI_SS, OUTPUT);
Then, use the usual commands to interact with the SPI devices, whether you’re using a sensor library or the SPI library methods.
You can find an example of how to use multiple SPI buses on the arduino-esp32 SPI library. See the example below:
/* 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 initialize 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 analyzer 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 !defined(CONFIG_IDF_TARGET_ESP32)
#define VSPI FSPI
#endif
static const int spiClk = 1000000; // 1 MHz
//uninitialized pointers to SPI objects
SPIClass *vspi = NULL;
SPIClass *hspi = NULL;
void setup() {
//initialize 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
//initialize 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
//initialize 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();
}
Wrapping Up
This article was a quick and simple guide showing you how to use SPI communication with the ESP32 using the Arduino core—with the ESP32 acting as a controller (master).
In summary, the ESP32 has four SPI buses, but only two can be used to control peripherals, the HSPI and VSPI. Most ESP32 have pre-assigned HSPI and VSPI GPIOs, but you can always change the pin assignment in the code.
You can use the HSPI and VSPI buses simultaneously to drive multiple SPI peripherals, or you can use multiple peripherals on the same bus as long as their CS pin is connected to a different GPIO.
We didn’t dive deeply into examples, because each sensor, library, and case scenario is different. But, now you should have a better idea of how to interface one or multiple SPI devices with the ESP32.
For more detailed information about the SPI Master driver on the ESP32, you can check the espressif official documentation.
We didn’t cover setting the ESP32 as an SPI slave, but you can check these examples.
We hope you find this tutorial useful. We have a similar article, but about I2C communication protocol. Check it out on the following link:
Learn more about the ESP32 with our resources:
- Learn ESP32 with Arduino IDE
- Build Web Servers with ESP32 and ESP8266
- Firebase Web App with ESP32 and ESP8266
- Free ESP32 Projects and Tutorials
Thanks for reading.
Can you please show how to use the ESP32 as an SPI device (slave).
I want to use it to read sensors for a Raspberry Pi as most sensors only have Arduino libraries.
Thank you
Dale
Hi.
At the moment, we don’t have any tutorials about setting the ESP32 as an SPI slave.
But you can find some examples on the following link: https://github.com/hideakitai/ESP32SPISlave
Regards,
Sara
Is there a special version of SPI.h library? I compiled this and got this error several times
C:\ESP32_SPI_Example\ESP32_SPI_Example.ino: In function ‘void setup()’:
ESP32_SPI_Example:80:17: error: ‘class SPIClass’ has no member named ‘pinSS’
pinMode(vspi->pinSS(), OUTPUT); //VSPI SS
Thanks
Bill
Hi.
Do you have an ESP32 selected in Tools>Board?
What’s the version of your ESP32 installation?
Regards,
Sara
the program above finds the HSPI pins, but not the VSPI pins.
On a TTGO loRa OlED SD card T3 V2.16 board, that gives me lORA pins but not SD card pins
Absolutely nothing I try gets me SD card on the T3 V2.16, or I2C on the T3 V2.16 or TTGO T Beam. I did get a DS3231 to work on the T Beam, but no BME280 , and OlED only on the T3 V2.16
Thank you for the excellent tutorial about the ESP32 SPI interface. I like the code to check or verify the default SPI pins. I am new to ESP32, I have been using the Arduino MKR1010, but I think the ESP32 is much better, and less expensive.
When using the custom pins for either VSPI or HSPI, library uses soft SPI not the hardware, I guess. Because the IO matrix does not assign custom pins to the actual hardware registers. Please clarify about my opinion.
In my code, I want to write data to an micro SD card. This works well with an SD of 2Gb.
On 8 Gb SD, I can’t initialize … Somewhere I read that 2Gb is maximum ( thrue ?) Then I partitioned my 8 Gb in chuncks of 1.95 Gb but no success … 🙁 Is there away to overcome this limit ?
Hi.
I tried microSD cards with 16GB without a problem.
Maybe the issue is the type of SD card?
What is the error that you get?
Regards,
Sara
Hi, thank you for this great tutorial.
I am curious about using 2 SPI slave devices in the section about “ESP32 with Multiple SPI Devices”
How do you setup the Serial.begin with two devices? Like this?
SPI.begin(SCK, MISO, MOSI, SS1);
SPI.begin(SCK, MISO, MOSI, SS2);
Hi.
It’s explained in the tutorial. Check this section: https://randomnerdtutorials.com/esp32-spi-communication-arduino/#spi-multiple-bus-hspi-vspi
Regards,
Sara
Hi Sara,
Is it OK to config any pin on ESP32 as a SPI pin? such like:
#define TOUCH_MISO 27
#define TOUCH_MOSI 32
#define TOUCH_SCLK 25
Thanks
Summer
Is there a simple way to select HSPI as the default SPI instead of the VSPI?
I got boot error if GPIO12 set to HIGH if using HSPI.
Hi Sara,
Can you use both SPI’s (HSPI and VSPI) in a program(project)?.
Kindly confirm it. If is it possible give some example.
Thank you.
Great tutorial, will use SPI with the ESP-C3 super mini.
Also loved the code to find SPI pins. Extended to find I2C and Rx/Tx pins, wordks fine for the ESP32-C3 . Feel free to copy/use/
Serial.println(“*** I2C Pins “);
Serial.print(“SDA : “); Serial.println(SDA );
Serial.print(“SCL : “); Serial.println(SCL );
Serial.println(“ Rx Tx Pins ***”);
Serial.print(“RX : “); Serial.println(RX );
Serial.print(“TX : “); Serial.println(TX );