]> granicus.if.org Git - esp-idf/commitdiff
Add SPI Master driver, example, test and docs
authorJeroen Domburg <jeroen@espressif.com>
Fri, 6 Jan 2017 06:20:32 +0000 (14:20 +0800)
committerJeroen Domburg <jeroen@espressif.com>
Fri, 6 Jan 2017 06:20:32 +0000 (14:20 +0800)
14 files changed:
components/driver/include/driver/periph_ctrl.h
components/driver/include/driver/spi_master.h [new file with mode: 0644]
components/driver/periph_ctrl.c
components/driver/spi_master.c [new file with mode: 0644]
components/driver/test/component.mk [new file with mode: 0644]
components/driver/test/test_spi_master.c [new file with mode: 0644]
components/esp32/include/soc/gpio_sig_map.h
components/esp32/include/soc/spi_reg.h
components/esp32/include/soc/spi_struct.h
docs/api/spi_master.rst [new file with mode: 0644]
docs/index.rst
examples/26_spi_master/Makefile [new file with mode: 0644]
examples/26_spi_master/main/component.mk [new file with mode: 0644]
examples/26_spi_master/main/spi_master.c [new file with mode: 0644]

index 8c404e5b1367050d0d8b9d04cab9b8801a865012..0aab55088d722cc1c961a64464bd0a02b362df3d 100644 (file)
@@ -41,6 +41,9 @@ typedef enum {
     PERIPH_UHCI1_MODULE,
     PERIPH_RMT_MODULE,
     PERIPH_PCNT_MODULE,
+    PERIPH_SPI_MODULE,
+    PERIPH_HSPI_MODULE,
+    PERIPH_VSPI_MODULE,
 } periph_module_t;
 
 /**
diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h
new file mode 100644 (file)
index 0000000..4dd8738
--- /dev/null
@@ -0,0 +1,235 @@
+// 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
index 7fc4091aa585952ee726689615bdde69084bc569..d90fa595f918ac442e9375011686e70690d131f7 100644 (file)
@@ -97,6 +97,18 @@ void periph_module_enable(periph_module_t periph)
             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;
     }
@@ -179,6 +191,18 @@ void periph_module_disable(periph_module_t periph)
             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;
     }
diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c
new file mode 100644 (file)
index 0000000..fd4c5b2
--- /dev/null
@@ -0,0 +1,692 @@
+// 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;
+}
+
diff --git a/components/driver/test/component.mk b/components/driver/test/component.mk
new file mode 100644 (file)
index 0000000..5dd172b
--- /dev/null
@@ -0,0 +1,5 @@
+#
+#Component Makefile
+#
+
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c
new file mode 100644 (file)
index 0000000..0fd47cb
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ 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);
+
+}
+
+
index 4d2943fb9fa791776534cd8d72e75dde66bbc684..1d3dc5b04cecf8cd345fef775e33c6e1bf0029fc 100644 (file)
 #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
index d1eeedb9f0532dd8ef86dc089bfbe0e3d3f3fbcc..0cc27b8994bb415b68fd13fdaa46b076b60abc7e 100644 (file)
 #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
index c7aa29a7c988a35294d928268b30371a35553a1c..149782ff19d89152fb7c2e2eb11861306c721e73 100644 (file)
@@ -36,13 +36,7 @@ typedef volatile struct {
         };
         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*/
@@ -177,9 +171,10 @@ typedef volatile struct {
             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*/
@@ -193,7 +188,11 @@ typedef volatile struct {
             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.*/
diff --git a/docs/api/spi_master.rst b/docs/api/spi_master.rst
new file mode 100644 (file)
index 0000000..99f2f6a
--- /dev/null
@@ -0,0 +1,156 @@
+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
+
index 9c2b0643f4a2858ed1e81dcec3e11150c99e63cb..3161db345b89c3366a9641ca7f7c83ebc0809131 100644 (file)
@@ -70,7 +70,7 @@ Contents:
      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
@@ -111,6 +111,7 @@ Contents:
    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>
diff --git a/examples/26_spi_master/Makefile b/examples/26_spi_master/Makefile
new file mode 100644 (file)
index 0000000..7ca171b
--- /dev/null
@@ -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_master
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/26_spi_master/main/component.mk b/examples/26_spi_master/main/component.mk
new file mode 100644 (file)
index 0000000..4d3b30c
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# 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.)
+
diff --git a/examples/26_spi_master/main/spi_master.c b/examples/26_spi_master/main/spi_master.c
new file mode 100644 (file)
index 0000000..1cdbf72
--- /dev/null
@@ -0,0 +1,275 @@
+/* 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);
+}