Add SPI Master driver, example, test and docs

This commit is contained in:
Jeroen Domburg 2017-01-06 14:20:32 +08:00
parent e2d05d8592
commit 23455de4c2
14 changed files with 1505 additions and 25 deletions

View File

@ -41,6 +41,9 @@ typedef enum {
PERIPH_UHCI1_MODULE, PERIPH_UHCI1_MODULE,
PERIPH_RMT_MODULE, PERIPH_RMT_MODULE,
PERIPH_PCNT_MODULE, PERIPH_PCNT_MODULE,
PERIPH_SPI_MODULE,
PERIPH_HSPI_MODULE,
PERIPH_VSPI_MODULE,
} periph_module_t; } periph_module_t;
/** /**

View File

@ -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

View 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); 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); CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST);
break; 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: default:
break; 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); 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); SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST);
break; 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: default:
break; break;
} }

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -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);
}

View File

@ -135,7 +135,7 @@
#define HSPICS2_IN_IDX 62 #define HSPICS2_IN_IDX 62
#define HSPICS2_OUT_IDX 62 #define HSPICS2_OUT_IDX 62
#define VSPICLK_IN_IDX 63 #define VSPICLK_IN_IDX 63
#define VSPICLK_OUT_MUX_IDX 63 #define VSPICLK_OUT_IDX 63
#define VSPIQ_IN_IDX 64 #define VSPIQ_IN_IDX 64
#define VSPIQ_OUT_IDX 64 #define VSPIQ_OUT_IDX 64
#define VSPID_IN_IDX 65 #define VSPID_IN_IDX 65

View File

@ -133,12 +133,8 @@
#define SPI_FLASH_PER_S 16 #define SPI_FLASH_PER_S 16
#define SPI_ADDR_REG(i) (REG_SPI_BASE(i) + 0x4) #define SPI_ADDR_REG(i) (REG_SPI_BASE(i) + 0x4)
/* SPI_USR_ADDR_VALUE : R/W ;bitpos:[31:0] ;default: 32'h0 ; */ //The CSV actually is wrong here. It indicates that the lower 8 bits of this register are reserved. This is not true,
/*description: [31:8]:address to slave [7:0]:Reserved.*/ //all 32 bits of SPI_ADDR_REG are usable/used.
#define SPI_USR_ADDR_VALUE 0xFFFFFFFF
#define SPI_USR_ADDR_VALUE_M ((SPI_USR_ADDR_VALUE_V)<<(SPI_USR_ADDR_VALUE_S))
#define SPI_USR_ADDR_VALUE_V 0xFFFFFFFF
#define SPI_USR_ADDR_VALUE_S 0
#define SPI_CTRL_REG(i) (REG_SPI_BASE(i) + 0x8) #define SPI_CTRL_REG(i) (REG_SPI_BASE(i) + 0x8)
/* SPI_WR_BIT_ORDER : R/W ;bitpos:[26] ;default: 1'b0 ; */ /* SPI_WR_BIT_ORDER : R/W ;bitpos:[26] ;default: 1'b0 ; */
@ -601,19 +597,19 @@
#define SPI_CK_IDLE_EDGE_M (BIT(29)) #define SPI_CK_IDLE_EDGE_M (BIT(29))
#define SPI_CK_IDLE_EDGE_V 0x1 #define SPI_CK_IDLE_EDGE_V 0x1
#define SPI_CK_IDLE_EDGE_S 29 #define SPI_CK_IDLE_EDGE_S 29
/* SPI_MASTER_CK_SEL : R/W ;bitpos:[15:11] ;default: 5'b0 ; */ /* SPI_MASTER_CK_SEL : R/W ;bitpos:[13:11] ;default: 3'b0 ; */
/*description: In the master mode spi cs line is enable as spi clk it is combined /*description: 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.*/ with spi_cs0_dis spi_cs1_dis spi_cs2_dis.*/
#define SPI_MASTER_CK_SEL 0x0000001F #define SPI_MASTER_CK_SEL 0x00000007
#define SPI_MASTER_CK_SEL_M ((SPI_MASTER_CK_SEL_V)<<(SPI_MASTER_CK_SEL_S)) #define SPI_MASTER_CK_SEL_M ((SPI_MASTER_CK_SEL_V)<<(SPI_MASTER_CK_SEL_S))
#define SPI_MASTER_CK_SEL_V 0x1F #define SPI_MASTER_CK_SEL_V 0x07
#define SPI_MASTER_CK_SEL_S 11 #define SPI_MASTER_CK_SEL_S 11
/* SPI_MASTER_CS_POL : R/W ;bitpos:[10:6] ;default: 5'b0 ; */ /* SPI_MASTER_CS_POL : R/W ;bitpos:[8:6] ;default: 3'b0 ; */
/*description: In the master mode the bits are the polarity of spi cs line /*description: In the master mode the bits are the polarity of spi cs line
the value is equivalent to spi_cs ^ spi_master_cs_pol.*/ the value is equivalent to spi_cs ^ spi_master_cs_pol.*/
#define SPI_MASTER_CS_POL 0x0000001F #define SPI_MASTER_CS_POL 0x00000007
#define SPI_MASTER_CS_POL_M ((SPI_MASTER_CS_POL_V)<<(SPI_MASTER_CS_POL_S)) #define SPI_MASTER_CS_POL_M ((SPI_MASTER_CS_POL_V)<<(SPI_MASTER_CS_POL_S))
#define SPI_MASTER_CS_POL_V 0x1F #define SPI_MASTER_CS_POL_V 0x7
#define SPI_MASTER_CS_POL_S 6 #define SPI_MASTER_CS_POL_S 6
/* SPI_CK_DIS : R/W ;bitpos:[5] ;default: 1'b0 ; */ /* SPI_CK_DIS : R/W ;bitpos:[5] ;default: 1'b0 ; */
/*description: 1: spi clk out disable 0: spi clk out enable*/ /*description: 1: spi clk out disable 0: spi clk out enable*/

View File

@ -36,13 +36,7 @@ typedef volatile struct {
}; };
uint32_t val; uint32_t val;
} cmd; } cmd;
union { uint32_t addr; /*addr to slave / from master */
struct {
uint32_t reserved : 8;
uint32_t usr_addr_value:24; /*[31:8]:address to slave [7:0]:Reserved.*/
};
uint32_t val;
} addr;
union { union {
struct { struct {
uint32_t reserved0: 10; /*reserved*/ 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 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 reserved3: 2; /*reserved*/
uint32_t ck_dis: 1; /*1: spi clk out disable 0: spi clk out enable*/ 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_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 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 reserved9: 2; /*reserved*/
uint32_t reserved16: 13; /*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 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 cs_keep_active: 1; /*spi cs line keep low when the bit is set.*/
uint32_t reserved31: 1; /*reserved*/ 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 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 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 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 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 reserved12: 5; /*reserved*/
uint32_t last_command: 3; /*In the slave mode it is the value of command.*/ uint32_t last_command: 3; /*In the slave mode it is the value of command.*/

156
docs/api/spi_master.rst Normal file
View File

@ -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

View File

@ -70,7 +70,7 @@ Contents:
6.4. UART 6.4. UART
6.5. I2C - TBA 6.5. I2C - TBA
6.6. I2S - TBA 6.6. I2S - TBA
6.7. SPI - TBA 6.7. SPI - <api/spi_master>
6.8. CAN - TBA 6.8. CAN - TBA
6.9. SD Controller - TBA 6.9. SD Controller - TBA
6.10. Infrared - TBA 6.10. Infrared - TBA
@ -111,6 +111,7 @@ Contents:
Pulse Counter <api/pcnt> Pulse Counter <api/pcnt>
Sigma-delta Modulation <api/sigmadelta> Sigma-delta Modulation <api/sigmadelta>
SPI Flash and Partition APIs <api/spi_flash> SPI Flash and Partition APIs <api/spi_flash>
SPI Master API <api/spi_master>
Logging <api/log> Logging <api/log>
Non-Volatile Storage <api/nvs_flash> Non-Volatile Storage <api/nvs_flash>
Virtual Filesystem <api/vfs> Virtual Filesystem <api/vfs>

View File

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

View File

@ -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.)

View File

@ -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);
}