spi_slave_hd: add append mode example

This commit is contained in:
Armando 2021-07-19 10:49:01 +08:00 committed by Armando (Dou Yiwen)
parent aca2bd5fcf
commit b320945908
11 changed files with 554 additions and 0 deletions

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);
}