Merge branch 'feature/spi_slave' into 'master'

Split common SPI stuff out of master driver; add slave driver; add workaround for DMA issue.

This merge req mainly adds a slave device. In order to do this, the original master driver is refactored into common code shared by master and slave modes, and a slave driver is added.

The other things added are:
- Added a workaround for a 'feature' of the ESP32 silicon that can lock up the receive DMA channel in some situations. This can only be fixed by resetting *both* DMA channels. The workaround implemented makes sure that the reset only happens when both channels are idle
- Got rid of the automatic choice between register- and DMA-based transfers. The master (and slave) code will now always go for a DMA transfer if a DMA channel is given, and always go for register-based transfers if no DMA channel is given.
- Add in a bunch of fixes for outstanding Github issues.

See merge request !659
This commit is contained in:
Jeroen Domburg 2017-04-27 12:28:42 +08:00
commit 46fa2cfb46
19 changed files with 1967 additions and 294 deletions

View File

@ -0,0 +1,233 @@
// Copyright 2010-2017 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_COMMON_H_
#define _DRIVER_SPI_COMMON_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "soc/spi_struct.h"
#include "rom/lldesc.h"
#ifdef __cplusplus
extern "C"
{
#endif
//Maximum amount of bytes that can be put in one DMA descriptor
#define SPI_MAX_DMA_LEN (4096-4)
/**
* @brief Enum with the three SPI peripherals that are software-accessible in it
*/
typedef enum {
SPI_HOST=0, ///< SPI1, SPI
HSPI_HOST=1, ///< SPI2, HSPI
VSPI_HOST=2 ///< SPI3, VSPI
} spi_host_device_t;
/**
* @brief This is a configuration structure for a SPI bus.
*
* You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the
* GPIO matrix to route the signals. An exception is made when all signals either can be routed through
* the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds.
*/
typedef struct {
int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.
int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.
int sclk_io_num; ///< GPIO pin for Spi CLocK signal, or -1 if not used.
int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used.
int quadhd_io_num; ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used.
int max_transfer_sz; ///< Maximum transfer size, in bytes. Defaults to 4094 if 0.
} spi_bus_config_t;
/**
* @brief Try to claim a SPI peripheral
*
* Call this if your driver wants to manage a SPI peripheral.
*
* @param host Peripheral to claim
* @return True if peripheral is claimed successfully; false if peripheral already is claimed.
*/
bool spicommon_periph_claim(spi_host_device_t host);
/**
* @brief Return the SPI peripheral so another driver can claim it.
*
* @param host Peripheral to claim
* @return True if peripheral is returned successfully; false if peripheral was free to claim already.
*/
bool spicommon_periph_free(spi_host_device_t host);
#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_QUAD (1<<1) ///< Also initialize WP/HD pins, if specified
/**
* @brief Connect a SPI peripheral to GPIO pins
*
* This routine is used to connect a SPI peripheral to the IO-pads and DMA channel given in
* the arguments. Depending on the IO-pads requested, the routing is done either using the
* IO_mux or using the GPIO matrix.
*
* @param host SPI peripheral to be routed
* @param bus_config Pointer to a spi_bus_config struct detailing the GPIO pins
* @param dma_chan DMA-channel (1 or 2) to use, or 0 for no DMA.
* @param flags Combination of SPICOMMON_BUSFLAG_* flags
* @param[out] is_native A value of 'true' will be written to this address if the GPIOs can be
* routed using the IO_mux, 'false' if the GPIO matrix is used.
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan, int flags, bool *is_native);
/**
* @brief Free the IO used by a SPI peripheral
*
* @param host SPI peripheral to be freed
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t spicommon_bus_free_io(spi_host_device_t host);
/**
* @brief Initialize a Chip Select pin for a specific SPI peripheral
*
*
* @param host SPI peripheral
* @param cs_io_num GPIO pin to route
* @param cs_num CS id to route
* @param force_gpio_matrix If true, CS will always be routed through the GPIO matrix. If false,
* if the GPIO number allows it, the routing will happen through the IO_mux.
*/
void spicommon_cs_initialize(spi_host_device_t host, int cs_io_num, int cs_num, int force_gpio_matrix);
/**
* @brief Free a chip select line
*
* @param host SPI peripheral
* @param cs_num CS id to free
*/
void spicommon_cs_free(spi_host_device_t host, int cs_num);
/**
* @brief Setup a DMA link chain
*
* This routine will set up a chain of linked DMA descriptors in the array pointed to by
* ``dmadesc``. Enough DMA descriptors will be used to fit the buffer of ``len`` bytes in, and the
* descriptors will point to the corresponding positions in ``buffer`` and linked together. The
* end result is that feeding ``dmadesc[0]`` into DMA hardware results in the entirety ``len`` bytes
* of ``data`` being read or written.
*
* @param dmadesc Pointer to array of DMA descriptors big enough to be able to convey ``len`` bytes
* @param len Length of buffer
* @param data Data buffer to use for DMA transfer
* @param isrx True if data is to be written into ``data``, false if it's to be read from ``data``.
*/
void spicommon_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx);
/**
* @brief Get the position of the hardware registers for a specific SPI host
*
* @param host The SPI host
*
* @return A register descriptor stuct pointer, pointed at the hardware registers
*/
spi_dev_t *spicommon_hw_for_host(spi_host_device_t host);
/**
* @brief Get the IRQ source for a specific SPI host
*
* @param host The SPI host
*
* @return The hosts IRQ source
*/
int spicommon_irqsource_for_host(spi_host_device_t host);
/**
* Callback, to be called when a DMA engine reset is completed
*/
typedef void(*dmaworkaround_cb_t)(void *arg);
/**
* @brief Request a reset for a certain DMA channel
*
* @note In some (well-defined) cases in the ESP32 (at least rev v.0 and v.1), a SPI DMA channel will get confused. This can be remedied
* by resetting the SPI DMA hardware in case this happens. Unfortunately, the reset knob used for thsi will reset _both_ DMA channels, and
* as such can only done safely when both DMA channels are idle. These functions coordinate this.
*
* Essentially, when a reset is needed, a driver can request this using spicommon_dmaworkaround_req_reset. This is supposed to be called
* with an user-supplied function as an argument. If both DMA channels are idle, this call will reset the DMA subsystem and return true.
* If the other DMA channel is still busy, it will return false; as soon as the other DMA channel is done, however, it will reset the
* DMA subsystem and call the callback. The callback is then supposed to be used to continue the SPI drivers activity.
*
* @param dmachan DMA channel associated with the SPI host that needs a reset
* @param cb Callback to call in case DMA channel cannot be reset immediately
* @param arg Argument to the callback
*
* @return True when a DMA reset could be executed immediately. False when it could not; in this
* case the callback will be called with the specified argument when the logic can execute
* a reset, after that reset.
*/
bool spicommon_dmaworkaround_req_reset(int dmachan, dmaworkaround_cb_t cb, void *arg);
/**
* @brief Check if a DMA reset is requested but has not completed yet
*
* @return True when a DMA reset is requested but hasn't completed yet. False otherwise.
*/
bool spicommon_dmaworkaround_reset_in_progress();
/**
* @brief Mark a DMA channel as idle.
*
* A call to this function tells the workaround logic that this channel will
* not be affected by a global SPI DMA reset.
*/
void spicommon_dmaworkaround_idle(int dmachan);
/**
* @brief Mark a DMA channel as active.
*
* A call to this function tells the workaround logic that this channel will
* be affected by a global SPI DMA reset, and a reset like that should not be attempted.
*/
void spicommon_dmaworkaround_transfer_active(int dmachan);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -20,6 +20,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/spi_common.h"
#ifdef __cplusplus
@ -27,37 +28,10 @@ extern "C"
{
#endif
/**
* @brief Enum with the three SPI peripherals that are software-accessible in it
*/
typedef enum {
SPI_HOST=0, ///< SPI1, SPI
HSPI_HOST=1, ///< SPI2, HSPI
VSPI_HOST=2 ///< SPI3, VSPI
} spi_host_device_t;
/**
* @brief This is a configuration structure for a SPI bus.
*
* You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the
* GPIO matrix to route the signals. An exception is made when all signals either can be routed through
* the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds.
*/
typedef struct {
int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.
int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.
int sclk_io_num; ///< GPIO pin for Spi CLocK signal, or -1 if not used.
int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used.
int quadhd_io_num; ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used.
} spi_bus_config_t;
#define SPI_DEVICE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first
#define SPI_DEVICE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first
#define SPI_DEVICE_BIT_LSBFIRST (SPI_TXBIT_LSBFIRST|SPI_RXBIT_LSBFIRST); ///< Transmit and receive LSB first
#define SPI_DEVICE_3WIRE (1<<2) ///< Use spiq for both sending and receiving data
#define SPI_DEVICE_3WIRE (1<<2) ///< Use MOSI (=spid) for both sending and receiving data
#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
@ -122,9 +96,10 @@ 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 1 or 2. A SPI bus used by this driver must have a DMA channel associated with
* it. The SPI hardware has two DMA channels to share. This parameter indicates which
* one to use.
* @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
* internal memory. Selecting no DMA channel (by passing the value 0) limits the amount of
* bytes transfered to a maximum of 32.
* @return
* - ESP_ERR_INVALID_ARG if configuration is invalid
* - ESP_ERR_INVALID_STATE if host already is in use

View File

@ -0,0 +1,161 @@
// Copyright 2010-2017 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_SLAVE_H_
#define _DRIVER_SPI_SLAVE_H_
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/spi_common.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define SPI_SLAVE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first
#define SPI_SLAVE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first
#define SPI_SLAVE_BIT_LSBFIRST (SPI_TXBIT_LSBFIRST|SPI_RXBIT_LSBFIRST); ///< Transmit and receive LSB first
typedef struct spi_slave_transaction_t spi_slave_transaction_t;
typedef void(*slave_transaction_cb_t)(spi_slave_transaction_t *trans);
/**
* @brief This is a configuration for a SPI host acting as a slave device.
*/
typedef struct {
int spics_io_num; ///< CS GPIO pin for this device
uint32_t flags; ///< Bitwise OR of SPI_SLAVE_* flags
int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_slave_queue_trans but not yet finished using spi_slave_get_trans_result) at the same time
uint8_t mode; ///< SPI mode (0-3)
slave_transaction_cb_t post_setup_cb; ///< Callback called after the SPI registers are loaded with new data
slave_transaction_cb_t post_trans_cb; ///< Callback called after a transaction is done
} spi_slave_interface_config_t;
/**
* This structure describes one SPI transaction
*/
struct spi_slave_transaction_t {
size_t length; ///< Total data length, in bits
const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase
void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase
void *user; ///< User-defined variable. Can be used to store eg transaction ID.
};
/**
* @brief Initialize a SPI bus as a slave interface
*
* @warning For now, only supports HSPI and VSPI.
*
* @param host SPI peripheral to use as a SPI slave interface
* @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized
* @param slave_config Pointer to a spi_slave_interface_config_t struct specifying the details for the slave interface
* @param dma_chan Either 1 or 2. A SPI bus used by this driver must have a DMA channel associated with
* it. The SPI hardware has two DMA channels to share. This parameter indicates which
* one to use.
* @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
* - ESP_OK on success
*/
esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, const spi_slave_interface_config_t *slave_config, int dma_chan);
/**
* @brief Free a SPI bus claimed as a SPI slave interface
*
* @param host SPI peripheral to free
* @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
*/
esp_err_t spi_slave_free(spi_host_device_t host);
/**
* @brief Queue a SPI transaction for execution
*
* Queues a SPI transaction to be executed by this slave device. (The transaction queue size was specified when the slave
* device was initialised via spi_slave_initialize.) This function may block if the queue is full (depending on the
* ticks_to_wait parameter). No SPI operation is directly initiated by this function, the next queued transaction
* will happen when the master initiates a SPI transaction by pulling down CS and sending out clock signals.
*
* This function hands over ownership of the buffers in ``trans_desc`` to the SPI slave driver; the application is
* not to access this memory until ``spi_slave_queue_trans`` is called to hand ownership back to the application.
*
* @param host SPI peripheral that is acting as a slave
* @param trans_desc Description of transaction to execute. Not const because we may want to write status back
* into the transaction description.
* @param ticks_to_wait Ticks to wait until there's room in the queue; use portMAX_DELAY to
* never time out.
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait);
/**
* @brief Get the result of a SPI transaction queued earlier
*
* This routine will wait until a transaction to the given device (queued earlier with
* spi_slave_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
* re-use the buffers.
*
* It is mandatory to eventually use this function for any transaction queued by ``spi_slave_queue_trans``.
*
* @param host SPI peripheral to that is acting as a slave
* @param[out] trans_desc Pointer to variable able to contain a pointer to the description of the
* transaction that is executed
* @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time
* out.
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, TickType_t ticks_to_wait);
/**
* @brief Do a SPI transaction
*
* Essentially does the same as spi_slave_queue_trans followed by spi_slave_get_trans_result. Do
* not use this when there is still a transaction queued that hasn't been finalized
* using spi_slave_get_trans_result.
*
* @param host SPI peripheral to that is acting as a slave
* @param trans_desc Pointer to variable able to contain a pointer to the description of the
* transaction that is executed. Not const because we may want to write status back
* into the transaction description.
* @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time
* out.
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,397 @@
// Copyright 2015-2016 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.
#include <string.h>
#include "driver/spi_master.h"
#include "soc/gpio_sig_map.h"
#include "soc/spi_reg.h"
#include "soc/dport_reg.h"
#include "soc/spi_struct.h"
#include "rom/ets_sys.h"
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_intr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_err.h"
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "rom/lldesc.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#include "esp_heap_alloc_caps.h"
#include "driver/spi_common.h"
static const char *SPI_TAG = "spi";
#define SPI_CHECK(a, str, ret_val) \
if (!(a)) { \
ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
typedef struct spi_device_t spi_device_t;
/*
Stores a bunch of per-spi-peripheral data.
*/
typedef struct {
const uint8_t spiclk_out; //GPIO mux output signals
const uint8_t spiclk_in;
const uint8_t spid_out;
const uint8_t spiq_out;
const uint8_t spiwp_out;
const uint8_t spihd_out;
const uint8_t spid_in; //GPIO mux input signals
const uint8_t spiq_in;
const uint8_t spiwp_in;
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 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
spi_dev_t *hw; //Pointer to the hardware registers
} spi_signal_conn_t;
/*
Bunch of constants for every SPI peripheral: GPIO signals, irqs, hw addr of registers etc
*/
static const spi_signal_conn_t io_signal[3] = {
{
.spiclk_out = SPICLK_OUT_IDX,
.spiclk_in = SPICLK_IN_IDX,
.spid_out = SPID_OUT_IDX,
.spiq_out = SPIQ_OUT_IDX,
.spiwp_out = SPIWP_OUT_IDX,
.spihd_out = SPIHD_OUT_IDX,
.spid_in = SPID_IN_IDX,
.spiq_in = SPIQ_IN_IDX,
.spiwp_in = SPIWP_IN_IDX,
.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,
.irq = ETS_SPI1_INTR_SOURCE,
.irq_dma = ETS_SPI1_DMA_INTR_SOURCE,
.module = PERIPH_SPI_MODULE,
.hw = &SPI1
}, {
.spiclk_out = HSPICLK_OUT_IDX,
.spiclk_in = HSPICLK_IN_IDX,
.spid_out = HSPID_OUT_IDX,
.spiq_out = HSPIQ_OUT_IDX,
.spiwp_out = HSPIWP_OUT_IDX,
.spihd_out = HSPIHD_OUT_IDX,
.spid_in = HSPID_IN_IDX,
.spiq_in = HSPIQ_IN_IDX,
.spiwp_in = HSPIWP_IN_IDX,
.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,
.irq = ETS_SPI2_INTR_SOURCE,
.irq_dma = ETS_SPI2_DMA_INTR_SOURCE,
.module = PERIPH_HSPI_MODULE,
.hw = &SPI2
}, {
.spiclk_out = VSPICLK_OUT_IDX,
.spiclk_in = VSPICLK_IN_IDX,
.spid_out = VSPID_OUT_IDX,
.spiq_out = VSPIQ_OUT_IDX,
.spiwp_out = VSPIWP_OUT_IDX,
.spihd_out = VSPIHD_OUT_IDX,
.spid_in = VSPID_IN_IDX,
.spiq_in = VSPIQ_IN_IDX,
.spiwp_in = VSPIWP_IN_IDX,
.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,
.irq = ETS_SPI3_INTR_SOURCE,
.irq_dma = ETS_SPI3_DMA_INTR_SOURCE,
.module = PERIPH_VSPI_MODULE,
.hw = &SPI3
}
};
//Periph 1 is 'claimed' by SPI flash code.
static bool spi_periph_claimed[3] = {true, false, false};
//Returns true if this peripheral is successfully claimed, false if otherwise.
bool spicommon_periph_claim(spi_host_device_t host)
{
bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], false, true);
if (ret) periph_module_enable(io_signal[host].module);
return ret;
}
//Returns true if this peripheral is successfully freed, false if otherwise.
bool spicommon_periph_free(spi_host_device_t host)
{
bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], true, false);
if (ret) periph_module_disable(io_signal[host].module);
return ret;
}
int spicommon_irqsource_for_host(spi_host_device_t host)
{
return io_signal[host].irq;
}
spi_dev_t *spicommon_hw_for_host(spi_host_device_t host)
{
return io_signal[host].hw;
}
/*
Do the common stuff to hook up a SPI host to a bus defined by a bunch of GPIO pins. Feed it a host number and a
bus config struct and it'll set up the GPIO matrix and enable the device. It will set is_native to 1 if the bus
config can be done using the IOMUX instead of using the GPIO matrix.
*/
esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan, int flags, bool *is_native)
{
bool native = true;
bool use_quad = (flags & SPICOMMON_BUSFLAG_QUAD) != 0;
SPI_CHECK(bus_config->mosi_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->mosi_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->sclk_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->miso_io_num < 0 || GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG);
if (use_quad) {
SPI_CHECK(bus_config->quadwp_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->quadhd_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG);
}
//Check if the selected pins correspond to the native pins of the peripheral
if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num != io_signal[host].spid_native) native = false;
if (bus_config->miso_io_num >= 0 && bus_config->miso_io_num != io_signal[host].spiq_native) native = false;
if (bus_config->sclk_io_num >= 0 && bus_config->sclk_io_num != io_signal[host].spiclk_native) native = false;
if (use_quad) {
if (bus_config->quadwp_io_num >= 0 && bus_config->quadwp_io_num != io_signal[host].spiwp_native) native = false;
if (bus_config->quadhd_io_num >= 0 && bus_config->quadhd_io_num != io_signal[host].spihd_native) native = false;
}
*is_native = native;
if (native) {
//All SPI native 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.
if (bus_config->mosi_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1);
if (bus_config->miso_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1);
if (use_quad && bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1);
if (use_quad && bus_config->quadhd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1);
if (bus_config->sclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1);
} else {
//Use GPIO
if (bus_config->mosi_io_num > 0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->mosi_io_num, GPIO_MODE_INPUT_OUTPUT);
gpio_matrix_out(bus_config->mosi_io_num, io_signal[host].spid_out, false, false);
gpio_matrix_in(bus_config->mosi_io_num, io_signal[host].spid_in, false);
}
if (bus_config->miso_io_num > 0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->miso_io_num, GPIO_MODE_INPUT_OUTPUT);
gpio_matrix_out(bus_config->miso_io_num, io_signal[host].spiq_out, false, false);
gpio_matrix_in(bus_config->miso_io_num, io_signal[host].spiq_in, false);
}
if (use_quad && bus_config->quadwp_io_num > 0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->quadwp_io_num, GPIO_MODE_INPUT_OUTPUT);
gpio_matrix_out(bus_config->quadwp_io_num, io_signal[host].spiwp_out, false, false);
gpio_matrix_in(bus_config->quadwp_io_num, io_signal[host].spiwp_in, false);
}
if (use_quad && bus_config->quadhd_io_num > 0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->quadhd_io_num, GPIO_MODE_INPUT_OUTPUT);
gpio_matrix_out(bus_config->quadhd_io_num, io_signal[host].spihd_out, false, false);
gpio_matrix_in(bus_config->quadhd_io_num, io_signal[host].spihd_in, false);
}
if (bus_config->sclk_io_num > 0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->sclk_io_num, GPIO_MODE_INPUT_OUTPUT);
gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false);
gpio_matrix_in(bus_config->sclk_io_num, io_signal[host].spiclk_in, false);
}
}
//Select DMA channel.
SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, dma_chan, (host * 2));
return ESP_OK;
}
//Find any pin with output muxed to ``func`` and reset it to GPIO
static void reset_func_to_gpio(int func)
{
for (int x = 0; x < GPIO_PIN_COUNT; x++) {
if (GPIO_IS_VALID_GPIO(x) && (READ_PERI_REG(GPIO_FUNC0_OUT_SEL_CFG_REG + (x * 4))&GPIO_FUNC0_OUT_SEL_M) == func) {
gpio_matrix_out(x, SIG_GPIO_OUT_IDX, false, false);
}
}
}
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);
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);
reset_func_to_gpio(io_signal[host].spiwp_out);
reset_func_to_gpio(io_signal[host].spihd_out);
return ESP_OK;
}
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) {
//The cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define.
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[cs_io_num], 1);
} else {
//Use GPIO matrix
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[cs_io_num], PIN_FUNC_GPIO);
gpio_matrix_out(cs_io_num, io_signal[host].spics_out[cs_num], false, false);
if (cs_num == 0) gpio_matrix_in(cs_io_num, io_signal[host].spics_in, false);
}
}
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);
}
reset_func_to_gpio(io_signal[host].spics_out[cs_io_num]);
}
//Set up a list of dma descriptors. dmadesc is an array of descriptors. Data is the buffer to point to.
void spicommon_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx)
{
int n = 0;
while (len) {
int dmachunklen = len;
if (dmachunklen > SPI_MAX_DMA_LEN) dmachunklen = SPI_MAX_DMA_LEN;
if (isrx) {
//Receive needs DMA length rounded to next 32-bit boundary
dmadesc[n].size = (dmachunklen + 3) & (~3);
dmadesc[n].length = (dmachunklen + 3) & (~3);
} else {
dmadesc[n].size = dmachunklen;
dmadesc[n].length = dmachunklen;
}
dmadesc[n].buf = (uint8_t *)data;
dmadesc[n].eof = 0;
dmadesc[n].sosf = 0;
dmadesc[n].owner = 1;
dmadesc[n].qe.stqe_next = &dmadesc[n + 1];
len -= dmachunklen;
data += dmachunklen;
n++;
}
dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream.
dmadesc[n - 1].qe.stqe_next = NULL;
}
/*
Code for workaround for DMA issue in ESP32 v0/v1 silicon
*/
static volatile int dmaworkaround_channels_busy[2] = {0, 0};
static dmaworkaround_cb_t dmaworkaround_cb;
static void *dmaworkaround_cb_arg;
static portMUX_TYPE dmaworkaround_mux = portMUX_INITIALIZER_UNLOCKED;
static int dmaworkaround_waiting_for_chan = 0;
bool IRAM_ATTR spicommon_dmaworkaround_req_reset(int dmachan, dmaworkaround_cb_t cb, void *arg)
{
int otherchan = (dmachan == 1) ? 2 : 1;
bool ret;
portENTER_CRITICAL(&dmaworkaround_mux);
if (dmaworkaround_channels_busy[otherchan]) {
//Other channel is busy. Call back when it's done.
dmaworkaround_cb = cb;
dmaworkaround_cb_arg = arg;
dmaworkaround_waiting_for_chan = otherchan;
ret = false;
} else {
//Reset DMA
SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST);
CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST);
ret = true;
}
portEXIT_CRITICAL(&dmaworkaround_mux);
return ret;
}
bool IRAM_ATTR spicommon_dmaworkaround_reset_in_progress()
{
return (dmaworkaround_waiting_for_chan != 0);
}
void IRAM_ATTR spicommon_dmaworkaround_idle(int dmachan)
{
portENTER_CRITICAL(&dmaworkaround_mux);
dmaworkaround_channels_busy[dmachan] = 0;
if (dmaworkaround_waiting_for_chan == dmachan) {
//Reset DMA
SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST);
CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST);
dmaworkaround_waiting_for_chan = 0;
//Call callback
dmaworkaround_cb(dmaworkaround_cb_arg);
}
portEXIT_CRITICAL(&dmaworkaround_mux);
}
void IRAM_ATTR spicommon_dmaworkaround_transfer_active(int dmachan)
{
portENTER_CRITICAL(&dmaworkaround_mux);
dmaworkaround_channels_busy[dmachan] = 1;
portEXIT_CRITICAL(&dmaworkaround_mux);
}

View File

@ -34,12 +34,12 @@ queue and re-enabling the interrupt will trigger the interrupt again, which can
#include <string.h>
#include "driver/spi_common.h"
#include "driver/spi_master.h"
#include "soc/gpio_sig_map.h"
#include "soc/spi_reg.h"
#include "soc/dport_reg.h"
#include "soc/spi_struct.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/ets_sys.h"
#include "esp_types.h"
#include "esp_attr.h"
@ -54,9 +54,7 @@ queue and re-enabling the interrupt will trigger the interrupt again, which can
#include "freertos/ringbuf.h"
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "soc/uart_struct.h"
#include "rom/lldesc.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#include "esp_heap_alloc_caps.h"
@ -71,8 +69,11 @@ typedef struct {
spi_dev_t *hw;
spi_transaction_t *cur_trans;
int cur_cs;
lldesc_t dmadesc_tx, dmadesc_rx;
lldesc_t *dmadesc_tx;
lldesc_t *dmadesc_rx;
bool no_gpio_matrix;
int dma_chan;
int max_transfer_sz;
} spi_host_t;
struct spi_device_t {
@ -92,182 +93,47 @@ static const char *SPI_TAG = "spi_master";
return (ret_val); \
}
/*
Stores a bunch of per-spi-peripheral data.
*/
typedef struct {
const uint8_t spiclk_out; //GPIO mux output signals
const uint8_t spid_out;
const uint8_t spiq_out;
const uint8_t spiwp_out;
const uint8_t spihd_out;
const uint8_t spid_in; //GPIO mux input signals
const uint8_t spiq_in;
const uint8_t spiwp_in;
const uint8_t spihd_in;
const uint8_t spics_out[3]; // /CS GPIO output mux signals
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 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
spi_dev_t *hw; //Pointer to the hardware registers
} spi_signal_conn_t;
/*
Bunch of constants for every SPI peripheral: GPIO signals, irqs, hw addr of registers etc
*/
static const spi_signal_conn_t io_signal[3]={
{
.spiclk_out=SPICLK_OUT_IDX,
.spid_out=SPID_OUT_IDX,
.spiq_out=SPIQ_OUT_IDX,
.spiwp_out=SPIWP_OUT_IDX,
.spihd_out=SPIHD_OUT_IDX,
.spid_in=SPID_IN_IDX,
.spiq_in=SPIQ_IN_IDX,
.spiwp_in=SPIWP_IN_IDX,
.spihd_in=SPIHD_IN_IDX,
.spics_out={SPICS0_OUT_IDX, SPICS1_OUT_IDX, SPICS2_OUT_IDX},
.spiclk_native=6,
.spid_native=8,
.spiq_native=7,
.spiwp_native=10,
.spihd_native=9,
.spics0_native=11,
.irq=ETS_SPI1_INTR_SOURCE,
.irq_dma=ETS_SPI1_DMA_INTR_SOURCE,
.module=PERIPH_SPI_MODULE,
.hw=&SPI1
}, {
.spiclk_out=HSPICLK_OUT_IDX,
.spid_out=HSPID_OUT_IDX,
.spiq_out=HSPIQ_OUT_IDX,
.spiwp_out=HSPIWP_OUT_IDX,
.spihd_out=HSPIHD_OUT_IDX,
.spid_in=HSPID_IN_IDX,
.spiq_in=HSPIQ_IN_IDX,
.spiwp_in=HSPIWP_IN_IDX,
.spihd_in=HSPIHD_IN_IDX,
.spics_out={HSPICS0_OUT_IDX, HSPICS1_OUT_IDX, HSPICS2_OUT_IDX},
.spiclk_native=14,
.spid_native=13,
.spiq_native=12,
.spiwp_native=2,
.spihd_native=4,
.spics0_native=15,
.irq=ETS_SPI2_INTR_SOURCE,
.irq_dma=ETS_SPI2_DMA_INTR_SOURCE,
.module=PERIPH_HSPI_MODULE,
.hw=&SPI2
}, {
.spiclk_out=VSPICLK_OUT_IDX,
.spid_out=VSPID_OUT_IDX,
.spiq_out=VSPIQ_OUT_IDX,
.spiwp_out=VSPIWP_OUT_IDX,
.spihd_out=VSPIHD_OUT_IDX,
.spid_in=VSPID_IN_IDX,
.spiq_in=VSPIQ_IN_IDX,
.spiwp_in=VSPIWP_IN_IDX,
.spihd_in=VSPIHD_IN_IDX,
.spics_out={VSPICS0_OUT_IDX, VSPICS1_OUT_IDX, VSPICS2_OUT_IDX},
.spiclk_native=18,
.spid_native=23,
.spiq_native=19,
.spiwp_native=22,
.spihd_native=21,
.spics0_native=5,
.irq=ETS_SPI3_INTR_SOURCE,
.irq_dma=ETS_SPI3_DMA_INTR_SOURCE,
.module=PERIPH_VSPI_MODULE,
.hw=&SPI3
}
};
static void spi_intr(void *arg);
esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan)
{
bool native=true;
bool native, claimed;
/* ToDo: remove this when we have flash operations cooperating with this */
SPI_CHECK(host!=SPI_HOST, "SPI1 is not supported", ESP_ERR_NOT_SUPPORTED);
SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host]==NULL, "host already in use", ESP_ERR_INVALID_STATE);
SPI_CHECK(bus_config->mosi_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->mosi_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->sclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->miso_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->quadwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_config->quadhd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG);
//The host struct contains two dma descriptors, so we need DMA'able memory for this.
spihost[host]=pvPortMallocCaps(sizeof(spi_host_t), MALLOC_CAP_DMA);
if (spihost[host]==NULL) return ESP_ERR_NO_MEM;
claimed=spicommon_periph_claim(host);
SPI_CHECK(claimed, "host already in use", ESP_ERR_INVALID_STATE);
spihost[host]=malloc(sizeof(spi_host_t));
if (spihost[host]==NULL) goto nomem;
memset(spihost[host], 0, sizeof(spi_host_t));
//Check if the selected pins correspond to the native pins of the peripheral
if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num!=io_signal[host].spid_native) native=false;
if (bus_config->miso_io_num >= 0 && bus_config->miso_io_num!=io_signal[host].spiq_native) native=false;
if (bus_config->sclk_io_num >= 0 && bus_config->sclk_io_num!=io_signal[host].spiclk_native) native=false;
if (bus_config->quadwp_io_num >= 0 && bus_config->quadwp_io_num!=io_signal[host].spiwp_native) native=false;
if (bus_config->quadhd_io_num >= 0 && bus_config->quadhd_io_num!=io_signal[host].spihd_native) native=false;
spicommon_bus_initialize_io(host, bus_config, dma_chan, SPICOMMON_BUSFLAG_MASTER|SPICOMMON_BUSFLAG_QUAD, &native);
spihost[host]->no_gpio_matrix=native;
if (native) {
//All SPI native 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.
if (bus_config->mosi_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1);
if (bus_config->miso_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1);
if (bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1);
if (bus_config->quadhd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1);
if (bus_config->sclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1);
spihost[host]->dma_chan=dma_chan;
if (dma_chan == 0) {
spihost[host]->max_transfer_sz = 32;
} else {
//Use GPIO
if (bus_config->mosi_io_num>0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->mosi_io_num, GPIO_MODE_OUTPUT);
gpio_matrix_out(bus_config->mosi_io_num, io_signal[host].spid_out, false, false);
gpio_matrix_in(bus_config->mosi_io_num, io_signal[host].spid_in, false);
}
if (bus_config->miso_io_num>0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->miso_io_num, GPIO_MODE_INPUT);
gpio_matrix_out(bus_config->miso_io_num, io_signal[host].spiq_out, false, false);
gpio_matrix_in(bus_config->miso_io_num, io_signal[host].spiq_in, false);
}
if (bus_config->quadwp_io_num>0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->quadwp_io_num, GPIO_MODE_OUTPUT);
gpio_matrix_out(bus_config->quadwp_io_num, io_signal[host].spiwp_out, false, false);
gpio_matrix_in(bus_config->quadwp_io_num, io_signal[host].spiwp_in, false);
}
if (bus_config->quadhd_io_num>0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->quadhd_io_num, GPIO_MODE_OUTPUT);
gpio_matrix_out(bus_config->quadhd_io_num, io_signal[host].spihd_out, false, false);
gpio_matrix_in(bus_config->quadhd_io_num, io_signal[host].spihd_in, false);
}
if (bus_config->sclk_io_num>0) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->sclk_io_num, GPIO_MODE_OUTPUT);
gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false);
}
//See how many dma descriptors we need and allocate them
int dma_desc_ct=(bus_config->max_transfer_sz+SPI_MAX_DMA_LEN-1)/SPI_MAX_DMA_LEN;
if (dma_desc_ct==0) dma_desc_ct=1; //default to 4k when max is not given
spihost[host]->max_transfer_sz = dma_desc_ct*SPI_MAX_DMA_LEN;
spihost[host]->dmadesc_tx=pvPortMallocCaps(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA);
spihost[host]->dmadesc_rx=pvPortMallocCaps(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA);
if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) goto nomem;
}
periph_module_enable(io_signal[host].module);
esp_intr_alloc(io_signal[host].irq, ESP_INTR_FLAG_INTRDISABLED, spi_intr, (void*)spihost[host], &spihost[host]->intr);
spihost[host]->hw=io_signal[host].hw;
esp_intr_alloc(spicommon_irqsource_for_host(host), ESP_INTR_FLAG_INTRDISABLED, spi_intr, (void*)spihost[host], &spihost[host]->intr);
spihost[host]->hw=spicommon_hw_for_host(host);
//Reset DMA
spihost[host]->hw->dma_conf.val|=SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_conf.val|=SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_out_link.start=0;
spihost[host]->hw->dma_in_link.start=0;
spihost[host]->hw->dma_conf.val&=~(SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
spihost[host]->hw->dma_conf.val&=~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
//Reset timing
spihost[host]->hw->ctrl2.val=0;
@ -287,10 +153,16 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus
spihost[host]->hw->slave.trans_inten=1;
spihost[host]->hw->slave.trans_done=1;
//Select DMA channel.
SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, dma_chan, (host * 2));
return ESP_OK;
nomem:
if (spihost[host]) {
free(spihost[host]->dmadesc_tx);
free(spihost[host]->dmadesc_rx);
}
free(spihost[host]);
spicommon_periph_free(host);
return ESP_ERR_NO_MEM;
}
esp_err_t spi_bus_free(spi_host_device_t host)
@ -304,7 +176,7 @@ esp_err_t spi_bus_free(spi_host_device_t host)
spihost[host]->hw->slave.trans_inten=0;
spihost[host]->hw->slave.trans_done=0;
esp_intr_free(spihost[host]->intr);
periph_module_disable(io_signal[host].module);
spicommon_periph_free(host);
free(spihost[host]);
spihost[host]=NULL;
return ESP_OK;
@ -336,13 +208,14 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config
//Allocate memory for device
spi_device_t *dev=malloc(sizeof(spi_device_t));
if (dev==NULL) return ESP_ERR_NO_MEM;
if (dev==NULL) goto nomem;
memset(dev, 0, sizeof(spi_device_t));
spihost[host]->device[freecs]=dev;
//Allocate queues, set defaults
dev->trans_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_transaction_t *));
dev->ret_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_transaction_t *));
if (!dev->trans_queue || !dev->ret_queue) goto nomem;
if (dev_config->duty_cycle_pos==0) dev_config->duty_cycle_pos=128;
dev->host=spihost[host];
@ -351,15 +224,8 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config
//Set CS pin, CS options
if (dev_config->spics_io_num > 0) {
if (spihost[host]->no_gpio_matrix &&dev_config->spics_io_num == io_signal[host].spics0_native && freecs==0) {
//Again, the cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define.
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], 1);
} else {
//Use GPIO matrix
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], PIN_FUNC_GPIO);
gpio_set_direction(dev_config->spics_io_num, GPIO_MODE_OUTPUT);
gpio_matrix_out(dev_config->spics_io_num, io_signal[host].spics_out[freecs], false, false);
}
gpio_set_direction(dev_config->spics_io_num, GPIO_MODE_OUTPUT);
spicommon_cs_initialize(host, dev_config->spics_io_num, freecs, spihost[host]->no_gpio_matrix == false);
}
if (dev_config->flags&SPI_DEVICE_CLK_AS_CS) {
spihost[host]->hw->pin.master_ck_sel |= (1<<freecs);
@ -373,6 +239,14 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config
}
*handle=dev;
return ESP_OK;
nomem:
if (dev) {
if (dev->trans_queue) vQueueDelete(dev->trans_queue);
if (dev->ret_queue) vQueueDelete(dev->ret_queue);
}
free(dev);
return ESP_ERR_NO_MEM;
}
esp_err_t spi_bus_remove_device(spi_device_handle_t handle)
@ -426,7 +300,7 @@ static int spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) {
int bestpre=-1;
int besterr=0;
int errval;
for (n=1; n<=64; n++) {
for (n=2; n<=64; n++) { //Start at 2: we need to be able to set h/l so we have at least one high and one low pulse.
//Effectively, this does pre=round((fapb/n)/hz).
pre=((fapb/n)+(hz/2))/hz;
if (pre<=0) pre=1;
@ -457,10 +331,6 @@ static int spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) {
}
//If a transaction is smaller than or equal to of bits, we do not use DMA; instead, we directly copy/paste
//bits from/to the work registers. Keep between 32 and (8*32) please.
#define THRESH_DMA_TRANS (8*32)
//This is run in interrupt context and apart from initialization and destruction, this is the only code
//touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are
//no muxes in this code.
@ -478,7 +348,7 @@ static void IRAM_ATTR spi_intr(void *arg)
if (host->cur_trans) {
//Okay, transaction is done.
if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_TRANS_USE_RXDATA)) && host->cur_trans->rxlength<=THRESH_DMA_TRANS) {
if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_TRANS_USE_RXDATA)) && host->dma_chan == 0) {
//Need to copy from SPI regs to result buffer.
uint32_t *data;
if (host->cur_trans->flags & SPI_TRANS_USE_RXDATA) {
@ -489,7 +359,9 @@ static void IRAM_ATTR spi_intr(void *arg)
for (int x=0; x < host->cur_trans->rxlength; x+=32) {
//Do a memcpy to get around possible alignment issues in rx_buffer
uint32_t word=host->hw->data_buf[x/32];
memcpy(&data[x/32], &word, 4);
int len=host->cur_trans->rxlength-x;
if (len>32) len=32;
memcpy(&data[x/32], &word, (len+7)/8);
}
}
//Call post-transaction callback, if any
@ -499,6 +371,8 @@ static void IRAM_ATTR spi_intr(void *arg)
host->cur_trans=NULL;
prevCs=host->cur_cs;
}
//Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
if (host->dma_chan) spicommon_dmaworkaround_idle(host->dma_chan);
//ToDo: This is a stupidly simple low-cs-first priority scheme. Make this configurable somehow. - JD
for (i=0; i<NO_CS; i++) {
if (host->device[i]) {
@ -523,7 +397,7 @@ static void IRAM_ATTR spi_intr(void *arg)
if (trans->rxlength==0) {
trans->rxlength=trans->length;
}
//Reconfigure according to device settings, but only if we change CSses.
if (i!=prevCs) {
//Assumes a hardcoded 80MHz Fapb for now. ToDo: figure out something better once we have
@ -590,12 +464,13 @@ static void IRAM_ATTR spi_intr(void *arg)
host->hw->pin.cs1_dis=(i==1)?0:1;
host->hw->pin.cs2_dis=(i==2)?0:1;
}
//Reset DMA
host->hw->dma_conf.val |= SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
//Reset SPI peripheral
host->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
host->hw->dma_out_link.start=0;
host->hw->dma_in_link.start=0;
host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
//QIO/DIO
host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
host->hw->dma_conf.out_data_burst_en=1;
//Set up QIO/DIO if needed
host->hw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO);
host->hw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO);
if (trans->flags & SPI_TRANS_MODE_DIO) {
@ -627,17 +502,13 @@ static void IRAM_ATTR spi_intr(void *arg)
} else {
data=trans->rx_buffer;
}
if (trans->rxlength <= THRESH_DMA_TRANS) {
//No need for DMA; we'll copy the result out of the work registers directly later.
host->hw->user.usr_miso_highpart=0;
if (host->dma_chan == 0) {
//No need to setup anything; we'll copy the result out of the work registers directly later.
} else {
host->hw->user.usr_miso_highpart=0;
host->dmadesc_rx.size=(trans->rxlength+7)/8;
host->dmadesc_rx.length=(trans->rxlength+7)/8;
host->dmadesc_rx.buf=(uint8_t*)data;
host->dmadesc_rx.eof=1;
host->dmadesc_rx.sosf=0;
host->dmadesc_rx.owner=1;
host->hw->dma_in_link.addr=(int)(&host->dmadesc_rx)&0xFFFFF;
spicommon_dmaworkaround_transfer_active(host->dma_chan); //mark channel as active
spicommon_setup_dma_desc_links(host->dmadesc_rx, ((trans->rxlength+7)/8), (uint8_t*)data, true);
host->hw->dma_in_link.addr=(int)(&host->dmadesc_rx[0]) & 0xFFFFF;
host->hw->dma_in_link.start=1;
}
host->hw->user.usr_miso=1;
@ -652,8 +523,8 @@ static void IRAM_ATTR spi_intr(void *arg)
} else {
data=(uint32_t *)trans->tx_buffer;
}
if (trans->length <= THRESH_DMA_TRANS) {
//No need for DMA.
if (host->dma_chan == 0) {
//Need to copy data to registers manually
for (int x=0; x < trans->length; x+=32) {
//Use memcpy to get around alignment issues for txdata
uint32_t word;
@ -662,19 +533,17 @@ static void IRAM_ATTR spi_intr(void *arg)
}
host->hw->user.usr_mosi_highpart=1;
} else {
spicommon_dmaworkaround_transfer_active(host->dma_chan); //mark channel as active
spicommon_setup_dma_desc_links(host->dmadesc_tx, (trans->length+7)/8, (uint8_t*)data, false);
host->hw->user.usr_mosi_highpart=0;
host->dmadesc_tx.size=(trans->length+7)/8;
host->dmadesc_tx.length=(trans->length+7)/8;
host->dmadesc_tx.buf=(uint8_t*)data;
host->dmadesc_tx.eof=1;
host->dmadesc_tx.sosf=0;
host->dmadesc_tx.owner=1;
host->hw->dma_out_link.addr=(int)(&host->dmadesc_tx) & 0xFFFFF;
host->hw->dma_out_link.addr=(int)(&host->dmadesc_tx[0]) & 0xFFFFF;
host->hw->dma_out_link.start=1;
host->hw->user.usr_mosi_highpart=0;
}
}
host->hw->mosi_dlen.usr_mosi_dbitlen=trans->length-1;
host->hw->miso_dlen.usr_miso_dbitlen=trans->rxlength-1;
host->hw->user2.usr_command_value=trans->command;
if (dev->cfg.address_bits>32) {
host->hw->addr=trans->address >> 32;
@ -682,8 +551,8 @@ static void IRAM_ATTR spi_intr(void *arg)
} else {
host->hw->addr=trans->address & 0xffffffff;
}
host->hw->user.usr_mosi=(trans->tx_buffer==NULL)?0:1;
host->hw->user.usr_miso=(trans->rx_buffer==NULL)?0:1;
host->hw->user.usr_mosi=(trans->tx_buffer!=NULL || (trans->flags & SPI_TRANS_USE_TXDATA))?1:0;
host->hw->user.usr_miso=(trans->rx_buffer!=NULL || (trans->flags & SPI_TRANS_USE_RXDATA))?1:0;
//Call pre-transmission callback, if any
if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans);
@ -702,6 +571,8 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *
SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32 bits", ESP_ERR_INVALID_ARG);
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(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);
r=xQueueSend(handle->trans_queue, (void*)&trans_desc, ticks_to_wait);
if (!r) return ESP_ERR_TIMEOUT;
esp_intr_enable(handle->host->intr);

View File

@ -0,0 +1,393 @@
// Copyright 2015-2016 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.
#include <string.h>
#include "driver/spi_common.h"
#include "driver/spi_slave.h"
#include "soc/gpio_sig_map.h"
#include "soc/spi_reg.h"
#include "soc/dport_reg.h"
#include "soc/spi_struct.h"
#include "rom/ets_sys.h"
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_intr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_api.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "rom/lldesc.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#include "esp_heap_alloc_caps.h"
static const char *SPI_TAG = "spi_slave";
#define SPI_CHECK(a, str, ret_val) \
if (!(a)) { \
ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
#define VALID_HOST(x) (x>SPI_HOST && x<=VSPI_HOST)
typedef struct {
spi_slave_interface_config_t cfg;
intr_handle_t intr;
spi_dev_t *hw;
spi_slave_transaction_t *cur_trans;
lldesc_t *dmadesc_tx;
lldesc_t *dmadesc_rx;
bool no_gpio_matrix;
int max_transfer_sz;
QueueHandle_t trans_queue;
QueueHandle_t ret_queue;
int dma_chan;
} spi_slave_t;
static spi_slave_t *spihost[3];
static void IRAM_ATTR spi_intr(void *arg);
esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, const spi_slave_interface_config_t *slave_config, int dma_chan)
{
bool native, claimed;
//We only support HSPI/VSPI, period.
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
claimed = spicommon_periph_claim(host);
SPI_CHECK(claimed, "host already in use", ESP_ERR_INVALID_STATE);
spihost[host] = malloc(sizeof(spi_slave_t));
if (spihost[host] == NULL) goto nomem;
memset(spihost[host], 0, sizeof(spi_slave_t));
memcpy(&spihost[host]->cfg, slave_config, sizeof(spi_slave_interface_config_t));
spicommon_bus_initialize_io(host, bus_config, dma_chan, SPICOMMON_BUSFLAG_SLAVE, &native);
gpio_set_direction(slave_config->spics_io_num, GPIO_MODE_INPUT);
spicommon_cs_initialize(host, slave_config->spics_io_num, 0, native == false);
spihost[host]->no_gpio_matrix = native;
spihost[host]->dma_chan = dma_chan;
if (dma_chan != 0) {
//See how many dma descriptors we need and allocate them
int dma_desc_ct = (bus_config->max_transfer_sz + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN;
if (dma_desc_ct == 0) dma_desc_ct = 1; //default to 4k when max is not given
spihost[host]->max_transfer_sz = dma_desc_ct * SPI_MAX_DMA_LEN;
spihost[host]->dmadesc_tx = pvPortMallocCaps(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
spihost[host]->dmadesc_rx = pvPortMallocCaps(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) goto nomem;
} else {
//We're limited to non-DMA transfers: the SPI work registers can hold 64 bytes at most.
spihost[host]->max_transfer_sz = 16 * 4;
}
//Create queues
spihost[host]->trans_queue = xQueueCreate(slave_config->queue_size, sizeof(spi_slave_transaction_t *));
spihost[host]->ret_queue = xQueueCreate(slave_config->queue_size, sizeof(spi_slave_transaction_t *));
if (!spihost[host]->trans_queue || !spihost[host]->ret_queue) goto nomem;
esp_intr_alloc(spicommon_irqsource_for_host(host), ESP_INTR_FLAG_INTRDISABLED, spi_intr, (void *)spihost[host], &spihost[host]->intr);
spihost[host]->hw = spicommon_hw_for_host(host);
//Configure slave
spihost[host]->hw->clock.val = 0;
spihost[host]->hw->user.val = 0;
spihost[host]->hw->ctrl.val = 0;
spihost[host]->hw->slave.wr_rd_buf_en = 1; //no sure if needed
spihost[host]->hw->user.doutdin = 1; //we only support full duplex
spihost[host]->hw->user.sio = 0;
spihost[host]->hw->slave.slave_mode = 1;
spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_out_link.start = 0;
spihost[host]->hw->dma_in_link.start = 0;
spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
spihost[host]->hw->dma_conf.out_data_burst_en = 1;
spihost[host]->hw->slave.sync_reset = 1;
spihost[host]->hw->slave.sync_reset = 0;
bool nodelay = true;
spihost[host]->hw->ctrl.rd_bit_order = (slave_config->flags & SPI_SLAVE_RXBIT_LSBFIRST) ? 1 : 0;
spihost[host]->hw->ctrl.wr_bit_order = (slave_config->flags & SPI_SLAVE_TXBIT_LSBFIRST) ? 1 : 0;
if (slave_config->mode == 0) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
} else if (slave_config->mode == 1) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
} else if (slave_config->mode == 2) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
} else if (slave_config->mode == 3) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
}
//Reset DMA
spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_out_link.start = 0;
spihost[host]->hw->dma_in_link.start = 0;
spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
//Disable unneeded ints
spihost[host]->hw->slave.rd_buf_done = 0;
spihost[host]->hw->slave.wr_buf_done = 0;
spihost[host]->hw->slave.rd_sta_done = 0;
spihost[host]->hw->slave.wr_sta_done = 0;
spihost[host]->hw->slave.rd_buf_inten = 0;
spihost[host]->hw->slave.wr_buf_inten = 0;
spihost[host]->hw->slave.rd_sta_inten = 0;
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
//any transactions that are queued.
spihost[host]->hw->slave.trans_inten = 1;
spihost[host]->hw->slave.trans_done = 1;
return ESP_OK;
nomem:
if (spihost[host]) {
if (spihost[host]->trans_queue) vQueueDelete(spihost[host]->trans_queue);
if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue);
free(spihost[host]->dmadesc_tx);
free(spihost[host]->dmadesc_rx);
}
free(spihost[host]);
spihost[host] = NULL;
spicommon_periph_free(host);
return ESP_ERR_NO_MEM;
}
esp_err_t spi_slave_free(spi_host_device_t host)
{
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
if (spihost[host]->trans_queue) vQueueDelete(spihost[host]->trans_queue);
if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue);
free(spihost[host]->dmadesc_tx);
free(spihost[host]->dmadesc_rx);
free(spihost[host]);
spihost[host] = NULL;
spicommon_periph_free(host);
spihost[host] = NULL;
return ESP_OK;
}
esp_err_t spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait)
{
BaseType_t r;
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans_desc->length <= spihost[host]->max_transfer_sz * 8, "data transfer > host maximum", ESP_ERR_INVALID_ARG);
r = xQueueSend(spihost[host]->trans_queue, (void *)&trans_desc, ticks_to_wait);
if (!r) return ESP_ERR_TIMEOUT;
esp_intr_enable(spihost[host]->intr);
return ESP_OK;
}
esp_err_t spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, TickType_t ticks_to_wait)
{
BaseType_t r;
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
r = xQueueReceive(spihost[host]->ret_queue, (void *)trans_desc, ticks_to_wait);
if (!r) return ESP_ERR_TIMEOUT;
return ESP_OK;
}
esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait)
{
esp_err_t ret;
spi_slave_transaction_t *ret_trans;
//ToDo: check if any spi transfers in flight
ret = spi_slave_queue_trans(host, trans_desc, ticks_to_wait);
if (ret != ESP_OK) return ret;
ret = spi_slave_get_trans_result(host, &ret_trans, ticks_to_wait);
if (ret != ESP_OK) return ret;
assert(ret_trans == trans_desc);
return ESP_OK;
}
#ifdef DEBUG_SLAVE
static void dumpregs(spi_dev_t *hw)
{
ets_printf("***REG DUMP ***\n");
ets_printf("mosi_dlen : %08X\n", hw->mosi_dlen.val);
ets_printf("miso_dlen : %08X\n", hw->miso_dlen.val);
ets_printf("slv_wrbuf_dlen : %08X\n", hw->slv_wrbuf_dlen.val);
ets_printf("slv_rdbuf_dlen : %08X\n", hw->slv_rdbuf_dlen.val);
ets_printf("slave : %08X\n", hw->slave.val);
ets_printf("slv_rdata_bit : %x\n", hw->slv_rd_bit.slv_rdata_bit);
ets_printf("dma_rx_status : %08X\n", hw->dma_rx_status);
ets_printf("dma_tx_status : %08X\n", hw->dma_tx_status);
}
static void dumpll(lldesc_t *ll)
{
ets_printf("****LL DUMP****\n");
ets_printf("Size %d\n", ll->size);
ets_printf("Len: %d\n", ll->length);
ets_printf("Owner: %s\n", ll->owner ? "dma" : "cpu");
}
#endif
static void IRAM_ATTR spi_slave_restart_after_dmareset(void *arg)
{
spi_slave_t *host = (spi_slave_t *)arg;
esp_intr_enable(host->intr);
}
//This is run in interrupt context and apart from initialization and destruction, this is the only code
//touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are
//no muxes in this code.
static void IRAM_ATTR spi_intr(void *arg)
{
BaseType_t r;
BaseType_t do_yield = pdFALSE;
spi_slave_transaction_t *trans = NULL;
spi_slave_t *host = (spi_slave_t *)arg;
#ifdef DEBUG_SLAVE
dumpregs(host->hw);
if (host->dmadesc_rx) dumpll(&host->dmadesc_rx[0]);
#endif
//Ignore all but the trans_done int.
if (!host->hw->slave.trans_done) return;
if (host->cur_trans) {
if (host->dma_chan == 0 && host->cur_trans->rx_buffer) {
//Copy result out
uint32_t *data = host->cur_trans->rx_buffer;
for (int x = 0; x < host->cur_trans->length; x += 32) {
uint32_t word;
int len = host->cur_trans->length - x;
if (len > 32) len = 32;
word = host->hw->data_buf[(x / 32)];
memcpy(&data[x / 32], &word, (len + 7) / 8);
}
} else if (host->dma_chan != 0 && host->cur_trans->rx_buffer) {
int i;
//In case CS goes high too soon, the transfer is aborted while the DMA channel still thinks it's going. This
//leads to issues later on, so in that case we need to reset the channel. The state can be detected because
//the DMA system doesn't give back the offending descriptor; the owner is still set to DMA.
for (i = 0; host->dmadesc_rx[i].eof == 0 && host->dmadesc_rx[i].owner == 0; i++) ;
if (host->dmadesc_rx[i].owner) {
spicommon_dmaworkaround_req_reset(host->dma_chan, spi_slave_restart_after_dmareset, host);
}
}
if (host->cfg.post_trans_cb) host->cfg.post_trans_cb(host->cur_trans);
//Okay, transaction is done.
//Return transaction descriptor.
xQueueSendFromISR(host->ret_queue, &host->cur_trans, &do_yield);
host->cur_trans = NULL;
}
if (host->dma_chan != 0) {
spicommon_dmaworkaround_idle(host->dma_chan);
if (spicommon_dmaworkaround_reset_in_progress()) {
//We need to wait for the reset to complete. Disable int (will be re-enabled on reset callback) and exit isr.
esp_intr_disable(host->intr);
if (do_yield) portYIELD_FROM_ISR();
return;
}
}
//Grab next transaction
r = xQueueReceiveFromISR(host->trans_queue, &trans, &do_yield);
if (!r) {
//No packet waiting. Disable interrupt.
esp_intr_disable(host->intr);
} else {
//We have a transaction. Send it.
host->hw->slave.trans_done = 0; //clear int bit
host->cur_trans = trans;
if (host->dma_chan != 0) {
spicommon_dmaworkaround_transfer_active(host->dma_chan);
host->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
host->hw->dma_out_link.start = 0;
host->hw->dma_in_link.start = 0;
host->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
host->hw->dma_conf.out_data_burst_en = 0;
host->hw->dma_conf.indscr_burst_en = 0;
host->hw->dma_conf.outdscr_burst_en = 0;
//Fill DMA descriptors
if (trans->rx_buffer) {
host->hw->user.usr_miso_highpart = 0;
spicommon_setup_dma_desc_links(host->dmadesc_rx, ((trans->length + 7) / 8), trans->rx_buffer, true);
host->hw->dma_in_link.addr = (int)(&host->dmadesc_rx[0]) & 0xFFFFF;
host->hw->dma_in_link.start = 1;
}
if (trans->tx_buffer) {
spicommon_setup_dma_desc_links(host->dmadesc_tx, (trans->length + 7) / 8, trans->tx_buffer, false);
host->hw->user.usr_mosi_highpart = 0;
host->hw->dma_out_link.addr = (int)(&host->dmadesc_tx[0]) & 0xFFFFF;
host->hw->dma_out_link.start = 1;
}
host->hw->slave.sync_reset = 1;
host->hw->slave.sync_reset = 0;
} else {
//No DMA. Turn off SPI and copy data to transmit buffers.
host->hw->cmd.usr = 0;
host->hw->slave.sync_reset = 1;
host->hw->slave.sync_reset = 0;
host->hw->user.usr_miso_highpart = 0;
host->hw->user.usr_mosi_highpart = 0;
if (trans->tx_buffer) {
const uint32_t *data = host->cur_trans->tx_buffer;
for (int x = 0; x < trans->length; x += 32) {
uint32_t word;
memcpy(&word, &data[x / 32], 4);
host->hw->data_buf[(x / 32)] = word;
}
}
}
host->hw->slv_rd_bit.slv_rdata_bit = 0;
host->hw->slv_wrbuf_dlen.bit_len = trans->length - 1;
host->hw->slv_rdbuf_dlen.bit_len = trans->length - 1;
host->hw->mosi_dlen.usr_mosi_dbitlen = trans->length - 1;
host->hw->miso_dlen.usr_miso_dbitlen = trans->length - 1;
host->hw->user.usr_mosi = (trans->tx_buffer == NULL) ? 0 : 1;
host->hw->user.usr_miso = (trans->rx_buffer == NULL) ? 0 : 1;
//Kick off transfer
host->hw->cmd.usr = 1;
if (host->cfg.post_setup_cb) host->cfg.post_setup_cb(trans);
}
if (do_yield) portYIELD_FROM_ISR();
}

View File

@ -18,6 +18,7 @@
#include "soc/dport_reg.h"
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "esp_heap_alloc_caps.h"
static void check_spi_pre_n_for(int clk, int pre, int n)
@ -60,7 +61,7 @@ TEST_CASE("SPI Master clockdiv calculation routines", "[spi]")
{
spi_bus_config_t buscfg={
.mosi_io_num=4,
.miso_io_num=16,
.miso_io_num=26,
.sclk_io_num=25,
.quadwp_io_num=-1,
.quadhd_io_num=-1
@ -69,81 +70,192 @@ TEST_CASE("SPI Master clockdiv calculation routines", "[spi]")
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
TEST_ASSERT(ret==ESP_OK);
check_spi_pre_n_for(26000000, 1, 3);
check_spi_pre_n_for(20000000, 1, 4);
check_spi_pre_n_for(8000000, 1, 10);
check_spi_pre_n_for(800000, 2, 50);
check_spi_pre_n_for(100000, 16, 50);
check_spi_pre_n_for(333333, 4, 60);
check_spi_pre_n_for(1, 8192, 64); //Actually should generate the minimum clock speed, 152Hz
check_spi_pre_n_for(900000, 4, 60);
// check_spi_pre_n_for(1, 8192, 64); //Actually should generate the minimum clock speed, 152Hz
check_spi_pre_n_for(26000000, 1, 3);
ret=spi_bus_free(HSPI_HOST);
TEST_ASSERT(ret==ESP_OK);
}
static void test_spi_bus_speed(int hz) {
esp_err_t ret;
spi_device_handle_t handle;
static spi_device_handle_t setup_spi_bus(int clkspeed, bool dma) {
spi_bus_config_t buscfg={
.mosi_io_num=4,
.miso_io_num=26,
.sclk_io_num=25,
.quadwp_io_num=-1,
.quadhd_io_num=-1,
.max_transfer_sz=4096*3
};
spi_device_interface_config_t devcfg={
.command_bits=8,
.address_bits=64,
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=hz,
.clock_speed_hz=clkspeed,
.duty_cycle_pos=128,
.mode=0,
.spics_io_num=21,
.queue_size=3,
};
esp_err_t ret;
spi_device_handle_t handle;
printf("THIS TEST NEEDS A JUMPER BETWEEN IO4 AND IO26\n");
ret=spi_bus_initialize(HSPI_HOST, &buscfg, dma?1:0);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_add_device(HSPI_HOST, &devcfg, &handle);
TEST_ASSERT(ret==ESP_OK);
printf("Bus/dev inited.\n");
spi_transaction_t t;
char sendbuf[64]="Hello World!";
char recvbuf[64]="UUUUUUUUUUUUUUU";
memset(&t, 0, sizeof(t));
return handle;
}
t.length=64*8;
static void spi_test(spi_device_handle_t handle, int num_bytes) {
esp_err_t ret;
int x;
srand(num_bytes);
char *sendbuf=pvPortMallocCaps(num_bytes, MALLOC_CAP_DMA);
char *recvbuf=pvPortMallocCaps(num_bytes, MALLOC_CAP_DMA);
for (x=0; x<num_bytes; x++) {
sendbuf[x]=rand()&0xff;
recvbuf[x]=0x55;
}
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length=num_bytes*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
t.address=0xA00000000000000FL;
t.command=0x55;
printf("Transmit...\n");
printf("Transmitting %d bytes...\n", num_bytes);
ret=spi_device_transmit(handle, &t);
TEST_ASSERT(ret==ESP_OK);
printf("Send vs recv:\n");
for (int x=0; x<16; x++) printf("%02X ", (int)sendbuf[x]);
printf("<sent\n");
for (int x=0; x<16; x++) printf("%02X ", (int)recvbuf[x]);
printf("<recv\n");
ret=spi_bus_remove_device(handle);
TEST_ASSERT(ret==ESP_OK);
TEST_ASSERT_EQUAL_INT8_ARRAY(sendbuf, recvbuf, 64);
}
TEST_CASE("SPI Master test", "[spi][ignore]")
{
spi_bus_config_t buscfg={
.mosi_io_num=4,
.miso_io_num=16,
.sclk_io_num=25,
.quadwp_io_num=-1,
.quadhd_io_num=-1
};
esp_err_t ret;
printf("THIS TEST NEEDS A JUMPER BETWEEN IO4 AND IO16\n");
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
TEST_ASSERT(ret==ESP_OK);
int freqs[]={8000, 1000000, 5000000, 10000000, 20000000, 26666666, 0};
for (int x=0; freqs[x]!=0; x++) {
printf("Testing clock speed of %dHz...\n", freqs[x]);
test_spi_bus_speed(freqs[x]);
srand(num_bytes);
for (x=0; x<num_bytes; x++) {
if (sendbuf[x]!=(rand()&0xff)) {
printf("Huh? Sendbuf corrupted at byte %d\n", x);
TEST_ASSERT(0);
}
if (sendbuf[x]!=recvbuf[x]) break;
}
if (x!=num_bytes) {
int from=x-16;
if (from<0) from=0;
printf("Error at %d! Sent vs recved: (starting from %d)\n" , x, from);
for (int i=0; i<32; i++) {
if (i+from<num_bytes) printf("%02X ", sendbuf[from+i]);
}
printf("\n");
for (int i=0; i<32; i++) {
if (i+from<num_bytes) printf("%02X ", recvbuf[from+i]);
}
printf("\n");
// TEST_ASSERT(0);
}
printf("Success!\n");
free(sendbuf);
free(recvbuf);
}
static void destroy_spi_bus(spi_device_handle_t handle) {
esp_err_t ret;
ret=spi_bus_remove_device(handle);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_free(HSPI_HOST);
TEST_ASSERT(ret==ESP_OK);
}
#define TEST_LEN 111
TEST_CASE("SPI Master test", "[spi][ignore]")
{
printf("Testing bus at 80KHz\n");
spi_device_handle_t handle=setup_spi_bus(80000, true);
spi_test(handle, 16); //small
spi_test(handle, 21); //small, unaligned
spi_test(handle, 36); //aligned
spi_test(handle, 128); //aligned
spi_test(handle, 129); //unaligned
spi_test(handle, 4096-2); //multiple descs, edge case 1
spi_test(handle, 4096-1); //multiple descs, edge case 2
spi_test(handle, 4096*3); //multiple descs
destroy_spi_bus(handle);
printf("Testing bus at 80KHz, non-DMA\n");
handle=setup_spi_bus(80000, false);
spi_test(handle, 4); //aligned
spi_test(handle, 16); //small
spi_test(handle, 21); //small, unaligned
destroy_spi_bus(handle);
printf("Testing bus at 26MHz\n");
handle=setup_spi_bus(20000000, true);
spi_test(handle, 128); //DMA, aligned
spi_test(handle, 4096*3); //DMA, multiple descs
destroy_spi_bus(handle);
printf("Testing bus at 900KHz\n");
handle=setup_spi_bus(9000000, true);
spi_test(handle, 128); //DMA, aligned
spi_test(handle, 4096*3); //DMA, multiple descs
destroy_spi_bus(handle);
}
TEST_CASE("SPI Master test, interaction of multiple devs", "[spi][ignore]") {
esp_err_t ret;
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=1000000,
.duty_cycle_pos=128,
.mode=0,
.spics_io_num=23,
.queue_size=3,
};
spi_device_handle_t handle1=setup_spi_bus(80000, true);
spi_device_handle_t handle2;
spi_bus_add_device(HSPI_HOST, &devcfg, &handle2);
printf("Sending to dev 1\n");
spi_test(handle1, 7);
printf("Sending to dev 1\n");
spi_test(handle1, 15);
printf("Sending to dev 2\n");
spi_test(handle2, 15);
printf("Sending to dev 1\n");
spi_test(handle1, 32);
printf("Sending to dev 2\n");
spi_test(handle2, 32);
printf("Sending to dev 1\n");
spi_test(handle1, 63);
printf("Sending to dev 2\n");
spi_test(handle2, 63);
printf("Sending to dev 1\n");
spi_test(handle1, 5000);
printf("Sending to dev 2\n");
spi_test(handle2, 5000);
ret=spi_bus_remove_device(handle2);
destroy_spi_bus(handle1);
}

View File

@ -72,6 +72,7 @@
#define MCU_SEL_M (MCU_SEL_V << MCU_SEL_S)
#define MCU_SEL_V 0x7
#define MCU_SEL_S 12
#define MCU_SEL_V 0x7
#define PIN_INPUT_ENABLE(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME,FUN_IE)
#define PIN_INPUT_DISABLE(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME,FUN_IE)

View File

@ -14,6 +14,7 @@ Peripherals API
SD/MMC Card Host <../storage/sdmmc>
Sigma-delta Modulation <sigmadelta>
SPI Master <spi_master>
SPI Slave <spi_slave>
Remote Control <rmt>
Timer <timer>
UART <uart>

View File

@ -8,8 +8,7 @@ The ESP32 has four SPI peripheral devices, called SPI0, SPI1, HSPI and VSPI. SPI
the flash cache the ESP32 uses to map the SPI flash device it is connected to into memory. SPI1 is
connected to the same hardware lines as SPI0 and is used to write to the flash chip. HSPI and VSPI
are free to use. SPI1, HSPI and VSPI all have three chip select lines, allowing them to drive up to
three SPI devices each as a master. The SPI peripherals also can be used in slave mode, driven from
another SPI master.
three SPI devices each as a master.
The spi_master driver
^^^^^^^^^^^^^^^^^^^^^
@ -74,7 +73,7 @@ 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 conencted 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.

View File

@ -0,0 +1,142 @@
SPI Slave driver
=================
Overview
--------
The ESP32 has four SPI peripheral devices, called SPI0, SPI1, HSPI and VSPI. SPI0 is entirely dedicated to
the flash cache the ESP32 uses to map the SPI flash device it is connected to into memory. SPI1 is
connected to the same hardware lines as SPI0 and is used to write to the flash chip. HSPI and VSPI
are free to use, and with the spi_slave driver, these can be used as a SPI slave, driven from a
connected SPI master.
The spi_slave driver
^^^^^^^^^^^^^^^^^^^^^
The spi_slave driver allows using the HSPI and/or VSPI peripheral as a full-duplex SPI slave. It can make
use of DMA to send/receive transactions of arbitrary length.
Terminology
^^^^^^^^^^^
The spi_slave driver uses the following terms:
* Host: The SPI peripheral inside the ESP32 initiating the SPI transmissions. One of HSPI or VSPI.
* Bus: The SPI bus, common to all SPI devices connected to a master. In general the bus consists of the
miso, mosi, sclk and optionally quadwp and quadhd signals. The SPI slaves are connected to these
signals in parallel. Each SPI slave is also connected to one CS signal.
- miso - Also known as q, this is the output of the serial stream from the ESP32 to the SPI master
- mosi - Also known as d, this is the output of the serial stream from the SPI master to the ESP32
- sclk - Clock signal. Each data bit is clocked out or in on the positive or negative edge of this signal
- cs - Chip Select. An active Chip Select delineates a single transaction to/from a slave.
* Transaction: One instance of CS going active, data transfer from and to a master happening, and
CS going inactive again. Transactions are atomic, as in they will never be interrupted by another
transaction.
SPI transactions
^^^^^^^^^^^^^^^^
A full-duplex SPI transaction starts with the master pulling CS low. After this happens, the master
starts sending out clock pulses on the CLK line: every clock pulse causes a data bit to be shifted from
the master to the slave on the MOSI line and vice versa on the MISO line. At the end of the transaction,
the master makes CS high again.
Using the spi_slave driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Initialize a SPI peripheral as a slave by calling ``spi_slave_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.
A DMA channel (either 1 or 2) must be given if transactions will be larger than 32 bytes, if not
the dma_chan parameter may be 0.
- To set up a transaction, fill one or more spi_transaction_t structure with any transaction
parameters you need. Either queue all transactions by calling ``spi_slave_queue_trans``, later
quering the result using ``spi_slave_get_trans_result``, or handle all requests synchroneously
by feeding them into ``spi_slave_transmit``. The latter two functions will block until the
master has initiated and finished a transaction, causing the queued data to be sent and received.
- Optional: to unload the SPI slave driver, call ``spi_slave_free``.
Transaction data and master/slave length mismatches
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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. The SPI driver
may decide to use DMA for transfers, so these buffers should be allocated in DMA-capable memory using
``pvPortMallocCaps(size, MALLOC_CAP_DMA)``.
The amount of data written to the buffers is limited by the ``length`` member of the transaction structure:
the driver will never read/write more data than indicated there. The ``length`` cannot define the actual
length of the SPI transaction; this is determined by the master as it drives the clock and CS lines. In
case the length of the transmission is larger than the buffer length, only the start of the transmission
will be sent and received. In case the transmission length is shorter than the buffer length, only data up
to the length of the buffer will be exchanged.
Warning: Due to a design peculiarity in the ESP32, if the amount of bytes sent by the master or the length
of the transmission queues in the slave driver, in bytes, is not both larger than eight and dividable by
four, the SPI hardware can fail to write the last one to seven bytes to the receive buffer.
Application Example
-------------------
Slave/master communication: :example:`peripherals/spi_slave`.
API Reference
-------------
Header Files
^^^^^^^^^^^^
* :component_file:`driver/include/driver/spi_slave.h`
Macros
^^^^^^
.. doxygendefine:: SPI_SLAVE_TXBIT_LSBFIRST
.. doxygendefine:: SPI_SLAVE_RXBIT_LSBFIRST
.. doxygendefine:: SPI_SLAVE_BIT_LSBFIRST
.. doxygendefine:: SPI_SLAVE_POSITIVE_CS
Enumerations
^^^^^^^^^^^^
.. doxygenenum:: spi_host_device_t
Type Definitions
^^^^^^^^^^^^^^^^
Structures
^^^^^^^^^^
.. doxygenstruct:: spi_slave_transaction_t
:members:
.. doxygenstruct:: spi_slave_interface_config_t
:members:
.. doxygenstruct:: spi_bus_config_t
:members:
Be advised that the slave driver does not use the quadwp/quadhd lines and fields in ``spi_bus_config_t`` refering to these lines
will be ignored and can thus safely be left uninitialized.
Functions
---------
.. doxygenfunction:: spi_slave_initialize
.. doxygenfunction:: spi_slave_free
.. doxygenfunction:: spi_slave_queue_trans
.. doxygenfunction:: spi_slave_get_trans_result
.. doxygenfunction:: spi_slave_transmit

View File

@ -0,0 +1,22 @@
## SPI slave example
These two projects illustrate the SPI Slave driver. They're supposed to be flashed into two separate ESP32s connected to
eachother using the SPI pins defined in app_main.c. Once connected and flashed, they will use the spi master and spi slave
driver to communicate with eachother. The example also includes a handshaking line to allow the master to only poll the
slave when it is actually ready to parse a transaction.
Please run wires between the following GPIOs between the slave and master to make the example function:
========= ====== =======
Signal Slave Master
========= ====== =======
Handshake GPIO2 GPIO2
MOSI GPIO12 GPIO12
MISO GPIO13 GPIO13
SCLK GPIO15 GPIO15
CS GPIO14 GPIO14
========= ====== =======
Be aware that the example by default uses lines normally reserved for JTAG. If this is an issue, either because of hardwired
JTAG hardware or because of the need to do JTAG debugging, feel free to change the GPIO settings by editing defines in the top
of main.c in the master/slave source code.

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := spi-slave-receiver
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,142 @@
/* SPI Slave example, receiver (uses SPI Slave driver to communicate with sender)
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/cache.h"
#include "driver/spi_slave.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
/*
SPI receiver (slave) example.
This example is supposed to work together with the SPI sender. It uses the standard SPI pins (MISO, MOSI, SCLK, CS) to
transmit data over in a full-duplex fashion, that is, while the master puts data on the MOSI pin, the slave puts its own
data on the MISO pin.
This example uses one extra pin: GPIO_HANDSHAKE is used as a handshake pin. After a transmission has been set up and we're
ready to send/receive data, this code uses a callback to set the handshake pin high. The sender will detect this and start
sending a transaction. As soon as the transaction is done, the line gets set low again.
*/
/*
Pins in use. The SPI Master can use the GPIO mux, so feel free to change these if needed.
*/
#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 12
#define GPIO_MISO 13
#define GPIO_SCLK 15
#define GPIO_CS 14
//Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
void my_post_setup_cb(spi_slave_transaction_t *trans) {
WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
}
//Called after transaction is sent/received. We use this to set the handshake line low.
void my_post_trans_cb(spi_slave_transaction_t *trans) {
WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
}
//Main application
void app_main()
{
int n=0;
esp_err_t ret;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK
};
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg={
.mode=0,
.spics_io_num=GPIO_CS,
.queue_size=3,
.flags=0,
.post_setup_cb=my_post_setup_cb,
.post_trans_cb=my_post_trans_cb
};
//Configuration for the handshake line
gpio_config_t io_conf={
.intr_type=GPIO_INTR_DISABLE,
.mode=GPIO_MODE_OUTPUT,
.pin_bit_mask=(1<<GPIO_HANDSHAKE)
};
//Configure handshake line as output
gpio_config(&io_conf);
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);
//Initialize SPI slave interface
ret=spi_slave_initialize(HSPI_HOST, &buscfg, &slvcfg, 1);
assert(ret==ESP_OK);
char sendbuf[129]="";
char recvbuf[129]="";
memset(recvbuf, 0, 33);
spi_slave_transaction_t t;
memset(&t, 0, sizeof(t));
while(1) {
//Clear receive buffer, set send buffer to something sane
memset(recvbuf, 0xA5, 129);
sprintf(sendbuf, "This is the receiver, sending data for transmission number %04d.", n);
//Set up a transaction of 128 bytes to send/receive
t.length=128*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
/* This call enables the SPI slave interface to send/receive to the sendbuf and recvbuf. The transaction is
initialized by the SPI master, however, so it will not actually happen until the master starts a hardware transaction
by pulling CS low and pulsing the clock etc. In this specific example, we use the handshake line, pulled up by the
.post_setup_cb callback that is called as soon as a transaction is ready, to let the master know it is free to transfer
data.
*/
ret=spi_slave_transmit(HSPI_HOST, &t, portMAX_DELAY);
//spi_slave_transmit does not return until the master has done a transmission, so by here we have sent our data and
//received data from the master. Print it.
printf("Received: %s\n", recvbuf);
n++;
}
}

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := spi-slave-sender
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,160 @@
/* SPI Slave example, sender (uses SPI master driver)
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/cache.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "soc/gpio_reg.h"
#include "driver/gpio.h"
#include "esp_intr_alloc.h"
/*
SPI sender (master) example.
This example is supposed to work together with the SPI receiver. It uses the standard SPI pins (MISO, MOSI, SCLK, CS) to
transmit data over in a full-duplex fashion, that is, while the master puts data on the MOSI pin, the slave puts its own
data on the MISO pin.
This example uses one extra pin: GPIO_HANDSHAKE is used as a handshake pin. The slave makes this pin high as soon as it is
ready to receive/send data. This code connects this line to a GPIO interrupt which gives the rdySem semaphore. The main
task waits for this semaphore to be given before queueing a transmission.
*/
/*
Pins in use. The SPI Master can use the GPIO mux, so feel free to change these if needed.
*/
#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 12
#define GPIO_MISO 13
#define GPIO_SCLK 15
#define GPIO_CS 14
//The semaphore indicating the slave is ready to receive stuff.
static xQueueHandle rdySem;
/*
This ISR is called when the handshake line goes high.
*/
static void IRAM_ATTR gpio_handshake_isr_handler(void* arg)
{
//Sometimes due to interference or ringing or something, we get two irqs after eachother. This is solved by
//looking at the time between interrupts and refusing any interrupt too close to another one.
static uint32_t lasthandshaketime;
uint32_t currtime=xthal_get_ccount();
uint32_t diff=currtime-lasthandshaketime;
if (diff<240000) return; //ignore everything <1ms after an earlier irq
lasthandshaketime=currtime;
//Give the semaphore.
BaseType_t mustYield=false;
xSemaphoreGiveFromISR(rdySem, &mustYield);
if (mustYield) portYIELD_FROM_ISR();
}
//Main application
void app_main()
{
esp_err_t ret;
spi_device_handle_t handle;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num=-1,
.quadhd_io_num=-1
};
//Configuration for the SPI device on the other side of the bus
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=5000000,
.duty_cycle_pos=128, //50% duty cycle
.mode=0,
.spics_io_num=GPIO_CS,
.cs_ena_posttrans=3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
.queue_size=3
};
//GPIO config for the handshake line.
gpio_config_t io_conf={
.intr_type=GPIO_PIN_INTR_POSEDGE,
.mode=GPIO_MODE_INPUT,
.pull_up_en=1,
.pin_bit_mask=(1<<GPIO_HANDSHAKE)
};
int n=0;
char sendbuf[128]="";
char recvbuf[128]="";
spi_transaction_t t;
memset(&t, 0, sizeof(t));
//Create the semaphore.
rdySem=xSemaphoreCreateBinary();
//Set up handshake line interrupt.
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_set_intr_type(GPIO_HANDSHAKE, GPIO_PIN_INTR_POSEDGE);
gpio_isr_handler_add(GPIO_HANDSHAKE, gpio_handshake_isr_handler, NULL);
//Initialize the SPI bus and add the device we want to send stuff to.
ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
assert(ret==ESP_OK);
ret=spi_bus_add_device(HSPI_HOST, &devcfg, &handle);
assert(ret==ESP_OK);
//Assume the slave is ready for the first transmission: if the slave started up before us, we will not detect
//positive edge on the handshake line.
xSemaphoreGive(rdySem);
while(1) {
snprintf(sendbuf, 128, "Sender, transmission no. %04i. Last time, I received: \"%s\"", n, recvbuf);
t.length=128*8; //128 bytes
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
//Wait for slave to be ready for next byte before sending
xSemaphoreTake(rdySem, 100);//portMAX_DELAY); //Wait until slave is ready
ret=spi_device_transmit(handle, &t);
printf("Received: %s\n", recvbuf);
n++;
}
//Never reached.
ret=spi_bus_remove_device(handle);
assert(ret==ESP_OK);
}

View File

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@ -88,12 +88,7 @@ CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
#
# Component config
#
#
# Amazon Web Service IoT Config
#
CONFIG_AWS_IOT_MQTT_HOST=""
CONFIG_AWS_IOT_MQTT_PORT=8883
# CONFIG_AWS_IOT_SDK is not set
# CONFIG_BT_ENABLED is not set
CONFIG_BT_RESERVE_DRAM=0
@ -150,6 +145,9 @@ CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=0
CONFIG_WIFI_ENABLED=y
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=0
# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_AMPDU_ENABLED=y
CONFIG_ESP32_WIFI_NVS_ENABLED=y
@ -164,6 +162,34 @@ CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_ETHERNET is not set
#
# FAT Filesystem support
#
CONFIG_FATFS_CODEPAGE_ASCII=y
# CONFIG_FATFS_CODEPAGE_437 is not set
# CONFIG_FATFS_CODEPAGE_720 is not set
# CONFIG_FATFS_CODEPAGE_737 is not set
# CONFIG_FATFS_CODEPAGE_771 is not set
# CONFIG_FATFS_CODEPAGE_775 is not set
# CONFIG_FATFS_CODEPAGE_850 is not set
# CONFIG_FATFS_CODEPAGE_852 is not set
# CONFIG_FATFS_CODEPAGE_855 is not set
# CONFIG_FATFS_CODEPAGE_857 is not set
# CONFIG_FATFS_CODEPAGE_860 is not set
# CONFIG_FATFS_CODEPAGE_861 is not set
# CONFIG_FATFS_CODEPAGE_862 is not set
# CONFIG_FATFS_CODEPAGE_863 is not set
# CONFIG_FATFS_CODEPAGE_864 is not set
# CONFIG_FATFS_CODEPAGE_865 is not set
# CONFIG_FATFS_CODEPAGE_866 is not set
# CONFIG_FATFS_CODEPAGE_869 is not set
# CONFIG_FATFS_CODEPAGE_932 is not set
# CONFIG_FATFS_CODEPAGE_936 is not set
# CONFIG_FATFS_CODEPAGE_949 is not set
# CONFIG_FATFS_CODEPAGE_950 is not set
CONFIG_FATFS_CODEPAGE=1
CONFIG_FATFS_MAX_LFN=255
#
# FreeRTOS
#
@ -185,6 +211,10 @@ CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG=y
CONFIG_FREERTOS_ISR_STACKSIZE=1536
# CONFIG_FREERTOS_LEGACY_HOOKS is not set
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
# CONFIG_SUPPORT_STATIC_ALLOCATION is not set
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_FREERTOS_DEBUG_INTERNALS is not set
#