mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Add SPI Master driver, example, test and docs
This commit is contained in:
parent
e2d05d8592
commit
23455de4c2
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
235
components/driver/include/driver/spi_master.h
Normal file
235
components/driver/include/driver/spi_master.h
Normal 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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
692
components/driver/spi_master.c
Normal file
692
components/driver/spi_master.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
5
components/driver/test/component.mk
Normal file
5
components/driver/test/component.mk
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
#Component Makefile
|
||||||
|
#
|
||||||
|
|
||||||
|
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
80
components/driver/test/test_spi_master.c
Normal file
80
components/driver/test/test_spi_master.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
@ -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*/
|
||||||
|
@ -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
156
docs/api/spi_master.rst
Normal 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
|
||||||
|
|
@ -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>
|
||||||
|
9
examples/26_spi_master/Makefile
Normal file
9
examples/26_spi_master/Makefile
Normal 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
|
||||||
|
|
5
examples/26_spi_master/main/component.mk
Normal file
5
examples/26_spi_master/main/component.mk
Normal 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.)
|
||||||
|
|
275
examples/26_spi_master/main/spi_master.c
Normal file
275
examples/26_spi_master/main/spi_master.c
Normal 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);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user