Skip to main content

SPI Communication

Important Note: Development Board Compatibility

The core logic of this tutorial applies to all ESP32 development boards, but all operational steps are explained using the Waveshare ESP32-S3-Zero mini development board. If you are using a different model of development board, please adjust the corresponding settings based on your actual hardware.

This section introduces the basic concepts of the SPI communication protocol and demonstrates how to use MicroPython's machine.SPI class to drive an OLED display.

SPI (Serial Peripheral Interface) is a high-speed, full-duplex synchronous serial communication protocol. SPI is widely used for communication between microcontrollers and various peripherals, such as TF cards, displays, sensors, and Flash memory.

Key characteristics of SPI include:

  • Four-wire communication: Uses 4 signal lines for communication (can be reduced to 3 lines with a single slave device)
  • Master-slave architecture: A clear master-slave relationship where the master device controls the communication timing
  • Full-duplex: Can simultaneously send and receive data
  • High-speed transmission: Typically faster than I2C and UART, reaching tens of MHz
  • Synchronous communication: The clock signal is provided by the master device, ensuring reliable data transmission

The four core signal lines of the SPI protocol are:

SPI Wiring

  • SCK (Serial Clock): Serial clock line. The clock signal generated by the host for synchronizing data transmission.
  • MOSI (Master Out Slave In): Master output, slave input line. The master device sends data to the slave device through this line.
  • MISO (Master In Slave Out): Master input, slave output line. The slave device sends data to the master device through this line.
  • SS/CS (Slave Select/Chip Select): Chip select signal line. The host uses this line to select the currently targeted slave device; active low. The SPI master selects the target slave via different CS (Chip Select) lines. Each additional SPI peripheral requires a dedicated CS pin.
Supplementary Note on SPI Terminology Updates

You may notice in some recent technical manuals or open-source projects that SPI terminology is undergoing changes. Understanding these new terms will help you read the latest materials without barriers. The following table shows the correspondence between old and new terminology:

CategoryTraditional TermNew Term
Device RoleMasterController
Device RoleSlavePeripheral
Signal LineMOSIPICO (Peripheral In / Controller Out)
Signal LineMISOPOCI (Peripheral Out / Controller In)

1. SPI in ESP32

1.1 Hardware Resources

ESP32 series chips typically have multiple built-in SPI controllers, but their purposes differ:

  • SPI0 & SPI1: These two controllers are primarily used to connect to external Flash and PSRAM and are dedicated system resources.
  • SPI2 & SPI3: These are two general-purpose controllers, fully available for user applications. They can be used for high-speed communication with displays, TF cards, sensors, and other devices.

1.2 ID Mapping in MicroPython

MicroPython uses the machine.SPI class to drive these hardware resources. To distinguish between different controllers, MicroPython uses the id parameter for mapping:

  • id=1: Corresponds to hardware SPI2. Often called HSPI in classic ESP32; called FSPI in ESP32-S3.
  • id=2: Corresponds to hardware SPI3. Often called VSPI in classic ESP32.
Tip

Although the naming (HSPI/VSPI/FSPI) in different chip manuals may vary, you only need to use the id parameter in MicroPython code to distinguish them.

1.3 Hardware SPI Pins

ESP32's SPI pin assignment is very flexible, thanks to its internal GPIO Matrix. The SCK, MOSI, and MISO signals of SPI can be mapped to any GPIO pin that supports output/input.

However, each SPI controller has a set of default pins (also called IO MUX pins).

  • Using default pins: Signals are transmitted directly through the IO MUX, supporting clock frequencies up to 80MHz.
  • Using custom pins: Signals need to be routed through the GPIO matrix, with the maximum clock frequency typically limited to around 40MHz (still very fast and sufficient for most applications).

The following table provides a reference for the default SPI pins of common models:

Chip ModelMicroPython IDHardware NameMOSIMISOSCK
ESP321HSPIGPIO 13GPIO 12GPIO 14
2VSPIGPIO 23GPIO 19GPIO 18
ESP32-S31SPI2 (FSPI)GPIO 11GPIO 13GPIO 12
2SPI3GPIO 35GPIO 37GPIO 36
Note

Different ESP chip models have varying numbers of SPI peripherals and pin configurations. Please refer to the target chip's datasheet for detailed information.

You can also check the default pins in the REPL using the following commands:

from machine import SPI
SPI(1)  # Check default pins for SPI2
SPI(2)  # Check default pins for SPI3

2. Example 1: Using SPI to Control a Display

This example demonstrates how to use the SPI interface to drive an OLED display. Compared to I2C, the SPI version of the display typically offers faster refresh rates.

2.1 Build the Circuit

The components required are:

Connect the circuit according to the wiring diagram below:

ESP32-S3-Zero Pinout Diagram

ESP32-S3-Zero-Pinout

Wiring Diagram
ESP32 PinOLED ModuleModule
GPIO 13SCKSPI Clock Line
GPIO 11MOSISPI Data Output
GPIO 10CSChip Select Signal
GPIO 8DCData/Command Select
3.3VVCCPower Positive
GNDGNDPower Ground

2.2 Code

tip

This code example requires the ssd1327.py driver library, which is based on the micropython-ssd1327 project by community developer mcauser.

Download link: micropython-ssd1327-master.zip

Please upload the ssd1327.py file from this library to the root directory of your development board.

from machine import Pin, SPI
import ssd1327

# SPI pin configuration
SCK_PIN = 13
MOSI_PIN = 11
CS_PIN = 10
DC_PIN = 8
RST_PIN = 9 # Reset pin

# Initialize hardware SPI
# Use id=1 (HSPI), set clock frequency to 10 MHz
spi = SPI(1, baudrate=10000000, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN))

# Initialize display
oled = ssd1327.SSD1327_SPI(128, 128, spi, dc=Pin(DC_PIN), res=Pin(RST_PIN), cs=Pin(CS_PIN))

print("SSD1327 OLED test")

# Clear screen (fill with black)
oled.fill(0)

# Display text
# framebuf.text(s, x, y, c)
# c is the color value. For 4-bit grayscale, the range is 0-15. 15 is brightest, 0 is black.
oled.text("Hello,", 10, 10, 15)
oled.text("MicroPython!", 10, 25, 8)
oled.text("ESP32", 10, 40, 1) # Lower brightness text

# Use framebuf to draw graphics
# Draw a rectangle frame
oled.framebuf.rect(0, 0, 128, 128, 15)
# Draw a circle or ellipse
oled.framebuf.ellipse(0, 0, 128, 128, 15)

# Refresh the display
oled.show()

2.3 Code Analysis

  1. from machine import Pin, SPI: Imports MicroPython's MicroPython's Pin and SPI class.

  2. SPI(1, baudrate=10000000, sck=Pin(SCK_PIN), mosi=Pin(MOSI_PIN)): Initializes hardware SPI.

    • 1: Specifies using the SPI controller with ID 1.
    • baudrate=10000000: Sets the SPI clock frequency to 10 MHz.
    • sck, mosi: Specifies the SPI clock and data output pins.
    • Since the OLED display typically only needs to receive data, the miso pin is not configured in this example.
  3. ssd1327.SSD1327_SPI(...): Creates an SSD1327 display object.

    • 128, 128: Specifies the screen resolution.
    • spi: Passes in the initialized SPI object.
    • dc, res, cs: Specifies the Data/Command select pin, Reset pin, and Chip Select pin, respectively.
  4. oled.fill(0): Clears the screen buffer (fills with black).

  5. oled.text(...): Writes text to the buffer.

    • The first parameter is the text content to display.
    • The second parameter is the x-coordinate of the text.
    • The third parameter is the y-coordinate of the text.
    • The fourth parameter is the color value (brightness). SSD1327 supports 16 levels of grayscale (4-bit), so you can pass an integer between 0 and 15 to control brightness. 15 is brightest, 0 is black.
  6. About the framebuf module:

    • The ssd1327 driver library is built on top of MicroPython's built-in framebuf (Frame Buffer) module. framebuf provides a standard set of graphics drawing APIs, including drawing text, lines, rectangles, circles, and other basic shapes.
    • In the code, methods like oled.framebuf.rect() and oled.framebuf.ellipse() directly call the framebuf drawing functions.
    • framebuf supports multiple color formats and drawing operations and is the standard tool for graphics display in MicroPython. For more drawing methods and detailed usage, refer to the MicroPython framebuf official documentation.
  7. oled.show(): Sends the data from the buffer to the OLED screen, updating the display. The screen content will not change until this method is called.

2.4 Expected Output

After uploading and running the code, the OLED display will show the following:

  • The first line shows "Hello," at high brightness.
  • The second line shows "MicroPython!" at medium brightness.
  • The third line shows "ESP32" at low brightness.
  • A rectangular frame and an inscribed circle will also be displayed on the screen.
Example Output