mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
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:
commit
ab538bb9f3
@ -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.
|
||||
|
@ -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,12 +48,12 @@ 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.
|
||||
*/
|
||||
#define SPI_DEVICE_NO_DUMMY (1<<6)
|
||||
#define SPI_DEVICE_NO_DUMMY (1<<6)
|
||||
|
||||
|
||||
typedef struct spi_transaction_t spi_transaction_t;
|
||||
@ -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
|
||||
@ -87,9 +105,9 @@ struct spi_transaction_t {
|
||||
*/
|
||||
uint64_t addr; /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t.
|
||||
*
|
||||
* <b>NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.</b>
|
||||
* <b>NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.</b>
|
||||
*
|
||||
* Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).
|
||||
* Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).
|
||||
*/
|
||||
size_t length; ///< Total data length, in bits
|
||||
size_t rxlength; ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``).
|
||||
@ -125,14 +143,14 @@ typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a
|
||||
* @param host SPI peripheral that controls this bus
|
||||
* @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized
|
||||
* @param dma_chan Either channel 1 or 2, or 0 in the case when no DMA is required. Selecting a DMA channel
|
||||
* for a SPI bus allows transfers on the bus to have sizes only limited by the amount of
|
||||
* for a SPI bus allows transfers on the bus to have sizes only limited by the amount of
|
||||
* internal memory. Selecting no DMA channel (by passing the value 0) limits the amount of
|
||||
* bytes transfered to a maximum of 32.
|
||||
*
|
||||
* @warning If a DMA channel is selected, any transmit and receive buffer used should be allocated in
|
||||
* @warning If a DMA channel is selected, any transmit and receive buffer used should be allocated in
|
||||
* DMA-capable memory.
|
||||
*
|
||||
* @return
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if configuration is invalid
|
||||
* - ESP_ERR_INVALID_STATE if host already is in use
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
@ -146,7 +164,7 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus
|
||||
* @warning In order for this to succeed, all devices have to be removed first.
|
||||
*
|
||||
* @param host SPI peripheral to free
|
||||
* @return
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE if not all devices on the bus are freed
|
||||
* - ESP_OK on success
|
||||
@ -166,12 +184,12 @@ esp_err_t spi_bus_free(spi_host_device_t host);
|
||||
* @param host SPI peripheral to allocate device on
|
||||
* @param dev_config SPI interface protocol config for the device
|
||||
* @param handle Pointer to variable to hold the device handle
|
||||
* @return
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_NOT_FOUND if host doesn't have any free CS slots
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
* - ESP_OK on success
|
||||
*/
|
||||
*/
|
||||
esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);
|
||||
|
||||
|
||||
@ -179,7 +197,7 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_
|
||||
* @brief Remove a device from the SPI bus
|
||||
*
|
||||
* @param handle Device handle to free
|
||||
* @return
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE if device already is freed
|
||||
* - ESP_OK on success
|
||||
@ -206,18 +224,18 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
|
||||
/**
|
||||
* @brief Get the result of a SPI transaction queued earlier
|
||||
*
|
||||
* This routine will wait until a transaction to the given device (queued earlier with
|
||||
* This routine will wait until a transaction to the given device (queued earlier with
|
||||
* spi_device_queue_trans) has succesfully completed. It will then return the description of the
|
||||
* completed transaction so software can inspect the result and e.g. free the memory or
|
||||
* completed transaction so software can inspect the result and e.g. free the memory or
|
||||
* re-use the buffers.
|
||||
*
|
||||
* @param handle Device handle obtained using spi_host_add_dev
|
||||
* @param trans_desc Pointer to variable able to contain a pointer to the description of the transaction
|
||||
that is executed. The descriptor should not be modified until the descriptor is returned by
|
||||
* @param trans_desc Pointer to variable able to contain a pointer to the description of the transaction
|
||||
that is executed. The descriptor should not be modified until the descriptor is returned by
|
||||
spi_device_get_trans_result.
|
||||
* @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time
|
||||
out.
|
||||
* @return
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_TIMEOUT if there was no completed transaction before ticks_to_wait expired
|
||||
* - ESP_OK on success
|
||||
@ -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
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -21,13 +21,13 @@ is a combination of SPI port and CS pin, plus some information about the specifi
|
||||
|
||||
The essence of the interface to a device is a set of queues; one per device. The idea is that to send something to a SPI
|
||||
device, you allocate a transaction descriptor. It contains some information about the transfer like the lenghth, address,
|
||||
command etc, plus pointers to transmit and receive buffer. The address of this block gets pushed into the transmit queue.
|
||||
The SPI driver does its magic, and sends and retrieves the data eventually. The data gets written to the receive buffers,
|
||||
command etc, plus pointers to transmit and receive buffer. The address of this block gets pushed into the transmit queue.
|
||||
The SPI driver does its magic, and sends and retrieves the data eventually. The data gets written to the receive buffers,
|
||||
if needed the transaction descriptor is modified to indicate returned parameters and the entire thing goes into the return
|
||||
queue, where whatever software initiated the transaction can retrieve it.
|
||||
|
||||
The entire thing is run from the SPI interrupt handler. If SPI is done transmitting/receiving but nothing is in the queue,
|
||||
it will not clear the SPI interrupt but just disable it. This way, when a new thing is sent, pushing the packet into the send
|
||||
The entire thing is run from the SPI interrupt handler. If SPI is done transmitting/receiving but nothing is in the queue,
|
||||
it will not clear the SPI interrupt but just disable it. This way, when a new thing is sent, pushing the packet into the send
|
||||
queue and re-enabling the interrupt will trigger the interrupt again, which can then take care of the sending.
|
||||
*/
|
||||
|
||||
@ -68,8 +68,8 @@ typedef typeof(SPI1.clock) spi_clock_reg_t;
|
||||
|
||||
|
||||
/// struct to hold private transaction data (like tx and rx buffer for DMA).
|
||||
typedef struct {
|
||||
spi_transaction_t *trans;
|
||||
typedef struct {
|
||||
spi_transaction_t *trans;
|
||||
uint32_t *buffer_to_send; //equals to tx_data, if SPI_TRANS_USE_RXDATA is applied; otherwise if original buffer wasn't in DMA-capable memory, this gets the address of a temporary buffer that is;
|
||||
//otherwise sets to the original buffer or NULL if no buffer is assigned.
|
||||
uint32_t *buffer_to_rcv; // similar to buffer_to_send
|
||||
@ -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); \
|
||||
}
|
||||
|
||||
@ -177,7 +178,7 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
err = esp_intr_alloc(spicommon_irqsource_for_host(host), ESP_INTR_FLAG_INTRDISABLED, spi_intr, (void*)spihost[host], &spihost[host]->intr);
|
||||
if (err != ESP_OK) {
|
||||
ret = err;
|
||||
@ -207,7 +208,7 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus
|
||||
spihost[host]->hw->slave.wr_sta_inten=0;
|
||||
|
||||
//Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as
|
||||
//disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling
|
||||
//disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling
|
||||
//any transactions that are queued.
|
||||
spihost[host]->hw->slave.trans_inten=1;
|
||||
spihost[host]->hw->slave.trans_done=1;
|
||||
@ -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);
|
||||
|
||||
//Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections.
|
||||
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);
|
||||
|
||||
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) ||
|
||||
eff_clk = spi_cal_clock(apbclk, dev_config->clock_speed_hz, duty_cycle, (uint32_t*)&clk_reg);
|
||||
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 );
|
||||
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, freq_limit/1000./1000 );
|
||||
|
||||
//Allocate memory for device
|
||||
spi_device_t *dev=malloc(sizeof(spi_device_t));
|
||||
@ -310,17 +343,18 @@ Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output dat
|
||||
//Allocate queues, set defaults
|
||||
dev->trans_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_trans_priv));
|
||||
dev->ret_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_trans_priv));
|
||||
if (!dev->trans_queue || !dev->ret_queue) goto nomem;
|
||||
if (!dev->trans_queue || !dev->ret_queue) goto nomem;
|
||||
dev->host=spihost[host];
|
||||
|
||||
//We want to save a copy of the dev config in the dev struct.
|
||||
memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t));
|
||||
dev->cfg.duty_cycle_pos = duty_cycle;
|
||||
// TODO: if we have to change the apb clock among transactions, re-calculate this each time the apb clock lock is acquired.
|
||||
// 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;
|
||||
@ -460,7 +496,7 @@ static void IRAM_ATTR spi_intr(void *arg)
|
||||
/*------------ deal with the in-flight transaction -----------------*/
|
||||
if (host->cur_cs != NO_CS) {
|
||||
spi_transaction_t *cur_trans = host->cur_trans_buf.trans;
|
||||
//Okay, transaction is done.
|
||||
//Okay, transaction is done.
|
||||
if (host->cur_trans_buf.buffer_to_rcv && host->dma_chan == 0 ) {
|
||||
//Need to copy from SPI regs to result buffer.
|
||||
for (int x=0; x < cur_trans->rxlength; x+=32) {
|
||||
@ -474,7 +510,7 @@ static void IRAM_ATTR spi_intr(void *arg)
|
||||
//Call post-transaction callback, if any
|
||||
if (host->device[host->cur_cs]->cfg.post_cb) host->device[host->cur_cs]->cfg.post_cb(cur_trans);
|
||||
//Return transaction descriptor.
|
||||
xQueueSendFromISR(host->device[host->cur_cs]->ret_queue, &host->cur_trans_buf, &do_yield);
|
||||
xQueueSendFromISR(host->device[host->cur_cs]->ret_queue, &host->cur_trans_buf, &do_yield);
|
||||
host->cur_cs = NO_CS;
|
||||
}
|
||||
//Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
|
||||
@ -505,46 +541,27 @@ static void IRAM_ATTR spi_intr(void *arg)
|
||||
host->cur_cs=i;
|
||||
//We should be done with the transmission.
|
||||
assert(host->hw->cmd.usr == 0);
|
||||
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
//Configure polarity
|
||||
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;
|
||||
@ -607,13 +627,14 @@ static void IRAM_ATTR spi_intr(void *arg)
|
||||
extra_dummy=dev->clk_cfg.dummy_num;
|
||||
}
|
||||
} else {
|
||||
//DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon
|
||||
//DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon
|
||||
if (host->dma_chan != 0 ) {
|
||||
host->hw->dma_in_link.addr=0;
|
||||
host->hw->dma_in_link.start=1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
@ -667,8 +709,8 @@ static void IRAM_ATTR spi_intr(void *arg)
|
||||
// output command will be sent from bit 7 to 0 of command_value, and then bit 15 to 8 of the same register field.
|
||||
uint16_t command = trans->cmd << (16-cmdlen); //shift to MSB
|
||||
host->hw->user2.usr_command_value = (command>>8)|(command<<8); //swap the first and second byte
|
||||
// shift the address to MSB of addr (and maybe slv_wr_status) register.
|
||||
// output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register.
|
||||
// shift the address to MSB of addr (and maybe slv_wr_status) register.
|
||||
// output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register.
|
||||
if (addrlen>32) {
|
||||
host->hw->addr = trans->addr >> (addrlen- 32);
|
||||
host->hw->slv_wr_status = trans->addr << (64 - addrlen);
|
||||
@ -693,13 +735,13 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
|
||||
esp_err_t ret = ESP_OK;
|
||||
BaseType_t r;
|
||||
SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG);
|
||||
//check transmission length
|
||||
//check transmission length
|
||||
SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_RXDATA)==0 ||trans_desc->rxlength <= 32, "rxdata transfer > 32 bits without configured DMA", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32 bits without configured DMA", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->length <= handle->host->max_transfer_sz*8, "txdata transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->rxlength <= handle->host->max_transfer_sz*8, "rxdata transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK((handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) || trans_desc->rxlength <= trans_desc->length, "rx length > tx length in full duplex mode", ESP_ERR_INVALID_ARG);
|
||||
//check working mode
|
||||
//check working mode
|
||||
SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && (!(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX))), "incompatible iface params", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK( !(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) || handle->host->dma_chan == 0 || !(trans_desc->flags & SPI_TRANS_USE_RXDATA || trans_desc->rx_buffer != NULL)
|
||||
@ -717,7 +759,7 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
|
||||
// rx memory assign
|
||||
if ( trans_desc->flags & SPI_TRANS_USE_RXDATA ) {
|
||||
trans_buf.buffer_to_rcv = (uint32_t*)&trans_desc->rx_data[0];
|
||||
} else {
|
||||
} else {
|
||||
//if not use RXDATA neither rx_buffer, buffer_to_rcv assigned to NULL
|
||||
trans_buf.buffer_to_rcv = trans_desc->rx_buffer;
|
||||
}
|
||||
@ -730,12 +772,12 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
|
||||
goto clean_up;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const uint32_t *txdata;
|
||||
// tx memory assign
|
||||
if ( trans_desc->flags & SPI_TRANS_USE_TXDATA ) {
|
||||
txdata = (uint32_t*)&trans_desc->tx_data[0];
|
||||
} else {
|
||||
} else {
|
||||
//if not use TXDATA neither tx_buffer, tx data assigned to NULL
|
||||
txdata = trans_desc->tx_buffer ;
|
||||
}
|
||||
@ -748,11 +790,11 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
|
||||
goto clean_up;
|
||||
}
|
||||
memcpy( trans_buf.buffer_to_send, txdata, (trans_desc->length+7)/8 );
|
||||
} else {
|
||||
} else {
|
||||
// else use the original buffer (forced-conversion) or assign to NULL
|
||||
trans_buf.buffer_to_send = (uint32_t*)txdata;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_acquire(handle->host->pm_lock);
|
||||
#endif
|
||||
@ -772,10 +814,10 @@ clean_up:
|
||||
// free malloc-ed buffer (if needed) before return.
|
||||
if ( (void*)trans_buf.buffer_to_rcv != trans_desc->rx_buffer && (void*)trans_buf.buffer_to_rcv != &trans_desc->rx_data[0] ) {
|
||||
free( trans_buf.buffer_to_rcv );
|
||||
}
|
||||
}
|
||||
if ( (void*)trans_buf.buffer_to_send!= trans_desc->tx_buffer && (void*)trans_buf.buffer_to_send != &trans_desc->tx_data[0] ) {
|
||||
free( trans_buf.buffer_to_send );
|
||||
}
|
||||
}
|
||||
assert( ret != ESP_OK );
|
||||
return ret;
|
||||
}
|
||||
@ -784,12 +826,12 @@ esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transactio
|
||||
{
|
||||
BaseType_t r;
|
||||
spi_trans_priv trans_buf;
|
||||
|
||||
|
||||
SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG);
|
||||
r=xQueueReceive(handle->ret_queue, (void*)&trans_buf, ticks_to_wait);
|
||||
if (!r) {
|
||||
// The memory occupied by rx and tx DMA buffer destroyed only when receiving from the queue (transaction finished).
|
||||
// If timeout, wait and retry.
|
||||
// If timeout, wait and retry.
|
||||
// Every on-flight transaction request occupies internal memory as DMA buffer if needed.
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@ -798,12 +840,12 @@ esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transactio
|
||||
|
||||
if ( (void*)trans_buf.buffer_to_send != &(*trans_desc)->tx_data[0] && trans_buf.buffer_to_send != (*trans_desc)->tx_buffer ) {
|
||||
free( trans_buf.buffer_to_send );
|
||||
}
|
||||
}
|
||||
|
||||
//copy data from temporary DMA-capable buffer back to IRAM buffer and free the temporary one.
|
||||
if ( (void*)trans_buf.buffer_to_rcv != &(*trans_desc)->rx_data[0] && trans_buf.buffer_to_rcv != (*trans_desc)->rx_buffer ) {
|
||||
if ( (*trans_desc)->flags & SPI_TRANS_USE_RXDATA ) {
|
||||
memcpy( (uint8_t*)&(*trans_desc)->rx_data[0], trans_buf.buffer_to_rcv, ((*trans_desc)->rxlength+7)/8 );
|
||||
memcpy( (uint8_t*)&(*trans_desc)->rx_data[0], trans_buf.buffer_to_rcv, ((*trans_desc)->rxlength+7)/8 );
|
||||
} else {
|
||||
memcpy( (*trans_desc)->rx_buffer, trans_buf.buffer_to_rcv, ((*trans_desc)->rxlength+7)/8 );
|
||||
}
|
||||
|
@ -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;
|
||||
@ -231,7 +262,7 @@ TEST_CASE("SPI Master test, interaction of multiple devs", "[spi][ignore]") {
|
||||
.clock_speed_hz=1000000,
|
||||
.duty_cycle_pos=128,
|
||||
.mode=0,
|
||||
.spics_io_num=23,
|
||||
.spics_io_num=23,
|
||||
.queue_size=3,
|
||||
};
|
||||
spi_device_handle_t handle1=setup_spi_bus(80000, true);
|
||||
@ -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,37 +340,37 @@ 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,
|
||||
.max_transfer_sz = 8, .flags = flags_expected};
|
||||
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 );
|
||||
|
||||
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,
|
||||
.max_transfer_sz = 8, .flags = flags_expected};
|
||||
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 );
|
||||
|
||||
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
|
||||
@ -541,7 +573,7 @@ TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]")
|
||||
trans[4].rxlength = 8*4;
|
||||
trans[4].tx_buffer = data_drom;
|
||||
trans[4].flags = SPI_TRANS_USE_RXDATA;
|
||||
|
||||
|
||||
trans[5].length = 8*4;
|
||||
trans[5].flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;
|
||||
|
||||
@ -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,
|
||||
@ -592,7 +617,7 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
|
||||
.mode=0, //SPI mode 0
|
||||
.spics_io_num=PIN_NUM_CS, //CS pin
|
||||
.queue_size=7, //We want to be able to queue 7 transactions at a time
|
||||
.pre_cb=NULL,
|
||||
.pre_cb=NULL,
|
||||
};
|
||||
//Initialize the SPI bus
|
||||
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
|
||||
@ -601,18 +626,18 @@ 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);
|
||||
|
||||
|
||||
for ( int i = 0; i < 8; i ++ ) {
|
||||
memset( rx_buf, 0x66, sizeof(rx_buf));
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.length = 8*(i+1);
|
||||
t.rxlength = 0;
|
||||
t.tx_buffer = tx_buf+2*i;
|
||||
t.tx_buffer = tx_buf+2*i;
|
||||
t.rx_buffer = rx_buf + i;
|
||||
|
||||
if ( i == 1 ) {
|
||||
@ -621,7 +646,7 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
|
||||
} else if ( i == 2 ) {
|
||||
//test rx length != tx_length
|
||||
t.rxlength = t.length - 8;
|
||||
}
|
||||
}
|
||||
spi_device_transmit( spi, &t );
|
||||
|
||||
for( int i = 0; i < 16; i ++ ) {
|
||||
@ -633,11 +658,11 @@ 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 );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT(spi_bus_remove_device(spi) == ESP_OK);
|
||||
@ -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);
|
||||
@ -871,7 +893,7 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]")
|
||||
handle_slave = 0;
|
||||
|
||||
deinit_slave_context(&slave_context);
|
||||
|
||||
|
||||
TEST_ASSERT(spi_slave_free(VSPI_HOST) == ESP_OK);
|
||||
|
||||
TEST_ASSERT(spi_bus_remove_device(spi) == ESP_OK);
|
||||
@ -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);
|
||||
@ -930,12 +1289,12 @@ static void sorted_array_insert(uint32_t* array, int* size, uint32_t item)
|
||||
|
||||
TEST_CASE("spi_speed","[spi]")
|
||||
{
|
||||
RECORD_TIME_PREPARE();
|
||||
RECORD_TIME_PREPARE();
|
||||
uint32_t t_flight;
|
||||
//to get rid of the influence of randomly interrupts, we measured the performance by median value
|
||||
uint32_t t_flight_sorted[TEST_TIMES];
|
||||
int t_flight_num = 0;
|
||||
|
||||
|
||||
spi_device_handle_t spi;
|
||||
const bool use_dma = true;
|
||||
WORD_ALIGNED_ATTR spi_transaction_t trans = {
|
||||
@ -948,37 +1307,38 @@ TEST_CASE("spi_speed","[spi]")
|
||||
|
||||
//first time introduces a device switch, which costs more time. we skip this
|
||||
spi_device_transmit(spi, &trans);
|
||||
|
||||
|
||||
//record flight time by isr, with DMA
|
||||
t_flight_num = 0;
|
||||
for (int i = 0; i < TEST_TIMES; i++) {
|
||||
RECORD_TIME_START();
|
||||
spi_device_transmit(spi, &trans);
|
||||
RECORD_TIME_END(&t_flight);
|
||||
RECORD_TIME_END(&t_flight);
|
||||
sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight);
|
||||
}
|
||||
TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]);
|
||||
for (int i = 0; i < TEST_TIMES; i++) {
|
||||
ESP_LOGI(TAG, "%d", t_flight_sorted[i]);
|
||||
}
|
||||
|
||||
|
||||
speed_deinit(spi);
|
||||
speed_setup(&spi, !use_dma);
|
||||
|
||||
|
||||
//first time introduces a device switch, which costs more time. we skip this
|
||||
spi_device_transmit(spi, &trans);
|
||||
|
||||
|
||||
//record flight time by isr, without DMA
|
||||
t_flight_num = 0;
|
||||
for (int i = 0; i < TEST_TIMES; i++) {
|
||||
RECORD_TIME_START();
|
||||
spi_device_transmit(spi, &trans);
|
||||
RECORD_TIME_END(&t_flight);
|
||||
RECORD_TIME_END(&t_flight);
|
||||
sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight);
|
||||
}
|
||||
TEST_PERFORMANCE_LESS_THAN( SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]);
|
||||
for (int i = 0; i < TEST_TIMES; i++) {
|
||||
ESP_LOGI(TAG, "%d", t_flight_sorted[i]);
|
||||
}
|
||||
speed_deinit(spi);
|
||||
}
|
||||
speed_deinit(spi);
|
||||
}
|
||||
|
||||
|
32
components/soc/esp32/include/driver/spi_pins.h
Normal file
32
components/soc/esp32/include/driver/spi_pins.h
Normal 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
|
17
docs/_static/diagrams/spi_master/miso_timing_waveform.rst
vendored
Normal file
17
docs/_static/diagrams/spi_master/miso_timing_waveform.rst
vendored
Normal 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 }
|
||||
}
|
19
docs/_static/diagrams/spi_master/miso_timing_waveform_async.rst
vendored
Normal file
19
docs/_static/diagrams/spi_master/miso_timing_waveform_async.rst
vendored
Normal 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'
|
||||
],
|
||||
}
|
33
docs/_static/diagrams/spi_master/spi_master_freq_tv.plt
vendored
Normal file
33
docs/_static/diagrams/spi_master/spi_master_freq_tv.plt
vendored
Normal 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"
|
||||
|
||||
|
BIN
docs/_static/diagrams/spi_master/spi_timing.pptx
vendored
Normal file
BIN
docs/_static/diagrams/spi_master/spi_timing.pptx
vendored
Normal file
Binary file not shown.
29
docs/_static/diagrams/spi_master/tv.csv
vendored
Normal file
29
docs/_static/diagrams/spi_master/tv.csv
vendored
Normal 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
|
|
BIN
docs/_static/miso_timing_waveform.png
vendored
Normal file
BIN
docs/_static/miso_timing_waveform.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
docs/_static/miso_timing_waveform_async.png
vendored
Normal file
BIN
docs/_static/miso_timing_waveform_async.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
docs/_static/spi_master_freq_tv.png
vendored
Normal file
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
BIN
docs/_static/spi_miso.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -14,7 +14,7 @@ The spi_master driver
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The spi_master driver allows easy communicating with SPI slave devices, even in a multithreaded environment.
|
||||
It fully transparently handles DMA transfers to read and write data and automatically takes care of
|
||||
It fully transparently handles DMA transfers to read and write data and automatically takes care of
|
||||
multiplexing between different SPI slaves on the same master
|
||||
|
||||
Terminology
|
||||
@ -22,11 +22,11 @@ Terminology
|
||||
|
||||
The spi_master driver uses the following terms:
|
||||
|
||||
* Host: The SPI peripheral inside the ESP32 initiating the SPI transmissions. One of SPI, HSPI or VSPI. (For
|
||||
now, only HSPI or VSPI are actually supported in the driver; it will support all 3 peripherals
|
||||
* Host: The SPI peripheral inside the ESP32 initiating the SPI transmissions. One of SPI, HSPI or VSPI. (For
|
||||
now, only HSPI or VSPI are actually supported in the driver; it will support all 3 peripherals
|
||||
somewhere in the future.)
|
||||
* Bus: The SPI bus, common to all SPI devices connected to one host. In general the bus consists of the
|
||||
miso, mosi, sclk and optionally quadwp and quadhd signals. The SPI slaves are connected to these
|
||||
miso, mosi, sclk and optionally quadwp and quadhd signals. The SPI slaves are connected to these
|
||||
signals in parallel.
|
||||
|
||||
- miso - Also known as q, this is the input of the serial stream into the ESP32
|
||||
@ -58,33 +58,40 @@ A transaction on the SPI bus consists of five phases, any of which may be skippe
|
||||
* The read phase. The slave sends data to the master.
|
||||
|
||||
In full duplex mode, the read and write phases are combined, and the SPI host reads and
|
||||
writes data simultaneously. The total transaction length is decided by
|
||||
writes data simultaneously. The total transaction length is decided by
|
||||
``command_bits + address_bits + trans_conf.length``, while the ``trans_conf.rx_length``
|
||||
only determins length of data received into the buffer.
|
||||
|
||||
While in half duplex mode, the host have independent write and read phases. The length of write phase and read phase are
|
||||
decided by ``trans_conf.length`` and ``trans_conf.rx_length`` respectively.
|
||||
decided by ``trans_conf.length`` and ``trans_conf.rx_length`` respectively.
|
||||
|
||||
The command and address phase are optional in that not every SPI device will need to be sent a command
|
||||
and/or address. This is reflected in the device configuration: when the ``command_bits`` or ``address_bits``
|
||||
fields are set to zero, no command or address phase is done.
|
||||
|
||||
Something similar is true for the read and write phase: not every transaction needs both data to be written
|
||||
as well as data to be read. When ``rx_buffer`` is NULL (and SPI_USE_RXDATA) is not set) the read phase
|
||||
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 |
|
||||
@ -112,11 +119,11 @@ Using the spi_master driver
|
||||
- Initialize a SPI bus by calling ``spi_bus_initialize``. Make sure to set the correct IO pins in
|
||||
the ``bus_config`` struct. Take care to set signals that are not needed to -1.
|
||||
|
||||
- Tell the driver about a SPI slave device connected to the bus by calling spi_bus_add_device.
|
||||
- Tell the driver about a SPI slave device connected to the bus by calling spi_bus_add_device.
|
||||
Make sure to configure any timing requirements the device has in the ``dev_config`` structure.
|
||||
You should now have a handle for the device, to be used when sending it a transaction.
|
||||
|
||||
- To interact with the device, fill one or more spi_transaction_t structure with any transaction
|
||||
- To interact with the device, fill one or more spi_transaction_t structure with any transaction
|
||||
parameters you need. Either queue all transactions by calling ``spi_device_queue_trans``, later
|
||||
quering the result using ``spi_device_get_trans_result``, or handle all requests synchroneously
|
||||
by feeding them into ``spi_device_transmit``.
|
||||
@ -124,7 +131,7 @@ Using the spi_master driver
|
||||
- Optional: to unload the driver for a device, call ``spi_bus_remove_device`` with the device
|
||||
handle as an argument
|
||||
|
||||
- Optional: to remove the driver for a bus, make sure no more drivers are attached and call
|
||||
- Optional: to remove the driver for a bus, make sure no more drivers are attached and call
|
||||
``spi_bus_free``.
|
||||
|
||||
Command and address phases
|
||||
@ -135,26 +142,26 @@ During the command and address phases, ``cmd`` and ``addr`` field in the
|
||||
same time. The default length of command and address phase are set in the
|
||||
``spi_device_interface_config_t`` and by ``spi_bus_add_device``. When the the
|
||||
flag ``SPI_TRANS_VARIABLE_CMD`` and ``SPI_TRANS_VARIABLE_ADDR`` are not set in
|
||||
the ``spi_transaction_t``,the driver automatically set the length of these
|
||||
the ``spi_transaction_t``,the driver automatically set the length of these
|
||||
phases to the default value as set when the device is initialized respectively.
|
||||
|
||||
If the length of command and address phases needs to be variable, declare a
|
||||
``spi_transaction_ext_t`` descriptor, set the flag ``SPI_TRANS_VARIABLE_CMD``
|
||||
or/and ``SPI_TRANS_VARIABLE_ADDR`` in the ``flags`` of ``base`` member and
|
||||
configure the rest part of ``base`` as usual. Then the length of each phases
|
||||
``spi_transaction_ext_t`` descriptor, set the flag ``SPI_TRANS_VARIABLE_CMD``
|
||||
or/and ``SPI_TRANS_VARIABLE_ADDR`` in the ``flags`` of ``base`` member and
|
||||
configure the rest part of ``base`` as usual. Then the length of each phases
|
||||
will be ``command_bits`` and ``address_bits`` set in the ``spi_transaction_ext_t``.
|
||||
|
||||
Write and read phases
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Normally, data to be transferred to or from a device will be read from or written to a chunk of memory
|
||||
indicated by the ``rx_buffer`` and ``tx_buffer`` members of the transaction structure.
|
||||
indicated by the ``rx_buffer`` and ``tx_buffer`` members of the transaction structure.
|
||||
When DMA is enabled for transfers, these buffers are highly recommended to meet the requirements as below:
|
||||
|
||||
1. allocated in DMA-capable memory using ``pvPortMallocCaps(size, MALLOC_CAP_DMA)``;
|
||||
2. 32-bit aligned (start from the boundary and have length of multiples of 4 bytes).
|
||||
|
||||
If these requirements are not satisfied, efficiency of the transaction will suffer due to the allocation and
|
||||
If these requirements are not satisfied, efficiency of the transaction will suffer due to the allocation and
|
||||
memcpy of temporary buffers.
|
||||
|
||||
.. note:: Half duplex transactions with both read and write phases are not supported when using DMA. See
|
||||
@ -179,25 +186,27 @@ speed a lot if small transactions are used.
|
||||
|
||||
1. Transaction interval: The interval mainly comes from the cost of FreeRTOS queues and the time switching between
|
||||
tasks and the ISR. It also takes time for the software to setup spi peripheral registers as well as copy data to
|
||||
FIFOs, or setup DMA links. Depending on whether the DMA is used, the interval of an one-byte transaction is around
|
||||
25us typically.
|
||||
|
||||
FIFOs, or setup DMA links. Depending on whether the DMA is used, the interval of an one-byte transaction is around
|
||||
25us typically.
|
||||
|
||||
1. The CPU is blocked and switched to other tasks when the
|
||||
transaction is in flight. This save the cpu time but increase the interval.
|
||||
transaction is in flight. This save the cpu time but increase the interval.
|
||||
2. When the DMA is enabled, it needs about 2us per transaction to setup the linked list. When the master is
|
||||
transferring, it automatically read data from the linked list. If the DMA is not enabled,
|
||||
CPU has to write/read each byte to/from the FIFO by itself. Usually this is faster than 2us, but the
|
||||
transaction length is limited to 32 bytes for both write and read.
|
||||
|
||||
|
||||
Typical transaction interval with one byte data is as below:
|
||||
|
||||
+-----------------------+---------+
|
||||
| Transaction Time (us) | Typical |
|
||||
+=======================+=========+
|
||||
| DMA | 24 |
|
||||
+-----------------------+---------+
|
||||
| No DMA | 22 |
|
||||
+-----------------------+---------+
|
||||
+--------+------------------+
|
||||
| | 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 |
|
||||
+-------------+-------------+ 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.
|
||||
+-------------+-------------+------------+-----------------------------+
|
||||
| Frequency Limit (MHz) | Dummy Bits | Comments |
|
||||
+-------------+-------------+ Used + +
|
||||
| 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 |
|
||||
+=============+======================+
|
||||
| 40 | 80 |
|
||||
+-------------+----------------------+
|
||||
+-------------------+------------------+
|
||||
| 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
|
||||
-------------
|
||||
@ -282,7 +374,7 @@ Known Issues
|
||||
|
||||
1. use full-duplex mode instead.
|
||||
2. disable the DMA by setting the last parameter to 0 in bus initialization function just as below:
|
||||
``ret=spi_bus_initialize(VSPI_HOST, &buscfg, 0);``
|
||||
``ret=spi_bus_initialize(VSPI_HOST, &buscfg, 0);``
|
||||
|
||||
this may prohibit you from transmitting and receiving data longer than 32 bytes.
|
||||
3. try to use command and address field to replace the write phase.
|
||||
@ -293,7 +385,7 @@ Known Issues
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
|
||||
Display graphics on the 320x240 LCD of WROVER-Kits: :example:`peripherals/spi_master`.
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user