Fundamentals of Peripheral Control with ESP32: SPI Communication

1. Basics of SPI Protocol

SPI (Serial Peripheral Interface) is a high-speed, full-duplex synchronous serial communication protocol with the following core features:

  • Four-wire Communication:

    • MOSI (Master Out Slave In): Master output, slave input
    • MISO (Master In Slave Out): Master input, slave output
    • SCLK: Serial clock generated by the master
    • CS/SS: Chip select signal (active low)
  • Master-Slave Architecture: Supports one master and multiple slaves, each slave requires an independent CS pin

  • Full-Duplex Transmission: Data can be sent and received simultaneously

  • High-Speed Transmission: Typically reaches several MHz to tens of MHz

  • Four Operating Modes: Determined by the combination of CPOL (Clock Polarity) and CPHA (Clock Phase)

Typical Application Scenarios

  • Display Drivers (OLED, TFT LCD)
  • Storage Devices (SD Cards, SPI Flash)
  • Wireless Communication Modules (nRF24L01, LoRa)
  • Sensor Interfaces (High Data Rate Sensors)

2. ESP32 SPI Hardware Features

The ESP32 integrates four SPI controllers:

Controller Usage Availability Default Pins
SPI0 Cache Flash System Reserved Non-configurable
SPI1 Cache PSRAM System Reserved Non-configurable
HSPI (SPI2) General SPI User Available MISO:12, MOSI:13, SCLK:14, CS:15
VSPI (SPI3) General SPI User Available MISO:19, MOSI:23, SCLK:18, CS:5

Key Features:

  • Supports Master/Slave mode (typically used in Master mode)
  • Clock frequency up to 80MHz
  • Programmable clock polarity and phase
  • Supports DMA transfer
  • Pins can be flexibly mapped through IO matrix

3. ESP-IDF SPI Programming Interface

1. Bus Configuration Structure

typedef struct {
int mosi_io_num;      // MOSI pin
int miso_io_num;      // MISO pin
int sclk_io_num;      // SCLK pin
int quadwp_io_num;    // WP signal (for QSPI)
int quadhd_io_num;    // HD signal (for QSPI)
int max_transfer_sz;  // Maximum transfer size in bytes
int flags;            // Special flags
} spi_bus_config_t;

2. Device Configuration Structure

typedef struct {
uint8_t command_bits;     // Command bit length
uint8_t address_bits;     // Address bit length
uint8_t dummy_bits;       // Number of dummy cycles
spi_mode_t mode;          // SPI mode (0-3)
uint32_t clock_speed_hz;  // Clock frequency
int spics_io_num;         // CS pin
uint32_t flags;           // Special flags
int queue_size;           // Transaction queue size
} spi_device_interface_config_t;

3. Common API Functions

Function Description
<span>spi_bus_initialize()</span> Initialize SPI bus
<span>spi_bus_add_device()</span> Add SPI device
<span>spi_device_transmit()</span> Execute SPI transaction
<span>spi_device_queue_trans()</span> Add transaction to queue
<span>spi_device_get_trans_result()</span> Get transaction result
<span>spi_bus_remove_device()</span> Remove SPI device
<span>spi_bus_free()</span> Free SPI bus

4. SPI Communication Examples

1. SPI Master Initialization

#include "driver/spi_master.h"
#include "esp_log.h"

#define SPI_HOST    SPI3_HOST  // Use VSPI
#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK  18
#define PIN_NUM_CS   5

void spi_init() {
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 10 * 1000 * 1000,  // 10MHz
        .mode = 0,                       // SPI mode 0
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 7,
    };

    // Initialize SPI bus
    ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));

    // Add device
    spi_device_handle_t spi;
    ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST, &devcfg, &spi));
}

2. Data Sending Example

void spi_send_data(spi_device_handle_t spi, uint8_t* data, size_t len) {
    spi_transaction_t t = {
        .length = len * 8,  // Bit count
        .tx_buffer = data,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));
}

3. Data Transfer Example (Full Duplex)

void spi_transfer(spi_device_handle_t spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) {
    spi_transaction_t t = {
        .length = len * 8,
        .tx_buffer = tx_data,
        .rx_buffer = rx_data,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));
}

5. Advanced Application Techniques

1. Reading Flash JEDEC ID

void read_flash_id(spi_device_handle_t spi) {
    uint8_t tx_buf[4] = {0x9F, 0x00, 0x00, 0x00}; // JEDEC ID command
    uint8_t rx_buf[4];

    spi_transaction_t t = {
        .length = 32,
        .tx_buffer = tx_buf,
        .rx_buffer = rx_buf,
    };

    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));

    ESP_LOGI("SPI", "Manufacturer ID: 0x%02X", rx_buf[1]);
    ESP_LOGI("SPI", "Memory Type: 0x%02X", rx_buf[2]);
    ESP_LOGI("SPI", "Capacity: 0x%02X", rx_buf);
}

2. Using DMA Transfer

// Specify DMA channel during initialization
spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);

// Large data transfer
#define BUF_SIZE 4096
uint8_t tx_buf[BUF_SIZE], rx_buf[BUF_SIZE];
spi_transaction_t t = {
    .length = BUF_SIZE * 8,
    .tx_buffer = tx_buf,
    .rx_buffer = rx_buf,
};
ESP_ERROR_CHECK(spi_device_transmit(spi, &t));

3. Multi-Device Management

// Add second device
#define PIN_NUM_CS2 17
spi_device_interface_config_t devcfg2 = {
    .clock_speed_hz = 1 * 1000 * 1000,
    .mode = 0,
    .spics_io_num = PIN_NUM_CS2,
    .queue_size = 3,
};

spi_device_handle_t spi2;
ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST, &devcfg2, &spi2));

Leave a Comment