ESP32 SPI Master (ESP-IDF)

Introduction

Learn how to use the ESP32 SPI peripheral with the ESP-IDF in this article. SPI instances and the APIs needed to interact with them will be covered. You will create a project that uses SPI to operate a MAX7219 module with an 8×8 LED matrix.

ESP32 SPI APIs

Initialisation

When working with ESP32 SPI, you would need to call spi_bus_initialize() first to configure SPI. This function has the following prototype

esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);

This function accepts the following arguments:

  • host_id: is a variable of type spi_host_device_t. This variable indicates which SPI instance is being used. There are 4 SPI instances in ESP32 (SPI0, SPI1, SPI2, and SPI3). Of those, SPI0 and SPI1 are reserved for accessing attached flash memory, so you should use SPI2 or SPI3. You pass the value SPI2_HOST to tell that you are using SPI2 instance, and SPI3_HOST if you intend to use SPI3 instance.
  • bus_config is a pointer to a constant configuration structure of type spi_bus_config_t. This variable tells which IO pins are used for MISO, MOSI, CLK and CS. For example, you can define a configuration variable like this to tell you want to use IO32 as MOSI line, IO33 as CLK line. -1 means a pin is not used. max_transfer_sz indicates maximum transfer in bytes.
spi_bus_config_t buscfg={
    .miso_io_num = -1,
    .mosi_io_num = 32,
    .sclk_io_num = 33,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 32,
};

  • dma_chan is a variable indicating which DMA channel is used. DMA or Direct Memory Access is a mechanism for SPI instance to access RAM memory directly as transfer buffer. You can select between SPI_DMA_DISABLED, SPI_DMA_CH1, SPI_DMA_CH2 or auto SPI_DMA_CH_AUTO.

Register Slave Device

After initialising, you need to call API spi_bus_add_device() to register a slave device.

esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);

This function takes a number of parameters:

  • host_id: it needs to know which SPI instance the slave is attaching to, so if you have used SPI2_HOST in the above initialisation step, you need to pass the same instance here.
  • dev_config is a pointer to a constant configuration structure of type spi_device_interface_config_t. This variable tells information about the slave device, such as which SPI mode it is working, what is the clock speed of the slave device, etc. The following example defines a configuration variable for a slave device operate at 1 MHz clock, mode 0 and use IO25 as the chip select pin.
spi_device_interface_config_t devcfg={
    .clock_speed_hz = 1000000,  // 1 MHz
    .mode = 0,                  //SPI mode 0
    .spics_io_num = 25,         // CS Pin
    .queue_size = 1,
    .flags = SPI_DEVICE_HALFDUPLEX,
    .pre_cb = NULL,
    .post_cb = NULL,
};

  • handle is a pointer to a variable of type spi_device_handle_t. After calling spi_bus_add_device, it will return a handle, and you can use this variable to refer to the current slave device. For example
spi_device_handle_t spi2;

ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi2));

Transfer data

To transfer data to SPI Slave, you can use either polling mode or interrupt mode. To transfer data using polling mode, you call spi_device_polling_transmit() API

esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);

This function takes two arguments:

  • handle of type spi_device_handle_t is the variable referring to the slave device which you obtained by calling spi_bus_add_device() previously.
  • trans_desc is a pointer to a variable of type spi_transaction_t and contains description of the transaction. The following example shows how to transfer 2 bytes of data to slave.
uint8_t data[2] = { 0x01, 0x02 };

spi_transaction_t t = {
    .tx_buffer = data,
    .length = 2 * 8
};

ESP_ERROR_CHECK(spi_device_polling_transmit(spi2, &t));

ESP32 SPI Sample Project

We will implement a sample project to demonstrate how to use SPI instances of ESP32. Our project is to control a 8×8 LED Matrix MAX7219 module. To control this module, you need to write data to MAX7219 registers using SPI protocol.

Hardware

The following table describes required components used in this project

QTYComponentBuy on Amazon
1ESP32 DevKit Camazon.com
18×8 LED Matrix MAX7219 moduleamazon.com
1Breadboardamazon.com
1Jumper Wire Kitamazon.com

Affiliate Disclosure: When you click on links in this section and make a purchase, this may result in this site earning a commission at no extra cost to you.

Connection

Connect ESP32 Pins with MAX7219 module according to this table

ESP32 PinMAX7219 Module Pin
32DIN
33CLK
25CS
3.3VVCC
GNDGND

You should have a circuit look like this

esp32 max7219 circuit

Code

Code of the project is shown below

#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"

#define CLK_PIN     33
#define MOSI_PIN    32
#define CS_PIN      25

#define DECODE_MODE_REG     0x09
#define INTENSITY_REG       0x0A
#define SCAN_LIMIT_REG      0x0B
#define SHUTDOWN_REG        0x0C
#define DISPLAY_TEST_REG    0x0F

spi_device_handle_t spi2;

static void spi_init() {
    esp_err_t ret;

    spi_bus_config_t buscfg={
        .miso_io_num = -1,
        .mosi_io_num = MOSI_PIN,
        .sclk_io_num = CLK_PIN,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 32,
    };

    ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
    ESP_ERROR_CHECK(ret);

    spi_device_interface_config_t devcfg={
        .clock_speed_hz = 1000000,  // 1 MHz
        .mode = 0,                  //SPI mode 0
        .spics_io_num = CS_PIN,     
        .queue_size = 1,
        .flags = SPI_DEVICE_HALFDUPLEX,
        .pre_cb = NULL,
        .post_cb = NULL,
    };

    ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi2));
};

static void write_reg(uint8_t reg, uint8_t value) {
    uint8_t tx_data[2] = { reg, value };

    spi_transaction_t t = {
        .tx_buffer = tx_data,
        .length = 2 * 8
    };

    ESP_ERROR_CHECK(spi_device_polling_transmit(spi2, &t));
}

static void set_row(uint8_t row_index) {
  write_reg(row_index + 1, 0xFF);
}

static void set_col(uint8_t col_index) {
  for (int i = 0; i < 8; i++) {
    write_reg(i + 1, 0x01 << col_index);
  }
}

static void clear(void) {
  for (int i = 0; i < 8; i++) {
    write_reg(i + 1, 0x00);
  }
}

static void max7219_init() {
    write_reg(DISPLAY_TEST_REG, 0);
    write_reg(SCAN_LIMIT_REG, 7);
    write_reg(DECODE_MODE_REG, 0);
    write_reg(SHUTDOWN_REG, 1);
    clear();
}

void app_main(void)
{
    spi_init();
    max7219_init();

    while (1) {
        for (int i = 0; i < 8; i++) {
            clear();
            set_row(i);
            vTaskDelay(1000/portTICK_PERIOD_MS);
        }

        for (int i = 0; i < 8; i++) {
            clear();
            set_col(i);
            vTaskDelay(1000/portTICK_PERIOD_MS);
        }
    }
}

Full project files can be seen on Github.

Code explanation

  • As described previously, you implement a function spi_init() in which you call ESP-IDF APIs spi_bus_initialize() and spi_bus_add_device() to initialise the bus and the slave device. In this project, we are using SPI2 to control MAX7219.
  • You implement a function write_reg() to write a value to a MAX7219 register. In this function, you call spi_device_polling_transmit() to initiate a blocking transfer of 2 bytes to MAX7219.
  • To turn on LEDs in each row and each column, you call set_row() and set_col() respectively. These functions in turn call write_reg() to initiate SPI transfer to slave device.
  • In main loop, you simply turn on each row and then each column, then delay for 1 second using freeRTOS vTaskDelay() API.

Wrapping Up

In this article, you have learnt how to use SPI in Master mode with ESP-IDF. You learnt about APIs to initialise, configure and transfer data to a slave device. You practice by building a project to control 8×8 LED Matrix MAX7219. To go further from here, you can check full API reference from Espressif.

Leave a Comment