Skip to content

Commit

Permalink
Merge branch 'feat/spi_slave_timing' into 'master'
Browse files Browse the repository at this point in the history
feat(spi_slave): fine tune the timing of SPI slave

See merge request idf/esp-idf!3925
  • Loading branch information
igrr committed Jan 29, 2019
2 parents 44b97b7 + 41e58bc commit 4725364
Show file tree
Hide file tree
Showing 20 changed files with 1,795 additions and 567 deletions.
25 changes: 25 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 7 additions & 2 deletions components/driver/spi_master.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
64 changes: 50 additions & 14 deletions components/driver/spi_slave.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions components/driver/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
3 changes: 3 additions & 0 deletions components/driver/test/component.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
187 changes: 187 additions & 0 deletions components/driver/test/include/test/test_common_spi.h
Original file line number Diff line number Diff line change
@@ -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_
Loading

0 comments on commit 4725364

Please sign in to comment.