SPI Slave Sender Example

[中文]

Note

This document is automatically translated using AI. Please excuse any detailed errors. The official English version is still in progress.

Example Description

This example demonstrates how to use the SPI master driver in ESP-IDF to communicate with another device. The master sends data while receiving information returned by the slave, and initiates transactions only when the slave is ready, ensured by a handshake signal. For information about SPI master mode, refer to SPI Master Driver. Before studying this example, it is recommended to understand the related content of FreeRTOS semaphores and queues. The basic description and API usage examples can be found in the Semaphores and Mutexes and Queue Management documents.

Running Method

This example needs to be used in conjunction with the Receiver Example. Both are burned into two ESP32 series chips and the signal lines are correctly connected to achieve complete data exchange. Although the example name contains Slave, the Sender is actually configured as an SPI master, taking on the role of generating clocks and controlling chip selection in communication, and driving the Receiver (SPI slave) to complete transmission and reception.

The complete code of the example can be found in the SPI Slave Sender Example. For instructions on configuration before running, corresponding GPIO pins, build and burn process, please refer to the README.md file in the spi_slave directory.

For custom configuration items and default values, refer to the Macro Definition Explanation section.

Header File Description

The header files used in this example cover FreeRTOS task management and synchronization, log output, SPI master driver, and GPIO and timer control modules, providing support for SPI master initialization, data transmission, and task scheduling.

The header files are categorized by function as follows:

  1. FreeRTOS Task Scheduling: Provides task creation and scheduling, semaphore and queue functions for concurrent task management and communication between multiple tasks.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
  1. System Tools: Provides standard input and output, data type definitions, and log output functions.

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_log.h"
  1. SPI Slave Driver: Provides initialization, device configuration, and data transmission functions for SPI master mode.

#include "driver/spi_master.h"
  1. GPIO Control: Provides GPIO pin configuration and level control functions.

#include "driver/gpio.h"
  1. Timer: Provides timing and callback functions with microsecond precision, used for task delay or periodic operations.

#include "esp_timer.h"

Macro Definition Explanation

The macro definitions involved in this example are mainly used for configuring the pins of the SPI peripherals, controller selection, and handshake signal pins, which are convenient for subsequent SPI data transmission and task scheduling.

The macro definitions are classified by function as follows:

  1. SPI Host Configuration

  • Defines the controller used by the SPI host, this example uses SPI2_HOST.

#define RCV_HOST    SPI2_HOST
  • Defines the key pins of SPI, including handshake, data input/output, clock, and chip select pins.

#define GPIO_HANDSHAKE      2    // Handshake signal
#define GPIO_MOSI           12   // Master output, slave input
#define GPIO_MISO           13   // Master input, slave output
#define GPIO_SCLK           15   // SPI clock
#define GPIO_CS             14   // Chip select signal

Note

  • The handshake pin is used for data synchronization control between the host and the slave, and the appropriate GPIO can be selected according to the hardware design.

  • The chip select pin is used to select the SPI slave to ensure correct communication of multiple slaves on the bus.

Task Function Explanation

This example defines an Interrupt Service Routine (ISR), which responds to the interrupt triggered by the handshake GPIO. When the slave sends a ready signal through the handshake pin, this interrupt is triggered, and the host knows through this function that the slave is ready and can transmit data.

static void IRAM_ATTR gpio_handshake_isr_handler( void* arg );

Note

  • The function uses the IRAM_ATTR modifier to ensure that the function is placed in internal RAM, improving interrupt response speed.

  • The function argument is a user-defined argument pointer, often used to pass trigger source or context information. In this example, this function does not access or modify the argument content, it is only used to meet the function prototype requirements of the callback function.

The execution logic of this function is:

  1. Interrupt Debounce Filtering: To avoid interference or signal bounce on the handshake GPIO causing repeated triggering of interrupts, the function records the last interrupt time and compares it with the current interrupt time. If the interval between two interrupts is less than the set threshold (1 ms), this interrupt is ignored.

  • Define the variable lasthandshaketime_us to record the time of the last interrupt.

  • Call esp_timer_get_time() to get the current time (microsecond level) and save it to the variable currtime_us. For further introduction and parameter explanation, please refer to ESP Timer.

  • Get the time interval diff between two interrupt triggers through the above two variables.

  • Determine whether the time interval is less than the set threshold (1 ms). If it is less, return directly and ignore this interrupt; otherwise, update the time of the last interrupt to the current time and continue to perform subsequent operations.

  1. Issue Synchronization Signal: In order to notify the waiting task that the slave is ready and can start SPI data transmission, the function uses the FreeRTOS semaphore mechanism to safely release the semaphore from the ISR and trigger task switching when necessary.

  • Define the flag variable mustYield, which indicates whether it is necessary to switch tasks immediately after the interrupt ends, initialized as false.

  • Invoke xSemaphoreGiveFromISR() to release the semaphore. For further introduction and parameter description, please refer to Release Semaphore Resources.

  • Determine whether mustYield is true. If so, call portYIELD_FROM_ISR() to switch to the execution of higher priority tasks.

Main Function Description

This example demonstrates the initialization of the SPI host, establishing communication with the slave device and transmitting data, as well as the steps to release resources.

  1. Define variables:

  • Define the return value variable ret to save the return status of SPI peripheral related functions, judge whether the operation is successful, and perform error handling or print prompt information according to the return value.

  • Define the device handle variable handle, which is used to identify and manage the slave devices registered on the SPI bus.

  1. Configure/Initialize SPI Bus

  2. Configure/Initialize SPI Slave Interface

  3. Configure handshake signal GPIO: In the communication between the SPI slave and the host, in order to achieve reliable data transmission, a handshake signal (handshake line) is usually used to notify the host that the slave is ready to send or receive data.

  1. Configure handshake line Interrupt Service.

  • Install and bind the interrupt service so that the corresponding processing can be triggered when the handshake signal changes.

  • Before binding the interrupt service, explicitly set the interrupt trigger mode of the pin to GPIO_INTR_POSEDGE again to prevent the hardware initialization or driver library from modifying the interrupt configuration.

Note

Setting the interrupt trigger mode again is a redundant but safe operation, ensuring that the interrupt trigger type of the handshake GPIO is indeed triggered by the rising edge, thus ensuring the reliability of the master-slave communication.

  1. Preparation for SPI host send/receive transaction:

  • Define the count variable n and initialize it to 0, which is used to record or identify the current number of transmissions, convenient for debugging or generating sending data.

  • Define data buffer arrays to provide storage space for SPI host data sending and receiving, ensuring that each transmission has a definite storage area:

    • Define the variable sendbuf and initialize it to 0, used as the send data buffer.

    • Define the variable recvbuf and initialize it to 0, used as the receive data buffer.

    • The array length is set to 128, indicating that the buffer can store up to 128 bytes of data, and the byte capacity can be adjusted according to actual application needs.

  • Define SPI Transaction Structure t.

  • Invoke xSemaphoreCreateBinary() to create a semaphore and save its handle to the variable rdySem. For further introduction and parameter description, refer to Creating Binary Semaphore.

  • Invoke xSemaphoreGive() to send an “available” signal to the semaphore rdySem, notifying the waiting task that the slave is ready or data has arrived, and subsequent operations can be performed. For further introduction and parameter description, refer to Releasing Semaphore Resources.

  1. Construct the data to be sent to the SPI slave, and record the transmission number and the data returned by the previous slave in the data.

  • Invoke snprintf() to write the formatted string into the send buffer sendbuf, ensuring no overflow.

  • Check the return value, if the sent data exceeds the buffer size, print Data truncated to prevent overflow.

  1. Set transaction information and execute:

  • Before executing the transaction, you need to call xSemaphoreTake() to block and wait for the semaphore until the slave indicates that the next transmission can be performed through the handshake signal. The timeout is set to portMAX_DELAY which means waiting indefinitely. For further introduction and parameter description, refer to Acquiring Semaphore Resources.

  1. Print the data returned by the slave and update the transmission count for this time.

  2. After the loop ends, call spi_bus_remove_device() to release the SPI bus resources, disconnect the association between the host and the slave, and check the return value to ensure success. For further introduction and parameter description, refer to SPI Master Driver.

Note

In actual applications, the SPI data transmission task is usually a long-term or resident task, so this step is generally not executed, but it can be used to prevent potential program exceptions or memory leaks.