Merge branch 'feature/essp_spi_driver' into 'master'

spi: essl spi driver and spi_slv_hd append mode example

Closes IDF-1698, IDF-2279, and IDF-2932

See merge request espressif/esp-idf!12104
This commit is contained in:
Armando (Dou Yiwen) 2021-07-29 09:53:10 +00:00
commit 3c3e802048
16 changed files with 1036 additions and 79 deletions

View File

@ -919,16 +919,14 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start(spi_device_handle_t handl
{
esp_err_t ret;
SPI_CHECK(ticks_to_wait == portMAX_DELAY, "currently timeout is not available for polling transactions", ESP_ERR_INVALID_ARG);
spi_host_t *host = handle->host;
ret = check_trans_valid(handle, trans_desc);
if (ret!=ESP_OK) return ret;
SPI_CHECK(!spi_bus_device_is_polling(handle), "Cannot send polling transaction while the previous polling transaction is not terminated.", ESP_ERR_INVALID_STATE );
/* If device_acquiring_lock is set to handle, it means that the user has already
* acquired the bus thanks to the function `spi_device_acquire_bus()`.
* In that case, we don't need to take the lock again. */
spi_host_t *host = handle->host;
if (host->device_acquiring_lock != handle) {
/* The user cannot ask for the CS to keep active has the bus is not locked/acquired. */
if ((trans_desc->flags & SPI_TRANS_CS_KEEP_ACTIVE) != 0) {

View File

@ -62,10 +62,7 @@ esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms)
esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (start == NULL || length == 0) {
if (handle == NULL || start == NULL || length == 0) {
return ESP_ERR_INVALID_ARG;
}
if (handle->send_packet == NULL) {
@ -87,9 +84,9 @@ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t lengt
} else if (err != ESP_ERR_NOT_FOUND) {
return err;
} // else ESP_ERR_NOT_FOUND
//the slave has no enough memory, retry
//the slave is not ready, retry
} while (remain_wait_ms > 0);
return ESP_OK;
return err;
}
esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms)

View File

@ -15,10 +15,48 @@
#include <string.h>
#include <sys/param.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/spi_master.h"
#include "driver/periph_ctrl.h"
#include "essl_spi/esp32s2_defs.h"
#include "essl_internal.h"
#include "essl_spi.h"
#include "essl_spi/esp32s2_defs.h"
/**
* Initialise device function list of SPI by this macro.
*/
#define ESSL_SPI_DEFAULT_DEV_FUNC() (essl_dev_t) {\
.get_tx_buffer_num = essl_spi_get_tx_buffer_num,\
.update_tx_buffer_num = essl_spi_update_tx_buffer_num,\
.get_rx_data_size = essl_spi_get_rx_data_size,\
.update_rx_data_size = essl_spi_update_rx_data_size,\
.send_packet = essl_spi_send_packet,\
.get_packet = essl_spi_get_packet,\
.write_reg = essl_spi_write_reg,\
.read_reg = essl_spi_read_reg,\
}
static const char TAG[] = "essl_spi";
typedef struct {
spi_device_handle_t spi; // Pointer to SPI device handle.
/* Master TX, Slave RX */
struct {
size_t sent_buf_num; // Number of TX buffers that has been sent out by the master.
size_t slave_rx_buf_num; // Number of RX buffers laoded by the slave.
uint16_t tx_buffer_size; /* Buffer size for Master TX / Slave RX direction.
* Data with length within this size will still be regarded as one buffer.
* E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. */
uint8_t tx_sync_reg; // The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
} master_out;
/* Master RX, Slave TX */
struct {
size_t received_bytes; // Number of the RX bytes that has been received by the Master.
size_t slave_tx_bytes; // Number of the TX bytes that has been loaded by the Slave
uint8_t rx_sync_reg; // The pre-negotiated register ID for Master-RX-SLAVE-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
} master_in;
} essl_spi_context_t;
static uint16_t get_hd_command(uint16_t cmd_i, uint32_t flags)
@ -153,12 +191,12 @@ esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, in
seg_len = (seg_len > 0)? seg_len : len;
uint8_t* read_ptr = out_data;
esp_err_t err = ESP_OK;
esp_err_t ret = ESP_OK;
while (len > 0) {
int send_len = MIN(seg_len, len);
err = essl_spi_rddma_seg(spi, read_ptr, send_len, flags);
if (err != ESP_OK) return err;
ret = essl_spi_rddma_seg(spi, read_ptr, send_len, flags);
if (ret != ESP_OK) return ret;
len -= send_len;
read_ptr += send_len;
@ -217,3 +255,235 @@ esp_err_t essl_spi_int(spi_device_handle_t spi, int int_n, uint32_t flags)
};
return spi_device_transmit(spi, &end_t);
}
//------------------------------------ APPEND MODE ----------------------------------//
static uint32_t essl_spi_get_rx_data_size(void *arg);
static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms);
static uint32_t essl_spi_get_tx_buffer_num(void *arg);
static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms);
esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config)
{
ESP_RETURN_ON_FALSE(init_config->spi, ESP_ERR_INVALID_STATE, TAG, "Check SPI initialization first");
ESP_RETURN_ON_FALSE(init_config->tx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, ESP_ERR_INVALID_ARG, TAG, "GPSPI supports %d-byte-width internal registers", SOC_SPI_MAXIMUM_BUFFER_SIZE);
ESP_RETURN_ON_FALSE(init_config->rx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, ESP_ERR_INVALID_ARG, TAG, "GPSPI supports %d-byte-width internal registers", SOC_SPI_MAXIMUM_BUFFER_SIZE);
ESP_RETURN_ON_FALSE(init_config->tx_sync_reg != init_config->rx_sync_reg, ESP_ERR_INVALID_ARG, TAG, "Should use different word of registers for synchronization");
essl_spi_context_t *context = calloc(1, sizeof(essl_spi_context_t));
essl_dev_t *dev = calloc(1, sizeof(essl_dev_t));
if (!context || !dev) {
free(context);
free(dev);
return ESP_ERR_NO_MEM;
}
*context = (essl_spi_context_t) {
.spi = *init_config->spi,
.master_out.tx_buffer_size = init_config->tx_buf_size,
.master_out.tx_sync_reg = init_config->tx_sync_reg,
.master_in.rx_sync_reg = init_config->rx_sync_reg
};
*dev = ESSL_SPI_DEFAULT_DEV_FUNC();
dev->args = context;
*out_handle = dev;
return ESP_OK;
}
esp_err_t essl_spi_deinit_dev(essl_handle_t handle)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "ESSL SPI is not in use");
free(handle->args);
free(handle);
return ESP_OK;
}
void essl_spi_reset_cnt(void *arg)
{
essl_spi_context_t *ctx = arg;
if (ctx) {
ctx->master_out.sent_buf_num = 0;
ctx->master_in.received_bytes = 0;
}
}
//------------------------------------ RX ----------------------------------//
esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms)
{
essl_spi_context_t *ctx = arg;
ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first");
uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg;
uint8_t reserved_1_tail = reserved_1_head + 3;
uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg;
uint8_t reserved_2_tail = reserved_2_head + 3;
ESP_RETURN_ON_FALSE(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, ESP_ERR_INVALID_ARG, TAG, "Invalid address");
return essl_spi_rdbuf(ctx->spi, out_value, addr, sizeof(uint8_t), 0);
}
static uint32_t essl_spi_get_rx_data_size(void *arg)
{
essl_spi_context_t *ctx = arg;
ESP_LOGV(TAG, "slave tx buffer: %d bytes, master has read: %d bytes", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes);
return ctx->master_in.slave_tx_bytes - ctx->master_in.received_bytes;
}
static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms)
{
essl_spi_context_t *ctx = arg;
uint32_t updated_size;
uint32_t previous_size;
esp_err_t ret;
ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0);
if (ret != ESP_OK) {
return ret;
}
/**
* Read until the last 2 reading result are same. Reason:
* SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the
* register value is changed by Slave at this time, Master may get wrong data.
*/
while (1) {
ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0);
if (ret != ESP_OK) {
return ret;
}
if (updated_size == previous_size) {
ctx->master_in.slave_tx_bytes = updated_size;
ESP_LOGV(TAG, "updated: slave prepared tx buffer is: %d bytes", updated_size);
return ret;
}
previous_size = updated_size;
}
}
esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms)
{
ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first");
if (!esp_ptr_dma_capable(out_data) || ((intptr_t)out_data % 4) != 0) {
return ESP_ERR_INVALID_ARG;
}
essl_spi_context_t *ctx = arg;
esp_err_t ret;
if (essl_spi_get_rx_data_size(arg) < size) {
/**
* For realistic situation, usually there will be a large overhead (Slave will load large amount of data),
* so here we only update the Slave's TX size when the last-updated size is smaller than what Master requires.
*/
ret = essl_spi_update_rx_data_size(arg, wait_ms);
if (ret != ESP_OK) {
return ret;
}
//Slave still did not load enough size of buffer
if (essl_spi_get_rx_data_size(arg) < size) {
ESP_LOGV(TAG, "slave buffer: %d is not enough, %d is required", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes + size);
return ESP_ERR_NOT_FOUND;
}
}
ESP_LOGV(TAG, "get_packet: size to read is: %d", size);
ret = essl_spi_rddma_seg(ctx->spi, out_data, size, 0);
if (ret != ESP_OK) {
return ret;
}
ctx->master_in.received_bytes += size;
return ESP_OK;
}
//------------------------------------ TX ----------------------------------//
esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms)
{
essl_spi_context_t *ctx = arg;
ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first");
uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg;
uint8_t reserved_1_tail = reserved_1_head + 3;
uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg;
uint8_t reserved_2_tail = reserved_2_head + 3;
ESP_RETURN_ON_FALSE(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, ESP_ERR_INVALID_ARG, TAG, "Invalid address");
ESP_RETURN_ON_FALSE(out_value == NULL, ESP_ERR_NOT_SUPPORTED, TAG, "This feature is not supported");
return essl_spi_wrbuf(ctx->spi, &value, addr, sizeof(uint8_t), 0);
}
static uint32_t essl_spi_get_tx_buffer_num(void *arg)
{
essl_spi_context_t *ctx = arg;
ESP_LOGV(TAG, "slave rx buffer: %d, master has sent: %d", ctx->master_out.slave_rx_buf_num, ctx->master_out.sent_buf_num);
return ctx->master_out.slave_rx_buf_num - ctx->master_out.sent_buf_num;
}
static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms)
{
essl_spi_context_t *ctx = arg;
uint32_t updated_num;
uint32_t previous_size;
esp_err_t ret;
ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0);
if (ret != ESP_OK) {
return ret;
}
/**
* Read until the last 2 reading result are same. Reason:
* SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the
* register value is changed by Slave at this time, Master may get wrong data.
*/
while (1) {
ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_num, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0);
if (ret != ESP_OK) {
return ret;
}
if (updated_num == previous_size) {
ctx->master_out.slave_rx_buf_num = updated_num;
ESP_LOGV(TAG, "updated: slave prepared rx buffer: %d", updated_num);
return ret;
}
previous_size = updated_num;
}
}
esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms)
{
ESP_RETURN_ON_FALSE(arg, ESP_ERR_INVALID_STATE, TAG, "Check ESSL SPI initialization first");
if (!esp_ptr_dma_capable(data)) {
return ESP_ERR_INVALID_ARG;
}
essl_spi_context_t *ctx = arg;
esp_err_t ret;
uint32_t buf_num_to_use = (size + ctx->master_out.tx_buffer_size - 1) / ctx->master_out.tx_buffer_size;
if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) {
/**
* For realistic situation, usually there will be a large overhead (Slave will load enough number of RX buffers),
* so here we only update the Slave's RX buffer number when the last-updated number is smaller than what Master requires.
*/
ret = essl_spi_update_tx_buffer_num(arg, wait_ms);
if (ret != ESP_OK) {
return ret;
}
//Slave still did not load a sufficient amount of buffers
if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) {
ESP_LOGV(TAG, "slave buffer: %d is not enough, %d is required", ctx->master_out.slave_rx_buf_num, ctx->master_out.sent_buf_num + buf_num_to_use);
return ESP_ERR_NOT_FOUND;
}
}
ESP_LOGV(TAG, "send_packet: size to write is: %d", size);
ret = essl_spi_wrdma_seg(ctx->spi, data, size, 0);
if (ret != ESP_OK) {
return ret;
}
ctx->master_out.sent_buf_num += buf_num_to_use;
return essl_spi_wrdma_done(ctx->spi, 0);
}

View File

@ -29,42 +29,48 @@ typedef struct essl_dev_t* essl_handle_t;
*
* @param handle Handle of an ESSL device.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
* @return ESP_OK if success, or other value returned from lower layer `init`.
* @return
* - ESP_OK: If success
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - Other value returned from lower layer `init`.
*/
esp_err_t essl_init(essl_handle_t handle, uint32_t wait_ms);
/** Wait for interrupt of an ESP slave device.
/** Wait for interrupt of an ESSL slave device.
*
* @param handle Handle of an ESSL device.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK if success
* - ESP_OK: If success
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - One of the error codes from SDMMC host controller
*/
esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms);
/** Get buffer num for the host to send data to the slave. The buffers are size of ``buffer_size``.
*
* @param handle Handle of an ESSL device.
* @param out_tx_num Output of buffer num that host can send data to an ESP slave.
* @param handle Handle of a ESSL device.
* @param out_tx_num Output of buffer num that host can send data to ESSL slave.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode
* - One of the error codes from SDMMC/SPI host controller
*/
esp_err_t essl_get_tx_buffer_num(essl_handle_t handle, uint32_t *out_tx_num, uint32_t wait_ms);
/** Get amount of data the ESP slave preparing to send to host.
/** Get the size, in bytes, of the data that the ESSL slave is ready to send
*
* @param handle Handle of an ESSL device.
* @param out_rx_size Output of data size to read from slave.
* @param out_rx_size Output of data size to read from slave, in bytes
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode
* - One of the error codes from SDMMC/SPI host controller
*/
esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uint32_t wait_ms);
@ -72,10 +78,15 @@ esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uin
/** Reset the counters of this component. Usually you don't need to do this unless you know the slave is reset.
*
* @param handle Handle of an ESSL device.
*
* @return
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode
* - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init.
*/
esp_err_t essl_reset_cnt(essl_handle_t handle);
/** Send a packet to the ESP slave. The slave receive the packet into buffers whose size is ``buffer_size`` (configured during initialization).
/** Send a packet to the ESSL Slave. The Slave receives the packet into buffers whose size is ``buffer_size`` (configured during initialization).
*
* @param handle Handle of an ESSL device.
* @param start Start address of the packet to send
@ -84,12 +95,15 @@ esp_err_t essl_reset_cnt(essl_handle_t handle);
*
* @return
* - ESP_OK Success
* - ESP_ERR_TIMEOUT No buffer to use, or error ftrom SDMMC host controller
* - One of the error codes from SDMMC host controller
* - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init or other argument is not valid.
* - ESP_ERR_TIMEOUT: No buffer to use, or error ftrom SDMMC host controller.
* - ESP_ERR_NOT_FOUND: Slave is not ready for receiving.
* - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode
* - One of the error codes from SDMMC/SPI host controller.
*/
esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms);
/** Get a packet from an ESP slave.
/** Get a packet from ESSL slave.
*
* @param handle Handle of an ESSL device.
* @param[out] out_data Data output address
@ -98,16 +112,19 @@ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t lengt
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success, all the data are read from the slave.
* - ESP_ERR_NOT_FINISHED Read success, while there're data remaining.
* - One of the error codes from SDMMC host controller
* - ESP_OK Success: All the data has been read from the slave.
* - ESP_ERR_INVALID_ARG: Invalid argument, The handle is not initialized or the other arguments are invalid.
* - ESP_ERR_NOT_FINISHED: Read was successful, but there is still data remaining.
* - ESP_ERR_NOT_FOUND: Slave is not ready to send data.
* - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode
* - One of the error codes from SDMMC/SPI host controller.
*/
esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms);
/** Write general purpose R/W registers (8-bit) of an ESP slave.
/** Write general purpose R/W registers (8-bit) of ESSL slave.
*
* @param handle Handle of an ESSL device.
* @param addr Address of register to write. Valid address: 0-59.
* @param addr Address of register to write. For SDIO, valid address: 0-59. For SPI, see ``essl_spi.h``
* @param value Value to write to the register.
* @param value_o Output of the returned written value.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
@ -116,22 +133,20 @@ esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, siz
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Address not valid.
* - One of the error codes from SDMMC host controller
* - One of the error codes from SDMMC/SPI host controller
*/
esp_err_t essl_write_reg(essl_handle_t handle, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms);
/** Read general purpose R/W registers (8-bit) of an ESP slave.
/** Read general purpose R/W registers (8-bit) of ESSL slave.
*
* @param handle Handle of an ESSL device.
* @param add Address of register to read. Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read).
* @param handle Handle of a ``essl`` device.
* @param add Address of register to read. For SDIO, Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read). For SPI, see ``essl_spi.h``
* @param value_o Output value read from the register.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Address not valid.
* - One of the error codes from SDMMC host controller
* - One of the error codes from SDMMC/SPI host controller
*/
esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uint32_t wait_ms);
@ -141,25 +156,26 @@ esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uin
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Currently our driver doesnot support SDIO with SPI interface.
* - ESP_OK If interrupt triggered.
* - ESP_ERR_TIMEOUT No interrupts before timeout.
* - ESP_OK: If interrupt is triggered.
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - ESP_ERR_TIMEOUT: No interrupts before timeout.
*/
esp_err_t essl_wait_int(essl_handle_t handle, uint32_t wait_ms);
/** Clear interrupt bits of an ESP slave. All the bits set in the mask will be cleared, while other bits will stay the same.
/** Clear interrupt bits of ESSL slave. All the bits set in the mask will be cleared, while other bits will stay the same.
*
* @param handle Handle of an ESSL device.
* @param intr_mask Mask of interrupt bits to clear.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - One of the error codes from SDMMC host controller
*/
esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms);
/** Get interrupt bits of an ESP slave.
/** Get interrupt bits of ESSL slave.
*
* @param handle Handle of an ESSL device.
* @param intr_raw Output of the raw interrupt bits. Set to NULL if only masked bits are read.
@ -167,25 +183,27 @@ esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wai
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - ESP_INVALID_ARG if both ``intr_raw`` and ``intr_st`` are NULL.
* - ESP_OK: Success
* - ESP_INVALID_ARG: If both ``intr_raw`` and ``intr_st`` are NULL.
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - One of the error codes from SDMMC host controller
*/
esp_err_t essl_get_intr(essl_handle_t handle, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms);
/** Set interrupt enable bits of an ESP slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set.
/** Set interrupt enable bits of ESSL slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set.
*
* @param handle Handle of an ESSL device.
* @param ena_mask Mask of the interrupt bits to enable.
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - One of the error codes from SDMMC host controller
*/
esp_err_t essl_set_intr_ena(essl_handle_t handle, uint32_t ena_mask, uint32_t wait_ms);
/** Get interrupt enable bits of an ESP slave.
/** Get interrupt enable bits of ESSL slave.
*
* @param handle Handle of an ESSL device.
* @param ena_mask_o Output of interrupt bit enable mask.
@ -204,7 +222,8 @@ esp_err_t essl_get_intr_ena(essl_handle_t handle, uint32_t *ena_mask_o, uint32_t
* @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9.
*
* @return
* - ESP_OK Success
* - ESP_OK: Success
* - ESP_ERR_NOT_SUPPORTED: Current device does not support this function.
* - One of the error codes from SDMMC host controller
*/
esp_err_t essl_send_slave_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms);

View File

@ -23,19 +23,138 @@ extern "C"
{
#endif
/// Configuration of ESSL SPI device
typedef struct {
spi_device_handle_t *spi; ///< Pointer to SPI device handle.
uint32_t tx_buf_size; ///< The pre-negotiated Master TX buffer size used by both the host and the slave.
uint8_t tx_sync_reg; ///< The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
uint8_t rx_sync_reg; ///< The pre-negotiated register ID for Master-RX-Slave-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
} essl_spi_config_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// APIs for DMA Append Mode
// This mode has a better performance for continuous Half Duplex SPI transactions.
//
// * You can use the ``essl_spi_init_dev`` and ``essl_spi_deinit_dev`` together with APIs in ``essl.h`` to communicate
// with ESP SPI Slaves in Half Duplex DMA Append Mode. See example for SPI SLAVE HALFDUPLEX APPEND MODE.
// * You can also use the following APIs to create your own logic.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief Initialize the ESSL SPI device function list and get its handle
*
* @param[out] out_handle Output of the handle
* @param init_config Configuration for the ESSL SPI device
* @return
* - ESP_OK: On success
* - ESP_ERR_NO_MEM: Memory exhausted
* - ESP_ERR_INVALID_STATE: SPI driver is not initialized
* - ESP_ERR_INVALID_ARG: Wrong register ID
*/
esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config);
/**
* @brief Deinitialize the ESSL SPI device and free the memory used by the device
*
* @param handle Handle of the ESSL SPI device
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_STATE: ESSL SPI is not in use
*/
esp_err_t essl_spi_deinit_dev(essl_handle_t handle);
/**
* @brief Read from the shared registers
*
* @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `rx_sync_reg` in `essl_spi_config_t`)
*
* @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``)
* @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1).
* @param[out] out_value Read buffer for the shared registers.
* @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0).
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
* - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1.
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms);
/**
* @brief Get a packet from Slave
*
* @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``)
* @param[out] out_data Output data address
* @param size The size of the output data.
* @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0).
* @return
* - ESP_OK: On Success
* - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
* - ESP_ERR_INVALID_ARG: The output data address is neither DMA capable nor 4 byte-aligned
* - ESP_ERR_INVALID_SIZE: Master requires ``size`` bytes of data but Slave did not load enough bytes.
*/
esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms);
/**
* @brief Write to the shared registers
*
* @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `tx_sync_reg` in `essl_spi_config_t`)
* @note Feature of checking the actual written value (``out_value``) is not supported.
*
* @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``)
* @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1)
* @param value Buffer for data to send, should be align to 4.
* @param[out] out_value Not supported, should be set to NULL.
* @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0).
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
* - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1.
* - ESP_ERR_NOT_SUPPORTED: Should set ``out_value`` to NULL. See note 2.
* - or other return value from :cpp:func:`spi_device_transmit`.
*
*/
esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms);
/**
* @brief Send a packet to Slave
*
* @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``)
* @param data Address of the data to send
* @param size Size of the data to send.
* @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0).
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
* - ESP_ERR_INVALID_ARG: The data address is not DMA capable
* - ESP_ERR_INVALID_SIZE: Master will send ``size`` bytes of data but Slave did not load enough RX buffer
*/
esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms);
/**
* @brief Reset the counter in Master context
*
* @note Shall only be called if the slave has reset its counter. Else, Slave and Master would be desynchronized
*
* @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``)
*/
void essl_spi_reset_cnt(void *arg);
////////////////////////////////////////////////////////////////////////////////
// Basic commands to communicate with the SPI Slave HD on ESP32-S2
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Read the shared buffer from the slave in ISR way
*
* @note The slave's HW doesn't guarantee the data in one SPI transaction is consistent. It sends data in unit of byte.
* In other words, if the slave SW attempts to update the shared register when a rdbuf SPI transaction is in-flight,
* the data got by the master will be the combination of bytes of different writes of slave SW.
*
* @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
* by the DMA. When a byte is written, the remaining bytes in the same word will also be
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer for read data, strongly suggested to be in the DRAM and align to 4
* @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned to 4
* @param addr Address of the slave shared buffer
* @param len Length to read
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
@ -53,7 +172,7 @@ esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, i
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer for read data, strongly suggested to be in the DRAM and align to 4
* @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned to 4
* @param addr Address of the slave shared buffer
* @param len Length to read
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
@ -71,7 +190,7 @@ esp_err_t essl_spi_rdbuf_polling(spi_device_handle_t spi, uint8_t *out_data, int
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param data Buffer for data to send, strongly suggested to be in the DRAM
* @param addr Address of the slave shared buffer,
* @param len Length to write
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
@ -89,7 +208,7 @@ esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr,
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param data Buffer for data to send, strongly suggested to be in the DRAM
* @param addr Address of the slave shared buffer,
* @param len Length to write
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
@ -106,7 +225,7 @@ esp_err_t essl_spi_wrbuf_polling(spi_device_handle_t spi, const uint8_t *data, i
* :cpp:func:`essl_spi_rddma_done` at the end. Used when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer to hold the received data, strongly suggested to be in the DRAM and align to 4
* @param[out] out_data Buffer to hold the received data, strongly suggested to be in the DRAM and aligned to 4
* @param len Total length of data to receive.
* @param seg_len Length of each segment, which is not larger than the maximum transaction length
* allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
@ -124,7 +243,7 @@ esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, in
* @note To read long buffer, call :cpp:func:`essl_spi_rddma` instead.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer to hold the received data, strongly suggested to be in the DRAM and align to 4
* @param[out] out_data Buffer to hold the received data. strongly suggested to be in the DRAM and aligned to 4
* @param seg_len Length of this segment
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
@ -155,7 +274,7 @@ esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags);
* :cpp:func:`essl_spi_wrdma_done` at the end. Used when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param data Buffer for data to send, strongly suggested to be in the DRAM
* @param len Total length of data to send.
* @param seg_len Length of each segment, which is not larger than the maximum transaction length
* allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
@ -173,7 +292,7 @@ esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len,
* @note To send long buffer, call :cpp:func:`essl_spi_wrdma` instead.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param data Buffer for data to send, strongly suggested to be in the DRAM
* @param seg_len Length of this segment
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return

View File

@ -0,0 +1,106 @@
# SPI Halfduplex Slave Append Mode Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to use the SPI Slave HD driver and ESSL driver in IDF to communicate:
(ESSL driver is an encapsulated layer based on SPI Master driver to communicate with halfduplex mode SPI Slave.)
* Slave waits for Master to initiate ESSL SPI send and receive request using the SPI Slave HD driver.
* Slave will continuously prepare TX / RX buffers to the HW for Master to read / write. It will always load its buffer to the HW when the internal queue (see `queue_size` in `spi_slave_hd_slot_config_t`) is not full. It doesn't need to wait until a transaction finishes to prepare next buffer to the HW, so as to increase the transaction speed.
* Master will receive a bunch of packets from the slave for a fixed number of iterations, then send a bunch of packets for a fixed number of iterations.
If you have your own Master, please follow the [Espressif ESP32-S2 Technical Reference Manual-SPI Slave Halfduplex](https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf) for the data format.
**Suggest building/flashing/monitoring Slave example first**
Usually SPI Slave will try to make its buffer ready to be able to respond immediately. In this example, you could run Slave code first to prepare more buffer to the HW. Therefore you will have `queue_size` number of buffers which are ready for the transaction. And each time there is a vacancy, Slave can load its buffer to the HW.
## How to use example
### Hardware Required
This example requires 2 targets. Here we use 2 ESP32S2 DevKits to act as the SPI Master and SPI Slave respectively. Suggested pin connections are here:
| Signal | Master | Slave |
|-----------|--------|--------|
| CS | GPIO10 | GPIO10 |
| SCLK | GPIO12 | GPIO12 |
| MOSI | GPIO11 | GPIO11 |
| MISO | GPIO13 | GPIO13 |
| GND | GND | GND |
Feel free to modify the pin setting defined on the top of the ``app_main.c``.
### Configure the project
* Set the target of the build (where `{IDF_TARGET}` stands for the target chip such as `esp32s2`).
```
idf.py set-target {IDF_TARGET}
```
### Build and Flash
For each target, build the project and flash it to the board, then run monitor tool to view the output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
SPI Master
```
I (320) Master: Receiver
I (470) Receiver: 8 bytes are actually received:
I (470) Receiver: 21 22 23 24 25 26 27 28
I (560) Receiver: 8 bytes are actually received:
I (560) Receiver: 29 2a 2b 2c 2d 2e 2f 30
I (650) Receiver: 8 bytes are actually received:
I (650) Receiver: 2b 2c 2d 2e 2f 30 31 32
I (740) Receiver: 8 bytes are actually received:
I (740) Receiver: 33 34 35 36 37 38 39 3a
I (830) Receiver: 8 bytes are actually received:
I (830) Receiver: 0c 0d 0e 0f 10 11 12 13
I (920) Receiver: 8 bytes are actually received:
I (920) Receiver: 14 15 16 17 18 19 1a 1b
I (1010) Receiver: 8 bytes are actually received:
I (1010) Receiver: 1d 1e 1f 20 21 22 23 24
I (1100) Receiver: 8 bytes are actually received:
I (1100) Receiver: 25 26 27 28 29 2a 2b 2c
I (1190) Receiver: 8 bytes are actually received:
I (1190) Receiver: 00 01 02 03 04 05 06 07
I (1280) Receiver: 8 bytes are actually received:
I (1280) Receiver: 08 09 0a 0b 0c 0d 0e 0f
I (1280) Master: Sender
```
SPI Slave
```
I (6343) Receiver: 16 bytes are actually received:
I (6343) slave RX:: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
I (6523) Receiver: 16 bytes are actually received:
I (6523) slave RX:: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
I (6703) Receiver: 16 bytes are actually received:
I (6703) slave RX:: 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11
I (6883) Receiver: 16 bytes are actually received:
I (6883) slave RX:: 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12
I (7063) Receiver: 16 bytes are actually received:
I (7063) slave RX:: 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
I (7243) Receiver: 16 bytes are actually received:
I (7243) slave RX:: 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
I (7473) Receiver: 16 bytes are actually received:
I (7473) slave RX:: 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15
I (7653) Receiver: 16 bytes are actually received:
I (7653) slave RX:: 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16
I (7833) Receiver: 16 bytes are actually received:
I (7833) slave RX:: 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
I (8013) Receiver: 16 bytes are actually received:
I (8013) slave RX:: 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18
```

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spi-slave-hd-append-master)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := spi-slave-hd-append-master
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1 @@
See README.md in the parent directory

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "app_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,159 @@
/* SPI Slave Halfduplex example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "esp_log.h"
#include "esp_err.h"
#include "driver/spi_common.h"
#include "driver/spi_master.h"
#include "esp_serial_slave_link/essl.h"
#include "esp_serial_slave_link/essl_spi.h"
#define GPIO_MOSI 11
#define GPIO_MISO 13
#define GPIO_SCLK 12
#define GPIO_CS 10
#define HOST_ID 1
#define TRANSACTION_LEN 16
//The SPI transaction cycles in this example. You may change the cycle. e.g., use the ``sender`` and change it to a infinite loop
#define EXAMPLE_CYCLES 10
//---------This should be negotiated with the Slave!!!!-------------//
#define SLAVE_READY_FLAG 0x88
#define READY_FLAG_REG 0
#define SYNC_REG_FROM_HOST (14 * 4)
#define SYNC_REG_TO_HOST (15 * 4)
static void init_driver(spi_device_handle_t *out_spi, essl_handle_t *out_essl)
{
spi_device_handle_t spi;
spi_bus_config_t bus_cfg = {
.mosi_io_num = GPIO_MOSI,
.miso_io_num = GPIO_MISO,
.sclk_io_num = GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 20000
};
ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_cfg, SPI_DMA_CH_AUTO));
spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = 1 * 1 * 1000,
.flags = SPI_DEVICE_HALFDUPLEX,
.spics_io_num = GPIO_CS,
.queue_size = 16,
.command_bits = 8,
.address_bits = 8,
.dummy_bits = 8,
.mode = 0
};
ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &dev_cfg, &spi));
*out_spi = spi;
essl_spi_config_t config = {
.spi = &spi,
.tx_buf_size = TRANSACTION_LEN,
.tx_sync_reg = SYNC_REG_FROM_HOST,
.rx_sync_reg = SYNC_REG_TO_HOST
};
ESP_ERROR_CHECK(essl_spi_init_dev(out_essl, &config));
}
static esp_err_t receiver(essl_handle_t essl)
{
ESP_LOGI("Master", "Receiver");
esp_err_t ret;
uint8_t *recv_buf = heap_caps_calloc(1, TRANSACTION_LEN, MALLOC_CAP_DMA);
if (!recv_buf) {
ESP_LOGE("Receiver", "No enough memory");
return ESP_ERR_NO_MEM;
}
int n = EXAMPLE_CYCLES;
while (n--) {
size_t actual_rx_length = 0;
ret = essl_get_packet(essl, recv_buf, TRANSACTION_LEN/2, &actual_rx_length, portMAX_DELAY);
if (ret == ESP_OK || ret == ESP_ERR_NOT_FINISHED) {
ESP_LOGI("Receiver", "%d bytes are actually received:", actual_rx_length);
ESP_LOG_BUFFER_HEX("Receiver", recv_buf, actual_rx_length);
} else if (ret == ESP_ERR_NOT_FOUND) {
/**
* ``ESP_ERR_NOT_FOUND``: If Slave is not ready to send data until Timeout, you'll get this return error.
* Here we set Timeout ``portMAX_DELAY``, so you'll never reach this branch.
* In your own app, when you reach this branch, either retry the ``essl_get_packet``, or handle this situation in your own way.
*/
ESP_LOGW("Receiver", "Slave has nothing to send now, wait....");
vTaskDelay(1000);
} else {
ESP_LOGE("Sender", "Check arguments / driver initialization, see ``essl.h``");
return ESP_ERR_INVALID_ARG;
}
}
free(recv_buf);
return ESP_OK;
}
static esp_err_t sender(essl_handle_t essl)
{
ESP_LOGI("Master", "Sender");
esp_err_t ret;
uint8_t *send_buf = heap_caps_calloc(1, TRANSACTION_LEN, MALLOC_CAP_DMA);
if (!send_buf) {
ESP_LOGE("Sender", "No enough memory");
return ESP_ERR_NO_MEM;
}
uint8_t data = 0;
int n = EXAMPLE_CYCLES;
while (n--) {
for (int i = 0; i < TRANSACTION_LEN; i++) {
send_buf[i] = data+i;
}
ret = essl_send_packet(essl, send_buf, TRANSACTION_LEN, portMAX_DELAY);
if (ret == ESP_OK) {
data++;
} else if (ret == ESP_ERR_NOT_FOUND) {
/**
* ``ESP_ERR_NOT_FOUND``: If Slave is not ready to receive data until Timeout, you'll get this return error.
* Here we set Timeout ``portMAX_DELAY``, so you'll never reach this branch.
* In your own app, when you reach this branch, either retry the ``essl_send_packet``, or handle this situation in your own way.
*/
ESP_LOGW("Sender", "Slave is not ready to receive data, wait...");
vTaskDelay(1000);
} else {
ESP_LOGE("Sender", "Check arguments / driver initialization, see ``essl.h``");
return ESP_ERR_INVALID_ARG;
}
}
free(send_buf);
return ESP_OK;
}
void app_main(void)
{
spi_device_handle_t spi;
essl_handle_t essl;
init_driver(&spi, &essl);
uint8_t slave_ready_flag = 0;
do {
ESP_ERROR_CHECK(essl_read_reg(essl, READY_FLAG_REG, &slave_ready_flag, 0));
printf("Waiting for Slave to be ready...\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
} while (slave_ready_flag != SLAVE_READY_FLAG);
ESP_ERROR_CHECK(receiver(essl));
ESP_ERROR_CHECK(sender(essl));
ESP_ERROR_CHECK(essl_spi_deinit_dev(essl));
ESP_ERROR_CHECK(spi_bus_remove_device(spi));
ESP_ERROR_CHECK(spi_bus_free(HOST_ID));
}

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spi-slave-hd-append-slave)

View File

@ -0,0 +1,4 @@
| Supported Targets | ESP32-S2 |
| ----------------- | -------- |
See README.md in the parent directory

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "app_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,252 @@
/* SPI Slave Halfduplex example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <time.h>
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/spi_slave_hd.h"
#include "esp_serial_slave_link/essl_spi.h"
#define GPIO_MOSI 11
#define GPIO_MISO 13
#define GPIO_SCLK 12
#define GPIO_CS 10
#define HOST_ID 1
#define QUEUE_SIZE 6
#define TRANSACTION_LEN 16
#define SYNC_REG_FROM_HOST (14 * 4)
#define SYNC_REG_TO_HOST (15 * 4)
//---------This should be negotiated with the Master!!!!-------------//
#define SLAVE_READY_FLAG 0x88
#define READY_FLAG_REG 0
struct trans_link_s {
spi_slave_hd_data_t trans;
struct trans_link_s *next;
bool recycled; //1: the current transaction descriptor is processed by the HW already, it is available and can be reused for new transaction
};
typedef struct trans_link_s trans_link_t;
/* Pointer to the current transaction */
trans_link_t *tx_curr_trans;
trans_link_t *rx_curr_trans;
static void init_slave_hd(void)
{
spi_bus_config_t bus_cfg = {};
bus_cfg.mosi_io_num = GPIO_MOSI;
bus_cfg.miso_io_num = GPIO_MISO;
bus_cfg.sclk_io_num = GPIO_SCLK;
bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1;
bus_cfg.max_transfer_sz = 50000;
spi_slave_hd_slot_config_t slave_hd_cfg = {};
slave_hd_cfg.spics_io_num = GPIO_CS;
slave_hd_cfg.flags |= SPI_SLAVE_HD_APPEND_MODE;
slave_hd_cfg.mode = 0;
slave_hd_cfg.command_bits = 8;
slave_hd_cfg.address_bits = 8;
slave_hd_cfg.dummy_bits = 8;
slave_hd_cfg.queue_size = QUEUE_SIZE;
slave_hd_cfg.dma_chan = SPI_DMA_CH_AUTO;
ESP_ERROR_CHECK(spi_slave_hd_init(HOST_ID, &bus_cfg, &slave_hd_cfg));
}
//Create a link to the transaction descriptors, malloc the transaction buffers
static esp_err_t create_transaction_pool(uint8_t **data_buf, trans_link_t *trans_link, uint16_t times)
{
for (int i = 0; i < times; i++) {
//malloc data buffers for transaction
data_buf[i] = heap_caps_calloc(1, TRANSACTION_LEN, MALLOC_CAP_DMA);
if (!data_buf[i]) {
ESP_LOGI("Create pool:", "No enough memory");
return ESP_ERR_NO_MEM;
}
//attach data buffer and transaction descriptor
trans_link[i].trans.data = data_buf[i];
//link the recycling transaction descriptors
if (i != QUEUE_SIZE - 1) {
trans_link[i].next = &trans_link[i+1];
} else {
trans_link[i].next = &trans_link[0];
}
//init transaction descriptor as available
trans_link[i].recycled = 1;
}
return ESP_OK;
}
//-----------------------------------------------------TX Transaction-----------------------------------------------//
static void prepare_tx_data(trans_link_t *tx_trans)
{
/**
* Apply Your Own Data Here
*/
uint8_t data = rand() % 50;
tx_trans->trans.len = TRANSACTION_LEN;
for(int i = 0; i < tx_trans->trans.len; i++) {
tx_trans->trans.data[i] = data + i;
}
tx_trans->recycled = 0;
}
static bool get_tx_transaction_descriptor(trans_link_t **out_trans)
{
if (tx_curr_trans->recycled == 0) {
return false;
}
*out_trans = tx_curr_trans;
tx_curr_trans = tx_curr_trans->next;
return true;
}
void sendTask(void *arg)
{
uint8_t *tx_buffer[QUEUE_SIZE] = {};
trans_link_t trans_link[QUEUE_SIZE] = {};
trans_link_t *trans_to_send; //The transaction to send data, should get from ``get_tx_transaction_descriptor``
tx_curr_trans = trans_link;
ESP_ERROR_CHECK(create_transaction_pool(tx_buffer, trans_link, QUEUE_SIZE));
//This variable is used to check if you're using transaction descriptors more than you prepared in the pool
bool get_desc_success = false;
//This is the total size of the buffers that are loaded by Slave
uint32_t total_load_buf_size = 0;
/**
* Start transactions until internal queue is full (equals QUEUE_SIZE)
*
* - The ``spi_slave_hd_append_trans`` API will pre-load Slave's transaction to the hardware.
* - You don't need a callback to achieve this (comparing to Segment Mode). Therefore, Slave doesn't need to wait until Master finishes its operation.
* These transactions would be a queue for Master to read. So the speed will be faster.
*/
for (int i = 0; i < QUEUE_SIZE; i++) {
get_desc_success = get_tx_transaction_descriptor(&trans_to_send);
if (get_desc_success) {
prepare_tx_data(trans_to_send);
ESP_ERROR_CHECK(spi_slave_hd_append_trans(HOST_ID, SPI_SLAVE_CHAN_TX, &trans_to_send->trans, portMAX_DELAY));
//Inform Master the number of bytes that Slave has loaded
total_load_buf_size += TRANSACTION_LEN;
spi_slave_hd_write_buffer(HOST_ID, SYNC_REG_TO_HOST, (uint8_t *)&total_load_buf_size, 4);
}
}
//Get one result and load a new transaction
while (1) {
spi_slave_hd_data_t *ret_trans;
trans_link_t *ret_link;
//Get the transaction descriptor that is already procecssed by the HW and can be recycled
ESP_ERROR_CHECK(spi_slave_hd_get_append_trans_res(HOST_ID, SPI_SLAVE_CHAN_TX, &ret_trans, portMAX_DELAY));
ret_link = __containerof(ret_trans, trans_link_t, trans);
ret_link->recycled = 1;
get_desc_success = get_tx_transaction_descriptor(&trans_to_send);
if (get_desc_success) {
prepare_tx_data(trans_to_send);
ESP_ERROR_CHECK(spi_slave_hd_append_trans(HOST_ID, SPI_SLAVE_CHAN_TX, &trans_to_send->trans, portMAX_DELAY));
//Inform Master the number of bytes that Slave has loaded
total_load_buf_size += TRANSACTION_LEN;
spi_slave_hd_write_buffer(HOST_ID, SYNC_REG_TO_HOST, (uint8_t *)&total_load_buf_size, 4);
}
}
}
//-----------------------------------------------------RX Transaction-----------------------------------------------//
static bool get_rx_transaction_descriptor(trans_link_t **out_trans)
{
if (rx_curr_trans->recycled == 0) {
return false;
}
rx_curr_trans->trans.len = TRANSACTION_LEN;
*out_trans = rx_curr_trans;
rx_curr_trans = rx_curr_trans->next;
return true;
}
void recvTask(void *arg)
{
uint8_t *rx_buffer[QUEUE_SIZE] = {};
trans_link_t trans_link[QUEUE_SIZE] = {};
trans_link_t *trans_for_recv; //The transaction to receive data, should get from ``get_rx_transaction_descriptor``
rx_curr_trans = trans_link;
ESP_ERROR_CHECK(create_transaction_pool(rx_buffer, trans_link, QUEUE_SIZE));
//This variable is used to check if you're using transaction descriptors more than you prepared in the pool
bool get_desc_success = false;
//This is the number of the buffers that are loaded by Slave. The buffer size (TRANSACTION_LEN) should be pre-negotiate with Master.
uint32_t total_load_buf_num = 0;
/**
* Start transactions until internal queue is full (equals QUEUE_SIZE)
*
* - The ``spi_slave_hd_append_trans`` API will pre-load Slave's transaction to the hardware.
* - You don't need a callback to achieve this (comparing to Segment Mode). Therefore, Slave doesn't need to wait until Master finishes its operation.
* These transactions would be a queue for Master to send its data. So the speed will be faster.
*/
for (int i = 0; i < QUEUE_SIZE; i++) {
get_desc_success = get_rx_transaction_descriptor(&trans_for_recv);
if (get_desc_success) {
ESP_ERROR_CHECK(spi_slave_hd_append_trans(HOST_ID, SPI_SLAVE_CHAN_RX, &trans_for_recv->trans, portMAX_DELAY));
//Inform Master the number of buffer that Slave has loaded
total_load_buf_num += 1;
spi_slave_hd_write_buffer(HOST_ID, SYNC_REG_FROM_HOST, (uint8_t *)&total_load_buf_num, 4);
}
}
while (1) {
spi_slave_hd_data_t *ret_trans;
trans_link_t *ret_link;
//Get the transaction descriptor that is already procecssed by the HW and can be recycled
spi_slave_hd_get_append_trans_res(HOST_ID, SPI_SLAVE_CHAN_RX, &ret_trans, portMAX_DELAY);
ret_link = __containerof(ret_trans, trans_link_t, trans);
ret_link->recycled = 1;
ESP_LOGI("Receiver", "%d bytes are actually received:", ret_trans->trans_len);
ESP_LOG_BUFFER_HEX("slave RX:", ret_trans->data, ret_trans->trans_len);
get_desc_success = get_rx_transaction_descriptor(&trans_for_recv);
if (get_desc_success) {
ESP_ERROR_CHECK(spi_slave_hd_append_trans(HOST_ID, SPI_SLAVE_CHAN_RX, &trans_for_recv->trans, portMAX_DELAY));
//Inform Master the number of buffer that Slave has loaded
total_load_buf_num += 1;
spi_slave_hd_write_buffer(HOST_ID, SYNC_REG_FROM_HOST, (uint8_t *)&total_load_buf_num, 4);
}
}
}
void app_main(void)
{
init_slave_hd();
//Init the shared register
uint8_t init_value[SOC_SPI_MAXIMUM_BUFFER_SIZE] = {0x0};
spi_slave_hd_write_buffer(HOST_ID, 0, init_value, SOC_SPI_MAXIMUM_BUFFER_SIZE);
uint8_t ready_flag = SLAVE_READY_FLAG;
spi_slave_hd_write_buffer(HOST_ID, READY_FLAG_REG, &ready_flag, 4);
xTaskCreate(sendTask, "sendTask", 4096, NULL, 1, NULL);
xTaskCreate(recvTask, "recvTask", 4096, NULL, 1, NULL);
}