From: Jeroen Domburg Date: Fri, 6 Jan 2017 06:20:32 +0000 (+0800) Subject: Add SPI Master driver, example, test and docs X-Git-Tag: v2.0-rc1~81^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=23455de4c2f6f8308f60d11badf4a95ed644bd57;p=esp-idf Add SPI Master driver, example, test and docs --- diff --git a/components/driver/include/driver/periph_ctrl.h b/components/driver/include/driver/periph_ctrl.h index 8c404e5b13..0aab55088d 100644 --- a/components/driver/include/driver/periph_ctrl.h +++ b/components/driver/include/driver/periph_ctrl.h @@ -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 index 0000000000..4dd8738ad7 --- /dev/null +++ b/components/driver/include/driver/spi_master.h @@ -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 diff --git a/components/driver/periph_ctrl.c b/components/driver/periph_ctrl.c index 7fc4091aa5..d90fa595f9 100644 --- a/components/driver/periph_ctrl.c +++ b/components/driver/periph_ctrl.c @@ -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 index 0000000000..fd4c5b2e29 --- /dev/null +++ b/components/driver/spi_master.c @@ -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 +#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; xdevice[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; freecsdevice[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<hw->pin.master_ck_sel &= (1<flags&SPI_DEVICE_POSITIVE_CS) { + spihost[host]->hw->pin.master_cs_pol |= (1<hw->pin.master_cs_pol &= (1<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; xhost->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; idevice[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->rxlengthhw->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 index 0000000000..5dd172bdb7 --- /dev/null +++ b/components/driver/test/component.mk @@ -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 index 0000000000..0fd47cbdba --- /dev/null +++ b/components/driver/test/test_spi_master.c @@ -0,0 +1,80 @@ +/* + Tests for the spi_master device driver +*/ + +#include +#include +#include +#include +#include +#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("`_ + +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 + diff --git a/docs/index.rst b/docs/index.rst index 9c2b0643f4..3161db345b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,7 +70,7 @@ Contents: 6.4. UART 6.5. I2C - TBA 6.6. I2S - TBA - 6.7. SPI - TBA + 6.7. SPI - 6.8. CAN - TBA 6.9. SD Controller - TBA 6.10. Infrared - TBA @@ -111,6 +111,7 @@ Contents: Pulse Counter Sigma-delta Modulation SPI Flash and Partition APIs + SPI Master API Logging Non-Volatile Storage Virtual Filesystem diff --git a/examples/26_spi_master/Makefile b/examples/26_spi_master/Makefile new file mode 100644 index 0000000000..7ca171bb52 --- /dev/null +++ b/examples/26_spi_master/Makefile @@ -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 index 0000000000..4d3b30caf3 --- /dev/null +++ b/examples/26_spi_master/main/component.mk @@ -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 index 0000000000..1cdbf72ad5 --- /dev/null +++ b/examples/26_spi_master/main/spi_master.c @@ -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 +#include +#include +#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); +}