PERIPH_UHCI1_MODULE,
PERIPH_RMT_MODULE,
PERIPH_PCNT_MODULE,
+ PERIPH_SPI_MODULE,
+ PERIPH_HSPI_MODULE,
+ PERIPH_VSPI_MODULE,
} periph_module_t;
/**
--- /dev/null
+// Copyright 2010-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.
+
+
+#ifndef _DRIVER_SPI_MASTER_H_
+#define _DRIVER_SPI_MASTER_H_
+
+#include "esp_err.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+
+
+
+#ifdef __cplusplus
+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 spid_io_num; ///< GPIO pin for spi_d (=MOSI)signal, or -1 if not used.
+ int spiq_io_num; ///< GPIO pin for spi_q (=MISO) signal, or -1 if not used.
+ int spiclk_io_num; ///< GPIO pin for spi_clk signal, or -1 if not used.
+ int spiwp_io_num; ///< GPIO pin for spi_wp signal, or -1 if not used.
+ int spihd_io_num; ///< GPIO pin for spi_hd signal, 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_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
+
+
+typedef struct spi_transaction_t spi_transaction_t;
+typedef void(*transaction_cb_t)(spi_transaction_t *trans);
+
+/**
+ * @brief This is a configuration for a SPI slave device that is connected to one of the SPI buses.
+ */
+typedef struct {
+ uint8_t command_bits; ///< Amount of bits in command phase (0-16)
+ uint8_t address_bits; ///< Amount of bits in address phase (0-64)
+ uint8_t dummy_bits; ///< Amount of dummy bits to insert between address and data phase
+ uint8_t mode; ///< SPI mode (0-3)
+ uint8_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
+ uint8_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.
+ uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)
+ int clock_speed_hz; ///< Clock speed, in Hz
+ int spics_io_num; ///< CS GPIO pin for this device, or -1 if not used
+ uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags
+ int queue_size; ///< Transaction queue size
+ transaction_cb_t pre_cb; ///< Callback to be called before a transmission is started. This callback is called within interrupt context.
+ transaction_cb_t post_cb; ///< Callback to be called after a transmission has completed. This callback is called within interrupt context.
+} spi_device_interface_config_t;
+
+
+#define SPI_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode
+#define SPI_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode
+#define SPI_MODE_DIOQIO_ADDR (1<<2) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO
+#define SPI_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer.
+#define SPI_USE_TXDATA (1<<3) ///< Transmit tx_data member of spi_transaction_t instead of data at tx_buffer. Do not set tx_buffer when using this.
+
+/**
+ * This structure describes one SPI transaction
+ */
+struct spi_transaction_t {
+ uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flags
+ uint16_t command; ///< Command data. Specific length was given when device was added to the bus.
+ uint64_t address; ///< Address. Specific length was given when device was added to the bus.
+ size_t length; ///< Total data length, in bits
+ size_t rxlength; ///< Total data length received, if different from length. (0 defaults this to the value of ``length``)
+ void *user; ///< User-defined variable. Can be used to store eg transaction ID.
+ union {
+ const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase
+ uint8_t tx_data[4]; ///< If SPI_USE_TXDATA is set, data set here is sent directly from this variable.
+ };
+ union {
+ void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase
+ uint8_t rx_data[4]; ///< If SPI_USE_RXDATA is set, data is received directly to this variable
+ };
+};
+
+
+typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a SPI bus
+
+/**
+ * @brief Initialize a SPI bus
+ *
+ * @warning For now, only supports HSPI and VSPI.
+ *
+ * @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.
+ * @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_bus_initialize(spi_host_device_t host, spi_bus_config_t *bus_config, int dma_chan);
+
+/**
+ * @brief Free a SPI bus
+ *
+ * @warning In order for this to succeed, all devices have to be removed first.
+ *
+ * @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_bus_free(spi_host_device_t host);
+
+/**
+ * @brief Allocate a device on a SPI bus
+ *
+ * This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master
+ * peripheral and routes it to the indicated GPIO. All SPI master devices have three CS pins and can thus control
+ * up to three devices.
+ *
+ * @param host SPI peripheral to allocate device on
+ * @param dev_config SPI interface protocol config for the device
+ * @param handle Pointer to variable to hold the device handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NOT_FOUND if host doesn't have any free CS slots
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);
+
+
+/**
+ * @brief Remove a device from the SPI bus
+ *
+ * @param handle Device handle to free
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_INVALID_STATE if device already is freed
+ * - ESP_OK on success
+ */
+esp_err_t spi_bus_remove_device(spi_device_handle_t handle);
+
+
+/**
+ * @brief Queue a SPI transaction for execution
+ *
+ * @param handle Device handle obtained using spi_host_add_dev
+ * @param trans_desc Description of transaction to execute
+ * @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_device_queue_trans(spi_device_handle_t handle, spi_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_device_queue_trans) has succesfully completed. It will then return the description of the
+ * completed transaction so software can inspect the result and e.g. free the memory or
+ * re-use the buffers.
+ *
+ * @param handle Device handle obtained using spi_host_add_dev
+ * @param trans_desc Pointer to variable able to contain a pointer to the description of the
+ * transaction that is executed
+ * @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_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait);
+
+
+/**
+ * @brief Do a SPI transaction
+ *
+ * Essentially does the same as spi_device_queue_trans followed by spi_device_get_trans_result. Do
+ * not use this when there is still a transaction queued that hasn't been finalized
+ * using spi_device_get_trans_result.
+ *
+ * @param handle Device handle obtained using spi_host_add_dev
+ * @param trans_desc Pointer to variable able to contain a pointer to the description of the
+ * transaction that is executed
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file
SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_PCNT_CLK_EN);
CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST);
break;
+ case PERIPH_SPI_MODULE:
+ SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_1);
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_1);
+ break;
+ case PERIPH_HSPI_MODULE:
+ SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN);
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST);
+ break;
+ case PERIPH_VSPI_MODULE:
+ SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_2);
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_2);
+ break;
default:
break;
}
CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_PCNT_CLK_EN);
SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST);
break;
+ case PERIPH_SPI_MODULE:
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_1);
+ SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_1);
+ break;
+ case PERIPH_HSPI_MODULE:
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN);
+ SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST);
+ break;
+ case PERIPH_VSPI_MODULE:
+ CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_2);
+ SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_2);
+ break;
default:
break;
}
--- /dev/null
+// 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.
+
+/*
+Architecture:
+
+We can initialize a SPI driver, but we don't talk to the SPI driver itself, we address a device. A device essentially
+is a combination of SPI port and CS pin, plus some information about the specifics of communication to the device
+(timing, command/address length etc)
+
+The essence of the interface to a device is a set of queues; one per device. The idea is that to send something to a SPI
+device, you allocate a transaction descriptor. It contains some information about the transfer like the lenghth, address,
+command etc, plus pointers to transmit and receive buffer. The address of this block gets pushed into the transmit queue.
+The SPI driver does its magic, and sends and retrieves the data eventually. The data gets written to the receive buffers,
+if needed the transaction descriptor is modified to indicate returned parameters and the entire thing goes into the return
+queue, where whatever software initiated the transaction can retrieve it.
+
+The entire thing is run from the SPI interrupt handler. If SPI is done transmitting/receiving but nothing is in the queue,
+it will not clear the SPI interrupt but just disable it. This way, when a new thing is sent, pushing the packet into the send
+queue and re-enabling the interrupt will trigger the interrupt again, which can then take care of the sending.
+*/
+
+
+
+#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 "soc/rtc_cntl_reg.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 "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"
+
+typedef struct spi_device_t spi_device_t;
+
+#define NO_CS 3 //Number of CS pins per SPI host
+
+typedef struct {
+ spi_device_t *device[NO_CS];
+ intr_handle_t intr;
+ spi_dev_t *hw;
+ spi_transaction_t *cur_trans;
+ int cur_cs;
+ lldesc_t dmadesc_tx, dmadesc_rx;
+ bool no_gpio_matrix;
+} spi_host_t;
+
+struct spi_device_t {
+ QueueHandle_t trans_queue;
+ QueueHandle_t ret_queue;
+ spi_device_interface_config_t cfg;
+ spi_host_t *host;
+};
+
+static spi_host_t *spihost[3];
+
+
+static const char *SPI_TAG = "spi_master";
+#define SPI_CHECK(a, str, ret_val) \
+ if (!(a)) { \
+ ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
+ 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, spi_bus_config_t *bus_config, int dma_chan)
+{
+ bool native=true;
+ /* 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->spid_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spid_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(bus_config->spiclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(bus_config->spiq_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->spiq_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(bus_config->spiwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(bus_config->spihd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spihd_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;
+ memset(spihost[host], 0, sizeof(spi_host_t));
+
+ //Check if the selected pins correspond to the native pins of the peripheral
+ if (bus_config->spid_io_num >= 0 && bus_config->spid_io_num!=io_signal[host].spid_native) native=false;
+ if (bus_config->spiq_io_num >= 0 && bus_config->spiq_io_num!=io_signal[host].spiq_native) native=false;
+ if (bus_config->spiclk_io_num >= 0 && bus_config->spiclk_io_num!=io_signal[host].spiclk_native) native=false;
+ if (bus_config->spiwp_io_num >= 0 && bus_config->spiwp_io_num!=io_signal[host].spiwp_native) native=false;
+ if (bus_config->spihd_io_num >= 0 && bus_config->spihd_io_num!=io_signal[host].spihd_native) native=false;
+
+ 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->spid_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], 1);
+ if (bus_config->spiq_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], 1);
+ if (bus_config->spiwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], 1);
+ if (bus_config->spihd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], 1);
+ if (bus_config->spiclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_io_num], 1);
+ } else {
+ //Use GPIO
+ if (bus_config->spid_io_num>0) {
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], PIN_FUNC_GPIO);
+ gpio_set_direction(bus_config->spid_io_num, GPIO_MODE_OUTPUT);
+ gpio_matrix_out(bus_config->spid_io_num, io_signal[host].spid_out, false, false);
+ gpio_matrix_in(bus_config->spid_io_num, io_signal[host].spid_in, false);
+ }
+ if (bus_config->spiq_io_num>0) {
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], PIN_FUNC_GPIO);
+ gpio_set_direction(bus_config->spiq_io_num, GPIO_MODE_INPUT);
+ gpio_matrix_out(bus_config->spiq_io_num, io_signal[host].spiq_out, false, false);
+ gpio_matrix_in(bus_config->spiq_io_num, io_signal[host].spiq_in, false);
+ }
+ if (bus_config->spiwp_io_num>0) {
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], PIN_FUNC_GPIO);
+ gpio_set_direction(bus_config->spiwp_io_num, GPIO_MODE_OUTPUT);
+ gpio_matrix_out(bus_config->spiwp_io_num, io_signal[host].spiwp_out, false, false);
+ gpio_matrix_in(bus_config->spiwp_io_num, io_signal[host].spiwp_in, false);
+ }
+ if (bus_config->spihd_io_num>0) {
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], PIN_FUNC_GPIO);
+ gpio_set_direction(bus_config->spihd_io_num, GPIO_MODE_OUTPUT);
+ gpio_matrix_out(bus_config->spihd_io_num, io_signal[host].spihd_out, false, false);
+ gpio_matrix_in(bus_config->spihd_io_num, io_signal[host].spihd_in, false);
+ }
+ if (bus_config->spiclk_io_num>0) {
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_io_num], PIN_FUNC_GPIO);
+ gpio_set_direction(bus_config->spiclk_io_num, GPIO_MODE_OUTPUT);
+ gpio_matrix_out(bus_config->spiclk_io_num, io_signal[host].spiclk_out, false, false);
+ }
+ }
+ 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;
+
+ //Reset DMA
+ spihost[host]->hw->dma_conf.val|=SPI_OUT_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);
+
+ //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;
+
+ //Select DMA channel.
+ SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, dma_chan, (host * 2));
+
+ return ESP_OK;
+}
+
+esp_err_t spi_bus_free(spi_host_device_t host)
+{
+ int x;
+ SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(spihost[host]!=NULL, "host not in use", ESP_ERR_INVALID_STATE);
+ for (x=0; x<NO_CS; x++) {
+ SPI_CHECK(spihost[host]->device[x]==NULL, "not all CSses freed", ESP_ERR_INVALID_STATE);
+ }
+ 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);
+ free(spihost[host]);
+ spihost[host]=NULL;
+ return ESP_OK;
+}
+
+/*
+ Add a device. This allocates a CS line for the device, allocates memory for the device structure and hooks
+ up the CS pin to whatever is specified.
+*/
+esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config_t *dev_config, spi_device_handle_t *handle)
+{
+ int freecs;
+ SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(spihost[host]!=NULL, "host not initialized", ESP_ERR_INVALID_STATE);
+ SPI_CHECK(dev_config->spics_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num), "spics pin invalid", ESP_ERR_INVALID_ARG);
+ for (freecs=0; freecs<NO_CS; freecs++) {
+ //See if this slot is free; reserve if it is by putting a dummy pointer in the slot. We use an atomic compare&swap to make this thread-safe.
+ if (__sync_bool_compare_and_swap(&spihost[host]->device[freecs], NULL, (spi_device_t *)1)) break;
+ }
+ SPI_CHECK(freecs!=NO_CS, "no free cs pins for host", ESP_ERR_NOT_FOUND);
+ //The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full
+ //duplex mode does absolutely nothing on the ESP32.
+ SPI_CHECK(dev_config->cs_ena_pretrans==0 || (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "cs pretrans delay incompatible with full-duplex", ESP_ERR_INVALID_ARG);
+
+ //Allocate memory for device
+ spi_device_t *dev=malloc(sizeof(spi_device_t));
+ if (dev==NULL) return ESP_ERR_NO_MEM;
+ 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_config->duty_cycle_pos==0) dev_config->duty_cycle_pos=128;
+ dev->host=spihost[host];
+
+ //We want to save a copy of the dev config in the dev struct.
+ memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t));
+
+ //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);
+ }
+ }
+ if (dev_config->flags&SPI_DEVICE_CLK_AS_CS) {
+ spihost[host]->hw->pin.master_ck_sel |= (1<<freecs);
+ } else {
+ spihost[host]->hw->pin.master_ck_sel &= (1<<freecs);
+ }
+ if (dev_config->flags&SPI_DEVICE_POSITIVE_CS) {
+ spihost[host]->hw->pin.master_cs_pol |= (1<<freecs);
+ } else {
+ spihost[host]->hw->pin.master_cs_pol &= (1<<freecs);
+ }
+ *handle=dev;
+ return ESP_OK;
+}
+
+esp_err_t spi_bus_remove_device(spi_device_handle_t handle)
+{
+ int x;
+ SPI_CHECK(handle!=NULL, "invalid handle", ESP_ERR_INVALID_ARG);
+ //These checks aren't exhaustive; another thread could sneak in a transaction inbetween. These are only here to
+ //catch design errors and aren't meant to be triggered during normal operation.
+ SPI_CHECK(uxQueueMessagesWaiting(handle->trans_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
+ SPI_CHECK(handle->host->cur_trans==0 || handle->host->device[handle->host->cur_cs]!=handle, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
+ SPI_CHECK(uxQueueMessagesWaiting(handle->ret_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
+
+ //Kill queues
+ vQueueDelete(handle->trans_queue);
+ vQueueDelete(handle->ret_queue);
+ //Remove device from list of csses and free memory
+ for (x=0; x<NO_CS; x++) {
+ if (handle->host->device[x] == handle) handle->host->device[x]=NULL;
+ }
+ free(handle);
+ return ESP_OK;
+}
+
+static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) {
+ int pre, n, h, l;
+ //In hw, n, h and l are 1-32, pre is 0-8K. Value written to register is one lower than used value.
+ if (hz>(fapb/2)) {
+ //Can only solve this using fapb directly.
+ hw->clock.clkcnt_l=0;
+ hw->clock.clkcnt_h=0;
+ hw->clock.clkcnt_n=0;
+ hw->clock.clkdiv_pre=0;
+ hw->clock.clk_equ_sysclk=1;
+ } else {
+ //For best duty cycle resolution, we want n to be as close to 32 as possible.
+ //ToDo:
+ //This algo could use some tweaking; at the moment it either fixes n to 32 and
+ //uses the prescaler to get a suitable division factor, or sets the prescaler to 0
+ //and uses n to set a value. In practice, sometimes a better result can be
+ //obtained by setting both n and pre to well-chosen valued... ToDo: fix up some algo to
+ //do this automatically (worst-case: bruteforce n/pre combo's) - JD
+ //Also ToDo:
+ //The ESP32 has a SPI_CK_OUT_HIGH_MODE and SPI_CK_OUT_LOW_MODE register; it looks like we can
+ //use those to specify the duty cycle in a more precise way. Figure out how to use these. - JD
+ n=(fapb/(hz*32));
+ if (n>32) {
+ //Need to use prescaler
+ n=32;
+ }
+ if (n<32) {
+ //No need for prescaler.
+ n=(fapb/hz);
+ }
+ pre=(fapb/n)/hz;
+ h=n;
+ l=(((256-duty_cycle)*n+127)/256);
+ hw->clock.clk_equ_sysclk=0;
+ hw->clock.clkcnt_n=n-1;
+ hw->clock.clkdiv_pre=pre-1;
+ hw->clock.clkcnt_h=h-1;
+ hw->clock.clkcnt_l=l-1;
+ }
+}
+
+
+//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.
+static void IRAM_ATTR spi_intr(void *arg)
+{
+ int i;
+ int prevCs=-1;
+ BaseType_t r;
+ BaseType_t do_yield=pdFALSE;
+ spi_transaction_t *trans=NULL;
+ spi_host_t *host=(spi_host_t*)arg;
+
+ //Ignore all but the trans_done int.
+ if (!host->hw->slave.trans_done) return;
+
+ if (host->cur_trans) {
+ //Okay, transaction is done.
+ if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_USE_RXDATA)) && host->cur_trans->rxlength<=THRESH_DMA_TRANS) {
+ //Need to copy from SPI regs to result buffer.
+ uint32_t *data;
+ if (host->cur_trans->flags & SPI_USE_RXDATA) {
+ data=(uint32_t*)&host->cur_trans->rx_data[0];
+ } else {
+ data=(uint32_t*)host->cur_trans->rx_buffer;
+ }
+ 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);
+ }
+ }
+ //Call post-transaction callback, if any
+ if (host->device[host->cur_cs]->cfg.post_cb) host->device[host->cur_cs]->cfg.post_cb(host->cur_trans);
+ //Return transaction descriptor.
+ xQueueSendFromISR(host->device[host->cur_cs]->ret_queue, &host->cur_trans, &do_yield);
+ host->cur_trans=NULL;
+ prevCs=host->cur_cs;
+ }
+ //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]) {
+ r=xQueueReceiveFromISR(host->device[i]->trans_queue, &trans, &do_yield);
+ //Stop looking if we have a transaction to send.
+ if (r) break;
+ }
+ }
+ if (i==NO_CS) {
+ //No packet waiting. Disable interrupt.
+ esp_intr_disable(host->intr);
+ } else {
+ host->hw->slave.trans_done=0; //clear int bit
+ //We have a transaction. Send it.
+ spi_device_t *dev=host->device[i];
+ host->cur_trans=trans;
+ //We should be done with the transmission.
+ assert(host->hw->cmd.usr == 0);
+
+ //Default rxlength to be the same as length, if not filled in.
+ if (trans->rxlength==0) {
+ trans->rxlength=trans->length;
+ }
+
+ //Reconfigure accoding 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
+ //clock scaling working.
+ int apbclk=APB_CLK_FREQ;
+ spi_set_clock(host->hw, apbclk, dev->cfg.clock_speed_hz, dev->cfg.duty_cycle_pos);
+ //Configure bit order
+ host->hw->ctrl.rd_bit_order=(dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0;
+ host->hw->ctrl.wr_bit_order=(dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0;
+
+ //Configure polarity
+ //SPI iface needs to be configured for a delay unless it is not routed through GPIO and clock is >=apb/2
+ int nodelay=(host->no_gpio_matrix && dev->cfg.clock_speed_hz >= (apbclk/2));
+ if (dev->cfg.mode==0) {
+ host->hw->pin.ck_idle_edge=0;
+ host->hw->user.ck_out_edge=0;
+ host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
+ } else if (dev->cfg.mode==1) {
+ host->hw->pin.ck_idle_edge=0;
+ host->hw->user.ck_out_edge=1;
+ host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
+ } else if (dev->cfg.mode==2) {
+ host->hw->pin.ck_idle_edge=1;
+ host->hw->user.ck_out_edge=1;
+ host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
+ } else if (dev->cfg.mode==3) {
+ host->hw->pin.ck_idle_edge=1;
+ host->hw->user.ck_out_edge=0;
+ host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
+ }
+
+ //Configure bit sizes, load addr and command
+ host->hw->user.usr_dummy=(dev->cfg.dummy_bits)?1:0;
+ host->hw->user.usr_addr=(dev->cfg.address_bits)?1:0;
+ host->hw->user.usr_command=(dev->cfg.command_bits)?1:0;
+ host->hw->user1.usr_addr_bitlen=dev->cfg.address_bits-1;
+ host->hw->user1.usr_dummy_cyclelen=dev->cfg.dummy_bits-1;
+ host->hw->user2.usr_command_bitlen=dev->cfg.command_bits-1;
+ //Configure misc stuff
+ host->hw->user.doutdin=(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1;
+ host->hw->user.sio=(dev->cfg.flags & SPI_DEVICE_3WIRE)?1:0;
+
+ host->hw->ctrl2.setup_time=dev->cfg.cs_ena_pretrans-1;
+ host->hw->user.cs_setup=dev->cfg.cs_ena_pretrans?1:0;
+ host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans-1;
+ host->hw->user.cs_hold=(dev->cfg.cs_ena_posttrans)?1:0;
+
+ //Configure CS pin
+ host->hw->pin.cs0_dis=(i==0)?0:1;
+ 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;
+ 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->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_MODE_DIO) {
+ if (trans->flags & SPI_MODE_DIOQIO_ADDR) {
+ host->hw->ctrl.fread_dio=1;
+ host->hw->user.fwrite_dio=1;
+ } else {
+ host->hw->ctrl.fread_dual=1;
+ host->hw->user.fwrite_dual=1;
+ }
+ host->hw->ctrl.fastrd_mode=1;
+ } else if (trans->flags & SPI_MODE_QIO) {
+ if (trans->flags & SPI_MODE_DIOQIO_ADDR) {
+ host->hw->ctrl.fread_qio=1;
+ host->hw->user.fwrite_qio=1;
+ } else {
+ host->hw->ctrl.fread_quad=1;
+ host->hw->user.fwrite_quad=1;
+ }
+ host->hw->ctrl.fastrd_mode=1;
+ }
+
+
+ //Fill DMA descriptors
+ if (trans->rx_buffer || (trans->flags & SPI_USE_RXDATA)) {
+ uint32_t *data;
+ if (trans->flags & SPI_USE_RXDATA) {
+ data=(uint32_t *)&trans->rx_data[0];
+ } 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.
+ } 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;
+ host->hw->dma_in_link.start=1;
+ }
+ host->hw->user.usr_miso=1;
+ } else {
+ host->hw->user.usr_miso=0;
+ }
+
+ if (trans->tx_buffer || (trans->flags & SPI_USE_TXDATA)) {
+ uint32_t *data;
+ if (trans->flags & SPI_USE_TXDATA) {
+ data=(uint32_t *)&trans->tx_data[0];
+ } else {
+ data=(uint32_t *)trans->tx_buffer;
+ }
+ if (trans->rxlength < 8*32) {
+ //No need for DMA.
+ for (int x=0; x < trans->rxlength; x+=32) {
+ //Use memcpy to get around alignment issues for txdata
+ uint32_t word;
+ memcpy(&word, &data[x/32], 4);
+ host->hw->data_buf[(x/32)+8]=word;
+ }
+ host->hw->user.usr_mosi_highpart=1;
+ } else {
+ 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.start=1;
+ }
+ }
+ 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;
+ host->hw->slv_wr_status=trans->address & 0xffffffff;
+ } else {
+ host->hw->addr=trans->address & 0xffffffff;
+ }
+ host->hw->user.usr_mosi=(trans->tx_buffer==NULL)?0:1;
+ host->hw->user.usr_miso=(trans->tx_buffer==NULL)?0:1;
+
+ //Call pre-transmission callback, if any
+ if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans);
+ //Kick off transfer
+ host->hw->cmd.usr=1;
+ }
+ if (do_yield) portYIELD_FROM_ISR();
+}
+
+
+esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait)
+{
+ BaseType_t r;
+ SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG);
+ SPI_CHECK((trans_desc->flags & SPI_USE_RXDATA)==0 ||trans_desc->length <= 32, "rxdata transfer > 32bytes", ESP_ERR_INVALID_ARG);
+ SPI_CHECK((trans_desc->flags & SPI_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32bytes", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG);
+ SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (!(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX))), "incompatible iface params", 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);
+ return ESP_OK;
+}
+
+esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait)
+{
+ BaseType_t r;
+ SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG);
+ r=xQueueReceive(handle->ret_queue, (void*)trans_desc, ticks_to_wait);
+ if (!r) return ESP_ERR_TIMEOUT;
+ return ESP_OK;
+}
+
+//Porcelain to do one blocking transmission.
+esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc)
+{
+ esp_err_t ret;
+ spi_transaction_t *ret_trans;
+ //ToDo: check if any spi transfers in flight
+ ret=spi_device_queue_trans(handle, trans_desc, portMAX_DELAY);
+ if (ret!=ESP_OK) return ret;
+ ret=spi_device_get_trans_result(handle, &ret_trans, portMAX_DELAY);
+ if (ret!=ESP_OK) return ret;
+ assert(ret_trans==trans_desc);
+ return ESP_OK;
+}
+
--- /dev/null
+#
+#Component Makefile
+#
+
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
--- /dev/null
+/*
+ Tests for the spi_master device driver
+*/
+
+#include <esp_types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+#include "rom/ets_sys.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "freertos/queue.h"
+#include "freertos/xtensa_api.h"
+#include "unity.h"
+#include "driver/spi_master.h"
+
+
+
+TEST_CASE("SPI Master test", "[spi]")
+{
+ spi_bus_config_t buscfg={
+ .spid_io_num=4,
+ .spiq_io_num=16,
+ .spiclk_io_num=25,
+ .spiwp_io_num=-1,
+ .spihd_io_num=-1
+ };
+ spi_device_interface_config_t devcfg={
+ .command_bits=8,
+ .address_bits=64,
+ .dummy_bits=0,
+ .clock_speed_hz=8000,
+ .duty_cycle_pos=128,
+ .cs_ena_pretrans=7,
+ .cs_ena_posttrans=7,
+ .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 IO16\n");
+
+ ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
+ 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[16]="Hello World!";
+ char recvbuf[16]="UUUUUUUUUUUUUUU";
+ memset(&t, 0, sizeof(t));
+
+ t.length=16*8;
+ t.tx_buffer=sendbuf;
+ t.rx_buffer=recvbuf;
+ t.address=0xA00000000000000FL;
+ t.command=0x55;
+ printf("Transmit...\n");
+ 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);
+ ret=spi_bus_free(HSPI_HOST);
+ TEST_ASSERT(ret==ESP_OK);
+
+ TEST_ASSERT_EQUAL_INT8_ARRAY(sendbuf, recvbuf, 16);
+
+}
+
+
#define HSPICS2_IN_IDX 62
#define HSPICS2_OUT_IDX 62
#define VSPICLK_IN_IDX 63
-#define VSPICLK_OUT_MUX_IDX 63
+#define VSPICLK_OUT_IDX 63
#define VSPIQ_IN_IDX 64
#define VSPIQ_OUT_IDX 64
#define VSPID_IN_IDX 65
#define SPI_FLASH_PER_S 16\r
\r
#define SPI_ADDR_REG(i) (REG_SPI_BASE(i) + 0x4)\r
-/* SPI_USR_ADDR_VALUE : R/W ;bitpos:[31:0] ;default: 32'h0 ; */\r
-/*description: [31:8]:address to slave [7:0]:Reserved.*/\r
-#define SPI_USR_ADDR_VALUE 0xFFFFFFFF\r
-#define SPI_USR_ADDR_VALUE_M ((SPI_USR_ADDR_VALUE_V)<<(SPI_USR_ADDR_VALUE_S))\r
-#define SPI_USR_ADDR_VALUE_V 0xFFFFFFFF\r
-#define SPI_USR_ADDR_VALUE_S 0\r
+//The CSV actually is wrong here. It indicates that the lower 8 bits of this register are reserved. This is not true,
+//all 32 bits of SPI_ADDR_REG are usable/used.
\r
#define SPI_CTRL_REG(i) (REG_SPI_BASE(i) + 0x8)\r
/* SPI_WR_BIT_ORDER : R/W ;bitpos:[26] ;default: 1'b0 ; */\r
#define SPI_CK_IDLE_EDGE_M (BIT(29))\r
#define SPI_CK_IDLE_EDGE_V 0x1\r
#define SPI_CK_IDLE_EDGE_S 29\r
-/* SPI_MASTER_CK_SEL : R/W ;bitpos:[15:11] ;default: 5'b0 ; */\r
+/* SPI_MASTER_CK_SEL : R/W ;bitpos:[13:11] ;default: 3'b0 ; */\r
/*description: In the master mode spi cs line is enable as spi clk it is combined\r
with spi_cs0_dis spi_cs1_dis spi_cs2_dis.*/\r
-#define SPI_MASTER_CK_SEL 0x0000001F\r
+#define SPI_MASTER_CK_SEL 0x00000007\r
#define SPI_MASTER_CK_SEL_M ((SPI_MASTER_CK_SEL_V)<<(SPI_MASTER_CK_SEL_S))\r
-#define SPI_MASTER_CK_SEL_V 0x1F\r
+#define SPI_MASTER_CK_SEL_V 0x07\r
#define SPI_MASTER_CK_SEL_S 11\r
-/* SPI_MASTER_CS_POL : R/W ;bitpos:[10:6] ;default: 5'b0 ; */\r
+/* SPI_MASTER_CS_POL : R/W ;bitpos:[8:6] ;default: 3'b0 ; */\r
/*description: In the master mode the bits are the polarity of spi cs line\r
the value is equivalent to spi_cs ^ spi_master_cs_pol.*/\r
-#define SPI_MASTER_CS_POL 0x0000001F\r
+#define SPI_MASTER_CS_POL 0x00000007\r
#define SPI_MASTER_CS_POL_M ((SPI_MASTER_CS_POL_V)<<(SPI_MASTER_CS_POL_S))\r
-#define SPI_MASTER_CS_POL_V 0x1F\r
+#define SPI_MASTER_CS_POL_V 0x7\r
#define SPI_MASTER_CS_POL_S 6\r
/* SPI_CK_DIS : R/W ;bitpos:[5] ;default: 1'b0 ; */\r
/*description: 1: spi clk out disable 0: spi clk out enable*/\r
};
uint32_t val;
} cmd;
- union {
- struct {
- uint32_t reserved : 8;
- uint32_t usr_addr_value:24; /*[31:8]:address to slave [7:0]:Reserved.*/
- };
- uint32_t val;
- } addr;
+ uint32_t addr; /*addr to slave / from master */
union {
struct {
uint32_t reserved0: 10; /*reserved*/
uint32_t cs2_dis: 1; /*SPI CS2 pin enable, 1: disable CS2, 0: spi_cs2 signal is from/to CS2 pin*/
uint32_t reserved3: 2; /*reserved*/
uint32_t ck_dis: 1; /*1: spi clk out disable 0: spi clk out enable*/
- uint32_t master_cs_pol: 5; /*In the master mode the bits are the polarity of spi cs line the value is equivalent to spi_cs ^ spi_master_cs_pol.*/
- uint32_t master_ck_sel: 5; /*In the master mode spi cs line is enable as spi clk it is combined with spi_cs0_dis spi_cs1_dis spi_cs2_dis.*/
- uint32_t reserved16: 13; /*reserved*/
+ uint32_t master_cs_pol: 3; /*In the master mode the bits are the polarity of spi cs line the value is equivalent to spi_cs ^ spi_master_cs_pol.*/
+ uint32_t reserved9: 2; /*reserved*/
+ uint32_t master_ck_sel: 3; /*In the master mode spi cs line is enable as spi clk it is combined with spi_cs0_dis spi_cs1_dis spi_cs2_dis.*/
+ uint32_t reserved14: 15; /*reserved*/
uint32_t ck_idle_edge: 1; /*1: spi clk line is high when idle 0: spi clk line is low when idle*/
uint32_t cs_keep_active: 1; /*spi cs line keep low when the bit is set.*/
uint32_t reserved31: 1; /*reserved*/
uint32_t rd_sta_done: 1; /*The interrupt raw bit for the completion of read-status operation in the slave mode.*/
uint32_t wr_sta_done: 1; /*The interrupt raw bit for the completion of write-status operation in the slave mode.*/
uint32_t trans_done: 1; /*The interrupt raw bit for the completion of any operation in both the master mode and the slave mode.*/
- uint32_t int_en: 5; /*Interrupt enable bits for the below 5 sources*/
+ uint32_t rd_buf_inten: 1; /*The interrupt enable bit for the completion of read-buffer operation in the slave mode.*/
+ uint32_t wr_buf_inten: 1; /*The interrupt enable bit for the completion of write-buffer operation in the slave mode.*/
+ uint32_t rd_sta_inten: 1; /*The interrupt enable bit for the completion of read-status operation in the slave mode.*/
+ uint32_t wr_sta_inten: 1; /*The interrupt enable bit for the completion of write-status operation in the slave mode.*/
+ uint32_t trans_inten: 1; /*The interrupt enable bit for the completion of any operation in both the master mode and the slave mode.*/
uint32_t cs_i_mode: 2; /*In the slave mode this bits used to synchronize the input spi cs signal and eliminate spi cs jitter.*/
uint32_t reserved12: 5; /*reserved*/
uint32_t last_command: 3; /*In the slave mode it is the value of command.*/
--- /dev/null
+SPI Master 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. 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.
+
+The spi_master driver
+^^^^^^^^^^^^^^^^^^^^^
+
+The spi_master driver allows easy communicating with SPI slave devices, even in a multithreaded environment.
+It fully transparently handles DMA transfers to read and write data and automatically takes care of
+multiplexing between different SPI slaves on the same master
+
+Terminology
+^^^^^^^^^^^
+
+The spi_master driver uses the following terms:
+
+* Host: The SPI peripheral inside the ESP32 initiating the SPI transmissions. One of SPI, HSPI or VSPI. (For
+ now, only HSPI or VSPI are actually supported in the driver; it will support all 3 peripherals
+ somewhere in the future.)
+* Bus: The SPI bus, common to all SPI devices connected to one host. In general the bus consists of the
+ spid, spiq, spiclk and optionally spiwp and spihd signals. The SPI slaves are connected to these
+ signals in parallel.
+* Device: A SPI slave. Each SPI slave has its own chip select (CS) line, which is made active when
+ a transmission to/from the SPI slave occurs.
+* Transaction: One instance of CS going active, data transfer from and/or to a device happening, and
+ CS going inactive again. Transactions are atomic, as in they will never be interrupted by another
+ transaction.
+
+
+SPI transactions
+^^^^^^^^^^^^^^^^
+
+A transaction on the SPI bus consists of five phases, any of which may be skipped:
+
+* The command phase. In this phase, a command (0-16 bit) is clocked out.
+* The address phase. In this phase, an address (0-64 bit) is clocked out.
+* The read phase. The slave sends data to the master.
+* The write phase. The master sends data to the slave.
+
+In full duplex, the read and write phases are combined, causing the SPI host to read and
+write data simultaneously.
+
+The command and address phase are optional in that not every SPI device will need to be sent a command
+and/or address. Tis is reflected in the device configuration: when the ``command_bits`` or ``data_bits``
+fields are set to zero, no command or address phase is done.
+
+Something similar is true for the read and write phase: not every transaction needs both data to be written
+as well as data to be read. When ``rx_buffer`` is NULL (and SPI_USE_RXDATA) is not set) the read phase
+is skipped. When ``tx_buffer`` is NULL (and SPI_USE_TXDATA) is not set) the write phase is skipped.
+
+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.
+ Make sure to configure any timing requirements the device has in the ``dev_config`` structure.
+ You should now have a handle for the device, to be used when sending it a transaction.
+
+- To interact with the device, fill one or more spi_transaction_t structure with any transaction
+ parameters you need. Either queue all transactions by calling ``spi_device_queue_trans``, later
+ quering the result using ``spi_device_get_trans_result``, or handle all requests synchroneously
+ by feeding them into ``spi_device_transmit``.
+
+- Optional: to unload the driver for a device, call ``spi_bus_remove_device`` with the device
+ handle as an argument
+
+- Optional: to remove the driver for a bus, make sure no more drivers are attached and call
+ ``spi_bus_free``.
+
+
+Transaction data
+^^^^^^^^^^^^^^^^
+
+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)``.
+
+Sometimes, the amount of data is very small making it less than optimal allocating a separate buffer
+for it. If the data to be transferred is 32 bits or less, it can be stored in the transaction struct
+itself. For transmitted data, use the ``tx_data`` member for this and set the ``SPI_USE_TXDATA`` flag
+on the transmission. For received data, use ``rx_data`` and set ``SPI_USE_RXDATA``. In both cases, do
+not touch the ``tx_buffer`` or ``rx_buffer`` members, because they use the same memory locations
+as ``tx_data`` and ``rx_data``.
+
+API Reference
+-------------
+
+Header Files
+^^^^^^^^^^^^
+
+ * `drivers/include/drivers/spi_master.h <https://github.com/espressif/esp-idf/blob/master/components/drivers/include/drivers/spi_master.h>`_
+
+Macros
+^^^^^^
+
+.. doxygendefine:: SPI_DEVICE_TXBIT_LSBFIRST
+.. doxygendefine:: SPI_DEVICE_RXBIT_LSBFIRST
+.. doxygendefine:: SPI_DEVICE_BIT_LSBFIRST
+.. doxygendefine:: SPI_DEVICE_3WIRE
+.. doxygendefine:: SPI_DEVICE_POSITIVE_CS
+.. doxygendefine:: SPI_DEVICE_HALFDUPLEX
+.. doxygendefine:: SPI_DEVICE_CLK_AS_CS
+
+.. doxygendefine:: SPI_MODE_DIO
+.. doxygendefine:: SPI_MODE_QIO
+.. doxygendefine:: SPI_MODE_DIOQIO_ADDR
+.. doxygendefine:: SPI_USE_RXDATA
+.. doxygendefine:: SPI_USE_TXDATA
+
+Type Definitions
+^^^^^^^^^^^^^^^^
+
+.. doxygentypedef:: spi_device_handle_t
+
+Enumerations
+^^^^^^^^^^^^
+
+.. doxygenenum:: spi_host_device_t
+
+Structures
+^^^^^^^^^^
+
+.. doxygenstruct:: spi_transaction_t
+ :members:
+
+.. doxygenstruct:: spi_bus_config_t
+ :members:
+
+.. doxygenstruct:: spi_device_interface_config_t
+ :members:
+
+
+
+Functions
+---------
+
+.. doxygenfunction:: spi_bus_initialize
+.. doxygenfunction:: spi_bus_free
+.. doxygenfunction:: spi_bus_add_device
+.. doxygenfunction:: spi_bus_remove_device
+.. doxygenfunction:: spi_device_queue_trans
+.. doxygenfunction:: spi_device_get_trans_result
+.. doxygenfunction:: spi_device_transmit
+
6.4. UART
6.5. I2C - TBA
6.6. I2S - TBA
- 6.7. SPI - TBA
+ 6.7. SPI - <api/spi_master>
6.8. CAN - TBA
6.9. SD Controller - TBA
6.10. Infrared - TBA
Pulse Counter <api/pcnt>
Sigma-delta Modulation <api/sigmadelta>
SPI Flash and Partition APIs <api/spi_flash>
+ SPI Master API <api/spi_master>
Logging <api/log>
Non-Volatile Storage <api/nvs_flash>
Virtual Filesystem <api/vfs>
--- /dev/null
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := spi_master
+
+include $(IDF_PATH)/make/project.mk
+
--- /dev/null
+#
+# Main Makefile. This is basically the same as a component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
--- /dev/null
+/* SPI Master example
+
+ 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 <stdlib.h>
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+#include "driver/spi_master.h"
+#include "soc/gpio_struct.h"
+#include "driver/gpio.h"
+
+
+/*
+ This code displays some fancy graphics on the ILI9341-based 320x240 LCD on an ESP-WROVER_KIT board.
+ It is not very fast, even when the SPI transfer itself happens at 8MHz and with DMA, because
+ the rest of the code is not very optimized. Especially calculating the image line-by-line
+ is inefficient; it would be quicker to send an entire screenful at once. This example does, however,
+ demonstrate the use of both spi_device_transmit as well as spi_device_queue_trans/spi_device_get_trans_result
+ as well as pre-transmit callbacks.
+
+ Some info about the ILI9341: It has an C/D line, which is connected to a GPIO here. It expects this
+ line to be low for a command and high for data. We use a pre-transmit callback here to control that
+ line: every transaction has as the user-definable argument the needed state of the D/C line and just
+ before the transaction is sent, the callback will set this line to the correct state.
+*/
+
+#define PIN_NUM_MISO 25
+#define PIN_NUM_MOSI 23
+#define PIN_NUM_CLK 19
+#define PIN_NUM_CS 22
+
+#define PIN_NUM_DC 21
+#define PIN_NUM_RST 18
+#define PIN_NUM_BCKL 5
+
+
+/*
+ The ILI9341 needs a bunch of command/argument values to be initialized. They are stored in this struct.
+*/
+typedef struct {
+ uint8_t cmd;
+ uint8_t data[16];
+ uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
+} ili_init_cmd_t;
+
+static const ili_init_cmd_t ili_init_cmds[]={
+ {0xCF, {0x00, 0x83, 0X30}, 3},
+ {0xED, {0x64, 0x03, 0X12, 0X81}, 4},
+ {0xE8, {0x85, 0x01, 0x79}, 3},
+ {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
+ {0xF7, {0x20}, 1},
+ {0xEA, {0x00, 0x00}, 2},
+ {0xC0, {0x26}, 1},
+ {0xC1, {0x11}, 1},
+ {0xC5, {0x35, 0x3E}, 2},
+ {0xC7, {0xBE}, 1},
+ {0x36, {0x28}, 1},
+ {0x3A, {0x55}, 1},
+ {0xB1, {0x00, 0x1B}, 2},
+ {0xF2, {0x08}, 1},
+ {0x26, {0x01}, 1},
+ {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
+ {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
+ {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
+ {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
+ {0x2C, {0}, 0},
+ {0xB7, {0x07}, 1},
+ {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4},
+ {0x11, {0}, 0x80},
+ {0x29, {0}, 0x80},
+ {0, {0}, 0xff},
+};
+
+//Send a command to the ILI9341. Uses spi_device_transmit, which waits until the transfer is complete.
+void ili_cmd(spi_device_handle_t spi, const uint8_t cmd)
+{
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t)); //Zero out the transaction
+ t.length=8; //Command is 8 bits
+ t.tx_buffer=&cmd; //The data is the cmd itself
+ t.user=(void*)0; //D/C needs to be set to 0
+ ret=spi_device_transmit(spi, &t); //Transmit!
+ assert(ret==ESP_OK); //Should have had no issues.
+}
+
+//Send data to the ILI9341. Uses spi_device_transmit, which waits until the transfer is complete.
+void ili_data(spi_device_handle_t spi, const uint8_t *data, int len)
+{
+ esp_err_t ret;
+ spi_transaction_t t;
+ if (len==0) return; //no need to send anything
+ memset(&t, 0, sizeof(t)); //Zero out the transaction
+ t.length=len*8; //Len is in bytes, transaction length is in bits.
+ t.tx_buffer=data; //Data
+ t.user=(void*)1; //D/C needs to be set to 1
+ ret=spi_device_transmit(spi, &t); //Transmit!
+ assert(ret==ESP_OK); //Should have had no issues.
+}
+
+//This function is called (in irq context!) just before a transmission starts. It will
+//set the D/C line to the value indicated in the user field.
+void ili_spi_pre_transfer_callback(spi_transaction_t *t)
+{
+ int dc=(int)t->user;
+ gpio_set_level(PIN_NUM_DC, dc);
+}
+
+//Initialize the display
+void ili_init(spi_device_handle_t spi)
+{
+ int cmd=0;
+ //Initialize non-SPI GPIOs
+ gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
+ gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
+ gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);
+
+ //Reset the display
+ gpio_set_level(PIN_NUM_RST, 0);
+ vTaskDelay(100 / portTICK_RATE_MS);
+ gpio_set_level(PIN_NUM_RST, 1);
+ vTaskDelay(100 / portTICK_RATE_MS);
+
+ //Send all the commands
+ while (ili_init_cmds[cmd].databytes!=0xff) {
+ ili_cmd(spi, ili_init_cmds[cmd].cmd);
+ ili_data(spi, ili_init_cmds[cmd].data, ili_init_cmds[cmd].databytes&0x1F);
+ if (ili_init_cmds[cmd].databytes&0x80) {
+ vTaskDelay(100 / portTICK_RATE_MS);
+ }
+ cmd++;
+ }
+
+ ///Enable backlight
+ gpio_set_level(PIN_NUM_BCKL, 0);
+}
+
+
+//To send a line we have to send a command, 2 data bytes, another command, 2 more data bytes and another command
+//before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction
+//because the D/C line needs to be toggled in the middle.)
+//This routine queues these commands up so they get sent as quickly as possible.
+void send_line(spi_device_handle_t spi, int ypos, uint16_t *line)
+{
+ esp_err_t ret;
+ int x;
+ //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
+ //function is finished because the SPI driver needs access to it even while we're already calculating the next line.
+ static spi_transaction_t trans[6];
+
+ //In theory, it's better to initialize trans and data only once and hang on to the initialized
+ //variables. We allocate them on the stack, so we need to re-init them each call.
+ for (x=0; x<6; x++) {
+ memset(&trans[x], 0, sizeof(spi_transaction_t));
+ if ((x&1)==0) {
+ //Even transfers are commands
+ trans[x].length=8;
+ trans[x].user=(void*)0;
+ } else {
+ //Odd transfers are data
+ trans[x].length=8*4;
+ trans[x].user=(void*)1;
+ }
+ trans[x].flags=SPI_USE_TXDATA;
+ }
+ trans[0].tx_data[0]=0x2A; //Column Address Set
+ trans[1].tx_data[0]=0; //Start Col High
+ trans[1].tx_data[1]=0; //Start Col Low
+ trans[1].tx_data[2]=(320)>>8; //End Col High
+ trans[1].tx_data[3]=(320)&0xff; //End Col Low
+ trans[2].tx_data[0]=0x2B; //Page address set
+ trans[3].tx_data[0]=ypos>>8; //Start page high
+ trans[3].tx_data[1]=ypos&0xff; //start page low
+ trans[3].tx_data[2]=(ypos+1)>>8; //end page high
+ trans[3].tx_data[3]=(ypos+1)&0xff; //end page low
+ trans[4].tx_data[0]=0x2C; //memory write
+ trans[5].tx_buffer=line; //finally send the line data
+ trans[5].length=320*2*8; //Data length, in bits
+ trans[5].flags=0; //undo SPI_USE_TXDATA flag
+
+ //Queue all transactions.
+ for (x=0; x<6; x++) {
+ ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
+ assert(ret==ESP_OK);
+ }
+
+ //When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
+ //mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
+ //finish because we may as well spend the time calculating the next line. When that is done, we can call
+ //send_line_finish, which will wait for the transfers to be done and check their status.
+}
+
+
+void send_line_finish(spi_device_handle_t spi)
+{
+ spi_transaction_t *rtrans;
+ esp_err_t ret;
+ //Wait for all 6 transactions to be done and get back the results.
+ for (int x=0; x<6; x++) {
+ ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
+ assert(ret==ESP_OK);
+ //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
+ }
+}
+
+
+//Simple routine to generate some patterns and send them to the LCD. Don't expect anything too
+//impressive. Because the SPI driver handles transactions in the background, we can calculate the next line
+//while the previous one is being sent.
+void display_pretty_colors(spi_device_handle_t spi)
+{
+ uint16_t line[2][320];
+ int x, y, frame=0;
+ //Indexes of the line currently being sent to the LCD and the line we're calculating.
+ int sending_line=-1;
+ int calc_line=0;
+
+ while(1) {
+ frame++;
+ for (y=0; y<240; y++) {
+ //Calculate a line.
+ for (x=0; x<320; x++) {
+ line[calc_line][x]=((x<<3)^(y<<3)^(frame+x*y));
+ }
+ //Finish up the sending process of the previous line, if any
+ if (sending_line!=-1) send_line_finish(spi);
+ //Swap sending_line and calc_line
+ sending_line=calc_line;
+ calc_line=(calc_line==1)?0:1;
+ //Send the line we currently calculated.
+ send_line(spi, y, line[sending_line]);
+ //The line is queued up for sending now; the actual sending happens in the
+ //background. We can go on to calculate the next line as long as we do not
+ //touch line[sending_line]; the SPI sending process is still reading from that.
+ }
+ }
+}
+
+
+void app_main()
+{
+ esp_err_t ret;
+ spi_device_handle_t spi;
+ spi_bus_config_t buscfg={
+ .spiq_io_num=PIN_NUM_MISO,
+ .spid_io_num=PIN_NUM_MOSI,
+ .spiclk_io_num=PIN_NUM_CLK,
+ .spiwp_io_num=-1,
+ .spihd_io_num=-1
+ };
+ spi_device_interface_config_t devcfg={
+ .clock_speed_hz=10000000, //Clock out at 10 MHz
+ .mode=0, //SPI mode 0
+ .spics_io_num=PIN_NUM_CS, //CS pin
+ .queue_size=7, //We want to be able to queue 7 transactions at a time
+ .pre_cb=ili_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line
+ };
+ //Initialize the SPI bus
+ ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
+ assert(ret==ESP_OK);
+ //Attach the LCD to the SPI bus
+ ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
+ assert(ret==ESP_OK);
+ //Initialize the LCD
+ ili_init(spi);
+ //Go do nice stuff.
+ display_pretty_colors(spi);
+}