mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-18 23:54:39 +00:00
Merge branch 'feature/spi_slave_hd_segment_example' into 'master'
spi_slave_halfduplex: add an example for segment mode Closes IDF-1699 See merge request espressif/esp-idf!10043
This commit is contained in:
106
examples/peripherals/spi_slave_hd/segment_mode/README.md
Normal file
106
examples/peripherals/spi_slave_hd/segment_mode/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# SPI Slave Halfduplex: Segment Mode Examples
|
||||
|
||||
(See the README.md file in the 'examples' directory for more information about IDF examples.)
|
||||
|
||||
These two projects illustrate the SPI Slave Halfduplex Segment Mode.
|
||||
* ``seg_slave`` shows one of the ways to use the SPI Slave Halfduplex driver. There are 2 tasks repeating to receive/send transactions from/to SPI Master respectively.
|
||||
* ``seg_master`` shows how to use the ESP Serial Slave Link APIs to communicate with SPI Slave Halfduplex driver. It receives/sends transactions from/to slave a few times.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
These two projects are supposed to be flashed onto two seperate boards and jumped together via correctly connected SPI pins defined in both of the ``app_main.c`` files. For the ``seg_master`` project, it could be flashed onto all the ESP Chips. Whereas the ``seg_slave`` currently could be flashed onto ESP32-S2. Once they are connected and flashed, they will use the SPI Master and SPI Slave Halfduplex drivers to communicate with each other.
|
||||
|
||||
Following is the connection between 2 ESP32S2 boards:
|
||||
|
||||
| Signal | Master | Slave |
|
||||
|-----------|--------|--------|
|
||||
| MOSI | GPIO11 | GPIO11 |
|
||||
| MISO | GPIO13 | GPIO13 |
|
||||
| SCLK | GPIO12 | GPIO12 |
|
||||
| CS | GPIO10 | GPIO10 |
|
||||
|
||||
(Feel free to change the GPIO settings by editing the macro definations at the top of ``app_main.c`` files.)
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to build, flash and monitor projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
``seg_master``
|
||||
```
|
||||
---------SLAVE INFO---------
|
||||
|
||||
Slave MAX Send Buffer Size: 5000
|
||||
Slave MAX Receive Buffer Size: 120
|
||||
I (328) SEG_MASTER: RECEIVING......
|
||||
Transaction No.0 from slave, length: 4640
|
||||
I (338) SEG_MASTER: SENDING......
|
||||
I (348) SEG_MASTER: RECEIVING......
|
||||
Transaction No.1 from slave, length: 3299
|
||||
I (348) SEG_MASTER: SENDING......
|
||||
I (358) SEG_MASTER: RECEIVING......
|
||||
Transaction No.2 from slave, length: 4836
|
||||
I (368) SEG_MASTER: SENDING......
|
||||
I (368) SEG_MASTER: RECEIVING......
|
||||
Transaction No.3 from slave, length: 3333
|
||||
I (378) SEG_MASTER: SENDING......
|
||||
I (378) SEG_MASTER: RECEIVING......
|
||||
Transaction No.4 from slave, length: 4965
|
||||
I (388) SEG_MASTER: SENDING......
|
||||
I (388) SEG_MASTER: RECEIVING......
|
||||
Transaction No.5 from slave, length: 3042
|
||||
I (398) SEG_MASTER: SENDING......
|
||||
I (408) SEG_MASTER: RECEIVING......
|
||||
Transaction No.6 from slave, length: 4892
|
||||
I (408) SEG_MASTER: SENDING......
|
||||
I (418) SEG_MASTER: RECEIVING......
|
||||
Transaction No.7 from slave, length: 3585
|
||||
I (428) SEG_MASTER: SENDING......
|
||||
I (428) SEG_MASTER: RECEIVING......
|
||||
Transaction No.8 from slave, length: 3348
|
||||
I (438) SEG_MASTER: SENDING......
|
||||
I (438) SEG_MASTER: RECEIVING......
|
||||
Transaction No.9 from slave, length: 4985
|
||||
I (448) SEG_MASTER: SENDING......
|
||||
I (448) SEG_MASTER: RECEIVING......
|
||||
Transaction No.10 from slave, length: 3710
|
||||
...
|
||||
```
|
||||
|
||||
``seg_slave``
|
||||
```
|
||||
77 bytes are received:
|
||||
this is master's transaction 0
|
||||
109 bytes are received:
|
||||
this is master's transaction 1
|
||||
83 bytes are received:
|
||||
this is master's transaction 2
|
||||
97 bytes are received:
|
||||
this is master's transaction 3
|
||||
47 bytes are received:
|
||||
this is master's transaction 4
|
||||
89 bytes are received:
|
||||
this is master's transaction 5
|
||||
80 bytes are received:
|
||||
this is master's transaction 6
|
||||
96 bytes are received:
|
||||
this is master's transaction 7
|
||||
83 bytes are received:
|
||||
this is master's transaction 8
|
||||
110 bytes are received:
|
||||
this is master's transaction 9
|
||||
113 bytes are received:
|
||||
this is master's transaction 10
|
||||
...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you.
|
@@ -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(seg-master)
|
@@ -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-seg-master
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
@@ -0,0 +1 @@
|
||||
See README.md in the parent directory
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,294 @@
|
||||
/* 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 "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_serial_slave_link/essl_spi.h"
|
||||
|
||||
//Pin setting
|
||||
#if !CONFIG_IDF_TARGET_ESP32C3
|
||||
#define GPIO_MOSI 11
|
||||
#define GPIO_MISO 13
|
||||
#define GPIO_SCLK 12
|
||||
#define GPIO_CS 10
|
||||
#else
|
||||
#define GPIO_MOSI 7
|
||||
#define GPIO_MISO 2
|
||||
#define GPIO_SCLK 6
|
||||
#define GPIO_CS 10
|
||||
#endif
|
||||
|
||||
#define MASTER_HOST SPI2_HOST
|
||||
#define DMA_CHAN SPI_DMA_CH_AUTO
|
||||
|
||||
#define TX_SIZE_MIN 40
|
||||
|
||||
/**
|
||||
* Helper Macros for Master-Slave synchronization, each setting is 4-byte-width
|
||||
*
|
||||
* The address and value should be negotiated with Master beforehand
|
||||
*/
|
||||
//----------------------General Settings---------------------//
|
||||
//Indicate Slave General Settings are ready
|
||||
#define SLAVE_READY_FLAG_REG 0
|
||||
#define SLAVE_READY_FLAG 0xEE
|
||||
//Value in these 4 registers (Byte 4, 5, 6, 7) indicates the MAX Slave TX buffer length
|
||||
#define SLAVE_MAX_TX_BUF_LEN_REG 4
|
||||
//Value in these 4 registers indicates the MAX Slave RX buffer length
|
||||
#define SLAVE_MAX_RX_BUF_LEN_REG 8
|
||||
|
||||
//----------------------Updating Info------------------------//
|
||||
//Value in these 4 registers indicates size of the TX buffer that Slave has loaded to the DMA
|
||||
#define SLAVE_TX_READY_BUF_SIZE_REG 12
|
||||
//Value in these 4 registers indicates number of the RX buffer that Slave has loaded to the DMA
|
||||
#define SLAVE_RX_READY_BUF_NUM_REG 16
|
||||
|
||||
static const char TAG[] = "SEG_MASTER";
|
||||
|
||||
|
||||
static void get_spi_bus_default_config(spi_bus_config_t *bus_cfg)
|
||||
{
|
||||
memset(bus_cfg, 0x0, sizeof(spi_bus_config_t));
|
||||
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 = 14000;
|
||||
bus_cfg->flags = 0;
|
||||
bus_cfg->intr_flags = 0;
|
||||
}
|
||||
|
||||
static void get_spi_device_default_config(spi_device_interface_config_t *dev_cfg)
|
||||
{
|
||||
memset(dev_cfg, 0x0, sizeof(spi_device_interface_config_t));
|
||||
dev_cfg->clock_speed_hz = 10*1000*1000;
|
||||
dev_cfg->mode = 0;
|
||||
dev_cfg->spics_io_num = GPIO_CS;
|
||||
dev_cfg->cs_ena_pretrans = 0;
|
||||
dev_cfg->cs_ena_posttrans = 0;
|
||||
dev_cfg->command_bits = 8;
|
||||
dev_cfg->address_bits = 8;
|
||||
dev_cfg->dummy_bits = 8;
|
||||
dev_cfg->queue_size = 16;
|
||||
dev_cfg->flags = SPI_DEVICE_HALFDUPLEX;
|
||||
dev_cfg->duty_cycle_pos = 0;
|
||||
dev_cfg->input_delay_ns = 0;
|
||||
dev_cfg->pre_cb = NULL;
|
||||
dev_cfg->post_cb = NULL;
|
||||
}
|
||||
|
||||
static void init_master_hd(spi_device_handle_t* out_spi)
|
||||
{
|
||||
//init bus
|
||||
spi_bus_config_t bus_cfg = {};
|
||||
get_spi_bus_default_config(&bus_cfg);
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(MASTER_HOST, &bus_cfg, DMA_CHAN));
|
||||
|
||||
//add device
|
||||
spi_device_interface_config_t dev_cfg = {};
|
||||
get_spi_device_default_config(&dev_cfg);
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(MASTER_HOST, &dev_cfg, out_spi));
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------Function used for Master-Slave Synchronization---------------------------//
|
||||
//Wait for Slave to init the shared registers for its configurations, see the Helper Macros above
|
||||
static esp_err_t wait_for_slave_ready(spi_device_handle_t spi)
|
||||
{
|
||||
esp_err_t ret;
|
||||
uint32_t slave_ready_flag;
|
||||
|
||||
while (1) {
|
||||
//Master sends CMD2 to get slave configuration
|
||||
//The first byte is a flag assigned by slave as a start signal, here it's 0xee
|
||||
ret = essl_spi_rdbuf(spi, (uint8_t *)&slave_ready_flag, SLAVE_READY_FLAG_REG, 4, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (slave_ready_flag != SLAVE_READY_FLAG) {
|
||||
printf("Waiting for Slave to be ready...\n");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
} else if (slave_ready_flag == SLAVE_READY_FLAG) {
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Get the MAX length of Slave's TX/RX buffer
|
||||
static esp_err_t get_slave_max_buf_size(spi_device_handle_t spi, uint32_t *out_send_size, uint32_t *out_recv_size)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
ret = essl_spi_rdbuf(spi, (uint8_t *)out_send_size, SLAVE_MAX_TX_BUF_LEN_REG, 4, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
ret = essl_spi_rdbuf(spi, (uint8_t *)out_recv_size, SLAVE_MAX_RX_BUF_LEN_REG, 4, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get the size of the ready Slave TX buffer
|
||||
* This size can be read from the pre-negotiated shared register (here `SLAVE_TX_READY_BUF_SIZE_REG`)
|
||||
*/
|
||||
static uint32_t get_slave_tx_buf_size(spi_device_handle_t spi)
|
||||
{
|
||||
uint32_t updated_size;
|
||||
uint32_t temp;
|
||||
|
||||
ESP_ERROR_CHECK(essl_spi_rdbuf_polling(spi, (uint8_t *)&temp, SLAVE_TX_READY_BUF_SIZE_REG, 4, 0));
|
||||
/**
|
||||
* 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
|
||||
* value is changed by Slave at this time, Master may get wrong data.
|
||||
*/
|
||||
while (1) {
|
||||
ESP_ERROR_CHECK(essl_spi_rdbuf_polling(spi, (uint8_t *)&updated_size, SLAVE_TX_READY_BUF_SIZE_REG, 4, 0));
|
||||
if (updated_size == temp) {
|
||||
return updated_size;
|
||||
}
|
||||
temp = updated_size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To get the number of the ready Slave RX buffers
|
||||
* This number can be read from the pre-negotiated shared register (here `SLAVE_RX_READY_BUF_NUM_REG`)
|
||||
*/
|
||||
static uint32_t get_slave_rx_buf_num(spi_device_handle_t spi)
|
||||
{
|
||||
uint32_t updated_num;
|
||||
uint32_t temp;
|
||||
|
||||
ESP_ERROR_CHECK(essl_spi_rdbuf_polling(spi, (uint8_t *)&temp, SLAVE_RX_READY_BUF_NUM_REG, 4, 0));
|
||||
/**
|
||||
* 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
|
||||
* value is changed by Slave at this time, Master may get wrong data.
|
||||
*/
|
||||
while (1) {
|
||||
ESP_ERROR_CHECK(essl_spi_rdbuf_polling(spi, (uint8_t *)&updated_num, SLAVE_RX_READY_BUF_NUM_REG, 4, 0));
|
||||
if (updated_num == temp) {
|
||||
return updated_num;
|
||||
}
|
||||
temp = updated_num;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
spi_device_handle_t spi;
|
||||
init_master_hd(&spi);
|
||||
|
||||
ESP_ERROR_CHECK(wait_for_slave_ready(spi));
|
||||
|
||||
/**
|
||||
* Here we let the Slave to claim its transaction size. You can modify it in your own way,
|
||||
* e.g. let the Senders to claim its MAX length, or let the Master/Slave determine the length themselves, without considering
|
||||
* throughputs in the opposite side.
|
||||
*/
|
||||
uint32_t slave_max_tx_buf_size;
|
||||
uint32_t slave_max_rx_buf_size;
|
||||
ESP_ERROR_CHECK(get_slave_max_buf_size(spi, &slave_max_tx_buf_size, &slave_max_rx_buf_size));
|
||||
uint32_t rx_buf_size = slave_max_tx_buf_size;
|
||||
printf("\n\n---------SLAVE INFO---------\n\n");
|
||||
printf("Slave MAX Send Buffer Size: %d\n", slave_max_tx_buf_size);
|
||||
printf("Slave MAX Receive Buffer Size: %d\n", slave_max_rx_buf_size);
|
||||
|
||||
uint8_t *recv_buf = heap_caps_calloc(1, rx_buf_size, MALLOC_CAP_DMA);
|
||||
if (!recv_buf) {
|
||||
ESP_LOGE(TAG, "No enough memory!");
|
||||
abort();
|
||||
}
|
||||
uint8_t *send_buf = heap_caps_calloc(1, slave_max_rx_buf_size, MALLOC_CAP_DMA);
|
||||
if (!send_buf) {
|
||||
ESP_LOGE(TAG, "No enough memory!");
|
||||
abort();
|
||||
}
|
||||
|
||||
uint32_t size_has_read = 0; //Counter of the size that Master has received.
|
||||
uint32_t size_to_read = 0;
|
||||
|
||||
uint32_t num_has_sent = 0; //Counter of the buffer number that Master has sent.
|
||||
uint32_t tx_trans_id = 0;
|
||||
srand(30);
|
||||
while (1) {
|
||||
//RECV
|
||||
ESP_LOGI(TAG, "RECEIVING......");
|
||||
/**
|
||||
* This (`size_has_read`) is the counter mentioned in examples/peripherals/spi_slave_hd/segment_mode/seg_slave/app_main.c.
|
||||
* See its Note for Function used for Master-Slave Synchronization.
|
||||
*
|
||||
* Condition when this counter overflows:
|
||||
* If the Slave increases its counter with the value smaller than 2^32, then the calculation is still safe. For example:
|
||||
* 1. Initially, Slave's counter is (2^32 - 1 - 10), Master's counter is (2^32 - 1 - 20). So the difference would be 10B initially.
|
||||
* 2. Slave loads 20 bytes to the DMA, and increase its counter. So the value would be ((2^32 - 1 - 10) + 20) = 9;
|
||||
* 3. The difference (`size_can_be_read`) would be (9 - (2^32 - 1 - 20)) = 30;
|
||||
*
|
||||
* If this is 0, it means Slave didn't load new TX buffer to the bus yet.
|
||||
*/
|
||||
uint32_t size_can_be_read = get_slave_tx_buf_size(spi) - size_has_read;
|
||||
|
||||
if (size_can_be_read > rx_buf_size) {
|
||||
ESP_LOGW(TAG, "Slave is going to send buffer(%d Bytes) larger than pre-negotiated MAX size", size_can_be_read);
|
||||
/**
|
||||
* NOTE:
|
||||
* In this condition, Master should still increase its counter (``size_has_read``) by the size that Slave has loaded,
|
||||
* because Master RX buffer is not large enough, and it should read as large as it can, then send the CMD8. The extra
|
||||
* bits will be missed by Master.
|
||||
*/
|
||||
size_to_read = rx_buf_size;
|
||||
} else {
|
||||
size_to_read = size_can_be_read;
|
||||
}
|
||||
|
||||
if (size_to_read) {
|
||||
ESP_ERROR_CHECK(essl_spi_rddma(spi, recv_buf, size_to_read, -1, 0));
|
||||
size_has_read += size_can_be_read; //See NOTE above
|
||||
|
||||
//Process the data. Here we just print it out.
|
||||
printf("%s\n", recv_buf);
|
||||
memset(recv_buf, 0x0, rx_buf_size);
|
||||
}
|
||||
|
||||
//SEND
|
||||
ESP_LOGI(TAG, "SENDING......");
|
||||
/**
|
||||
* Similar logic, see the comment for `size_can_be_read` above.
|
||||
* If this is 0, it means Slave didn't load RX buffer to the bus yet.
|
||||
*/
|
||||
uint32_t num_to_send = get_slave_rx_buf_num(spi) - num_has_sent;
|
||||
|
||||
//Prepare your TX transaction in your own way. Here is an example.
|
||||
//You can set any size to send (shorter, longer or equal to the Slave Max RX buf size), Slave can get the actual length by ``trans_len`` member of ``spi_slave_hd_data_t``
|
||||
uint32_t actual_tx_size = (rand() % (slave_max_rx_buf_size - TX_SIZE_MIN + 1)) + TX_SIZE_MIN;
|
||||
snprintf((char *)send_buf, slave_max_rx_buf_size, "this is master's transaction %d", tx_trans_id);
|
||||
|
||||
for (int i = 0; i < num_to_send; i++) {
|
||||
ESP_ERROR_CHECK(essl_spi_wrdma(spi, send_buf, actual_tx_size, -1, 0));
|
||||
num_has_sent++;
|
||||
tx_trans_id++;
|
||||
}
|
||||
}
|
||||
|
||||
free(recv_buf);
|
||||
free(send_buf);
|
||||
|
||||
spi_bus_remove_device(spi);
|
||||
spi_bus_free(MASTER_HOST);
|
||||
}
|
@@ -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.
|
||||
#
|
@@ -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(seg-slave)
|
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32-S2 |
|
||||
| ----------------- | -------- |
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,304 @@
|
||||
/* 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 "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/spi_slave_hd.h"
|
||||
|
||||
#define TIME_IS_OUT(start, end, timeout) (timeout) > ((end)-(start)) ? 0 : 1
|
||||
|
||||
//Pin setting
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define GPIO_MOSI 11
|
||||
#define GPIO_MISO 13
|
||||
#define GPIO_SCLK 12
|
||||
#define GPIO_CS 10
|
||||
|
||||
#define SLAVE_HOST SPI2_HOST
|
||||
#define DMA_CHAN SPI_DMA_CH_AUTO
|
||||
#define QUEUE_SIZE 4
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Helper Macros for Master-Slave synchronization, each setting is 4-byte-width
|
||||
*
|
||||
* The address and value should be negotiated with Master beforehand
|
||||
*/
|
||||
//----------------------General Settings---------------------//
|
||||
//Indicate Slave General Settings are ready
|
||||
#define SLAVE_READY_FLAG_REG 0
|
||||
#define SLAVE_READY_FLAG 0xEE
|
||||
//Value in these 4 registers (Byte 4, 5, 6, 7) indicates the MAX Slave TX buffer length
|
||||
#define SLAVE_MAX_TX_BUF_LEN_REG 4
|
||||
//Value in these 4 registers indicates the MAX Slave RX buffer length
|
||||
#define SLAVE_MAX_RX_BUF_LEN_REG 8
|
||||
|
||||
//----------------------Updating Info------------------------//
|
||||
//Value in these 4 registers indicates size of the TX buffer that Slave has loaded to the DMA
|
||||
#define SLAVE_TX_READY_BUF_SIZE_REG 12
|
||||
//Value in these 4 registers indicates number of the RX buffer that Slave has loaded to the DMA
|
||||
#define SLAVE_RX_READY_BUF_NUM_REG 16
|
||||
|
||||
|
||||
static const char TAG[] = "SEG_SLAVE";
|
||||
|
||||
/* Used for Master-Slave synchronization */
|
||||
static uint32_t s_tx_ready_buf_size; //See ``cb_set_tx_ready_buf_size()``
|
||||
static uint32_t s_rx_ready_buf_num; //See ``cb_set_rx_ready_buf_num()``
|
||||
|
||||
static uint32_t s_tx_data_id;
|
||||
|
||||
|
||||
//-------------------------------Function used for Master-Slave Synchronization---------------------------//
|
||||
/**
|
||||
* NOTE: Only if all the counters are in same size (here uint32_t), the calculation is safe (when the shared register overflows).
|
||||
* NOTE: If Slave resets the counters, Master needs to reset its counter accordingly.
|
||||
* NOTE: You can also implement your own synchronization method, BUT DO MIND the parallelled issue.
|
||||
*/
|
||||
|
||||
/**
|
||||
* To write the size of the TX buffer that is loaded to the hardware (DMA)
|
||||
*
|
||||
* This callback function will be called when each TX buffer is loaded to the DMA.
|
||||
* - For Slave, ``s_tx_ready_buf_size`` is a counter of the loaded buffer size and will be written to the pre-negotiated shared register.
|
||||
* - For Master, it also needs a counter for the size of buffers that have been received.
|
||||
* - The difference between these 2 counters will be the number that Master can read, without concern of reading meaningless data.
|
||||
*
|
||||
* There are 3 conditions:
|
||||
* 1. Master reads same length as Slave loaded size.
|
||||
* 2. If Master reads longer than the Slave loaded size, the extra bits that Master reads from the bus would be meaningless.
|
||||
* 3. If Master reads shorter than the Slave loaded size, Master should still increase its counter by the Slave loaded size (here ``event->trans->len``),
|
||||
* because the extra bits that Slave sends to the bus would be missed by the Master.
|
||||
*/
|
||||
static bool cb_set_tx_ready_buf_size(void *arg, spi_slave_hd_event_t *event, BaseType_t *awoken)
|
||||
{
|
||||
s_tx_ready_buf_size += event->trans->len;
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, SLAVE_TX_READY_BUF_SIZE_REG, (uint8_t *)&s_tx_ready_buf_size, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* To write the number of the RX buffers that are loaded to the hardware (DMA)
|
||||
*
|
||||
* This callback function will be called when each RX buffer is loaded to the DMA.
|
||||
* - For Slave, ``s_rx_ready_buf_num`` is a counter for the loaded buffer and will be written to the pre-negotiated shared register
|
||||
* - For Master, it also needs a counter for the number of buffers that have been sent out.
|
||||
* - The difference between these 2 counters will be the number that Master can send.
|
||||
*/
|
||||
static bool cb_set_rx_ready_buf_num(void *arg, spi_slave_hd_event_t *event, BaseType_t *awoken)
|
||||
{
|
||||
s_rx_ready_buf_num ++;
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, SLAVE_RX_READY_BUF_NUM_REG, (uint8_t *)&s_rx_ready_buf_num, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void get_spi_bus_default_config(spi_bus_config_t *bus_cfg)
|
||||
{
|
||||
memset(bus_cfg, 0x0, sizeof(spi_bus_config_t));
|
||||
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 = 14000;
|
||||
bus_cfg->flags = 0;
|
||||
bus_cfg->intr_flags = 0;
|
||||
}
|
||||
|
||||
static void get_spi_slot_default_config(spi_slave_hd_slot_config_t *slave_hd_cfg)
|
||||
{
|
||||
memset(slave_hd_cfg, 0x0, sizeof(spi_slave_hd_slot_config_t));
|
||||
slave_hd_cfg->spics_io_num = GPIO_CS;
|
||||
slave_hd_cfg->flags = 0;
|
||||
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 = DMA_CHAN;
|
||||
slave_hd_cfg->cb_config = (spi_slave_hd_callback_config_t) {
|
||||
.cb_send_dma_ready = cb_set_tx_ready_buf_size,
|
||||
.cb_recv_dma_ready = cb_set_rx_ready_buf_num
|
||||
};
|
||||
}
|
||||
|
||||
static void init_slave_hd(void)
|
||||
{
|
||||
spi_bus_config_t bus_cfg = {};
|
||||
get_spi_bus_default_config(&bus_cfg);
|
||||
|
||||
spi_slave_hd_slot_config_t slave_hd_cfg = {};
|
||||
get_spi_slot_default_config(&slave_hd_cfg);
|
||||
|
||||
ESP_ERROR_CHECK(spi_slave_hd_init(SLAVE_HOST, &bus_cfg, &slave_hd_cfg));
|
||||
}
|
||||
|
||||
//Example code for data to send. Use your own code for TX data here.
|
||||
static bool get_tx_data(uint8_t *data, uint32_t max_len, uint32_t *out_len)
|
||||
{
|
||||
uint32_t min_len = 0;
|
||||
*out_len = (rand() % (max_len - min_len + 1)) + min_len;
|
||||
if (*out_len < (max_len - min_len) / 2) {
|
||||
*out_len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf((char *)data, *out_len, "Transaction No.%d from slave, length: %d", s_tx_data_id, *out_len);
|
||||
s_tx_data_id++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void sender(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t send_buf_size = *(uint32_t *)arg; //Tx buffer max size
|
||||
uint8_t *send_buf[QUEUE_SIZE]; //TX buffer
|
||||
spi_slave_hd_data_t slave_trans[QUEUE_SIZE]; //Transaction descriptor
|
||||
spi_slave_hd_data_t *ret_trans; //Pointer to the descriptor of return result
|
||||
|
||||
for (int i = 0; i < QUEUE_SIZE; i++) {
|
||||
// slave_trans
|
||||
send_buf[i] = heap_caps_calloc(1, send_buf_size, MALLOC_CAP_DMA);
|
||||
if (!send_buf[i]) {
|
||||
ESP_LOGE(TAG, "No enough memory!");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These 2 counters are used to maintain the driver internal queue.
|
||||
* internal queue item number = queue_sent_cnt - queue_recv_cnt
|
||||
*/
|
||||
uint32_t queue_sent_cnt = 0;
|
||||
uint32_t queue_recv_cnt = 0;
|
||||
|
||||
uint32_t descriptor_id = 0;
|
||||
uint32_t ready_data_size = 0;
|
||||
bool data_ready = false;
|
||||
|
||||
while (1) {
|
||||
/**
|
||||
* Start TX transaction
|
||||
* If the internal queue number is equal to the QUEUE_SIZE you set when initialisation,
|
||||
* - you should not call ``spi_slave_hd_queue_trans``.
|
||||
* - you should call ``spi_slave_hd_get_trans_res`` instead.
|
||||
*/
|
||||
if (queue_sent_cnt - queue_recv_cnt < QUEUE_SIZE) {
|
||||
/**
|
||||
* Here we simply get some random data.
|
||||
* In your own code, you could prepare some buffers, and create a FreeRTOS task to generate/get data, and give a
|
||||
* Semaphore to unblock this `sender()` Task.
|
||||
*/
|
||||
data_ready = get_tx_data(send_buf[descriptor_id], send_buf_size, &ready_data_size);
|
||||
if (data_ready) {
|
||||
slave_trans[descriptor_id].data = send_buf[descriptor_id];
|
||||
slave_trans[descriptor_id].len = ready_data_size;
|
||||
//Due to the `queue_sent_cnt` and `queue_recv_cnt` logic above, we are sure there is space to send data, this will return ESP_OK immediately
|
||||
ESP_ERROR_CHECK(spi_slave_hd_queue_trans(SLAVE_HOST, SPI_SLAVE_CHAN_TX, &slave_trans[descriptor_id], portMAX_DELAY));
|
||||
descriptor_id = (descriptor_id + 1) % QUEUE_SIZE; //descriptor_id will be: 0, 1, 2, ..., QUEUE_SIZE, 0, 1, ....
|
||||
queue_sent_cnt++;
|
||||
}
|
||||
}
|
||||
//Normally the task would be blocked when there is no data to send, here we use a delay to yield to other tasks
|
||||
vTaskDelay(1);
|
||||
|
||||
//Recycle the transaction
|
||||
while (1) {
|
||||
/**
|
||||
* Get the TX transaction result
|
||||
*
|
||||
* The ``ret_trans`` will exactly point to the transaction descriptor passed to the driver before (here ``slave_trans``).
|
||||
* For TX, the ``ret_trans->trans_len`` is meaningless. But you do need this API to maintain the internal queue.
|
||||
*/
|
||||
err = spi_slave_hd_get_trans_res(SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ret_trans, 0);
|
||||
if (err != ESP_OK) {
|
||||
assert(err == ESP_ERR_TIMEOUT);
|
||||
//Recycle all the used descriptors until there is no in-the-flight descriptor
|
||||
break;
|
||||
}
|
||||
queue_recv_cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void receiver(void *arg)
|
||||
{
|
||||
spi_slave_hd_data_t *ret_trans;
|
||||
uint32_t recv_buf_size = *(uint32_t *)arg;
|
||||
uint8_t *recv_buf[QUEUE_SIZE];
|
||||
spi_slave_hd_data_t slave_trans[QUEUE_SIZE];
|
||||
for (int i = 0; i < QUEUE_SIZE; i++) {
|
||||
recv_buf[i] = heap_caps_calloc(1, recv_buf_size, MALLOC_CAP_DMA);
|
||||
if (!recv_buf[i]) {
|
||||
ESP_LOGE(TAG, "No enough memory!");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - For SPI Slave, you can use this way (making use of the internal queue) to pre-load transactions to driver. Thus if
|
||||
* Master's speed is slower than Slave, Slave won't need to wait until Master finishes.
|
||||
*/
|
||||
uint32_t descriptor_id = 0;
|
||||
for (int i = 0; i < QUEUE_SIZE; i++) {
|
||||
slave_trans[descriptor_id].data = recv_buf[descriptor_id];
|
||||
slave_trans[descriptor_id].len = recv_buf_size;
|
||||
ESP_ERROR_CHECK(spi_slave_hd_queue_trans(SLAVE_HOST, SPI_SLAVE_CHAN_RX, &slave_trans[descriptor_id], portMAX_DELAY));
|
||||
descriptor_id = (descriptor_id + 1) % QUEUE_SIZE; //descriptor_id will be: 0, 1, 2, ..., QUEUE_SIZE, 0, 1, ....
|
||||
}
|
||||
|
||||
while (1) {
|
||||
/**
|
||||
* Get the RX transaction result
|
||||
*
|
||||
* The actual used (by master) buffer size could be derived by ``ret_trans->trans_len``:
|
||||
* For example, when Master sends 4bytes, whereas slave prepares 512 bytes buffer. When master finish sending its
|
||||
* 4 bytes, it will send CMD7, which will force stopping the transaction. Slave will get the actual received length
|
||||
* from `ret_trans->trans_len`` member (here 4 bytes). The ``ret_trans`` will exactly point to the transaction descriptor
|
||||
* passed to the driver before (here ``slave_trans``). If the master sends longer than slave recv buffer,
|
||||
* slave will lose the extra bits.
|
||||
*/
|
||||
ESP_ERROR_CHECK(spi_slave_hd_get_trans_res(SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ret_trans, portMAX_DELAY));
|
||||
//Process the received data in your own code. Here we just print it out.
|
||||
printf("%d bytes are received: \n%s\n", ret_trans->trans_len, ret_trans->data);
|
||||
memset(ret_trans->data, 0x0, recv_buf_size);
|
||||
|
||||
/**
|
||||
* Prepared data for new transaction
|
||||
*/
|
||||
slave_trans[descriptor_id].data = recv_buf[descriptor_id];
|
||||
slave_trans[descriptor_id].len = recv_buf_size;
|
||||
//Start new transaction
|
||||
ESP_ERROR_CHECK(spi_slave_hd_queue_trans(SLAVE_HOST, SPI_SLAVE_CHAN_RX, &slave_trans[descriptor_id], portMAX_DELAY));
|
||||
descriptor_id = (descriptor_id + 1) % QUEUE_SIZE; //descriptor_id will be: 0, 1, 2, ..., QUEUE_SIZE, 0, 1, ....
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
init_slave_hd();
|
||||
|
||||
//Reset the shared register to 0
|
||||
uint8_t init_value[SOC_SPI_MAXIMUM_BUFFER_SIZE] = {0x0};
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, 0, init_value, SOC_SPI_MAXIMUM_BUFFER_SIZE);
|
||||
|
||||
uint32_t send_buf_size = 5000;
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, SLAVE_MAX_TX_BUF_LEN_REG, (uint8_t *)&send_buf_size, 4);
|
||||
|
||||
uint32_t recv_buf_size = 120;
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, SLAVE_MAX_RX_BUF_LEN_REG, (uint8_t *)&recv_buf_size, 4);
|
||||
|
||||
uint32_t slave_ready_flag = SLAVE_READY_FLAG;
|
||||
spi_slave_hd_write_buffer(SLAVE_HOST, SLAVE_READY_FLAG_REG, (uint8_t *)&slave_ready_flag, 4);
|
||||
|
||||
xTaskCreate(sender, "sendTask", 4096, &send_buf_size, 1, NULL);
|
||||
xTaskCreate(receiver, "recvTask", 4096, &recv_buf_size, 1, NULL);
|
||||
}
|
Reference in New Issue
Block a user