Merge branch 'feat/spi_slave_timing' into 'master'

feat(spi_slave): fine tune the timing of SPI slave

See merge request idf/esp-idf!3925
This commit is contained in:
Ivan Grokhotkov 2019-01-29 14:03:55 +08:00
commit 4725364e04
20 changed files with 1795 additions and 567 deletions

View file

@ -1629,6 +1629,31 @@ UT_012_05:
- UT_T1_1
- 8Mpsram
UT_013_01:
<<: *unit_test_template
tags:
- ESP32_IDF
- Example_SPI_Multi_device
UT_013_02:
<<: *unit_test_template
tags:
- ESP32_IDF
- Example_SPI_Multi_device
UT_013_03:
<<: *unit_test_template
tags:
- ESP32_IDF
- Example_SPI_Multi_device
UT_013_04:
<<: *unit_test_template
tags:
- ESP32_IDF
- Example_SPI_Multi_device
- psram
UT_014_01:
<<: *unit_test_template
tags:

View file

@ -380,11 +380,14 @@ esp_err_t spi_bus_free(spi_host_device_t host)
void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int* dummy_o, int* cycles_remain_o)
{
const int apbclk_kHz = APB_CLK_FREQ/1000;
//calculate how many apb clocks a period has
const int apbclk_n = APB_CLK_FREQ/eff_clk;
const int gpio_delay_ns = gpio_is_used ? 25 : 0;
//calculate how many apb clocks a period has, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
//calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000;
if (apb_period_n < 0) apb_period_n = 0;
int dummy_required = apb_period_n/apbclk_n;
int miso_delay = 0;
@ -406,8 +409,10 @@ int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns)
const int apbclk_kHz = APB_CLK_FREQ/1000;
const int gpio_delay_ns = gpio_is_used ? 25 : 0;
//calculate how many apb clocks a period has, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
//calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000;
if (apb_period_n < 0) apb_period_n = 0;
return APB_CLK_FREQ/(apb_period_n+1);
}

View file

@ -202,26 +202,62 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
spihost[host]->hw->slave.sync_reset = 1;
spihost[host]->hw->slave.sync_reset = 0;
bool nodelay = true;
spihost[host]->hw->ctrl.rd_bit_order = (slave_config->flags & SPI_SLAVE_RXBIT_LSBFIRST) ? 1 : 0;
spihost[host]->hw->ctrl.wr_bit_order = (slave_config->flags & SPI_SLAVE_TXBIT_LSBFIRST) ? 1 : 0;
if (slave_config->mode == 0) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
} else if (slave_config->mode == 1) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
} else if (slave_config->mode == 2) {
const int mode = slave_config->mode;
if (mode == 0) {
//The timing needs to be fixed to meet the requirements of DMA
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
} else if (slave_config->mode == 3) {
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 2;
spihost[host]->hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 1) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
spihost[host]->hw->ctrl2.miso_delay_mode = 2;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 0;
} else if (mode == 2) {
//The timing needs to be fixed to meet the requirements of DMA
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 1;
spihost[host]->hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 3) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = 1;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 0;
}
/* Silicon issues exists in mode 0 and 2 with DMA, change clock phase to
* avoid dma issue. This will cause slave output to appear at most half a
* spi clock before
*/
if (dma_chan != 0) {
if (mode == 0) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 2;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 3;
} else if (mode == 2) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 2;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 3;
}
}
//Reset DMA

View file

@ -1,5 +1,5 @@
set(COMPONENT_SRCDIRS ".")
set(COMPONENT_ADD_INCLUDEDIRS ".")
set(COMPONENT_SRCDIRS ". param_test")
set(COMPONENT_ADD_INCLUDEDIRS "include param_test/include")
set(COMPONENT_REQUIRES unity test_utils driver nvs_flash)

View file

@ -2,4 +2,7 @@
#Component Makefile
#
COMPONENT_SRCDIRS += param_test
COMPONENT_PRIV_INCLUDEDIRS += param_test/include
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View file

@ -0,0 +1,187 @@
#ifndef _TEST_COMMON_SPI_H_
#define _TEST_COMMON_SPI_H_
#include <esp_types.h>
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "freertos/task.h"
#include "unity.h"
#include "test_utils.h"
#include <string.h>
#include "param_test.h"
// All the tests using the header should use this definition as much as possible,
// so that the working host can be changed easily in the future.
#define TEST_SPI_HOST HSPI_HOST
#define TEST_SLAVE_HOST VSPI_HOST
#define FUNC_SPI 1
#define FUNC_GPIO 2
//Delay information
#define ESP_SPI_SLAVE_TV (12.5*3.5)
#define GPIO_DELAY (12.5*2)
#define WIRE_DELAY 12.5
#define TV_INT_CONNECT_GPIO (ESP_SPI_SLAVE_TV+GPIO_DELAY)
#define TV_INT_CONNECT (ESP_SPI_SLAVE_TV)
//when connecting to another board, the delay is usually increased by 12.5ns
#define TV_WITH_ESP_SLAVE_GPIO (TV_INT_CONNECT_GPIO+WIRE_DELAY)
#define TV_WITH_ESP_SLAVE (TV_INT_CONNECT+WIRE_DELAY)
//currently ESP32 slave only supports up to 20MHz, but 40MHz on the same board
#define ESP_SPI_SLAVE_MAX_FREQ SPI_MASTER_FREQ_20M
#define ESP_SPI_SLAVE_MAX_FREQ_SYNC SPI_MASTER_FREQ_40M
#define MAX_TEST_SIZE 16 ///< in this test we run several transactions, this is the maximum trans that can be run
#define PSET_NAME_LEN 30 ///< length of each param set name
//test low frequency, high frequency until freq limit for worst case (both GPIO)
#define TEST_FREQ_DEFAULT(){\
1*1000*1000, \
SPI_MASTER_FREQ_8M , \
SPI_MASTER_FREQ_9M , \
SPI_MASTER_FREQ_10M, \
SPI_MASTER_FREQ_11M, \
SPI_MASTER_FREQ_13M, \
SPI_MASTER_FREQ_16M, \
SPI_MASTER_FREQ_20M, \
SPI_MASTER_FREQ_26M, \
SPI_MASTER_FREQ_40M, \
SPI_MASTER_FREQ_80M, \
0,\
}
#define PIN_NUM_MISO HSPI_IOMUX_PIN_NUM_MISO
#define PIN_NUM_MOSI HSPI_IOMUX_PIN_NUM_MOSI
#define PIN_NUM_CLK HSPI_IOMUX_PIN_NUM_CLK
#define PIN_NUM_CS HSPI_IOMUX_PIN_NUM_CS
//default bus config for tests
#define SPI_BUS_TEST_DEFAULT_CONFIG() {\
.miso_io_num=PIN_NUM_MISO, \
.mosi_io_num=PIN_NUM_MOSI,\
.sclk_io_num=PIN_NUM_CLK,\
.quadwp_io_num=-1,\
.quadhd_io_num=-1\
}
//default device config for master devices
#define SPI_DEVICE_TEST_DEFAULT_CONFIG() {\
.clock_speed_hz=10*1000*1000,\
.mode=0,\
.spics_io_num=PIN_NUM_CS,\
.queue_size=16,\
.pre_cb=NULL, \
.cs_ena_pretrans = 0,\
.cs_ena_posttrans = 0,\
.input_delay_ns = 62.5,\
}
//default device config for slave devices
#define SPI_SLAVE_TEST_DEFAULT_CONFIG() {\
.mode=0,\
.spics_io_num=PIN_NUM_CS,\
.queue_size=3,\
.flags=0,\
}
typedef enum {
FULL_DUPLEX = 0,
HALF_DUPLEX_MISO = 1,
HALF_DUPLEX_MOSI = 2,
} spi_dup_t;
/*-------- slave task related stuff -----------*/
typedef struct {
uint32_t len;
uint8_t* tx_start;
uint8_t data[1];
} slave_rxdata_t;
typedef struct {
uint32_t len;
const uint8_t *start;
} slave_txdata_t;
typedef struct {
spi_host_device_t spi;
RingbufHandle_t data_received;
QueueHandle_t data_to_send;
} spi_slave_task_context_t;
// test data for master and slave
extern uint8_t spitest_master_send[];
extern uint8_t spitest_slave_send[];
//tags for master and slave app
extern const char MASTER_TAG[];
extern const char SLAVE_TAG[];
//parameter set definition
typedef struct {
const char pset_name[PSET_NAME_LEN];
/*The test work till the frequency below,
*set the frequency to higher and remove checks in the driver to know how fast the system can run.
*/
const int *freq_list; // list of tested frequency, terminated by 0
int freq_limit; //freq larger (not equal) than this will be ignored
spi_dup_t dup;
int mode;
bool length_aligned;
int test_size;
int master_limit; // the master disable dummy bits and discard readings over this freq
bool master_iomux;
int master_dma_chan;
bool slave_iomux;
int slave_dma_chan;
int slave_tv_ns;
bool slave_unaligned_addr;
} spitest_param_set_t;
//context definition for the parameterized test
typedef struct {
uint8_t master_rxbuf[480];
spi_transaction_t master_trans[MAX_TEST_SIZE];
TaskHandle_t handle_slave;
spi_slave_task_context_t slave_context;
slave_txdata_t slave_trans[MAX_TEST_SIZE];
} spitest_context_t;
// fill default value of spitest_param_set_t
void spitest_def_param(void* arg);
// functions for slave task
esp_err_t init_slave_context(spi_slave_task_context_t *context);
void deinit_slave_context(spi_slave_task_context_t *context);
void spitest_slave_task(void* arg);
//called by slave, pull-up all pins used by slave
void slave_pull_up(const spi_bus_config_t* cfg, int spics_io_num);
// to access data of pre-defined transactions.
void spitest_init_transactions(const spitest_param_set_t *cfg, spitest_context_t* context);
// print data from a transaction
void spitest_master_print_data(spi_transaction_t *t, int rxlength);
void spitest_slave_print_data(slave_rxdata_t *t, bool print_rxdata);
// Check whether master and slave data match
esp_err_t spitest_check_data(int len, spi_transaction_t *master_t, slave_rxdata_t *slave_t, bool check_master_data, bool check_slave_len, bool check_slave_data);
static inline int get_trans_len(spi_dup_t dup, spi_transaction_t *master_t)
{
if (dup!=HALF_DUPLEX_MISO) {
return master_t->length;
} else {
return master_t->rxlength;
}
}
//remove device from bus and free the bus
void master_free_device_bus(spi_device_handle_t spi);
//use this function to fix the output source when assign multiple funcitons to a same pin
void spitest_gpio_output_sel(uint32_t gpio_num, int func, uint32_t signal_idx);
#endif //_TEST_COMMON_SPI_H_

View file

@ -0,0 +1,164 @@
/*
* Parameterized Test Framework
*
* Peripherals like SPI has several parameters like: freq, mode, DMA, etc.
* This framework helps to test different parameter sets with the same program.
*
* Each "parameter set" is a set with different parameters (freq, mode, etc.).
* A parameter group is a group of several parameter sets. Each unit test performs
* tests among the sets in a group.
*
* The test will be execute in the following sequence:
* 1. ``pre_test``: initialize the context
* 2. take a set out of the parameter group
* 3. ``def_param``: fill in default value for the parameter set if not set
* 4. ``loop``: execute test program for the set in the initialized context
* 5. loop executing 2-4 until the last set
* 6. ``post_test``: free the resources used.
*
* Usage example:
*
* 1. Define your own parameter set type:
* typedef struct {
* const char pset_name[PSET_NAME_LEN];
* //The test work till the frequency below,
* //set the frequency to higher and remove checks in the driver to know how fast the system can run.
* const int *freq_list; // list of tested frequency, terminated by 0
* int freq_limit; //freq larger (not equal) than this will be ignored
* spi_dup_t dup;
* int mode;
* bool length_aligned;
* int test_size;
*
* int master_limit; // the master disable dummy bits and discard readings over this freq
* bool master_iomux;
* int master_dma_chan;
*
* bool slave_iomux;
* int slave_dma_chan;
* int slave_tv_ns;
* bool slave_unaligned_addr;
* } spitest_param_set_t;
*
* 2. Define a parameter set:
* spitest_param_set_t mode_pgroup[] = {
* //non-DMA tests
* { .pset_name = "mode 0, no DMA",
* .freq_list = test_freq_mode,
* .master_limit = FREQ_LIMIT_MODE,
* .dup = FULL_DUPLEX,
* .master_iomux= true,
* .slave_iomux = true,
* .slave_tv_ns = TV_WITH_ESP_SLAVE,
* .mode = 0,
* },
* { .pset_name = "mode 1, no DMA",
* .freq_list = test_freq_mode,
* .master_limit = FREQ_LIMIT_MODE,
* .dup = FULL_DUPLEX,
* .master_iomux= true,
* .slave_iomux = true,
* .slave_tv_ns = TV_WITH_ESP_SLAVE,
* .mode = 1,
* },
* // other configurations...
* };
*
* 3. Define your test functions, and wrap them in the ``param_test_func_t``:
* static const param_test_func_t master_test_func = {
* .pre_test = test_master_init,
* .post_test = test_master_deinit,
* .loop = test_master_loop,
* .def_param = spitest_def_param,
* };
*
* 4. Declare the group by PARAM_GROUP_DECLARE right after the param group:
* PARAM_GROUP_DECLARE(MODE, mode_pgroup)
*
* 5. Declare the test function by TEST_LOCAL (for single board test), or TEST_MASTER_SLAVE(for multiboard test)
* TEST_MASTER_SLAVE(MODE, mode_pgroup, "[spi][timeout=120]", &master_test_func, &slave_test_func)
*
* or
* TEST_LOCAL(TIMING, timing_pgroup, "[spi][timeout=120]", &local_test_func)
*
* NOTE: suggest to define your own macro to wrap 4 and 5 if your tag and test functions are the same. E.g.:
* #define TEST_SPI_MASTER_SLAVE(name, pgroup) (backslash)
* PARAM_GROUP_DECLARE(name, pgroup) (backslash)
* TEST_MASTER_SLAVE(name, pgroup, "[spi][timeout=120]", &master_test_func, &slave_test_func)
*
* Then declare tests conveniently by:
* TEST_SPI_MASTER_SLAVE(TIMING, timing_pgroup)
* TEST_SPI_MASTER_SLAVE(MODE, mode_pgroup)
*
*/
#define PGROUP_NAME_LEN 20 ///< name length of parameter group
#define PGROUP_NAME(name) PGROUP_##name ///< param group name
#define PTEST_MASTER_NAME(name) PTEST_MASTER_##name ///< test function name of master
#define PTEST_SLAVE_NAME(name) PTEST_SLAVE_##name ///< test function name of slave
/// Test set structure holding name, param set array pointer, item size and param set num.
typedef struct {
char name[PGROUP_NAME_LEN]; ///< Name of param group to print
void *param_group; ///< Start of the param group array
int pset_size; ///< Size of each param set
int pset_num; ///< Total number of param sets
} param_group_t;
/// Test functions for the frameowrk
typedef struct {
void (*pre_test)(void** contxt); ///< Initialization function called before tests begin. Initial your context here
void (*post_test)(void* context); ///< Deinit function called after all tests are done.
void (*def_param)(void* inout_pset); ///< Function to fill each pset structure before executed, left NULL if not used.
void (*loop)(const void* pset, void* context); ///< Function execute each param set
} ptest_func_t;
/**
* Test framework to execute init, loop and deinit.
*
* @param param_group Parameter group holder to test in turns.
* @param test_func Function set to execute.
*/
void test_serializer(const param_group_t *param_group, const ptest_func_t* test_func);
#define PARAM_GROUP_DECLARE_TYPE(group_name, pset_type, pgroup) \
static const param_group_t PGROUP_NAME(pgroup) = { \
.name = #group_name, \
.param_group = (void*)&pgroup, \
.pset_size = sizeof(pset_type), \
.pset_num = sizeof(pgroup)/sizeof(pset_type), \
};
/**
* Declare parameter group
*
* @param group_name Parameter group name to print in the beginning of the test
* @param param_group Parameter group structure, should be already defined above, and the size and type is defined.
*/
#define PARAM_GROUP_DECLARE(group_name, param_group) \
PARAM_GROUP_DECLARE_TYPE(group_name, typeof(param_group[0]), param_group)
/**
* Test parameter group on one board.
*
* @param name Test name to be printed in the menu.
* @param param_group Parameter group to be tested.
* @param tag Tag for environment, etc. e.g. [spi][timeout=120]
* @param test_func ``ptest_func_t`` to be executed.
*/
#define TEST_LOCAL(name, param_group, tag, test_func) \
TEST_CASE("local test: "#name, tag) { test_serializer(&PGROUP_NAME(param_group), test_func); }
/**
* Test parameter group for master-slave framework
*
* @param name Test name to be printed in the menu.
* @param param_group Parameter group to be tested.
* @param tag Tag for environment, etc. e.g. [spi][timeout=120]
* @param master_func ``ptest_func_t`` to be executed by master.
* @param slave_func ``ptest_func_t`` to be executed by slave.
*/
#define TEST_MASTER_SLAVE(name, param_group, tag, master_func, slave_func) \
static void PTEST_MASTER_NAME(name) () { test_serializer(&PGROUP_NAME(param_group), master_func); } \
static void PTEST_SLAVE_NAME(name) () { test_serializer(&PGROUP_NAME(param_group), slave_func); } \
TEST_CASE_MULTIPLE_DEVICES("master slave test: "#name, tag, PTEST_MASTER_NAME(name), PTEST_SLAVE_NAME(name))

View file

@ -0,0 +1,21 @@
#include "param_test.h"
#include "esp_log.h"
void test_serializer(const param_group_t *param_group, const ptest_func_t* test_func)
{
ESP_LOGI("test", "run test: %s", param_group->name);
//in this test case, we want to make two devices as similar as possible, so use the same context
void *context = NULL;
test_func->pre_test(&context);
void *pset = param_group->param_group;
for (int i = param_group->pset_num; i >0; i--) {
if (test_func->def_param) test_func->def_param(pset);
test_func->loop(pset, context);
pset+=param_group->pset_size;
}
test_func->post_test(context);
free(context);
context = NULL;
}

View file

@ -0,0 +1,192 @@
#include "test/test_common_spi.h"
#include "driver/spi_slave.h"
#include "esp_log.h"
int test_freq_default[]=TEST_FREQ_DEFAULT();
const char MASTER_TAG[] = "test_master";
const char SLAVE_TAG[] = "test_slave";
DRAM_ATTR uint8_t spitest_master_send[] = {
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
};
DRAM_ATTR uint8_t spitest_slave_send[] = {
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
};
void spitest_def_param(void* arg)
{
spitest_param_set_t *param_set=(spitest_param_set_t*)arg;
param_set->test_size = 8;
if (param_set->freq_list==NULL) param_set->freq_list = test_freq_default;
}
/**********************************************************************************
* functions for slave task
*********************************************************************************/
esp_err_t init_slave_context(spi_slave_task_context_t *context)
{
context->data_to_send = xQueueCreate( 16, sizeof( slave_txdata_t ));
if ( context->data_to_send == NULL ) {
return ESP_ERR_NO_MEM;
}
context->data_received = xRingbufferCreate( 1024, RINGBUF_TYPE_NOSPLIT );
if ( context->data_received == NULL ) {
return ESP_ERR_NO_MEM;
}
context->spi=VSPI_HOST;
return ESP_OK;
}
void deinit_slave_context(spi_slave_task_context_t *context)
{
TEST_ASSERT( context->data_to_send != NULL );
vQueueDelete( context->data_to_send );
context->data_to_send = NULL;
TEST_ASSERT( context->data_received != NULL );
vRingbufferDelete( context->data_received );
context->data_received = NULL;
}
/* The task requires a queue and a ringbuf, which should be initialized before task starts.
Send ``slave_txdata_t`` to the queue to make the task send data;
the task returns data got to the ringbuf, which should have sufficient size.
*/
void spitest_slave_task(void* arg)
{
spi_slave_task_context_t* context = (spi_slave_task_context_t*) arg;
QueueHandle_t queue = context->data_to_send;
RingbufHandle_t ringbuf = context->data_received;
uint8_t recvbuf[320+8];
slave_txdata_t txdata;
ESP_LOGI( SLAVE_TAG, "slave up" );
//never quit, but blocked by the queue, waiting to be killed, when no more send from main task.
while( 1 ) {
BaseType_t ret = xQueueReceive( queue, &txdata, portMAX_DELAY );
assert(ret);
spi_slave_transaction_t t = {};
t.length = txdata.len;
t.tx_buffer = txdata.start;
t.rx_buffer = recvbuf+8;
//loop until trans_len != 0 to skip glitches
do {
TEST_ESP_OK( spi_slave_transmit( context->spi, &t, portMAX_DELAY ) );
} while ( t.trans_len <= 2 );
memcpy(recvbuf, &t.trans_len, sizeof(uint32_t));
*(uint8_t**)(recvbuf+4) = (uint8_t*)txdata.start;
ESP_LOGI( SLAVE_TAG, "received: %d", t.trans_len );
xRingbufferSend( ringbuf, recvbuf, 8+(t.trans_len+7)/8, portMAX_DELAY );
}
}
void slave_pull_up(const spi_bus_config_t* cfg, int spics_io_num)
{
gpio_set_pull_mode(cfg->mosi_io_num, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(cfg->sclk_io_num, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(spics_io_num, GPIO_PULLUP_ONLY);
}
/**********************************************************************************
* functions for slave task
*********************************************************************************/
static int test_len[] = {1, 3, 5, 7, 9, 11, 33, 64};
void spitest_init_transactions(const spitest_param_set_t *cfg, spitest_context_t* context)
{
spi_transaction_t* trans = context->master_trans;
uint8_t *rx_buf_ptr = context->master_rxbuf;
const spi_dup_t dup = cfg->dup;
for (int i = 0; i < cfg->test_size; i++) {
const void* tx_buffer = spitest_master_send + i%8;
int length = 8*test_len[i];
if (cfg->length_aligned) length = (length+31)&(~31);
if (dup == HALF_DUPLEX_MISO) {
trans[i] = (spi_transaction_t) {
.rx_buffer = rx_buf_ptr,
.rxlength = length,
};
} else if (dup == HALF_DUPLEX_MOSI) {
trans[i] = (spi_transaction_t) {
.tx_buffer = tx_buffer,
.length = length,
};
} else {
trans[i] = (spi_transaction_t) {
.tx_buffer = tx_buffer,
.length = length,
.rx_buffer = rx_buf_ptr,
};
}
rx_buf_ptr = (uint8_t*)( (uint32_t)(rx_buf_ptr + (length+7)/8 + 3) & (~3));
const void* slave_tx = spitest_slave_send + (cfg->slave_unaligned_addr? i%3: (i%3)*4);
//prepare slave tx data
context->slave_trans[i] = (slave_txdata_t) {
.start = slave_tx,
.len = 512,
};
if (cfg->slave_dma_chan != 0) context->slave_trans[i].len = 1024;
}
}
void spitest_master_print_data(spi_transaction_t *t, int rxlength)
{
if (t->tx_buffer) ESP_LOG_BUFFER_HEX( "master tx", t->tx_buffer, t->length/8 );
if (t->rx_buffer) ESP_LOG_BUFFER_HEX( "master rx", t->rx_buffer, rxlength/8 );
}
void spitest_slave_print_data(slave_rxdata_t *t, bool print_rxdata)
{
int rcv_len = (t->len+7)/8;
ESP_LOGI(SLAVE_TAG, "trans_len: %d", t->len);
ESP_LOG_BUFFER_HEX("slave tx", t->tx_start, rcv_len);
if (print_rxdata) ESP_LOG_BUFFER_HEX("slave rx", t->data, rcv_len);
}
esp_err_t spitest_check_data(int len, spi_transaction_t *master_t, slave_rxdata_t *slave_t, bool check_master_data, bool check_slave_len, bool check_slave_data)
{
//currently the rcv_len can be in range of [t->length-1, t->length+3]
if (check_slave_len) {
uint32_t rcv_len = slave_t->len;
TEST_ASSERT(rcv_len >= len - 1 && rcv_len <= len + 4);
}
//if (dup!=HALF_DUPLEX_MOSI) {
if (check_master_data) {
TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_t->tx_start, master_t->rx_buffer, (len + 7) / 8);
}
//if (dup!=HALF_DUPLEX_MISO) {
if (check_slave_data) {
TEST_ASSERT_EQUAL_HEX8_ARRAY(master_t->tx_buffer, slave_t->data, (len + 7) / 8);
}
return ESP_OK;
}
void master_free_device_bus(spi_device_handle_t spi)
{
TEST_ESP_OK( spi_bus_remove_device(spi) );
TEST_ESP_OK( spi_bus_free(TEST_SPI_HOST) );
}
void spitest_gpio_output_sel(uint32_t gpio_num, int func, uint32_t signal_idx)
{
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio_num], func);
GPIO.func_out_sel_cfg[gpio_num].func_sel=signal_idx;
}

View file

@ -20,41 +20,13 @@
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "soc/spi_periph.h"
#include "freertos/ringbuf.h"
#include "test_utils.h"
#include "test/test_common_spi.h"
#include "soc/gpio_periph.h"
#include "sdkconfig.h"
#include "test_utils.h"
const static char TAG[] = "test_spi";
#define SPI_BUS_TEST_DEFAULT_CONFIG() {\
.miso_io_num=PIN_NUM_MISO, \
.mosi_io_num=PIN_NUM_MOSI,\
.sclk_io_num=PIN_NUM_CLK,\
.quadwp_io_num=-1,\
.quadhd_io_num=-1\
}
#define SPI_DEVICE_TEST_DEFAULT_CONFIG() {\
.clock_speed_hz=10*1000*1000,\
.mode=0,\
.spics_io_num=PIN_NUM_CS,\
.queue_size=16,\
.pre_cb=NULL, \
.cs_ena_pretrans = 0,\
.cs_ena_posttrans = 0,\
.input_delay_ns = 62.5,\
}
#define FUNC_SPI 1
#define FUNC_GPIO 2
void gpio_output_sel(uint32_t gpio_num, int func, uint32_t signal_idx)
{
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio_num], func);
GPIO.func_out_sel_cfg[gpio_num].func_sel=signal_idx;
}
static void check_spi_pre_n_for(int clk, int pre, int n)
{
esp_err_t ret;
@ -145,7 +117,7 @@ static spi_device_handle_t setup_spi_bus(int clkspeed, bool dma) {
ret=spi_bus_add_device(HSPI_HOST, &devcfg, &handle);
TEST_ASSERT(ret==ESP_OK);
//connect MOSI to two devices breaks the output, fix it.
gpio_output_sel(26, FUNC_GPIO, HSPID_OUT_IDX);
spitest_gpio_output_sel(26, FUNC_GPIO, HSPID_OUT_IDX);
printf("Bus/dev inited.\n");
return handle;
}
@ -203,17 +175,6 @@ static int spi_test(spi_device_handle_t handle, int num_bytes) {
return success;
}
static void destroy_spi_bus(spi_device_handle_t handle) {
esp_err_t ret;
ret=spi_bus_remove_device(handle);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_free(HSPI_HOST);
TEST_ASSERT(ret==ESP_OK);
}
#define TEST_LEN 111
TEST_CASE("SPI Master test", "[spi]")
{
bool success = true;
@ -228,7 +189,7 @@ TEST_CASE("SPI Master test", "[spi]")
success &= spi_test(handle, 4096-1); //multiple descs, edge case 2
success &= spi_test(handle, 4096*3); //multiple descs
destroy_spi_bus(handle);
master_free_device_bus(handle);
printf("Testing bus at 80KHz, non-DMA\n");
handle=setup_spi_bus(80000, false);
@ -240,22 +201,21 @@ TEST_CASE("SPI Master test", "[spi]")
success &= spi_test(handle, 63); //small
success &= spi_test(handle, 64); //small, unaligned
destroy_spi_bus(handle);
master_free_device_bus(handle);
printf("Testing bus at 26MHz\n");
handle=setup_spi_bus(20000000, true);
success &= spi_test(handle, 128); //DMA, aligned
success &= spi_test(handle, 4096*3); //DMA, multiple descs
destroy_spi_bus(handle);
master_free_device_bus(handle);
printf("Testing bus at 900KHz\n");
handle=setup_spi_bus(9000000, true);
success &= spi_test(handle, 128); //DMA, aligned
success &= spi_test(handle, 4096*3); //DMA, multiple descs
destroy_spi_bus(handle);
master_free_device_bus(handle);
TEST_ASSERT(success);
}
@ -299,7 +259,7 @@ TEST_CASE("SPI Master test, interaction of multiple devs", "[spi]") {
ret=spi_bus_remove_device(handle2);
TEST_ASSERT(ret==ESP_OK);
destroy_spi_bus(handle1);
master_free_device_bus(handle1);
TEST_ASSERT(success);
}
@ -522,22 +482,6 @@ static const uint8_t data_drom[320+3] = {
0x70, 0x22, 0x7D, 0x0A, 0x6D, 0xD3, 0x77, 0x73, 0xD0, 0xF4, 0x06, 0xB2, 0x19, 0x8C, 0xFF, 0x58, 0xE4, 0xDB, 0xE9, 0xEC, 0x89, 0x6A, 0xF4, 0x0E, 0x67, 0x12, 0xEC, 0x11, 0xD2, 0x1F, 0x8D, 0xD7,
};
#if 1 //HSPI
#define PIN_NUM_MISO HSPI_IOMUX_PIN_NUM_MISO
#define PIN_NUM_MOSI HSPI_IOMUX_PIN_NUM_MOSI
#define PIN_NUM_CLK HSPI_IOMUX_PIN_NUM_CLK
#define PIN_NUM_CS HSPI_IOMUX_PIN_NUM_CS
#elif 1 //VSPI
#define PIN_NUM_MISO VSPI_IOMUX_PIN_NUM_MISO
#define PIN_NUM_MOSI VSPI_IOMUX_PIN_NUM_MOSI
#define PIN_NUM_CLK VSPI_IOMUX_PIN_NUM_CLK
#define PIN_NUM_CS VSPI_IOMUX_PIN_NUM_CS
#endif
#define PIN_NUM_DC 21
#define PIN_NUM_RST 18
#define PIN_NUM_BCKL 5
TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]")
{
#ifdef CONFIG_SPIRAM_SUPPORT
@ -581,7 +525,7 @@ TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]")
ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
TEST_ASSERT(ret==ESP_OK);
//connect MOSI to two devices breaks the output, fix it.
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
#define TEST_REGION_SIZE 5
static spi_transaction_t trans[TEST_REGION_SIZE];
@ -663,7 +607,7 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
TEST_ASSERT(ret==ESP_OK);
//connect MOSI to two devices breaks the output, fix it.
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
memset(rx_buf, 0x66, 320);
@ -705,124 +649,6 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
TEST_ASSERT(spi_bus_free(HSPI_HOST) == ESP_OK);
}
static const char MASTER_TAG[] = "test_master";
static const char SLAVE_TAG[] = "test_slave";
DRAM_ATTR static uint8_t master_send[] = {
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43,
0x74,
};
DRAM_ATTR static uint8_t slave_send[] = {
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0,
0xda,
};
static void master_deinit(spi_device_handle_t spi)
{
TEST_ESP_OK( spi_bus_remove_device(spi) );
TEST_ESP_OK( spi_bus_free(HSPI_HOST) );
}
#define SPI_SLAVE_TEST_DEFAULT_CONFIG() {\
.mode=0,\
.spics_io_num=PIN_NUM_CS,\
.queue_size=3,\
.flags=0,\
}
static void slave_pull_up(const spi_bus_config_t* cfg, int spics_io_num)
{
gpio_set_pull_mode(cfg->mosi_io_num, GPIO_PULLUP_ENABLE);
gpio_set_pull_mode(cfg->sclk_io_num, GPIO_PULLUP_ENABLE);
gpio_set_pull_mode(spics_io_num, GPIO_PULLUP_ENABLE);
}
typedef struct {
uint32_t len;
uint8_t *start;
} slave_txdata_t;
typedef struct {
uint32_t len;
uint8_t* tx_start;
uint8_t data[1];
} slave_rxdata_t;
typedef struct {
spi_host_device_t spi;
RingbufHandle_t data_received;
QueueHandle_t data_to_send;
} spi_slave_task_context_t;
esp_err_t init_slave_context(spi_slave_task_context_t *context)
{
context->data_to_send = xQueueCreate( 16, sizeof( slave_txdata_t ));
if ( context->data_to_send == NULL ) {
return ESP_ERR_NO_MEM;
}
context->data_received = xRingbufferCreate( 1024, RINGBUF_TYPE_NOSPLIT );
if ( context->data_received == NULL ) {
return ESP_ERR_NO_MEM;
}
context->spi=VSPI_HOST;
return ESP_OK;
}
void deinit_slave_context(spi_slave_task_context_t *context)
{
TEST_ASSERT( context->data_to_send != NULL );
vQueueDelete( context->data_to_send );
context->data_to_send = NULL;
TEST_ASSERT( context->data_received != NULL );
vRingbufferDelete( context->data_received );
context->data_received = NULL;
}
/* The task requires a queue and a ringbuf, which should be initialized before task starts.
Send ``slave_txdata_t`` to the queue to make the task send data;
the task returns data got to the ringbuf, which should have sufficient size.
*/
static void task_slave(void* arg)
{
spi_slave_task_context_t* context = (spi_slave_task_context_t*) arg;
QueueHandle_t queue = context->data_to_send;
RingbufHandle_t ringbuf = context->data_received;
uint8_t recvbuf[320+8];
slave_txdata_t txdata;
ESP_LOGI( SLAVE_TAG, "slave up" );
//never quit, but blocked by the queue, waiting to be killed, when no more send from main task.
while( 1 ) {
xQueueReceive( queue, &txdata, portMAX_DELAY );
ESP_LOGI( "test", "to send: %p", txdata.start );
spi_slave_transaction_t t = {};
t.length = txdata.len;
t.tx_buffer = txdata.start;
t.rx_buffer = recvbuf+8;
//loop until trans_len != 0 to skip glitches
memset(recvbuf, 0x66, sizeof(recvbuf));
do {
TEST_ESP_OK( spi_slave_transmit( context->spi, &t, portMAX_DELAY ) );
} while ( t.trans_len == 0 );
memcpy(recvbuf, &t.trans_len, sizeof(uint32_t));
*(uint8_t**)(recvbuf+4) = txdata.start;
ESP_LOGI( SLAVE_TAG, "received: %d", t.trans_len );
xRingbufferSend( ringbuf, recvbuf, 8+(t.trans_len+7)/8, portMAX_DELAY );
}
}
#define TEST_SPI_HOST HSPI_HOST
#define TEST_SLAVE_HOST VSPI_HOST
static uint8_t bitswap(uint8_t in)
{
uint8_t out = 0;
@ -849,15 +675,15 @@ void test_cmd_addr(spi_slave_task_context_t *slave_context, bool lsb_first)
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &spi));
//connecting pins to two peripherals breaks the output, fix it.
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out);
gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiq_out);
gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spics_out[0]);
gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spiclk_out);
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out);
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiq_out);
spitest_gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spics_out[0]);
spitest_gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spiclk_out);
for (int i= 0; i < 8; i++) {
//prepare slave tx data
slave_txdata_t slave_txdata = (slave_txdata_t) {
.start = slave_send,
.start = spitest_slave_send + 4*(i%3),
.len = 256,
};
xQueueSend(slave_context->data_to_send, &slave_txdata, portMAX_DELAY);
@ -947,7 +773,7 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]")
esp_err_t err = init_slave_context( &slave_context );
TEST_ASSERT( err == ESP_OK );
TaskHandle_t handle_slave;
xTaskCreate( task_slave, "spi_slave", 4096, &slave_context, 0, &handle_slave);
xTaskCreate( spitest_slave_task, "spi_slave", 4096, &slave_context, 0, &handle_slave);
//initial slave, mode 0, no dma
int dma_chan = 0;
@ -971,354 +797,6 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]")
ESP_LOGI(MASTER_TAG, "test passed.");
}
/********************************************************************************
* Test Timing By Internal Connections
********************************************************************************/
typedef enum {
FULL_DUPLEX = 0,
HALF_DUPLEX_MISO = 1,
HALF_DUPLEX_MOSI = 2,
} spi_dup_t;
static int timing_speed_array[]={/**/
SPI_MASTER_FREQ_8M ,
SPI_MASTER_FREQ_9M ,
SPI_MASTER_FREQ_10M,
SPI_MASTER_FREQ_11M,
SPI_MASTER_FREQ_13M,
SPI_MASTER_FREQ_16M,
SPI_MASTER_FREQ_20M,
SPI_MASTER_FREQ_26M,
SPI_MASTER_FREQ_40M,
SPI_MASTER_FREQ_80M,
};
typedef struct {
uint8_t master_rxbuf[320];
spi_transaction_t master_trans[16];
TaskHandle_t handle_slave;
spi_slave_task_context_t slave_context;
slave_txdata_t slave_trans[16];
} timing_context_t;
void master_print_data(spi_transaction_t *t, spi_dup_t dup)
{
if (t->tx_buffer) {
ESP_LOG_BUFFER_HEX( "master tx", t->tx_buffer, t->length/8 );
} else {
ESP_LOGI( "master tx", "no data" );
}
int rxlength;
if (dup!=HALF_DUPLEX_MISO) {
rxlength = t->length/8;
} else {
rxlength = t->rxlength/8;
}
if (t->rx_buffer) {
ESP_LOG_BUFFER_HEX( "master rx", t->rx_buffer, rxlength );
} else {
ESP_LOGI( "master rx", "no data" );
}
}
void slave_print_data(slave_rxdata_t *t)
{
int rcv_len = (t->len+7)/8;
ESP_LOGI(SLAVE_TAG, "trans_len: %d", t->len);
ESP_LOG_BUFFER_HEX( "slave tx", t->tx_start, rcv_len);
ESP_LOG_BUFFER_HEX( "slave rx", t->data, rcv_len);
}
esp_err_t check_data(spi_transaction_t *t, spi_dup_t dup, slave_rxdata_t *slave_t)
{
int length;
if (dup!=HALF_DUPLEX_MISO) {
length = t->length;
} else {
length = t->rxlength;
}
TEST_ASSERT(length!=0);
//currently the rcv_len can be in range of [t->length-1, t->length+3]
uint32_t rcv_len = slave_t->len;
TEST_ASSERT(rcv_len >= length-1 && rcv_len <= length+3);
//the timing speed is temporarily only for master
if (dup!=HALF_DUPLEX_MISO) {
// TEST_ASSERT_EQUAL_HEX8_ARRAY(t->tx_buffer, slave_t->data, (t->length+7)/8);
}
if (dup!=HALF_DUPLEX_MOSI) {
TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_t->tx_start, t->rx_buffer, (length+7)/8);
}
return ESP_OK;
}
int test_len[] = {1, 3, 5, 7, 9, 11, 33, 64};
static void timing_init_transactions(spi_dup_t dup, timing_context_t* context)
{
spi_transaction_t* trans = context->master_trans;
uint8_t *rx_buf_ptr = context->master_rxbuf;
if (dup==HALF_DUPLEX_MISO) {
for (int i = 0; i < 8; i++ ) {
trans[i] = (spi_transaction_t) {
.flags = 0,
.rxlength = 8*test_len[i],
.rx_buffer = rx_buf_ptr,
};
rx_buf_ptr += ((context->master_trans[i].rxlength + 31)/8)&(~3);
}
} else if (dup==HALF_DUPLEX_MOSI) {
for (int i = 0; i < 8; i++ ) {
trans[i] = (spi_transaction_t) {
.flags = 0,
.length = 8*test_len[i],
.tx_buffer = master_send+i,
};
}
} else {
for (int i = 0; i < 8; i++ ) {
trans[i] = (spi_transaction_t) {
.flags = 0,
.length = 8*test_len[i],
.tx_buffer = master_send+i,
.rx_buffer = rx_buf_ptr,
};
rx_buf_ptr += ((context->master_trans[i].length + 31)/8)&(~3);
}
}
//prepare slave tx data
for (int i = 0; i < 8; i ++) {
context->slave_trans[i] = (slave_txdata_t) {
.start = slave_send + 4*(i%3),
.len = 512,
};
}
}
typedef struct {
const char cfg_name[30];
/*The test work till the frequency below,
*set the frequency to higher and remove checks in the driver to know how fast the system can run.
*/
int freq_limit;
spi_dup_t dup;
bool master_iomux;
bool slave_iomux;
int slave_tv_ns;
} test_timing_config_t;
#define ESP_SPI_SLAVE_TV (12.5*3)
#define GPIO_DELAY (12.5*2)
#define SAMPLE_DELAY 12.5
#define TV_INT_CONNECT_GPIO (ESP_SPI_SLAVE_TV+GPIO_DELAY)
#define TV_INT_CONNECT (ESP_SPI_SLAVE_TV)
#define TV_WITH_ESP_SLAVE_GPIO (ESP_SPI_SLAVE_TV+SAMPLE_DELAY+GPIO_DELAY)
#define TV_WITH_ESP_SLAVE (ESP_SPI_SLAVE_TV+SAMPLE_DELAY)
//currently ESP32 slave only supports up to 20MHz, but 40MHz on the same board
#define ESP_SPI_SLAVE_MAX_FREQ SPI_MASTER_FREQ_20M
#define ESP_SPI_SLAVE_MAX_FREQ_SYNC SPI_MASTER_FREQ_40M
static test_timing_config_t timing_master_conf_t[] = {
{ .cfg_name = "FULL_DUP, MASTER IOMUX",
.freq_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .cfg_name = "FULL_DUP, SLAVE IOMUX",
.freq_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .cfg_name = "FULL_DUP, BOTH GPIO",
.freq_limit = SPI_MASTER_FREQ_10M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .cfg_name = "HALF_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .cfg_name = "HALF_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .cfg_name = "HALF_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .cfg_name = "MOSI_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .cfg_name = "MOSI_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .cfg_name = "MOSI_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
};
//this case currently only checks master read
TEST_CASE("test timing_master","[spi][timeout=120]")
{
timing_context_t context;
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
//slave_pull_up(&slv_buscfg, slvcfg.spics_io_num);
context.slave_context = (spi_slave_task_context_t){};
esp_err_t err = init_slave_context( &context.slave_context );
TEST_ASSERT( err == ESP_OK );
xTaskCreate( task_slave, "spi_slave", 4096, &context.slave_context, 0, &context.handle_slave);
const int test_size = sizeof(timing_master_conf_t)/sizeof(test_timing_config_t);
for (int i = 0; i < test_size; i++) {
test_timing_config_t* conf = &timing_master_conf_t[i];
spi_device_handle_t spi;
timing_init_transactions(conf->dup, &context);
ESP_LOGI(MASTER_TAG, "****************** %s ***************", conf->cfg_name);
for (int j=0; j<sizeof(timing_speed_array)/sizeof(int); j++ ) {
if (timing_speed_array[j] > conf->freq_limit) break;
ESP_LOGI(MASTER_TAG, "======> %dk", timing_speed_array[j]/1000);
//master config
const int master_mode = 0;
spi_bus_config_t buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
spi_device_interface_config_t devcfg=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.mode = master_mode;
if (conf->dup==HALF_DUPLEX_MISO||conf->dup==HALF_DUPLEX_MOSI) {
devcfg.cs_ena_pretrans = 20;
devcfg.flags |= SPI_DEVICE_HALFDUPLEX;
} else {
devcfg.cs_ena_pretrans = 1;
}
devcfg.cs_ena_posttrans = 20;
devcfg.input_delay_ns = conf->slave_tv_ns;
devcfg.clock_speed_hz = timing_speed_array[j];
//slave config
int slave_mode = 0;
spi_slave_interface_config_t slvcfg=SPI_SLAVE_TEST_DEFAULT_CONFIG();
slvcfg.mode = slave_mode;
//pin config & initialize
//we can't have two sets of iomux pins on the same pins
assert(!conf->master_iomux || !conf->slave_iomux);
if (conf->slave_iomux) {
//only in this case, use VSPI iomux pins
buscfg.miso_io_num = VSPI_IOMUX_PIN_NUM_MISO;
buscfg.mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI;
buscfg.sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK;
devcfg.spics_io_num = VSPI_IOMUX_PIN_NUM_CS;
slvcfg.spics_io_num = VSPI_IOMUX_PIN_NUM_CS;
} else {
buscfg.miso_io_num = HSPI_IOMUX_PIN_NUM_MISO;
buscfg.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI;
buscfg.sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK;
devcfg.spics_io_num = HSPI_IOMUX_PIN_NUM_CS;
slvcfg.spics_io_num = HSPI_IOMUX_PIN_NUM_CS;
}
slave_pull_up(&buscfg, slvcfg.spics_io_num);
//this does nothing, but avoid the driver from using iomux pins if required
buscfg.quadhd_io_num = (!conf->master_iomux && !conf->slave_iomux? VSPI_IOMUX_PIN_NUM_MISO: -1);
TEST_ESP_OK(spi_bus_initialize(HSPI_HOST, &buscfg, 0));
TEST_ESP_OK(spi_bus_add_device(HSPI_HOST, &devcfg, &spi));
//slave automatically use iomux pins if pins are on VSPI_* pins
buscfg.quadhd_io_num = -1;
TEST_ESP_OK( spi_slave_initialize(VSPI_HOST, &buscfg, &slvcfg, 0) );
//initialize master and slave on the same pins break some of the output configs, fix them
if (conf->master_iomux) {
gpio_output_sel(buscfg.mosi_io_num, FUNC_SPI, HSPID_OUT_IDX);
gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, VSPIQ_OUT_IDX);
gpio_output_sel(devcfg.spics_io_num, FUNC_SPI, HSPICS0_OUT_IDX);
gpio_output_sel(buscfg.sclk_io_num, FUNC_SPI, HSPICLK_OUT_IDX);
} else if (conf->slave_iomux) {
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
gpio_output_sel(buscfg.miso_io_num, FUNC_SPI, VSPIQ_OUT_IDX);
gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, HSPICS0_OUT_IDX);
gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, HSPICLK_OUT_IDX);
} else {
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, VSPIQ_OUT_IDX);
gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, HSPICS0_OUT_IDX);
gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, HSPICLK_OUT_IDX);
}
//clear master receive buffer
memset(context.master_rxbuf, 0x66, sizeof(context.master_rxbuf));
//prepare slave tx data
for (int k = 0; k < 8; k ++) xQueueSend( context.slave_context.data_to_send, &context.slave_trans[k], portMAX_DELAY );
for( int k= 0; k < 8; k ++ ) {
//wait for both master and slave end
ESP_LOGI( MASTER_TAG, "=> test%d", k );
//send master tx data
vTaskDelay(9);
spi_transaction_t *t = &context.master_trans[k];
TEST_ESP_OK (spi_device_transmit( spi, t) );
master_print_data(t, conf->dup);
size_t rcv_len;
slave_rxdata_t *rcv_data = xRingbufferReceive( context.slave_context.data_received, &rcv_len, portMAX_DELAY );
slave_print_data(rcv_data);
//check result
TEST_ESP_OK(check_data(t, conf->dup, rcv_data));
//clean
vRingbufferReturnItem(context.slave_context.data_received, rcv_data);
}
master_deinit(spi);
TEST_ASSERT(spi_slave_free(VSPI_HOST) == ESP_OK);
}
}
vTaskDelete( context.handle_slave );
context.handle_slave = 0;
deinit_slave_context(&context.slave_context);
ESP_LOGI(MASTER_TAG, "test passed.");
}
/********************************************************************************
* Test SPI transaction interval
********************************************************************************/
@ -1340,12 +818,6 @@ static void speed_setup(spi_device_handle_t* spi, bool use_dma)
TEST_ASSERT(ret==ESP_OK);
}
static void speed_deinit(spi_device_handle_t spi)
{
TEST_ESP_OK( spi_bus_remove_device(spi) );
TEST_ESP_OK( spi_bus_free(HSPI_HOST) );
}
static void sorted_array_insert(uint32_t* array, int* size, uint32_t item)
{
int pos;
@ -1424,7 +896,8 @@ TEST_CASE("spi_speed","[spi]")
//release the bus
spi_device_release_bus(spi);
speed_deinit(spi);
master_free_device_bus(spi);
speed_setup(&spi, !use_dma);
//record flight time by isr, without DMA
@ -1454,7 +927,7 @@ TEST_CASE("spi_speed","[spi]")
//release the bus
spi_device_release_bus(spi);
speed_deinit(spi);
master_free_device_bus(spi);
}
typedef struct {

View file

@ -0,0 +1,969 @@
#include "test/test_common_spi.h"
#include "driver/spi_master.h"
#include "driver/spi_slave.h"
#include "esp_log.h"
#include "soc/spi_periph.h"
#include "test/test_common_spi.h"
/********************************************************************************
* Test By Internal Connections
********************************************************************************/
static void local_test_init(void** context);
static void local_test_deinit(void* context);
static void local_test_loop(const void *test_param, void* context);
static const ptest_func_t local_test_func = {
.pre_test = local_test_init,
.post_test = local_test_deinit,
.loop = local_test_loop,
.def_param = spitest_def_param,
};
#define TEST_SPI_LOCAL(name, param_set) \
PARAM_GROUP_DECLARE(name, param_set) \
TEST_LOCAL(name, param_set, "[spi][timeout=120]", &local_test_func)
static void local_test_init(void** arg)
{
TEST_ASSERT(*arg==NULL);
*arg = malloc(sizeof(spitest_context_t));
spitest_context_t* context = (spitest_context_t*)*arg;
TEST_ASSERT(context!=NULL);
context->slave_context = (spi_slave_task_context_t){};
esp_err_t err = init_slave_context( &context->slave_context);
TEST_ASSERT(err == ESP_OK);
xTaskCreate(spitest_slave_task, "spi_slave", 4096, &context->slave_context, 0, &context->handle_slave);
}
static void local_test_deinit(void* arg)
{
spitest_context_t* context = arg;
vTaskDelete(context->handle_slave);
context->handle_slave = 0;
deinit_slave_context(&context->slave_context);
}
static void local_test_start(spi_device_handle_t *spi, int freq, const spitest_param_set_t* pset, spitest_context_t* context)
{
//master config
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG();
//pin config & initialize
//we can't have two sets of iomux pins on the same pins
assert(!pset->master_iomux || !pset->slave_iomux);
if (pset->slave_iomux) {
//only in this case, use VSPI iomux pins
buscfg.miso_io_num = VSPI_IOMUX_PIN_NUM_MISO;
buscfg.mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI;
buscfg.sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK;
devcfg.spics_io_num = VSPI_IOMUX_PIN_NUM_CS;
slvcfg.spics_io_num = VSPI_IOMUX_PIN_NUM_CS;
} else {
buscfg.miso_io_num = HSPI_IOMUX_PIN_NUM_MISO;
buscfg.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI;
buscfg.sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK;
devcfg.spics_io_num = HSPI_IOMUX_PIN_NUM_CS;
slvcfg.spics_io_num = HSPI_IOMUX_PIN_NUM_CS;
}
//this does nothing, but avoid the driver from using iomux pins if required
buscfg.quadhd_io_num = (!pset->master_iomux && !pset->slave_iomux ? VSPI_IOMUX_PIN_NUM_MISO : -1);
devcfg.mode = pset->mode;
const int cs_pretrans_max = 15;
if (pset->dup == HALF_DUPLEX_MISO) {
devcfg.cs_ena_pretrans = cs_pretrans_max;
devcfg.flags |= SPI_DEVICE_HALFDUPLEX;
} else if (pset->dup == HALF_DUPLEX_MOSI) {
devcfg.cs_ena_pretrans = cs_pretrans_max;
devcfg.flags |= SPI_DEVICE_NO_DUMMY;
} else {
devcfg.cs_ena_pretrans = cs_pretrans_max;
}
const int cs_posttrans_max = 15;
devcfg.cs_ena_posttrans = cs_posttrans_max;
devcfg.input_delay_ns = pset->slave_tv_ns;
devcfg.clock_speed_hz = freq;
if (pset->master_limit != 0 && freq > pset->master_limit) devcfg.flags |= SPI_DEVICE_NO_DUMMY;
//slave config
slvcfg.mode = pset->mode;
slave_pull_up(&buscfg, slvcfg.spics_io_num);
TEST_ESP_OK(spi_bus_initialize(HSPI_HOST, &buscfg, pset->master_dma_chan));
TEST_ESP_OK(spi_bus_add_device(HSPI_HOST, &devcfg, spi));
//slave automatically use iomux pins if pins are on VSPI_* pins
buscfg.quadhd_io_num = -1;
TEST_ESP_OK(spi_slave_initialize(VSPI_HOST, &buscfg, &slvcfg, pset->slave_dma_chan));
//initialize master and slave on the same pins break some of the output configs, fix them
if (pset->master_iomux) {
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_SPI, HSPID_OUT_IDX);
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, VSPIQ_OUT_IDX);
spitest_gpio_output_sel(devcfg.spics_io_num, FUNC_SPI, HSPICS0_OUT_IDX);
spitest_gpio_output_sel(buscfg.sclk_io_num, FUNC_SPI, HSPICLK_OUT_IDX);
} else if (pset->slave_iomux) {
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_SPI, VSPIQ_OUT_IDX);
spitest_gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, HSPICS0_OUT_IDX);
spitest_gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, HSPICLK_OUT_IDX);
} else {
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, VSPIQ_OUT_IDX);
spitest_gpio_output_sel(devcfg.spics_io_num, FUNC_GPIO, HSPICS0_OUT_IDX);
spitest_gpio_output_sel(buscfg.sclk_io_num, FUNC_GPIO, HSPICLK_OUT_IDX);
}
//prepare slave tx data
for (int k = 0; k < pset->test_size; k++)
xQueueSend(context->slave_context.data_to_send, &context->slave_trans[k], portMAX_DELAY);
//clear master receive buffer
memset(context->master_rxbuf, 0x66, sizeof(context->master_rxbuf));
}
static void local_test_loop(const void* arg1, void* arg2)
{
const spitest_param_set_t *pset = arg1;
spitest_context_t *context = arg2;
spi_device_handle_t spi;
spitest_init_transactions(pset, context);
const int *timing_speed_array = pset->freq_list;
ESP_LOGI(MASTER_TAG, "****************** %s ***************", pset->pset_name);
for (int i = 0; ; i++) {
const int freq = timing_speed_array[i];
if (freq==0) break;
if (pset->freq_limit && freq > pset->freq_limit) break;
ESP_LOGI(MASTER_TAG, "======> %dk", freq / 1000);
local_test_start(&spi, freq, pset, context);
for (int k = 0; k < pset->test_size; k++) {
//wait for both master and slave end
ESP_LOGI(MASTER_TAG, "=> test%d", k);
//send master tx data
vTaskDelay(9);
spi_transaction_t *t = &context->master_trans[k];
TEST_ESP_OK(spi_device_transmit(spi, t));
int len = get_trans_len(pset->dup, t);
spitest_master_print_data(t, len);
size_t rcv_len;
slave_rxdata_t *rcv_data = xRingbufferReceive(context->slave_context.data_received, &rcv_len, portMAX_DELAY);
spitest_slave_print_data(rcv_data, true);
//check result
bool check_master_data = (pset->dup!=HALF_DUPLEX_MOSI &&
(pset->master_limit==0 || freq <= pset->master_limit));
bool check_slave_data = (pset->dup!=HALF_DUPLEX_MISO);
const bool check_len = true;
if (!check_master_data) ESP_LOGI(MASTER_TAG, "skip master data check");
if (!check_slave_data) ESP_LOGI(SLAVE_TAG, "skip slave data check");
TEST_ESP_OK(spitest_check_data(len, t, rcv_data, check_master_data, check_len, check_slave_data));
//clean
vRingbufferReturnItem(context->slave_context.data_received, rcv_data);
}
master_free_device_bus(spi);
TEST_ASSERT(spi_slave_free(VSPI_HOST) == ESP_OK);
}
}
/************ Timing Test ***********************************************/
static spitest_param_set_t timing_pgroup[] = {
{ .pset_name = "FULL_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .pset_name = "FULL_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "FULL_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.master_limit = SPI_MASTER_FREQ_10M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .pset_name = "MISO_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.master_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO+12.5,
//for freq lower than 20M, the delay is 60(62.5)ns, however, the delay becomes 75ns over 26M
},
{ .pset_name = "MISO_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
//.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT+12.5,
//for freq lower than 20M, the delay is 60(62.5)ns, however, the delay becomes 75ns over 26M
},
{ .pset_name = "MISO_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
//.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO+12.5,
//for freq lower than 20M, the delay is 60(62.5)ns, however, the delay becomes 75ns over 26M
},
{ .pset_name = "MOSI_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
//.freq_limit = ESP_SPI_SLAVE_MAX_READ_FREQ, //ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
{ .pset_name = "MOSI_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
//.freq_limit = ESP_SPI_SLAVE_MAX_READ_FREQ, //ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "MOSI_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ_SYNC,
//.freq_limit = ESP_SPI_SLAVE_MAX_READ_FREQ, //ESP_SPI_SLAVE_MAX_FREQ_SYNC,
.dup = HALF_DUPLEX_MOSI,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_INT_CONNECT_GPIO,
},
};
TEST_SPI_LOCAL(TIMING, timing_pgroup)
/************ Mode Test ***********************************************/
#define FREQ_LIMIT_MODE SPI_MASTER_FREQ_16M
static int test_freq_mode_local[]={
1*1000*1000,
SPI_MASTER_FREQ_9M, //maximum freq MISO stable before next latch edge
SPI_MASTER_FREQ_13M,
SPI_MASTER_FREQ_16M,
SPI_MASTER_FREQ_20M,
SPI_MASTER_FREQ_26M,
SPI_MASTER_FREQ_40M,
0,
};
static spitest_param_set_t mode_pgroup[] = {
{ .pset_name = "Mode 0",
.freq_list = test_freq_mode_local,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 0,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "Mode 1",
.freq_list = test_freq_mode_local,
.freq_limit = SPI_MASTER_FREQ_26M,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 1,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "Mode 2",
.freq_list = test_freq_mode_local,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "Mode 3",
.freq_list = test_freq_mode_local,
.freq_limit = SPI_MASTER_FREQ_26M,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 3,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "Mode 0, DMA",
.freq_list = test_freq_mode_local,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 0,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT, //at 16M, the MISO delay (-0.5T+(3+2)apb) equals to non-DMA mode delay (3apb).
.length_aligned = true,
},
{ .pset_name = "Mode 1, DMA",
.freq_list = test_freq_mode_local,
.freq_limit = SPI_MASTER_FREQ_26M,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 1,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
.length_aligned = true,
},
{ .pset_name = "Mode 2, DMA",
.freq_list = test_freq_mode_local,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 2,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT, //at 16M, the MISO delay (-0.5T+(3+2)apb) equals to non-DMA mode delay (3apb).
.length_aligned = true,
},
{ .pset_name = "Mode 3, DMA",
.freq_list = test_freq_mode_local,
.freq_limit = SPI_MASTER_FREQ_26M,
.master_limit = SPI_MASTER_FREQ_13M,
.dup = FULL_DUPLEX,
.mode = 3,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
.length_aligned = true,
},
// MISO ////////////////////////////////////
{ .pset_name = "MISO, Mode 0",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 0,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "MISO, Mode 1",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 1,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "MISO, Mode 2",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "MISO, Mode 3",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 3,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
},
{ .pset_name = "MISO, Mode 0, DMA",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 0,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT+12.5, //at 16M, the MISO delay (-0.5T+(3+2)apb) equals to non-DMA mode delay (3apb).
.length_aligned = true,
},
{ .pset_name = "MISO, Mode 1, DMA",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 1,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
.length_aligned = true,
},
{ .pset_name = "MISO, Mode 2, DMA",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 2,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT+12.5, //at 16M, the MISO delay (-0.5T+(3+2)apb) equals to non-DMA mode delay (3apb).
.length_aligned = true,
},
{ .pset_name = "MISO, Mode 3, DMA",
.freq_list = test_freq_mode_local,
.dup = HALF_DUPLEX_MISO,
.mode = 3,
.slave_dma_chan = 2,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_INT_CONNECT,
.length_aligned = true,
},
};
TEST_SPI_LOCAL(MODE, mode_pgroup)
/********************************************************************************
* Test By Master & Slave (2 boards)
********************************************************************************/
static void test_master_init(void** context);
static void test_master_deinit(void* context);
static void test_master_loop(const void *test_cfg, void* context);
static const ptest_func_t master_test_func = {
.pre_test = test_master_init,
.post_test = test_master_deinit,
.loop = test_master_loop,
.def_param = spitest_def_param,
};
static void test_slave_init(void** context);
static void test_slave_deinit(void* context);
static void test_slave_loop(const void *test_cfg, void* context);
static const ptest_func_t slave_test_func = {
.pre_test = test_slave_init,
.post_test = test_slave_deinit,
.loop = test_slave_loop,
.def_param = spitest_def_param,
};
#define TEST_SPI_MASTER_SLAVE(name, param_group) \
PARAM_GROUP_DECLARE(name, param_group) \
TEST_MASTER_SLAVE(name, param_group, "[spi_ms][test_env=Example_SPI_Multi_device][timeout=120]", &master_test_func, &slave_test_func)
/************ Master Code ***********************************************/
static void test_master_init(void** arg)
{
TEST_ASSERT(*arg==NULL);
*arg = malloc(sizeof(spitest_context_t));
spitest_context_t* context = *arg;
TEST_ASSERT(context!=NULL);
context->slave_context = (spi_slave_task_context_t){};
esp_err_t err = init_slave_context(&context->slave_context);
TEST_ASSERT(err == ESP_OK);
}
static void test_master_deinit(void* arg)
{
spitest_context_t* context = (spitest_context_t*)arg;
deinit_slave_context(&context->slave_context);
}
static void test_master_start(spi_device_handle_t *spi, int freq, const spitest_param_set_t* pset, spitest_context_t* context)
{
//master config
spi_bus_config_t buspset=SPI_BUS_TEST_DEFAULT_CONFIG();
buspset.miso_io_num = HSPI_IOMUX_PIN_NUM_MISO;
buspset.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI;
buspset.sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK;
//this does nothing, but avoid the driver from using native pins
if (!pset->master_iomux) buspset.quadhd_io_num = VSPI_IOMUX_PIN_NUM_MISO;
spi_device_interface_config_t devpset=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devpset.spics_io_num = HSPI_IOMUX_PIN_NUM_CS;
devpset.mode = pset->mode;
const int cs_pretrans_max = 15;
if (pset->dup==HALF_DUPLEX_MISO) {
devpset.cs_ena_pretrans = cs_pretrans_max;
devpset.flags |= SPI_DEVICE_HALFDUPLEX;
} else if (pset->dup == HALF_DUPLEX_MOSI) {
devpset.cs_ena_pretrans = cs_pretrans_max;
devpset.flags |= SPI_DEVICE_NO_DUMMY;
} else {
devpset.cs_ena_pretrans = cs_pretrans_max;//20;
}
const int cs_posttrans_max = 15;
devpset.cs_ena_posttrans = cs_posttrans_max;
devpset.input_delay_ns = pset->slave_tv_ns;
devpset.clock_speed_hz = freq;
if (pset->master_limit != 0 && freq > pset->master_limit) devpset.flags |= SPI_DEVICE_NO_DUMMY;
TEST_ESP_OK(spi_bus_initialize(HSPI_HOST, &buspset, pset->master_dma_chan));
TEST_ESP_OK(spi_bus_add_device(HSPI_HOST, &devpset, spi));
//prepare data for the slave
for (int i = 0; i < pset->test_size; i ++) {
/* in the single board, the data is send to the slave task, then to the driver.
* However, in this test we don't know the data received by the slave.
* So we send to the return queue of the slave directly.
*/
//xQueueSend( slave_context.data_to_send, &slave_txdata[i], portMAX_DELAY );
uint8_t slave_buffer[320+8];
int length;
if (pset->dup!=HALF_DUPLEX_MISO) {
length = context->master_trans[i].length;
} else {
length = context->master_trans[i].rxlength;
}
uint32_t* ptr = (uint32_t*)slave_buffer;
ptr[0] = length;
ptr[1] = (uint32_t)context->slave_trans[i].start;
if (context->master_trans[i].tx_buffer!=NULL) {
memcpy(ptr+2, context->master_trans[i].tx_buffer, (context->master_trans[i].length+7)/8);
}
//Send to return queue directly
xRingbufferSend(context->slave_context.data_received, slave_buffer, 8+(length+7)/8, portMAX_DELAY);
}
memset(context->master_rxbuf, 0x66, sizeof(context->master_rxbuf));
}
static void test_master_loop(const void *arg1, void* arg2)
{
const spitest_param_set_t *test_cfg = (spitest_param_set_t*)arg1;
spitest_context_t* context = (spitest_context_t*)arg2;
spi_device_handle_t spi;
spitest_init_transactions(test_cfg, context);
const int *timing_speed_array = test_cfg->freq_list;
ESP_LOGI(MASTER_TAG, "****************** %s ***************", test_cfg->pset_name);
for (int i=0; ; i++ ) {
const int freq = timing_speed_array[i];
if (freq==0) break;
if (test_cfg->freq_limit && freq > test_cfg->freq_limit) break;
ESP_LOGI(MASTER_TAG, "==============> %dk", freq/1000);
test_master_start(&spi, freq, test_cfg, context);
unity_wait_for_signal("slave ready");
for( int j= 0; j < test_cfg->test_size; j ++ ) {
//wait for both master and slave end
ESP_LOGI( MASTER_TAG, "=> test%d", j );
//send master tx data
vTaskDelay(20);
spi_transaction_t *t = &context->master_trans[j];
TEST_ESP_OK (spi_device_transmit(spi, t) );
int len = get_trans_len(test_cfg->dup, t);
spitest_master_print_data(t, len);
size_t rcv_len;
slave_rxdata_t *rcv_data = xRingbufferReceive( context->slave_context.data_received, &rcv_len, portMAX_DELAY );
spitest_slave_print_data(rcv_data, false);
//check result
bool check_master_data = (test_cfg->dup != HALF_DUPLEX_MOSI &&
(test_cfg->master_limit == 0 || freq <= test_cfg->master_limit));
const bool check_slave_data = false;
const bool check_len = false;
if (!check_master_data) {
ESP_LOGI(MASTER_TAG, "skip data check due to duplex mode or freq.");
} else {
TEST_ESP_OK(spitest_check_data(len, t, rcv_data, check_master_data,
check_len, check_slave_data));
}
//clean
vRingbufferReturnItem( context->slave_context.data_received, rcv_data );
}
master_free_device_bus(spi);
}
}
/************ Slave Code ***********************************************/
static void test_slave_init(void** arg)
{
TEST_ASSERT(*arg==NULL);
*arg = malloc(sizeof(spitest_context_t));
spitest_context_t* context = (spitest_context_t*)*arg;
TEST_ASSERT(context!=NULL);
context->slave_context = (spi_slave_task_context_t){};
esp_err_t err = init_slave_context( &context->slave_context );
TEST_ASSERT( err == ESP_OK );
xTaskCreate( spitest_slave_task, "spi_slave", 4096, &context->slave_context, 0, &context->handle_slave);
}
static void test_slave_deinit(void* arg)
{
spitest_context_t* context = (spitest_context_t*)arg;
vTaskDelete( context->handle_slave );
context->handle_slave = 0;
deinit_slave_context(&context->slave_context);
}
static void timing_slave_start(int speed, const spitest_param_set_t* pset, spitest_context_t *context)
{
//slave config
spi_bus_config_t slv_buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
slv_buscfg.miso_io_num = VSPI_IOMUX_PIN_NUM_MISO;
slv_buscfg.mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI;
slv_buscfg.sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK;
//this does nothing, but avoid the driver from using native pins
if (!pset->slave_iomux) slv_buscfg.quadhd_io_num = HSPI_IOMUX_PIN_NUM_CLK;
spi_slave_interface_config_t slvcfg=SPI_SLAVE_TEST_DEFAULT_CONFIG();
slvcfg.spics_io_num = VSPI_IOMUX_PIN_NUM_CS;
slvcfg.mode = pset->mode;
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
slave_pull_up(&slv_buscfg, slvcfg.spics_io_num);
TEST_ESP_OK( spi_slave_initialize(VSPI_HOST, &slv_buscfg, &slvcfg, pset->slave_dma_chan) );
//prepare data for the master
for (int i = 0; i < pset->test_size; i++) {
if (pset->dup==FULL_DUPLEX) {
memcpy(context->master_trans[i].rx_buffer, context->slave_trans[i].start, (context->master_trans[i].length+7)/8);
} else if (pset->dup==HALF_DUPLEX_MISO) {
memcpy(context->master_trans[i].rx_buffer, context->slave_trans[i].start, (context->master_trans[i].rxlength+7)/8);
}
}
}
static void test_slave_loop(const void *arg1, void* arg2)
{
const spitest_param_set_t *pset = (spitest_param_set_t*)arg1;
spitest_context_t* context = (spitest_context_t*)arg2;
ESP_LOGI(SLAVE_TAG, "****************** %s ***************", pset->pset_name);
spitest_init_transactions(pset, context);
const int *timing_speed_array = pset->freq_list;
for (int i=0; ; i++ ) {
const int freq = timing_speed_array[i];
if (freq==0) break;
if (pset->freq_limit != 0 && freq > pset->freq_limit) break;
ESP_LOGI(MASTER_TAG, "==============> %dk", timing_speed_array[i]/1000);
//Initialize SPI slave interface
timing_slave_start(freq, pset, context);
//prepare slave tx data
for (int i = 0; i < pset->test_size; i ++) {
xQueueSend( context->slave_context.data_to_send, &context->slave_trans[i], portMAX_DELAY );
//memcpy(context->master_trans[i].rx_buffer, context->slave_trans[i].start, (context->master_trans[i].length+7)/8);
}
vTaskDelay(50/portTICK_PERIOD_MS);
unity_send_signal("slave ready");
for( int i= 0; i < pset->test_size; i ++ ) {
//wait for both master and slave end
ESP_LOGI( MASTER_TAG, "===== test%d =====", i );
//send master tx data
vTaskDelay(20);
spi_transaction_t *t = &context->master_trans[i];
int len = get_trans_len(pset->dup, t);
spitest_master_print_data(t, FULL_DUPLEX);
size_t rcv_len;
slave_rxdata_t *rcv_data = xRingbufferReceive( context->slave_context.data_received, &rcv_len, portMAX_DELAY );
spitest_slave_print_data(rcv_data, true);
//check result
const bool check_master_data = false;
bool check_slave_data = (pset->dup!=HALF_DUPLEX_MISO);
const bool check_len = true;
TEST_ESP_OK(spitest_check_data(len, t, rcv_data, check_master_data, check_len, check_slave_data));
//clean
vRingbufferReturnItem( context->slave_context.data_received, rcv_data );
}
TEST_ASSERT(spi_slave_free(VSPI_HOST) == ESP_OK);
}
}
/************ Timing Test ***********************************************/
static spitest_param_set_t timing_conf[] = {
{ .pset_name = "FULL_DUP, BOTH IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.master_limit = SPI_MASTER_FREQ_16M,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "FULL_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.master_limit = SPI_MASTER_FREQ_11M,
.dup = FULL_DUPLEX,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
{ .pset_name = "FULL_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.master_limit = SPI_MASTER_FREQ_11M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "FULL_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.master_limit = SPI_MASTER_FREQ_9M,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
{ .pset_name = "MOSI_DUP, BOTH IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MOSI,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "MOSI_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MOSI,
.master_iomux= true,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
{ .pset_name = "MOSI_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MOSI,
.master_iomux= false,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "MOSI_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MOSI,
.master_iomux= false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
{ .pset_name = "MISO_DUP, BOTH IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MISO,
.master_iomux = true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "MISO_DUP, MASTER IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MISO,
.master_iomux = true,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
{ .pset_name = "MISO_DUP, SLAVE IOMUX",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
},
{ .pset_name = "MISO_DUP, BOTH GPIO",
.freq_limit = ESP_SPI_SLAVE_MAX_FREQ,
.dup = HALF_DUPLEX_MISO,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE_GPIO,
},
};
TEST_SPI_MASTER_SLAVE(TIMING, timing_conf)
/************ Mode Test ***********************************************/
#define FREQ_LIMIT_MODE SPI_MASTER_FREQ_16M
//Set to this input delay so that the master will read with delay until 7M
#define DELAY_HCLK_UNTIL_7M 12.5*3
static int test_freq_mode_ms[]={
100*1000,
6*1000*1000,
7*1000*1000,
SPI_MASTER_FREQ_8M, //maximum freq MISO stable before next latch edge
SPI_MASTER_FREQ_9M, //maximum freq MISO stable before next latch edge
SPI_MASTER_FREQ_10M,
SPI_MASTER_FREQ_11M,
SPI_MASTER_FREQ_13M,
SPI_MASTER_FREQ_16M,
SPI_MASTER_FREQ_20M,
0,
};
static int test_freq_20M_only[]={
SPI_MASTER_FREQ_20M,
0,
};
spitest_param_set_t mode_conf[] = {
//non-DMA tests
{ .pset_name = "mode 0, no DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 0,
},
{ .pset_name = "mode 1, no DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 1,
},
{ .pset_name = "mode 2, no DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 2,
},
{ .pset_name = "mode 3, no DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 3,
},
//the master can only read to 16MHz, use half-duplex mode to read at 20.
{ .pset_name = "mode 0, no DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 0,
},
{ .pset_name = "mode 1, no DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 1,
},
{ .pset_name = "mode 2, no DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 2,
},
{ .pset_name = "mode 3, no DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 3,
},
//DMA tests
{ .pset_name = "mode 0, DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = DELAY_HCLK_UNTIL_7M,
.mode = 0,
.master_dma_chan = 1,
.slave_dma_chan = 1,
.length_aligned = true,
},
{ .pset_name = "mode 1, DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 1,
.master_dma_chan = 1,
.slave_dma_chan = 1,
.length_aligned = true,
},
{ .pset_name = "mode 2, DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = DELAY_HCLK_UNTIL_7M,
.mode = 2,
.master_dma_chan = 1,
.slave_dma_chan = 1,
.length_aligned = true,
},
{ .pset_name = "mode 3, DMA",
.freq_list = test_freq_mode_ms,
.master_limit = FREQ_LIMIT_MODE,
.dup = FULL_DUPLEX,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 3,
.master_dma_chan = 1,
.slave_dma_chan = 1,
.length_aligned = true,
},
//the master can only read to 16MHz, use half-duplex mode to read at 20.
{ .pset_name = "mode 0, DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 0,
.master_dma_chan = 1,
.slave_dma_chan = 1,
},
{ .pset_name = "mode 1, DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 1,
.master_dma_chan = 1,
.slave_dma_chan = 1,
},
{ .pset_name = "mode 2, DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 2,
.master_dma_chan = 1,
.slave_dma_chan = 1,
},
{ .pset_name = "mode 3, DMA, 20M",
.freq_list = test_freq_20M_only,
.dup = HALF_DUPLEX_MISO,
.master_iomux= true,
.slave_iomux = true,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 3,
.master_dma_chan = 1,
.slave_dma_chan = 1,
},
};
TEST_SPI_MASTER_SLAVE(MODE, mode_conf)

View file

@ -17,6 +17,7 @@
#define PIN_NUM_CLK 19
#define PIN_NUM_CS 22
static const char MASTER_TAG[] = "test_master";
static const char SLAVE_TAG[] = "test_slave";
@ -45,7 +46,8 @@ static void master_init_nodma( spi_device_handle_t* spi)
.spics_io_num=PIN_NUM_CS, //CS pin
.queue_size=7, //We want to be able to queue 7 transactions at a time
.pre_cb=NULL,
.cs_ena_posttrans=1,
.cs_ena_posttrans=5,
.cs_ena_pretrans=1,
};
//Initialize the SPI bus
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 0);

View file

@ -0,0 +1,23 @@
.. This is the source code to generate figure by https://wavedrom.com/.
.. We can use the sphinx plugin sphinxcontrib-wavedrom to generate the figure and put it in the html doc (by include this source code), but it is not supported by the pdf. Currently we generate the figure manually and include the stataic figure in the doc.
.. wavedrom::
{ signal: [
{ name:'CS', wave:'10........'},
{ name: 'SCLK', wave: '0.10101010', node: '...ab'},
{ name: 'MISO (normal)', wave: 'x3.3.3.3.3', node: '...c.d', data: ['7','6','5','4','3'], phase:-0.8},
{ name: 'SCLK', wave: '0.10101010', node: '..e.f'},
{ name: 'MISO (DMA)', wave: 'x33.3.3.3', node: '..g.h', data: ['7','6','5','4','3','2'], phase:-0.8},
],
edge: [
'a|->c delay',
'b-|->c setup',
'b-|->d hold',
'e|->g delay',
'f|->g setup',
'f-|>h hold',
],
config:{hscale: 2}
}

BIN
docs/_static/spi_slave_miso_dma.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -13,8 +13,10 @@ connected SPI master.
The spi_slave driver
^^^^^^^^^^^^^^^^^^^^^
The spi_slave driver allows using the HSPI and/or VSPI peripheral as a full-duplex SPI slave. It can make
use of DMA to send/receive transactions of arbitrary length.
The spi_slave driver allows using the HSPI and/or VSPI peripheral as a
full-duplex SPI slave. It can send/receive transactions within 64 bytes, or
make use of DMA to send/receive transactions longer than that. However, there
are some `known issues <spi_dma_known_issues>`_ when the DMA is enabled.
Terminology
^^^^^^^^^^^
@ -47,6 +49,54 @@ starts sending out clock pulses on the CLK line: every clock pulse causes a data
the master to the slave on the MOSI line and vice versa on the MISO line. At the end of the transaction,
the master makes CS high again.
.. note:: The SPI slave peripheral relies on the control of software very
much. The master shouldn't start a transaction when the slave hasn't prepared
for it. Using one more GPIO as the handshake signal to sync is a good idea.
For more details, see :ref:`transaction_interval`.
GPIO matrix and IOMUX
^^^^^^^^^^^^^^^^^^^^^
Most peripheral signals in ESP32 can connect directly to a specific GPIO,
which is called its IOMUX pin. When a peripheral signal is routed to a pin
other than its IOMUX pin, ESP32 uses the less direct GPIO matrix to make this
connection.
If the driver is configured with all SPI signals set to their specific IOMUX
pins (or left unconnected), it will bypass the GPIO matrix. If any SPI signal
is configured to a pin other than its IOMUx pin, the driver will
automatically route all the signals via the GPIO Matrix. The GPIO matrix
samples all signals at 80MHz and sends them between the GPIO and the
peripheral.
When the GPIO matrix is used, setup time of MISO is more easily violated,
since the output delay of MISO signal is increased.
.. note:: More details about influence of output delay on the maximum clock
frequency, see :ref:`timing_considerations` below.
IOMUX pins for SPI controllers are as below:
+----------+------+------+
| Pin Name | HSPI | VSPI |
+ +------+------+
| | GPIO Number |
+==========+======+======+
| CS0* | 15 | 5 |
+----------+------+------+
| SCLK | 14 | 18 |
+----------+------+------+
| MISO | 12 | 19 |
+----------+------+------+
| MOSI | 13 | 23 |
+----------+------+------+
| QUADWP | 2 | 22 |
+----------+------+------+
| QUADHD | 4 | 21 |
+----------+------+------+
note * Only the first device attaching to the bus can use CS0 pin.
Using the spi_slave driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -85,6 +135,73 @@ Warning: Due to a design peculiarity in the ESP32, if the amount of bytes sent b
of the transmission queues in the slave driver, in bytes, is not both larger than eight and dividable by
four, the SPI hardware can fail to write the last one to seven bytes to the receive buffer.
Speed and Timing considerations
-------------------------------
.. _transaction_interval:
Transaction interval
^^^^^^^^^^^^^^^^^^^^
The SPI slave is designed as s general purpose device controlled by the CPU.
Different from dedicated devices, CPU-based SPI slave doesn't have too much
pre-defined registers. All transactions should be triggered by the CPU, which
means the response speed would not be real-time, and there'll always be
noticeable intervals between transfers.
During the transaction intervals, the device is not prepared for
transactions, the response is not meaningful at all. It is suggested to use
:cpp:func:`spi_slave_queue_trans` with :cpp:func:`spi_slave_get_trans_result`
to shorten the interval to half the case when using
:cpp:func:`spi_slave_transmit`.
The master should always wait for the slave to be ready to start new
transactions. Suggested way is to use a gpio by the slave to indicate whether
it's ready. The example is in :example:`peripherals/spi_slave`.
SCLK frequency requirement
^^^^^^^^^^^^^^^^^^^^^^^^^^
The spi slave is designed to work under 10MHz or lower. The clock and data
cannot be recognized or received correctly if the clock is too fast or
doesn't have a 50% duty cycle.
Moreover, there are more requirements if the data meets the timing
requirement:
- Read (MOSI):
Given that the MOSI is valid right at the launch edge, the slave can
read data correctly. Luckily, it's usually the case for most masters.
- Write (MISO):
To meet the requirement that MISO is stable before the next latch edge of
SPI clock, the output delay of MISO signal should be shorter than half a
clock. The output delay and frequency limitation (given that the clock is
balanced) of different cases are as below :
+-------------+---------------------------+------------------------+
| | Output delay of MISO (ns) | Freq. limit (MHZ) |
+=============+===========================+========================+
| IOMUX | 43.75 | <11.4 |
+-------------+---------------------------+------------------------+
| GPIO matrix | 68.75 | <7.2 |
+-------------+---------------------------+------------------------+
Note:
1. Random error will happen if the frequency exactly equals the
limitation
2. The clock uncertainty between master and slave (12.5ns) is
included.
3. The output delay is measured under ideal case (free of load). When
the loading of MISO pin is too heavy, the output delay will be longer,
and the maximum allowed frequency will be lower.
There is an exceptions: The frequency is allowed to be higher if the
master has more toleration for the MISO setup time, e.g. latch data at
the next edge than expected, or configurable latching time.
.. _spi_dma_known_issues:
Restrictions and Known issues
-------------------------------
@ -96,6 +213,17 @@ Restrictions and Known issues
Also, master should write lengths which are a multiple of 4 bytes. Data
longer than that will be discarded.
2. Furthurmore, the DMA requires a spi mode 1/3 timing. When using spi mode
0/2, the MISO signal has to output half a clock earlier to meet the timing.
The new timing is as below:
.. image:: /../_static/spi_slave_miso_dma.png
The hold time after the latch edge is 68.75ns (when GPIO matrix is
bypassed), no longer half a SPI clock. The master should sample immediately
at the latch edge, or communicate in mode 1/3. Or just initial the spi
slave without DMA.
Application Example
-------------------