mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
feat(spi_master): fine tune the timing of SPI
This commit is contained in:
parent
d441dd32ea
commit
5cf7d3768d
@ -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"
|
||||
@ -40,7 +53,7 @@ extern "C"
|
||||
* - 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 native 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
|
||||
}
|
||||
|
@ -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 native 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 );
|
||||
}
|
||||
|
28
components/soc/esp32/include/driver/spi_pins.h
Normal file
28
components/soc/esp32/include/driver/spi_pins.h
Normal file
@ -0,0 +1,28 @@
|
||||
// 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_NATIVE_PIN_NUM_MISO 12
|
||||
#define HSPI_NATIVE_PIN_NUM_MOSI 13
|
||||
#define HSPI_NATIVE_PIN_NUM_CLK 14
|
||||
#define HSPI_NATIVE_PIN_NUM_CS 15
|
||||
|
||||
#define VSPI_NATIVE_PIN_NUM_MISO 19
|
||||
#define VSPI_NATIVE_PIN_NUM_MOSI 23
|
||||
#define VSPI_NATIVE_PIN_NUM_CLK 18
|
||||
#define VSPI_NATIVE_PIN_NUM_CS 5
|
||||
|
||||
#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…
x
Reference in New Issue
Block a user