SPI Slave library for ESP32
Caution
Breaking API changes from v0.4.0 and above
This is the simple SPI slave library and does NOT use DMA. Please use ESP32DMASPI to transfer more than 32 bytes with DMA.
- Support SPI Slave mode based on ESP32's SPI Slave Driver
- Slave has several ways to send/receive transactions
transfer()
to send/receive transaction one by one (blocking)queue()
andwait()
to send/receive multiple transactions at once and wait for them (blocking but more efficient thantransfer()
many times)queue()
andtrigger()
to send/receive multiple transactions at once in the background (non-blocking)
- Various configurations based on driver APIs
- Register user-defined ISR callbacks
IDE | ESP32 Board Version |
---|---|
Arduino IDE | >= 2.0.11 |
PlatformIO | >= 5.0.0 |
If you have communication errors when trying examples, please check the following points.
- Check that the SPI Mode is the same
- Check if the pin number and connection destination are correct
- Connect pins as short as possible
- Be careful of signal line crosstalk (Be careful not to tangle wires)
- If you are using two devices, ensure they share a common ground level
- If you still have communication problems, try a lower frequency (1MHz or less)
Please refer examples for more information.
#include <ESP32SPISlave.h>
ESP32SPISlave slave;
static constexpr size_t BUFFER_SIZE = 8;
static constexpr size_t QUEUE_SIZE = 1;
uint8_t tx_buf[BUFFER_SIZE] {1, 2, 3, 4, 5, 6, 7, 8};
uint8_t rx_buf[BUFFER_SIZE] {0, 0, 0, 0, 0, 0, 0, 0};
void setup()
{
slave.setDataMode(SPI_MODE0); // default: SPI_MODE0
slave.setQueueSize(QUEUE_SIZE); // default: 1
// begin() after setting
slave.begin(); // default: HSPI (please refer README for pin assignments)
}
void loop()
{
// do some initialization for tx_buf and rx_buf
// start and wait to complete one BIG transaction (same data will be received from slave)
const size_t received_bytes = slave.transfer(tx_buf, rx_buf, BUFFER_SIZE);
// do something with received_bytes and rx_buf if needed
}
You can use Master and Slave almost the same way (omit the Slave example here).
void loop()
{
// do some initialization for tx_buf and rx_buf
// queue multiple transactions
// in this example, the master sends some data first,
slave.queue(NULL, rx_buf, BUFFER_SIZE);
// and the slave sends same data after that
slave.queue(tx_buf, NULL, BUFFER_SIZE);
// wait for the completion of the queued transactions
const std::vector<size_t> received_bytes = slave.wait();
// do something with received_bytes and rx_buf if needed
}
You can use Master and Slave almost the same way (omit the Slave example here).
void loop()
{
// if no transaction is in flight and all results are handled, queue new transactions
if (slave.hasTransactionsCompletedAndAllResultsHandled()) {
// do some initialization for tx_buf and rx_buf
// queue multiple transactions
// in this example, the master sends some data first,
slave.queue(NULL, rx_buf, BUFFER_SIZE);
// and the slave sends same data after that
slave.queue(tx_buf, NULL, BUFFER_SIZE);
// finally, we should trigger transaction in the background
slave.trigger();
}
// you can do some other stuff here
// NOTE: you can't touch dma_tx/rx_buf because it's in-flight in the background
// if all transactions are completed and all results are ready, handle results
if (slave.hasTransactionsCompletedAndAllResultsReady(QUEUE_SIZE)) {
// get received bytes for all transactions
const std::vector<size_t> received_bytes = slave.numBytesReceivedAll();
// do something with received_bytes and rx_buf if needed
}
}
This library's bool begin(const uint8_t spi_bus = HSPI)
function uses HSPI
as the default SPI bus as same as SPI
library of arduino-esp32
(reference).
The pins for SPI buses are automatically attached as follows. "Default SPI Pins" means the pins defined there are the same as MOSI
, MISO
, SCK
, and SS
.
Board | SPI | MOSI | MISO | SCK | SS | Default SPI Pins |
---|---|---|---|---|---|---|
esp32 |
HSPI | 13 | 12 | 14 | 15 | No |
esp32 |
VSPI/FSPI | 23 | 19 | 18 | 5 | Yes |
esp32s2 |
HSPI/FSPI | 35 | 37 | 36 | 34 | Yes |
esp32s3 |
HSPI/FSPI | 11 | 13 | 12 | 10 | Yes |
esp32c3 |
HSPI/FSPI | 6 | 5 | 4 | 7 | Yes |
Depending on your board, the default SPI pins are defined in pins_arduino.h
. For example, esp32
's default SPI pins are found here (MOSI: 23, MISO: 19, SCK: 18, SS: 5
). Please refer to arduino-esp32/variants for your board's default SPI pins.
The supported SPI buses are different from the ESP32 chip. Please note that there may be a restriction to use FSPI
for your SPI bus. (Note: though arduino-esp32
still uses FSPI
and HSPI
for all chips (v2.0.11), these are deprecated for the chips after esp32s2
)
Chip | FSPI | HSPI | VSPI |
---|---|---|---|
ESP32 | SPI1_HOST(0 ) 1 |
SPI2_HOST(1 ) 2 |
SPI3_HOST (2 ) 3 |
ESP32-S2 | SPI2_HOST(1 ) |
SPI3_HOST(2 ) |
- |
ESP32-S3 | SPI2_HOST(1 ) |
SPI3_HOST(2 ) |
- |
ESP32-C3 | SPI2_HOST(1 ) |
SPI2_HOST(1 ) |
- |
Reference
- https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/cores/esp32/esp32-hal-spi.h#L28-L37
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/libraries/SPI/src/SPI.cpp#L346-L350
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/cores/esp32/esp32-hal-spi.h#L28-L37
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/cores/esp32/esp32-hal-spi.c#L719-L752
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/tools/sdk/esp32/include/driver/include/driver/sdspi_host.h#L23-L29
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/tools/sdk/esp32/include/hal/include/hal/spi_types.h#L26-L31
- https://github.com/espressif/arduino-esp32/blob/099b432d10fb4ca1529c52241bcadcb8a4386f17/tools/sdk/esp32/include/hal/include/hal/spi_types.h#L77-L87
/// @brief initialize SPI with the default pin assignment for HSPI, or VSPI
bool begin(const uint8_t spi_bus = HSPI);
/// @brief initialize SPI with HSPI/FSPI/VSPI, sck, miso, mosi, and ss pins
bool begin(uint8_t spi_bus, int sck, int miso, int mosi, int ss);
/// @brief initialize SPI with HSPI/FSPI/VSPI and Qued SPI pins
bool begin(uint8_t spi_bus, int sck, int ss, int data0, int data1, int data2, int data3);
/// @brief initialize SPI with HSPI/FSPI/VSPI and Octo SPI pins
bool begin(uint8_t spi_bus, int sck, int ss, int data0, int data1, int data2, int data3, int data4, int data5, int data6, int data7);
/// @brief stop spi slave (terminate spi_slave_task and deinitialize spi)
void end();
/// @brief execute one transaction and wait for the completion
size_t transfer(const uint8_t* tx_buf, uint8_t* rx_buf, size_t size, uint32_t timeout_ms = 0);
size_t transfer(uint32_t flags, const uint8_t* tx_buf, uint8_t* rx_buf, size_t size, uint32_t timeout_ms);
/// @brief queue transaction to internal transaction buffer.
/// To start transaction, wait() or trigger() must be called.
bool queue(const uint8_t* tx_buf, uint8_t* rx_buf, size_t size);
bool queue(uint32_t flags, const uint8_t* tx_buf, uint8_t* rx_buf, size_t size);
/// @brief execute queued transactions and wait for the completion.
/// rx_buf is automatically updated after the completion of each transaction.
std::vector<size_t> wait(uint32_t timeout_ms = 0);
/// @brief execute queued transactions asynchronously in the background (without blocking)
/// numBytesReceivedAll() or numBytesReceived() is required to confirm the results of transactions
/// rx_buf is automatically updated after the completion of each transaction.
bool trigger();
/// @brief return the number of in-flight transactions
size_t numTransactionsInFlight();
/// @brief return the number of completed but not received transaction results
size_t numTransactionsCompleted();
/// @brief return the oldest result of the completed transaction (received bytes)
size_t numBytesReceived();
/// @brief return all results of the completed transactions (received bytes)
std::vector<size_t> numBytesReceivedAll();
/// @brief check if the queued transactions are completed and all results are handled
bool hasTransactionsCompletedAndAllResultsHandled();
/// @brief check if the queued transactions are completed
bool hasTransactionsCompletedAndAllResultsReady(size_t num_queued);
// ===== Main Configurations =====
// set these optional parameters before begin() if you want
/// @brief set spi data mode
void setDataMode(uint8_t mode);
/// @brief set queue size (default: 1)
void setQueueSize(size_t size);
// ===== Optional Configurations =====
// set these optional parameters before begin() if you want
/// @brief Bitwise OR of SPI_SLAVE_* flags.
void setSlaveFlags(uint32_t flags);
/// @brief SPI mode, representing a pair of (CPOL, CPHA) configuration: 0: (0, 0), 1: (0, 1), 2: (1, 0), 3: (1, 1)
void setSpiMode(uint8_t m);
/// @brief Callback called after the SPI registers are loaded with new data.
void setPostSetupCb(const slave_transaction_cb_t &post_setup_cb);
/// @brief Callback called after a transaction is done.
void setPostTransCb(const slave_transaction_cb_t &post_trans_cb);
/// @brief set post_setup callback (ISR) and its argument that are called after transaction setup completed.
/// you can call this function before every transfer() / queue() to change the behavior per transaction.
/// see more details about callbacks at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html#_CPPv4N29spi_device_interface_config_t6pre_cbE
void setUserPostSetupCbAndArg(const spi_slave_user_cb_t &cb, void *arg);
void setUserPostTransCbAndArg(const spi_slave_user_cb_t &cb, void *arg);
MIT