Merge branch 'feat/spi_master_timing' into 'master'

feature(spi_master): fine tune the timing of spi master

See merge request idf/esp-idf!2301
This commit is contained in:
Ivan Grokhotkov 2018-06-07 20:19:06 +08:00
commit ab538bb9f3
16 changed files with 999 additions and 335 deletions

View file

@ -100,11 +100,9 @@ bool spicommon_dma_chan_claim(int dma_chan);
*/
bool spicommon_dma_chan_free(int dma_chan);
#define SPICOMMON_BUSFLAG_SLAVE 0 ///< Initialize I/O in slave mode
#define SPICOMMON_BUSFLAG_MASTER (1<<0) ///< Initialize I/O in master mode
#define SPICOMMON_BUSFLAG_NATIVE_PINS (1<<1) ///< Check using native pins. Or indicates the pins are configured through the IO mux rather than GPIO matrix.
#define SPICOMMON_BUSFLAG_NATIVE_PINS (1<<1) ///< Check using iomux pins. Or indicates the pins are configured through the IO mux rather than GPIO matrix.
#define SPICOMMON_BUSFLAG_SCLK (1<<2) ///< Check existing of SCLK pin. Or indicates CLK line initialized.
#define SPICOMMON_BUSFLAG_MISO (1<<3) ///< Check existing of MISO pin. Or indicates MISO line initialized.
#define SPICOMMON_BUSFLAG_MOSI (1<<4) ///< Check existing of MOSI pin. Or indicates CLK line initialized.
@ -125,7 +123,7 @@ bool spicommon_dma_chan_free(int dma_chan);
* @param flags Combination of SPICOMMON_BUSFLAG_* flags, set to ensure the pins set are capable with some functions:
* - ``SPICOMMON_BUSFLAG_MASTER``: Initialize I/O in master mode
* - ``SPICOMMON_BUSFLAG_SLAVE``: Initialize I/O in slave mode
* - ``SPICOMMON_BUSFLAG_NATIVE_PINS``: Pins set should match the native pins of the controller.
* - ``SPICOMMON_BUSFLAG_NATIVE_PINS``: Pins set should match the iomux pins of the controller.
* - ``SPICOMMON_BUSFLAG_SCLK``, ``SPICOMMON_BUSFLAG_MISO``, ``SPICOMMON_BUSFLAG_MOSI``:
* Make sure SCLK/MISO/MOSI is/are set to a valid GPIO. Also check output capability according to the mode.
* - ``SPICOMMON_BUSFLAG_DUAL``: Make sure both MISO and MOSI are output capable so that DIO mode is capable.
@ -133,7 +131,7 @@ bool spicommon_dma_chan_free(int dma_chan);
* - ``SPICOMMON_BUSFLAG_QUAD``: Combination of ``SPICOMMON_BUSFLAG_DUAL`` and ``SPICOMMON_BUSFLAG_WPHD``.
* @param[out] flags_o A SPICOMMON_BUSFLAG_* flag combination of bus abilities will be written to this address.
* Leave to NULL if not needed.
* - ``SPICOMMON_BUSFLAG_NATIVE_PINS``: The bus is connected to native pins.
* - ``SPICOMMON_BUSFLAG_NATIVE_PINS``: The bus is connected to iomux pins.
* - ``SPICOMMON_BUSFLAG_SCLK``, ``SPICOMMON_BUSFLAG_MISO``, ``SPICOMMON_BUSFLAG_MOSI``: The bus has
* CLK/MISO/MOSI connected.
* - ``SPICOMMON_BUSFLAG_DUAL``: The bus is capable with DIO mode.

View file

@ -1,4 +1,4 @@
// Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2010-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -22,6 +22,19 @@
#include "driver/spi_common.h"
/** SPI master clock is divided by 80MHz apb clock. Below defines are example frequencies, and are accurate. Be free to specify a random frequency, it will be rounded to closest frequency (to macros below if above 8MHz).
* 8MHz
*/
#define SPI_MASTER_FREQ_8M (APB_CLK_FREQ/10)
#define SPI_MASTER_FREQ_9M (APB_CLK_FREQ/9) ///< 8.89MHz
#define SPI_MASTER_FREQ_10M (APB_CLK_FREQ/8) ///< 10MHz
#define SPI_MASTER_FREQ_11M (APB_CLK_FREQ/7) ///< 11.43MHz
#define SPI_MASTER_FREQ_13M (APB_CLK_FREQ/6) ///< 13.33MHz
#define SPI_MASTER_FREQ_16M (APB_CLK_FREQ/5) ///< 16MHz
#define SPI_MASTER_FREQ_20M (APB_CLK_FREQ/4) ///< 20MHz
#define SPI_MASTER_FREQ_26M (APB_CLK_FREQ/3) ///< 26.67MHz
#define SPI_MASTER_FREQ_40M (APB_CLK_FREQ/2) ///< 40MHz
#define SPI_MASTER_FREQ_80M (APB_CLK_FREQ/1) ///< 80MHz
#ifdef __cplusplus
extern "C"
@ -35,7 +48,7 @@ extern "C"
#define SPI_DEVICE_POSITIVE_CS (1<<3) ///< Make CS positive during a transaction instead of negative
#define SPI_DEVICE_HALFDUPLEX (1<<4) ///< Transmit data before receiving it, instead of simultaneously
#define SPI_DEVICE_CLK_AS_CS (1<<5) ///< Output clock on CS line if CS is active
/** There are timing issue when reading at high frequency (the frequency is related to whether native pins are used, valid time after slave sees the clock).
/** There are timing issue when reading at high frequency (the frequency is related to whether iomux pins are used, valid time after slave sees the clock).
* - In half-duplex mode, the driver automatically inserts dummy bits before reading phase to fix the timing issue. Set this flag to disable this feature.
* - In full-duplex mode, however, the hardware cannot use dummy bits, so there is no way to prevent data being read from getting corrupted.
* Set this flag to confirm that you're going to work with output only, or read without dummy bits at your own risk.
@ -57,7 +70,12 @@ typedef struct {
uint8_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
uint8_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.
uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)
int clock_speed_hz; ///< Clock speed, in Hz
int clock_speed_hz; ///< Clock speed, divisors of 80MHz, in Hz. See ``SPI_MASTER_FREQ_*``.
int input_delay_ns; /**< Maximum data valid time of slave. The time required between SCLK and MISO
valid, including the possible clock delay from slave to master. The driver uses this value to give an extra
delay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timing
performance at high frequency (over 8MHz), it's suggest to have the right value.
*/
int spics_io_num; ///< CS GPIO pin for this device, or -1 if not used
uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags
int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same time
@ -253,6 +271,30 @@ esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *tra
*/
int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t* reg_o);
/**
* @brief Calculate the timing settings of specified frequency and settings.
*
* @param gpio_is_used True if using GPIO matrix, or False if iomux pins are used.
* @param input_delay_ns Input delay from SCLK launch edge to MISO data valid.
* @param eff_clk Effective clock frequency (in Hz) from spi_cal_clock.
* @param dummy_o Address of dummy bits used output. Set to NULL if not needed.
* @param cycles_remain_o Address of cycles remaining (after dummy bits are used) output.
* - -1 If too many cycles remaining, suggest to compensate half a clock.
* - 0 If no remaining cycles or dummy bits are not used.
* - positive value: cycles suggest to compensate.
* @note If **dummy_o* is not zero, it means dummy bits should be applied in half duplex mode, and full duplex mode may not work.
*/
void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int* dummy_o, int* cycles_remain_o);
/**
* @brief Get the frequency limit of current configurations.
* SPI master working at this limit is OK, while above the limit, full duplex mode and DMA will not work,
* and dummy bits will be aplied in the half duplex mode.
* @param gpio_is_used True if using GPIO matrix, or False if native pins are used.
* @param input_delay_ns Input delay from SCLK launch edge to MISO data valid.
* @return Frequency limit of current configurations.
*/
int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns);
#ifdef __cplusplus
}

View file

@ -65,12 +65,12 @@ typedef struct {
const uint8_t spihd_in;
const uint8_t spics_out[3]; // /CS GPIO output mux signals
const uint8_t spics_in;
const uint8_t spiclk_native; //IO pins of IO_MUX muxed signals
const uint8_t spid_native;
const uint8_t spiq_native;
const uint8_t spiwp_native;
const uint8_t spihd_native;
const uint8_t spics0_native;
const uint8_t spiclk_iomux_pin; //IO pins of IO_MUX muxed signals
const uint8_t spid_iomux_pin;
const uint8_t spiq_iomux_pin;
const uint8_t spiwp_iomux_pin;
const uint8_t spihd_iomux_pin;
const uint8_t spics0_iomux_pin;
const uint8_t irq; //irq source for interrupt mux
const uint8_t irq_dma; //dma irq source for interrupt mux
const periph_module_t module; //peripheral module, for enabling clock etc
@ -94,12 +94,12 @@ static const spi_signal_conn_t io_signal[3] = {
.spihd_in = SPIHD_IN_IDX,
.spics_out = {SPICS0_OUT_IDX, SPICS1_OUT_IDX, SPICS2_OUT_IDX},
.spics_in = SPICS0_IN_IDX,
.spiclk_native = 6,
.spid_native = 8,
.spiq_native = 7,
.spiwp_native = 10,
.spihd_native = 9,
.spics0_native = 11,
.spiclk_iomux_pin = 6,
.spid_iomux_pin = 8,
.spiq_iomux_pin = 7,
.spiwp_iomux_pin = 10,
.spihd_iomux_pin = 9,
.spics0_iomux_pin = 11,
.irq = ETS_SPI1_INTR_SOURCE,
.irq_dma = ETS_SPI1_DMA_INTR_SOURCE,
.module = PERIPH_SPI_MODULE,
@ -117,12 +117,12 @@ static const spi_signal_conn_t io_signal[3] = {
.spihd_in = HSPIHD_IN_IDX,
.spics_out = {HSPICS0_OUT_IDX, HSPICS1_OUT_IDX, HSPICS2_OUT_IDX},
.spics_in = HSPICS0_IN_IDX,
.spiclk_native = 14,
.spid_native = 13,
.spiq_native = 12,
.spiwp_native = 2,
.spihd_native = 4,
.spics0_native = 15,
.spiclk_iomux_pin = 14,
.spid_iomux_pin = 13,
.spiq_iomux_pin = 12,
.spiwp_iomux_pin = 2,
.spihd_iomux_pin = 4,
.spics0_iomux_pin = 15,
.irq = ETS_SPI2_INTR_SOURCE,
.irq_dma = ETS_SPI2_DMA_INTR_SOURCE,
.module = PERIPH_HSPI_MODULE,
@ -140,12 +140,12 @@ static const spi_signal_conn_t io_signal[3] = {
.spihd_in = VSPIHD_IN_IDX,
.spics_out = {VSPICS0_OUT_IDX, VSPICS1_OUT_IDX, VSPICS2_OUT_IDX},
.spics_in = VSPICS0_IN_IDX,
.spiclk_native = 18,
.spid_native = 23,
.spiq_native = 19,
.spiwp_native = 22,
.spihd_native = 21,
.spics0_native = 5,
.spiclk_iomux_pin = 18,
.spid_iomux_pin = 23,
.spiq_iomux_pin = 19,
.spiwp_iomux_pin = 22,
.spihd_iomux_pin = 21,
.spics0_iomux_pin = 5,
.irq = ETS_SPI3_INTR_SOURCE,
.irq_dma = ETS_SPI3_DMA_INTR_SOURCE,
.module = PERIPH_VSPI_MODULE,
@ -228,7 +228,7 @@ it should be able to be initialized.
*/
esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan, uint32_t flags, uint32_t* flags_o)
{
bool native = true;
bool use_iomux = true;
uint32_t temp_flag=0;
bool quad_pins_exist = true;
//the MISO should be output capable in slave mode, or in DIO/QIO mode.
@ -236,24 +236,24 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
//the MOSI should be output capble in master mode, or in DIO/QIO mode.
bool mosi_output = (flags&SPICOMMON_BUSFLAG_MASTER)!=0 || flags&SPICOMMON_BUSFLAG_DUAL;
//check pins existence and if the selected pins correspond to the native pins of the peripheral
//check pins existence and if the selected pins correspond to the iomux pins of the peripheral
if (bus_config->sclk_io_num>=0) {
temp_flag |= SPICOMMON_BUSFLAG_SCLK;
SPI_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "sclk not valid", ESP_ERR_INVALID_ARG);
if (bus_config->sclk_io_num != io_signal[host].spiclk_native) native = false;
if (bus_config->sclk_io_num != io_signal[host].spiclk_iomux_pin) use_iomux = false;
} else {
SPI_CHECK((flags&SPICOMMON_BUSFLAG_SCLK)==0, "sclk pin required.", ESP_ERR_INVALID_ARG);
}
if (bus_config->quadwp_io_num>=0) {
SPI_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp not valid", ESP_ERR_INVALID_ARG);
if (bus_config->quadwp_io_num != io_signal[host].spiwp_native) native = false;
if (bus_config->quadwp_io_num != io_signal[host].spiwp_iomux_pin) use_iomux = false;
} else {
quad_pins_exist = false;
SPI_CHECK((flags&SPICOMMON_BUSFLAG_WPHD)==0, "spiwp pin required.", ESP_ERR_INVALID_ARG);
}
if (bus_config->quadhd_io_num>=0) {
SPI_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd not valid", ESP_ERR_INVALID_ARG);
if (bus_config->quadhd_io_num != io_signal[host].spihd_native) native = false;
if (bus_config->quadhd_io_num != io_signal[host].spihd_iomux_pin) use_iomux = false;
} else {
quad_pins_exist = false;
SPI_CHECK((flags&SPICOMMON_BUSFLAG_WPHD)==0, "spihd pin required.", ESP_ERR_INVALID_ARG);
@ -265,7 +265,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
} else {
SPI_CHECK(GPIO_IS_VALID_GPIO(bus_config->mosi_io_num), "mosi not valid", ESP_ERR_INVALID_ARG);
}
if (bus_config->mosi_io_num != io_signal[host].spid_native) native = false;
if (bus_config->mosi_io_num != io_signal[host].spid_iomux_pin) use_iomux = false;
} else {
SPI_CHECK((flags&SPICOMMON_BUSFLAG_MOSI)==0, "mosi pin required.", ESP_ERR_INVALID_ARG);
}
@ -276,7 +276,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
} else {
SPI_CHECK(GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "miso not valid", ESP_ERR_INVALID_ARG);
}
if (bus_config->miso_io_num != io_signal[host].spiq_native) native = false;
if (bus_config->miso_io_num != io_signal[host].spiq_iomux_pin) use_iomux = false;
} else {
SPI_CHECK((flags&SPICOMMON_BUSFLAG_MISO)==0, "miso pin required.", ESP_ERR_INVALID_ARG);
}
@ -287,13 +287,13 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
}
//set flags for QUAD mode according to the existence of wp and hd
if (quad_pins_exist) temp_flag |= SPICOMMON_BUSFLAG_WPHD;
//check native pins if required.
SPI_CHECK((flags&SPICOMMON_BUSFLAG_NATIVE_PINS)==0 || native, "not using native pins", ESP_ERR_INVALID_ARG);
//check iomux pins if required.
SPI_CHECK((flags&SPICOMMON_BUSFLAG_NATIVE_PINS)==0 || use_iomux, "not using iomux pins", ESP_ERR_INVALID_ARG);
if (native) {
//All SPI native pin selections resolve to 1, so we put that here instead of trying to figure
if (use_iomux) {
//All SPI iomux pin selections resolve to 1, so we put that here instead of trying to figure
//out which FUNC_GPIOx_xSPIxx to grab; they all are defined to 1 anyway.
ESP_LOGD(SPI_TAG, "SPI%d use native pins.", host );
ESP_LOGD(SPI_TAG, "SPI%d use iomux pins.", host );
if (bus_config->mosi_io_num >= 0) {
gpio_iomux_in(bus_config->mosi_io_num, io_signal[host].spid_in);
gpio_iomux_out(bus_config->mosi_io_num, FUNC_SPI, false);
@ -378,11 +378,11 @@ static void reset_func_to_gpio(int func)
esp_err_t spicommon_bus_free_io(spi_host_device_t host)
{
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spid_native], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spid_native], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiq_native], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiq_native], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiclk_native], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiclk_native], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiwp_native], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiwp_native], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spihd_native], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spihd_native], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spid_iomux_pin], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spid_iomux_pin], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiq_iomux_pin], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiq_iomux_pin], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiclk_iomux_pin], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiclk_iomux_pin], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spiwp_iomux_pin], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spiwp_iomux_pin], PIN_FUNC_GPIO);
if (REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spihd_iomux_pin], MCU_SEL) == 1) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spihd_iomux_pin], PIN_FUNC_GPIO);
reset_func_to_gpio(io_signal[host].spid_out);
reset_func_to_gpio(io_signal[host].spiq_out);
reset_func_to_gpio(io_signal[host].spiclk_out);
@ -393,7 +393,7 @@ esp_err_t spicommon_bus_free_io(spi_host_device_t host)
void spicommon_cs_initialize(spi_host_device_t host, int cs_io_num, int cs_num, int force_gpio_matrix)
{
if (!force_gpio_matrix && cs_io_num == io_signal[host].spics0_native && cs_num == 0) {
if (!force_gpio_matrix && cs_io_num == io_signal[host].spics0_iomux_pin && cs_num == 0) {
//The cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define.
gpio_iomux_in(cs_io_num, io_signal[host].spics_in);
gpio_iomux_out(cs_io_num, FUNC_SPI, false);
@ -407,8 +407,8 @@ void spicommon_cs_initialize(spi_host_device_t host, int cs_io_num, int cs_num,
void spicommon_cs_free(spi_host_device_t host, int cs_io_num)
{
if (cs_io_num == 0 && REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spics0_native], MCU_SEL) == 1) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spics0_native], PIN_FUNC_GPIO);
if (cs_io_num == 0 && REG_GET_FIELD(GPIO_PIN_MUX_REG[io_signal[host].spics0_iomux_pin], MCU_SEL) == 1) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[io_signal[host].spics0_iomux_pin], PIN_FUNC_GPIO);
}
reset_func_to_gpio(io_signal[host].spics_out[cs_io_num]);
}

View file

@ -96,6 +96,7 @@ typedef struct {
spi_clock_reg_t reg;
int eff_clk;
int dummy_num;
int miso_delay;
} clock_config_t;
struct spi_device_t {
@ -110,9 +111,9 @@ static spi_host_t *spihost[3];
static const char *SPI_TAG = "spi_master";
#define SPI_CHECK(a, str, ret_val) \
#define SPI_CHECK(a, str, ret_val, ...) \
if (!(a)) { \
ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
ESP_LOGE(SPI_TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
return (ret_val); \
}
@ -256,14 +257,38 @@ esp_err_t spi_bus_free(spi_host_device_t host)
return ESP_OK;
}
static inline uint32_t spi_dummy_limit(bool gpio_is_used)
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=APB_CLK_FREQ;
if (!gpio_is_used) {
return apbclk; //dummy bit workaround is not used when native pins are used
const int apbclk_kHz = APB_CLK_FREQ/1000;
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.
int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000;
int dummy_required = apb_period_n/apbclk_n;
int miso_delay = 0;
if (dummy_required > 0) {
//due to the clock delay between master and slave, there's a range in which data is random
//give MISO a delay if needed to make sure we sample at the time MISO is stable
miso_delay = (dummy_required+1)*apbclk_n-apb_period_n-1;
} else {
return apbclk/2; //the dummy bit workaround is used when freq is 40MHz and GPIO matrix is used.
//if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early
if (apb_period_n*4 <= apbclk_n) miso_delay = -1;
}
if (dummy_o!=NULL) *dummy_o = dummy_required;
if (cycles_remain_o!=NULL) *cycles_remain_o = miso_delay;
ESP_LOGD(SPI_TAG,"eff: %d, limit: %dk(/%d), %d dummy, %d delay", eff_clk/1000, apbclk_kHz/(apb_period_n+1), apb_period_n, dummy_required, miso_delay);
}
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.
int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000;
return APB_CLK_FREQ/(apb_period_n+1);
}
/*
@ -276,6 +301,9 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_
int apbclk=APB_CLK_FREQ;
int eff_clk;
int duty_cycle;
int dummy_required;
int miso_delay;
spi_clock_reg_t clk_reg;
SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host]!=NULL, "host not initialized", ESP_ERR_INVALID_STATE);
@ -288,18 +316,23 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_
SPI_CHECK(freecs!=NO_CS, "no free cs pins for host", ESP_ERR_NOT_FOUND);
//The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full
//duplex mode does absolutely nothing on the ESP32.
SPI_CHECK(dev_config->cs_ena_pretrans==0 || (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "cs pretrans delay incompatible with full-duplex", ESP_ERR_INVALID_ARG);
SPI_CHECK(dev_config->cs_ena_pretrans <= 1 || (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "cs pretrans delay > 1 incompatible with full-duplex", ESP_ERR_INVALID_ARG);
SPI_CHECK( dev_config->cs_ena_pretrans != 1 || (dev_config->address_bits == 0 && dev_config->command_bits == 0) ||
(dev_config->flags & SPI_DEVICE_HALFDUPLEX), "In full-duplex mode, only support cs pretrans delay = 1 and without address_bits and command_bits", ESP_ERR_INVALID_ARG);
//Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections.
duty_cycle = (dev_config->duty_cycle_pos==0? 128: dev_config->duty_cycle_pos);
eff_clk = spi_cal_clock(apbclk, dev_config->clock_speed_hz, duty_cycle, (uint32_t*)&clk_reg);
uint32_t dummy_limit = spi_dummy_limit(!(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS));
SPI_CHECK( dev_config->flags & SPI_DEVICE_HALFDUPLEX || (eff_clk/1000/1000) < (dummy_limit/1000/1000) ||
int freq_limit = spi_get_freq_limit(!(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS), dev_config->input_delay_ns);
//GPIO matrix can only change data at 80Mhz rate, which only allows 40MHz SPI clock.
SPI_CHECK(eff_clk <= 40*1000*1000 || spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS, "80MHz only supported on iomux pins", ESP_ERR_INVALID_ARG);
//Speed >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections.
spi_get_timing(!(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS), dev_config->input_delay_ns, eff_clk, &dummy_required, &miso_delay);
SPI_CHECK( dev_config->flags & SPI_DEVICE_HALFDUPLEX || dummy_required == 0 ||
dev_config->flags & SPI_DEVICE_NO_DUMMY,
"When GPIO matrix is used in full-duplex mode at frequency > 26MHz, device cannot read correct data.\n\
"When GPIO matrix is used in full-duplex mode at frequency > %.1fMHz, device cannot read correct data.\n\
Please note the SPI can only work at divisors of 80MHz, and the driver always tries to find the closest frequency to your configuration.\n\
Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output data at higher speed, or read data at your own risk.",
ESP_ERR_INVALID_ARG );
ESP_ERR_INVALID_ARG, freq_limit/1000./1000 );
//Allocate memory for device
spi_device_t *dev=malloc(sizeof(spi_device_t));
@ -319,8 +352,9 @@ Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output dat
// TODO: if we have to change the apb clock among transactions, re-calculate this each time the apb clock lock is acquired.
dev->clk_cfg= (clock_config_t) {
.eff_clk = eff_clk,
.dummy_num = (dev->clk_cfg.eff_clk >= dummy_limit? 1: 0),
.dummy_num = dummy_required,
.reg = clk_reg,
.miso_delay = miso_delay,
};
//Set CS pin, CS options
@ -338,6 +372,8 @@ Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output dat
} else {
spihost[host]->hw->pin.master_cs_pol &= (1<<freecs);
}
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 0;
*handle=dev;
ESP_LOGD(SPI_TAG, "SPI%d: New device added to CS%d, effective clock: %dkHz", host, freecs, dev->clk_cfg.eff_clk/1000);
return ESP_OK;
@ -508,43 +544,24 @@ static void IRAM_ATTR spi_intr(void *arg)
//Reconfigure according to device settings, but only if we change CSses.
if (i!=host->prev_cs) {
const int apbclk=APB_CLK_FREQ;
int effclk=dev->clk_cfg.eff_clk;
spi_set_clock(host->hw, dev->clk_cfg.reg);
//Configure bit order
host->hw->ctrl.rd_bit_order=(dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0;
host->hw->ctrl.wr_bit_order=(dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0;
//Configure polarity
//SPI iface needs to be configured for a delay in some cases.
int nodelay=0;
if ((host->flags&SPICOMMON_BUSFLAG_NATIVE_PINS)!=0) {
if (effclk >= apbclk/2) {
nodelay=1;
}
} else {
uint32_t delay_limit = apbclk/4;
if (effclk >= delay_limit) {
nodelay=1;
}
}
if (dev->cfg.mode==0) {
host->hw->pin.ck_idle_edge=0;
host->hw->user.ck_out_edge=0;
host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
} else if (dev->cfg.mode==1) {
host->hw->pin.ck_idle_edge=0;
host->hw->user.ck_out_edge=1;
host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
} else if (dev->cfg.mode==2) {
host->hw->pin.ck_idle_edge=1;
host->hw->user.ck_out_edge=1;
host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
} else if (dev->cfg.mode==3) {
host->hw->pin.ck_idle_edge=1;
host->hw->user.ck_out_edge=0;
host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
}
//Configure misc stuff
host->hw->user.doutdin=(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1;
@ -552,8 +569,11 @@ static void IRAM_ATTR spi_intr(void *arg)
host->hw->ctrl2.setup_time=dev->cfg.cs_ena_pretrans-1;
host->hw->user.cs_setup=dev->cfg.cs_ena_pretrans?1:0;
host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans-1;
host->hw->user.cs_hold=(dev->cfg.cs_ena_posttrans)?1:0;
//set hold_time to 0 will not actually append delay to CS
//set it to 1 since we do need at least one clock of hold time in most cases
host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans;
if ( host->hw->ctrl2.hold_time == 0 ) host->hw->ctrl2.hold_time = 1;
host->hw->user.cs_hold=1;
//Configure CS pin
host->hw->pin.cs0_dis=(i==0)?0:1;
@ -614,6 +634,7 @@ static void IRAM_ATTR spi_intr(void *arg)
}
}
if (trans_buf->buffer_to_send) {
if (host->dma_chan == 0) {
//Need to copy data to registers manually
@ -634,10 +655,31 @@ static void IRAM_ATTR spi_intr(void *arg)
}
}
//SPI iface needs to be configured for a delay in some cases.
//configure dummy bits
host->hw->user.usr_dummy=(dev->cfg.dummy_bits+extra_dummy)?1:0;
host->hw->user1.usr_dummy_cyclelen=dev->cfg.dummy_bits+extra_dummy-1;
int miso_long_delay = 0;
if (dev->clk_cfg.miso_delay<0) {
//if the data comes too late, delay half a SPI clock to improve reading
miso_long_delay = 1;
host->hw->ctrl2.miso_delay_num = 0;
} else {
//if the data is so fast that dummy_bit is used, delay some apb clocks to meet the timing
host->hw->ctrl2.miso_delay_num = (extra_dummy? dev->clk_cfg.miso_delay: 0);
}
if (dev->cfg.mode==0) {
host->hw->ctrl2.miso_delay_mode=miso_long_delay?2:0;
} else if (dev->cfg.mode==1) {
host->hw->ctrl2.miso_delay_mode=miso_long_delay?1:0;
} else if (dev->cfg.mode==2) {
host->hw->ctrl2.miso_delay_mode=miso_long_delay?1:0;
} else if (dev->cfg.mode==3) {
host->hw->ctrl2.miso_delay_mode=miso_long_delay?2:0;
}
host->hw->mosi_dlen.usr_mosi_dbitlen=trans->length-1;
if ( dev->cfg.flags & SPI_DEVICE_HALFDUPLEX ) {
host->hw->miso_dlen.usr_miso_dbitlen=trans->rxlength-1;

View file

@ -21,10 +21,41 @@
#include "soc/spi_struct.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "driver/spi_pins.h"
#include "freertos/ringbuf.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,\
}
//steal register definition from gpio.c
const uint32_t GPIO_PIN_MUX_REG[GPIO_PIN_COUNT];
#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;
@ -263,30 +294,24 @@ TEST_CASE("SPI Master test, interaction of multiple devs", "[spi][ignore]") {
destroy_spi_bus(handle1);
}
#define NATIVE_SCLK 14
#define NATIVE_MISO 12
#define NATIVE_MOSI 13
#define NATIVE_WP 2
#define NATIVE_HD 4
TEST_CASE("spi bus setting with different pin configs", "[spi]")
{
spi_bus_config_t cfg;
uint32_t flags_o;
uint32_t flags_expected;
ESP_LOGI(TAG, "test 6 native output pins...");
ESP_LOGI(TAG, "test 6 iomux output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_NATIVE_PINS | SPICOMMON_BUSFLAG_QUAD;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test 4 native output pins...");
ESP_LOGI(TAG, "test 4 iomux output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_NATIVE_PINS | SPICOMMON_BUSFLAG_DUAL;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
@ -296,7 +321,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "test 6 output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_QUAD;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MISO, .miso_io_num = NATIVE_MOSI, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MISO, .miso_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
@ -306,7 +331,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "test 4 output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_DUAL;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MISO, .miso_io_num = NATIVE_MOSI, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MISO, .miso_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
@ -315,14 +340,14 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "test master 5 output pins and MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = 34, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = 34, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test slave 5 output pins and MISO on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD;
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
@ -330,14 +355,14 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "test master 3 output pins and MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = 34, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = 34, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test slave 3 output pins and MISO on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO;
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
@ -345,7 +370,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "check native flag for 6 output pins...");
flags_expected = SPICOMMON_BUSFLAG_NATIVE_PINS;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MISO, .miso_io_num = NATIVE_MOSI, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MISO, .miso_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
@ -353,61 +378,61 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
ESP_LOGI(TAG, "check native flag for 4 output pins...");
flags_expected = SPICOMMON_BUSFLAG_NATIVE_PINS;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MISO, .miso_io_num = NATIVE_MOSI, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MISO, .miso_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check dual flag for master 5 output pins and MISO/MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_DUAL;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = 34, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = 34, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check dual flag for master 3 output pins and MISO/MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_DUAL;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = 34, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = 34, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = 34, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check sclk flag...");
flags_expected = SPICOMMON_BUSFLAG_SCLK;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = NATIVE_MISO, .sclk_io_num = -1, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = -1, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check mosi flag...");
flags_expected = SPICOMMON_BUSFLAG_MOSI;
cfg = (spi_bus_config_t){.mosi_io_num = -1, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = -1, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check miso flag...");
flags_expected = SPICOMMON_BUSFLAG_MISO;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = -1, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = -1, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check quad flag...");
flags_expected = SPICOMMON_BUSFLAG_QUAD;
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = -1, .quadwp_io_num = NATIVE_WP,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = -1, .quadwp_io_num = HSPI_IOMUX_PIN_NUM_WP,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
cfg = (spi_bus_config_t){.mosi_io_num = NATIVE_MOSI, .miso_io_num = NATIVE_MISO, .sclk_io_num = NATIVE_SCLK, .quadhd_io_num = NATIVE_HD, .quadwp_io_num = -1,
cfg = (spi_bus_config_t){.mosi_io_num = HSPI_IOMUX_PIN_NUM_MOSI, .miso_io_num = HSPI_IOMUX_PIN_NUM_MISO, .sclk_io_num = HSPI_IOMUX_PIN_NUM_CLK, .quadhd_io_num = HSPI_IOMUX_PIN_NUM_HD, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(HSPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
@ -474,14 +499,21 @@ TEST_CASE("SPI Master no response when switch from host1 (HSPI) to host2 (VSPI)"
}
IRAM_ATTR static uint32_t data_iram[320];
DRAM_ATTR static uint32_t data_dram[320];
DRAM_ATTR static uint32_t data_dram[320]={0};
//force to place in code area.
static const uint32_t data_drom[320] = {0};
#define PIN_NUM_MISO 25
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 19
#define PIN_NUM_CS 22
#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
@ -562,13 +594,6 @@ TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]")
TEST_ASSERT(spi_bus_free(HSPI_HOST) == ESP_OK);
}
static inline void int_connect( uint32_t gpio, uint32_t sigo, uint32_t sigi )
{
gpio_matrix_out( gpio, sigo, false, false );
gpio_matrix_in( gpio, sigi, false );
}
//this part tests 3 DMA issues in master mode, full-duplex in IDF2.1
// 1. RX buffer not aligned (start and end)
// 2. not setting rx_buffer
@ -581,7 +606,7 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
esp_err_t ret;
spi_device_handle_t spi;
spi_bus_config_t buscfg={
.miso_io_num=PIN_NUM_MISO,
.miso_io_num=PIN_NUM_MOSI,
.mosi_io_num=PIN_NUM_MOSI,
.sclk_io_num=PIN_NUM_CLK,
.quadwp_io_num=-1,
@ -601,8 +626,8 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
TEST_ASSERT(ret==ESP_OK);
//do internal connection
int_connect( PIN_NUM_MOSI, HSPID_OUT_IDX, HSPIQ_IN_IDX );
//connect MOSI to two devices breaks the output, fix it.
gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, HSPID_OUT_IDX);
memset(rx_buf, 0x66, 320);
@ -633,10 +658,10 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
// no rx, skip check
} else if ( i == 2 ) {
//test rx length = tx length-1
TEST_ASSERT( memcmp(t.tx_buffer, t.rx_buffer, t.length/8-1)==0 );
TEST_ASSERT_EQUAL_HEX8_ARRAY(t.tx_buffer, t.rx_buffer, t.length/8-1 );
} else {
//normal check
TEST_ASSERT( memcmp(t.tx_buffer, t.rx_buffer, t.length/8)==0 );
TEST_ASSERT_EQUAL_HEX8_ARRAY(t.tx_buffer, t.rx_buffer, t.length/8 );
}
}
@ -649,53 +674,25 @@ 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};
DRAM_ATTR static uint8_t slave_send[] = { 0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0 };
static void master_init( spi_device_handle_t* spi, int mode, uint32_t speed)
static void master_deinit(spi_device_handle_t spi)
{
esp_err_t ret;
spi_bus_config_t buscfg={
.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
};
spi_device_interface_config_t devcfg={
.clock_speed_hz=speed, //currently only up to 4MHz for internel connect
.mode=mode, //SPI mode 0
.spics_io_num=PIN_NUM_CS, //CS pin
.queue_size=16, //We want to be able to queue 7 transactions at a time
.pre_cb=NULL,
.cs_ena_pretrans = 0,
};
//Initialize the SPI bus
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
TEST_ASSERT(ret==ESP_OK);
//Attach the LCD to the SPI bus
ret=spi_bus_add_device(HSPI_HOST, &devcfg, spi);
TEST_ASSERT(ret==ESP_OK);
TEST_ESP_OK( spi_bus_remove_device(spi) );
TEST_ESP_OK( spi_bus_free(HSPI_HOST) );
}
static void slave_init(int mode, int dma_chan)
#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)
{
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=PIN_NUM_MOSI,
.miso_io_num=PIN_NUM_MISO,
.sclk_io_num=PIN_NUM_CLK
};
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg={
.mode=mode,
.spics_io_num=PIN_NUM_CS,
.queue_size=3,
.flags=0,
};
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
gpio_set_pull_mode(PIN_NUM_MOSI, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(PIN_NUM_CLK, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(PIN_NUM_CS, GPIO_PULLUP_ONLY);
//Initialize SPI slave interface
TEST_ESP_OK( spi_slave_initialize(VSPI_HOST, &buscfg, &slvcfg, dma_chan) );
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 {
@ -705,10 +702,12 @@ typedef struct {
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;
@ -723,6 +722,7 @@ esp_err_t init_slave_context(spi_slave_task_context_t *context)
if ( context->data_received == NULL ) {
return ESP_ERR_NO_MEM;
}
context->spi=VSPI_HOST;
return ESP_OK;
}
@ -736,12 +736,16 @@ void deinit_slave_context(spi_slave_task_context_t *context)
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+4];
uint8_t recvbuf[320+8];
slave_txdata_t txdata;
ESP_LOGI( SLAVE_TAG, "slave up" );
@ -749,18 +753,19 @@ static void task_slave(void* arg)
while( 1 ) {
xQueueReceive( queue, &txdata, portMAX_DELAY );
ESP_LOGI( "test", "received: %p", txdata.start );
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+4;
t.rx_buffer = recvbuf+8;
//loop until trans_len != 0 to skip glitches
do {
TEST_ESP_OK( spi_slave_transmit( VSPI_HOST, &t, portMAX_DELAY ) );
TEST_ESP_OK( spi_slave_transmit( context->spi, &t, portMAX_DELAY ) );
} while ( t.trans_len == 0 );
*(uint32_t*)recvbuf = t.trans_len;
*(uint8_t**)(recvbuf+4) = txdata.start;
ESP_LOGI( SLAVE_TAG, "received: %d", t.trans_len );
xRingbufferSend( ringbuf, recvbuf, 4+(t.trans_len+7)/8, portMAX_DELAY );
xRingbufferSend( ringbuf, recvbuf, 8+(t.trans_len+7)/8, portMAX_DELAY );
}
}
@ -775,16 +780,33 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]")
TEST_ASSERT( err == ESP_OK );
spi_device_handle_t spi;
//initial master, mode 0, 1MHz
master_init( &spi, 0, 1*1000*1000 );
//initial slave, mode 0, no dma
slave_init(0, 0);
//do internal connection
int_connect( PIN_NUM_MOSI, HSPID_OUT_IDX, VSPIQ_IN_IDX );
int_connect( PIN_NUM_MISO, VSPIQ_OUT_IDX, HSPID_IN_IDX );
int_connect( PIN_NUM_CS, HSPICS0_OUT_IDX, VSPICS0_IN_IDX );
int_connect( PIN_NUM_CLK, HSPICLK_OUT_IDX, VSPICLK_IN_IDX );
//initial master, mode 0, 1MHz
spi_bus_config_t buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
TEST_ESP_OK(spi_bus_initialize(HSPI_HOST, &buscfg, 1));
spi_device_interface_config_t devcfg=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.clock_speed_hz = 1*1000*1000; //currently only up to 4MHz for internel connect
devcfg.mode = 0;
devcfg.cs_ena_posttrans = 2;
TEST_ESP_OK(spi_bus_add_device(HSPI_HOST, &devcfg, &spi));
//initial slave, mode 0, no dma
int dma_chan = 0;
int slave_mode = 0;
spi_bus_config_t slv_buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
spi_slave_interface_config_t slvcfg=SPI_SLAVE_TEST_DEFAULT_CONFIG();
slvcfg.mode = slave_mode;
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
slave_pull_up(&buscfg, slvcfg.spics_io_num);
//Initialize SPI slave interface
TEST_ESP_OK( spi_slave_initialize(VSPI_HOST, &slv_buscfg, &slvcfg, dma_chan) );
//connecting pins to two peripherals breaks the output, fix it.
gpio_output_sel(PIN_NUM_MOSI, FUNC_GPIO, HSPID_OUT_IDX);
gpio_output_sel(PIN_NUM_MISO, FUNC_GPIO, VSPIQ_OUT_IDX);
gpio_output_sel(PIN_NUM_CS, FUNC_GPIO, HSPICS0_OUT_IDX);
gpio_output_sel(PIN_NUM_CLK, FUNC_GPIO, HSPICLK_OUT_IDX);
TaskHandle_t handle_slave;
xTaskCreate( task_slave, "spi_slave", 4096, &slave_context, 0, &handle_slave);
@ -879,7 +901,355 @@ 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;
}
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*(i*2+1),
.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*(i*2+1),
.tx_buffer = master_send+i,
};
}
} else {
for (int i = 0; i < 8; i++ ) {
trans[i] = (spi_transaction_t) {
.flags = 0,
.length = 8*(i*2+1),
.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 = 256,
};
}
}
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
********************************************************************************/
#define RECORD_TIME_PREPARE() uint32_t __t1, __t2
#define RECORD_TIME_START() do {__t1 = xthal_get_ccount();}while(0)
#define RECORD_TIME_END(p_time) do{__t2 = xthal_get_ccount(); *p_time = (__t2-__t1)/240;}while(0)
@ -887,21 +1257,10 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]")
static void speed_setup(spi_device_handle_t* spi, bool use_dma)
{
esp_err_t ret;
spi_bus_config_t buscfg={
.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
};
spi_device_interface_config_t devcfg={
.clock_speed_hz=10*1000*1000, //currently only up to 4MHz for internel connect
.mode=0, //SPI mode 0
.spics_io_num=PIN_NUM_CS, //CS pin
.queue_size=8, //We want to be able to queue 7 transactions at a time
.pre_cb=NULL,
.cs_ena_pretrans = 0,
};
spi_bus_config_t buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
spi_device_interface_config_t devcfg=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.queue_size=8; //We want to be able to queue 7 transactions at a time
//Initialize the SPI bus and the device to test
ret=spi_bus_initialize(HSPI_HOST, &buscfg, (use_dma?1:0));
TEST_ASSERT(ret==ESP_OK);
@ -982,3 +1341,4 @@ TEST_CASE("spi_speed","[spi]")
}
speed_deinit(spi);
}

View file

@ -0,0 +1,32 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _DRIVER_SPI_PINS_H_
#define _DRIVER_SPI_PINS_H_
#define HSPI_IOMUX_PIN_NUM_MISO 12
#define HSPI_IOMUX_PIN_NUM_MOSI 13
#define HSPI_IOMUX_PIN_NUM_CLK 14
#define HSPI_IOMUX_PIN_NUM_CS 15
#define HSPI_IOMUX_PIN_NUM_WP 2
#define HSPI_IOMUX_PIN_NUM_HD 4
#define VSPI_IOMUX_PIN_NUM_MISO 19
#define VSPI_IOMUX_PIN_NUM_MOSI 23
#define VSPI_IOMUX_PIN_NUM_CLK 18
#define VSPI_IOMUX_PIN_NUM_CS 5
#define VSPI_IOMUX_PIN_NUM_WP 22
#define VSPI_IOMUX_PIN_NUM_HD 21
#endif

View file

@ -0,0 +1,17 @@
.. this picture is generated by https://wavedrom.com/, using the sphinx plugin sphinxcontrib-wavedrom
.. due to plugin issue, we cannot place only the picture information in a standalone file, but have to take .. wavedrom:: inside
.. wavedrom::
{ signal: [
{ name: 'SCLK', wave: 'p...', node: '.ad...' },
{ name: 'MISO', wave: 'x3x.', node: '.b...', phase:-1.8 },
{ name: 'MISO delayed', wave: 'x3x.', node: '.c.', phase:-2.4 },
],
edge: [
'a|->b input delay',
'b|->c gpio delay',
'c-|>d setup slack'
],
config: { hscale: 3 }
}

View file

@ -0,0 +1,19 @@
.. this picture is generated by https://wavedrom.com/, using the sphinx plugin sphinxcontrib-wavedrom
.. due to plugin issue, we cannot place only the picture information in a standalone file, but have to take .. wavedrom:: inside
.. wavedrom::
{ signal: [
{ name: 'SCLK', wave: '0.1....0....1...', node: '..a.........e'},
{ name: 'SLV_CLK',wave: 'p..............', node: '..b..', phase: -0.5 },
{ name: 'MISO', wave: 'x...3.....x....', node: '....c', phase:-0.5},
{ name: 'MISO delayed', wave: 'x.......3.....x.', node: '........d'},
],
edge: [
'a|->b sample delay',
'b|->c slave output delay',
'c|->d gpio delay',
'd-|>e setup slack',
'a-|>c input delay'
],
}

View file

@ -0,0 +1,33 @@
# this is a GNUPLOT script generating the figure of spi_master_freq_tv.png
set xlabel "Input delay (ns)"
set xrange [0: 125]
set ylabel "Fmax (MHz)"
set yrange [0: 81]
set xtics 12.5 textcolor rgb "black"
set ytics 10 textcolor rgb "black"
set border 3 lc rgb "gray" lw 2
set grid lt -1 lc rgb "gray" lw 2
set samples 10000
set terminal png size 700,500
set output "plot.png"
apb = 12.5
#each line is broken into 10 pieces by the range determined by i
f1(i,x) = (x>= i*apb) && (x < (i+1)*apb) ? 80./(i+1) : 1/0
set style circle radius graph 0.008
#solid and empty circles are draw by the coordinates given in the csv
plot [0:125]\
f1(-1, x) lw 3lc rgb "blue" title "IOMUX",\
for [i=0:9] f1(i, x) with lines lw 3 lc rgb "blue" notitle,\
f1(0, x+25) lw 3 lc rgb "red" title "GPIO",\
for [i=2:11] f1(i, x+25) with lines lw 3 lc rgb "red" notitle, \
"tv.csv" using 1:2 with circles notitle fill solid fc rgb "blue", \
"tv.csv" using 1:4 with circles notitle fc rgb "blue",\
"tv.csv" using 1:3 with circles notitle fill solid fc rgb "red" ,\
"tv.csv" using 1:5 with circles notitle fc rgb "red"

Binary file not shown.

29
docs/_static/diagrams/spi_master/tv.csv vendored Normal file
View file

@ -0,0 +1,29 @@
0 80 26.66666667 #DIV/0! #DIV/0!
12.5 40 20 80 26.66666667
25 26.66666667 16 40 20
37.5 20 13.33333333 26.66666667 16
50 16 11.42857143 20 13.33333333
62.5 13.33333333 10 16 11.42857143
75 11.42857143 8.888888889 13.33333333 10
87.5 10 8 11.42857143 8.888888889
100 8.888888889 7.272727273 10 8
112.5 8 6.666666667 8.888888889 7.272727273
125 7.272727273 6.153846154 8 6.666666667
137.5 6.666666667 5.714285714 7.272727273 6.153846154
150 6.153846154 5.333333333 6.666666667 5.714285714
162.5 5.714285714 5 6.153846154 5.333333333
175 5.333333333 4.705882353 5.714285714 5
187.5 5 4.444444444 5.333333333 4.705882353
200 4.705882353 4.210526316 5 4.444444444
212.5 4.444444444 4 4.705882353 4.210526316
225 4.210526316 3.80952381 4.444444444 4
237.5 4 3.636363636 4.210526316 3.80952381
250 3.80952381 3.47826087 4 3.636363636
262.5 3.636363636 3.333333333 3.80952381 3.47826087
275 3.47826087 3.2 3.636363636 3.333333333
287.5 3.333333333 3.076923077 3.47826087 3.2
300 3.2 2.962962963 3.333333333 3.076923077
312.5 3.076923077 2.857142857 3.2 2.962962963
325 2.962962963 2.75862069 3.076923077 2.857142857
337.5 2.857142857 2.666666667 2.962962963 2.75862069
350 2.75862069 2.580645161 2.857142857 2.666666667
1 0 80 26.66666667 #DIV/0! #DIV/0!
2 12.5 40 20 80 26.66666667
3 25 26.66666667 16 40 20
4 37.5 20 13.33333333 26.66666667 16
5 50 16 11.42857143 20 13.33333333
6 62.5 13.33333333 10 16 11.42857143
7 75 11.42857143 8.888888889 13.33333333 10
8 87.5 10 8 11.42857143 8.888888889
9 100 8.888888889 7.272727273 10 8
10 112.5 8 6.666666667 8.888888889 7.272727273
11 125 7.272727273 6.153846154 8 6.666666667
12 137.5 6.666666667 5.714285714 7.272727273 6.153846154
13 150 6.153846154 5.333333333 6.666666667 5.714285714
14 162.5 5.714285714 5 6.153846154 5.333333333
15 175 5.333333333 4.705882353 5.714285714 5
16 187.5 5 4.444444444 5.333333333 4.705882353
17 200 4.705882353 4.210526316 5 4.444444444
18 212.5 4.444444444 4 4.705882353 4.210526316
19 225 4.210526316 3.80952381 4.444444444 4
20 237.5 4 3.636363636 4.210526316 3.80952381
21 250 3.80952381 3.47826087 4 3.636363636
22 262.5 3.636363636 3.333333333 3.80952381 3.47826087
23 275 3.47826087 3.2 3.636363636 3.333333333
24 287.5 3.333333333 3.076923077 3.47826087 3.2
25 300 3.2 2.962962963 3.333333333 3.076923077
26 312.5 3.076923077 2.857142857 3.2 2.962962963
27 325 2.962962963 2.75862069 3.076923077 2.857142857
28 337.5 2.857142857 2.666666667 2.962962963 2.75862069
29 350 2.75862069 2.580645161 2.857142857 2.666666667

BIN
docs/_static/miso_timing_waveform.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/_static/spi_master_freq_tv.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
docs/_static/spi_miso.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -73,18 +73,25 @@ Something similar is true for the read and write phase: not every transaction ne
as well as data to be read. When ``rx_buffer`` is NULL (and SPI_USE_RXDATA) is not set) the read phase
is skipped. When ``tx_buffer`` is NULL (and SPI_USE_TXDATA) is not set) the write phase is skipped.
GPIO matrix and native pins
GPIO matrix and IOMUX
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Most peripheral pins in ESP32 can directly connect to a GPIO, which is called *native pin*. When the peripherals are
required to work with other pins than the native pins, ESP32 use a *GPIO matrix* to realize this. If one of the pins is
not native, the driver automatically routes all the signals to the GPIO matrix, which works under 80MHz. The signals are
sampled and sent to peripherals or the GPIOs.
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.
When the GPIO matrix is used, signals cannot propogate to the peripherals over 40MHz, and the setup time of MISO is very
likely violated. Hence the clock frequency limitation is a little lower than the case without GPIO matrix.
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.
Native pins for SPI controllers are as below:
When the GPIO matrix is used, signals faster than 40MHz cannot propagate and the setup time of MISO is more easily
violated, since the input delay of MISO signal is increased. The maximum clock frequency with GPIO Matrix is 40MHz
or less, whereas using all IOMUX pins allows 80MHz.
.. note:: More details about influence of input delay on the maximum clock frequency, see :ref:`timing_considerations` below.
IOMUX pins for SPI controllers are as below:
+----------+------+------+
| Pin Name | HSPI | VSPI |
@ -191,13 +198,15 @@ speed a lot if small transactions are used.
Typical transaction interval with one byte data is as below:
+-----------------------+---------+
| Transaction Time (us) | Typical |
+=======================+=========+
+--------+------------------+
| | Transaction Time |
+========+==================+
| | Typical (us) |
+--------+------------------+
| DMA | 24 |
+-----------------------+---------+
+--------+------------------+
| No DMA | 22 |
+-----------------------+---------+
+--------+------------------+
2. SPI clock frequency: Each byte transferred takes 8 times of the clock period *8/fspi*. If the clock frequency is
too high, some functions may be limited to use. See :ref:`timing_considerations`.
@ -209,7 +218,7 @@ clock speed:
+-----------+----------------------+--------------------+------------+-------------+
| Frequency | Transaction Interval | Transaction Length | Total Time | Total Speed |
| | | | | |
| [MHz] | [us] | [bytes] | [us] | [kBps] |
| (MHz) | (us) | (bytes) | (us) | (kBps) |
+===========+======================+====================+============+=============+
| 8 | 25 | 1 | 26 | 38.5 |
+-----------+----------------------+--------------------+------------+-------------+
@ -229,9 +238,24 @@ into one transaction if possible to get higher transfer speed.
Timing considerations
^^^^^^^^^^^^^^^^^^^^^
Due to the input delay of MISO pin, ESP32 SPI master cannot read data at very high speed. The frequency allowed is
rather low when the GPIO matrix is used. Currently only frequency not greater than 8.8MHz is fully supported. When the
frequency is higher, you have to use the native pins or the *dummy bit workaround*.
As shown in the figure below, there is a delay on the MISO signal after SCLK
launch edge and before it's latched by the internal register. As a result,
the MISO pin setup time is the limiting factor for SPI clock speed. When the
delay is too large, setup slack is < 0 and the setup timing requirement is
violated, leads to the failure of reading correctly.
.. image:: /../_static/spi_miso.png
""" wavedrom don't support rendering pdflatex till now(1.3.1), so we use the png here
.. include:: /../_static/miso_timing_waveform.png
The maximum frequency allowed is related to the *input delay* (maximum valid
time after SCLK on the MISO bus), as well as the usage of GPIO matrix. The
maximum frequency allowed is reduced to about 33~77% (related to existing
*input delay*) when the GPIO matrix is used. To work at higher frequency, you
have to use the IOMUX pins or the *dummy bit workaround*. You can get the
maximum reading frequency of the master by ``spi_get_freq_limit``.
.. _dummy_bit_workaround:
@ -240,29 +264,97 @@ actually begins. The slave still sees the dummy clocks and gives out data, but t
phase. This compensates the lack of setup time of MISO required by the host, allowing the host reading at higher
frequency.
The maximum frequency (in MHz) host can read (or read and write) under different conditions is as below:
In the ideal case (the slave is so fast that the input delay is shorter than an apb clock, 12.5ns), the maximum
frequency host can read (or read and write) under different conditions is as below:
+-------------+-------------+-----------+-----------------------------+
| Frequency Limit | Dummy Bits| Comments |
+-------------+-------------+------------+-----------------------------+
| Frequency Limit (MHz) | Dummy Bits | Comments |
+-------------+-------------+ Used + +
| GPIO matrix | Native pins | By Driver | |
+=============+=============+===========+=============================+
| 8.8 | N.M. | 0 | |
+-------------+-------------+-----------+-----------------------------+
| N.M. | N.M. | 1 | Half Duplex, no DMA allowed |
+-------------+-------------+-----------+ +
| N.M. | N.M. | 2 | |
+-------------+-------------+-----------+-----------------------------+
N.M.: Not Measured Yet.
| GPIO matrix | IOMUX pins | By Driver | |
+=============+=============+============+=============================+
| 26.6 | 80 | No | |
+-------------+-------------+------------+-----------------------------+
| 40 | -- | Yes | Half Duplex, no DMA allowed |
+-------------+-------------+------------+-----------------------------+
And if the host only writes, the *dummy bit workaround* is not used and the frequency limit is as below:
+-------------+----------------------+
| GPIO matrix | Native pins |
+=============+======================+
+-------------------+------------------+
| GPIO matrix (MHz) | IOMUX pins (MHz) |
+===================+==================+
| 40 | 80 |
+-------------+----------------------+
+-------------------+------------------+
The spi master driver can work even if the *input delay* in the ``spi_device_interface_config_t`` is set to 0.
However, setting a accurate value helps to: (1) calculate the frequency limit in full duplex mode, and (2) compensate
the timing correctly by dummy bits in half duplex mode. You may find the maximum data valid time after the launch edge
of SPI clocks in the AC characteristics chapter of the device specifications, or measure the time on a oscilloscope or
logic analyzer.
""" wavedrom don't support rendering pdflatex till now(1.3.1), so we use the png here
.. include:: /../_static/miso_timing_waveform_async.png
As shown in the figure above, the input delay is usually:
*[input delay] = [sample delay] + [slave output delay]*
1. The sample delay is the maximum random delay due to the
asynchronization of SCLK and peripheral clock of the slave. It's usually
1 slave peripheral clock if the clock is asynchronize with SCLK, or 0 if
the slave just use the SCLK to latch the SCLK and launch MISO data. e.g.
for ESP32 slaves, the delay is 12.5ns (1 apb clock), while it is reduced
to 0 if the slave is in the same chip as the master.
2. The slave output delay is the time for the MOSI to be stable after the
launch edge. e.g. for ESP32 slaves, the output delay is 37.5ns (3 apb
clocks) when IOMUX pins in the slave is used, or 62.5ns (5 apb clocks) if
through the GPIO matrix.
Some typical delays are shown in the following table:
+--------------------+------------------+
| Device | Input delay (ns) |
+====================+==================+
| Ideal device | 0 |
+--------------------+------------------+
| ESP32 slave IOMUX* | 50 |
+--------------------+------------------+
| ESP32 slave GPIO* | 75 |
+--------------------+------------------+
| ESP32 slave is on an independent |
| chip, 12.5ns sample delay included. |
+---------------------------------------+
The MISO path delay(tv), consists of slave *input delay* and master *GPIO matrix delay*, finally determines the
frequency limit, above which the full duplex mode will not work, or dummy bits are used in the half duplex mode. The
frequency limit is:
*Freq limit[MHz] = 80 / (floor(MISO delay[ns]/12.5) + 1)*
The figure below shows the relations of frequency limit against the input delay. 2 extra apb clocks should be counted
into the MISO delay if the GPIO matrix in the master is used.
.. image:: /../_static/spi_master_freq_tv.png
Corresponding frequency limit for different devices with different *input delay* are shown in the following
table:
+--------+------------------+----------------------+-------------------+
| Master | Input delay (ns) | MISO path delay (ns) | Freq. limit (MHz) |
+========+==================+======================+===================+
| IOMUX | 0 | 0 | 80 |
+ (0ns) +------------------+----------------------+-------------------+
| | 50 | 50 | 16 |
+ +------------------+----------------------+-------------------+
| | 75 | 75 | 11.43 |
+--------+------------------+----------------------+-------------------+
| GPIO | 0 | 25 | 26.67 |
+ (25ns) +------------------+----------------------+-------------------+
| | 50 | 75 | 11.43 |
+ +------------------+----------------------+-------------------+
| | 75 | 100 | 8.89 |
+--------+------------------+----------------------+-------------------+
Thread Safety
-------------