11. SPI
11.1 What is SPI
SPI (Serial Peripheral Interface) is a synchronous serial communication protocol used for data transmission between microcontrollers and external devices. It consists of one master device (usually a microcontroller MCU) and one or more slave devices, that is, a one-master, multi-slave mode. It is usually used for short-distance, high-speed, full-duplex communication and is widely used in many embedded systems and electronic devices.
11.2 SPI Hardware Interface
SPI mainly uses 4 lines: the clock line (SCLK), the Master Out Slave In line (MOSI), the Master In Slave Out line (MISO), and the Chip Select line (CS).
- The master device sends data to the slave device through the MOSI line. In each clock cycle, the master device sends one bit to the MOSI line, and the slave device reads that bit in the next clock cycle.
- The slave device sends data to the master device through the MISO line. In each clock cycle, the slave device sends one bit to the MISO line, and the master device reads that bit in the next clock cycle.
- Data transmission can be full-duplex, that is, the master device and slave device can send and receive data at the same time.
- The length of data transmission can be variable, usually in bytes.
- Data transmission can be unidirectional, that is, the master device only sends data or only receives data.
- Data transmission can be multi-master, that is, multiple master devices can communicate with multiple slave devices. The master device selects the slave device to communicate with through the chip select line. Each slave device has a chip select line. When the chip select line is at a low level, it indicates that the slave device is selected. (There are also some devices that are active at high level, which need to be determined according to their data sheet.) The master device synchronizes data transmission by controlling the level of the clock line. The rising and falling edges of the clock line are used to control data transmission and sampling.
The master-slave wiring method of SPI is similar to that of a serial port, requiring cross-connection of transmit and receive.
11.3 SPI Mode Selection
The SPI protocol defines multiple transmission modes, also called SPI modes or timing modes, used to control the order of data transmission and the data sampling method under the clock signal. The transmission mode of SPI is mainly determined by two parameters: Clock Polarity (CKPL) and Clock Phase (CKPH). Clock Polarity (CKPL): Clock polarity defines the level of the clock signal in the idle state. CKPL = 0: The clock signal is at a low level in the idle state. CKPL = 1: The clock signal is at a high level in the idle state. Clock Phase (CKPH): The phase defines on which edge of the clock signal data sampling and updating occur. CKPH = 0: Data sampling occurs on the first edge of the clock, and data updating occurs on the second edge. CKPH = 1: Data sampling occurs on the second edge of the clock, and data updating occurs on the first edge. The following are the common SPI modes:
Mode 0 (CKPL=0, CKPH=0):
- Clock Polarity is 0, indicating that the clock idle state is low.
- Clock Phase is 0, indicating that data is sampled and stabilized on the first edge (rising edge) of the clock signal.
Mode 1 (CKPL=0, CKPH=1):
- Clock Polarity is 0, and the clock idle state is low.
- Clock Phase is 1, and data is sampled and stabilized on the second edge (falling edge) of the clock signal.
Mode 2 (CKPL=1, CKPH=0):
- Clock Polarity is 1, and the clock idle state is high.
- Clock Phase is 0, and data is sampled and stabilized on the first edge (falling edge) of the clock signal.
Mode 3 (CKPL=1, CKPH=1):
- Clock Polarity is 1, and the clock idle state is high.
- Clock Phase is 1, and data is sampled and stabilized on the second edge (rising edge) of the clock signal.
The decision to choose an SPI mode usually depends on the specification requirements of the slave device and the communication protocol. Different devices may use different modes, so before communicating with a specific slave device, you must understand the SPI mode required by the slave device. If the SPI mode is not explicitly specified, you can usually try the most common Mode 0 or Mode 3 according to the specification manual of the slave device or the communication protocol. In addition, you need to pay attention to the clock frequency limit of the SPI mode to ensure that the clock frequency matches between the master device and the slave device.
11.4 Basic Parameters of SPI
The SPI protocol defines a set of parameters that are very important for correctly setting communication parameters and achieving SPI communication. The basic parameters of SPI include:
- Clock Polarity (CPOL): Specifies the level of the signal line in the clock idle state. There are two states: high level when idle (CPOL=1) or low level when idle (CPOL=0).
- Clock Phase (CPHA): Specifies the moment of data sampling. There are two states: sampling data after the rising edge of the clock (CPHA=0) or sampling data before the falling edge of the clock (CPHA=1).
- Data bits: Specifies the number of bits contained in each SPI data packet (usually 8 bits), and can also be set to smaller or larger values.
- Transmission mode: Determines how data is transmitted on the SPI bus (such as full-duplex, half-duplex, or unidirectional mode).
- Clock rate: Specifies the clock rate of the SPI bus, in bits per second (bps).
- Master/Slave mode: Determines whether the device is a master or a slave on the SPI bus.
- Transmission order: Specifies the bit transmission order of data, MSB (most significant bit) first or LSB (least significant bit) first. These parameters can be set by configuring the SPI control register to ensure correct communication between SPI devices. When determining these parameters, the actual hardware setup and communication requirements should be considered. If you need to use SPI for communication, make sure to set these parameters correctly.
11.5 Software SPI and Hardware SPI
Like IIC, SPI is also divided into software SPI and hardware SPI. The software SPI part will not be explained again. This chapter focuses on the hardware SPI part. The ESP32-S3 chip integrates four SPI controllers: • SPI0 • SPI1 • General-purpose SPI2, namely GP-SPI2 • And general-purpose SPI3, namely GP-SPI3 The SPI0 and SPI1 controllers are mainly used internally to access external flash and PSRAM. We can only use SPI2 and SPI3. The hardware SPI supports the following features:
11.6 Advantages and Disadvantages of the SPI Protocol
AdvantagesSimple and easy to implement: The hardware and software implementation of the SPI protocol is relatively simple. The communication process is clear and easy to understand and debug. This makes the SPI protocol widely used in embedded systems. High-speed transmission: The SPI protocol can achieve high-speed data transmission. Since SPI is a point-to-point communication protocol, data is directly transmitted between the master device and the slave device without address and conflict detection. Transmission and reception are on different signal lines, so it has a high transmission speed. Strong flexibility: The SPI protocol allows the master device to communicate with multiple slave devices. The master device can communicate with a specific device by selecting the chip select signal of the slave device, thereby achieving serial communication with multiple devices. DisadvantagesLimited signal line length: Since the SPI protocol does not have specific specifications limiting the length of signal lines, longer signal lines may introduce stability and transmission speed issues. Signal transmission reliability needs to be considered for long-distance communication. Many signal lines: The SPI protocol requires a separate control line for each slave device, so for systems connecting a large number of devices, the number of signal lines required will be relatively large. No handshake communication: The SPI protocol does not provide a "handshake" mechanism like the I2C protocol, that is, the slave device cannot actively send data requests to the master device. Therefore, for application scenarios that require active interaction with the master device, SPI may not be suitable.
11.7 SPI FLASH Application
11.7.1 Introduction to W25Q64
The W25Q64 is a common serial flash device that uses the SPI (Serial Peripheral Interface) protocol and has high-speed read, write, and erase functions. It can be used to store and read data. The W25Q64 chip has a capacity of 64 Mbit (8 MB), where the number after the name represents different capacity options. Different models and capacity options can meet the needs of different applications, such as W25Q16, W25Q32, W25Q128, etc. It is usually used in embedded devices, storage devices, routers, and other high-performance electronic devices. The memory allocation of the W25Q64 flash chip is based on sectors and blocks. Each sector has a size of 4 KB, and each block contains 16 sectors, that is, a block size is 64 KB.
11.7.2 Hardware Interface
Using the common module W25Q64 on the market, the description of its pins is shown in the table below.
In this example, the connection with the ESP32S3R8N8 development board is as follows:
11.7.3 API Introduction
11.7.3.1 Initialization and Configuration
- spi_bus_initialize() Initializes the SPI bus and configures its I/O pins and clock parameters in master mode.
esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);- Parameters:
host: Specifies the host device of the SPI bus.bus_config: Pointer to thespi_bus_config_tstructure, used to configure the SCLK, MISO, MOSI pins and other parameters of the SPI bus.dma_chan: Specifies which DMA channel to use. Valid values areSPI_DMA_CH_AUTO,SPI_DMA_DISABLED, or a number between 1 and 2.
spi_bus_config_tStructure for configuring the SPI bus, including pins for SCLK, MISO, MOSI, and optional quad-mode pins.
typedef struct {
int miso_io_num; // MISO pin number
int mosi_io_num; // MOSI pin number
int sclk_io_num; // Clock pin number
int quadwp_io_num; // WP pin number for Quad mode, set to -1 when not used
int quadhd_io_num; // HD pin number for Quad mode, set to -1 when not used
int max_transfer_sz; // Maximum transfer size
} spi_bus_config_t;2
3
4
5
6
7
8
11.7.3.2 Device Configuration
- spi_bus_add_device() Adds an SPI device to the specified SPI bus.
esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);- Parameters:
host: Specifies the host device of the SPI bus.dev_config: Pointer to thespi_device_interface_config_tstructure, used to configure the communication parameters of the SPI device, such as clock rate, SPI mode, etc.handle: Returns the created device handle.
spi_device_interface_config_tStructure for configuring the SPI device interface.
typedef struct {
uint32_t command_bits; // Number of bits in the command phase
uint32_t address_bits; // Number of bits in the address phase
uint32_t dummy_bits; // Number of bits in the dummy phase
int clock_speed_hz; // Clock rate
uint32_t mode; // SPI mode (0-3)
int spics_io_num; // CS pin number
... // Other device-specific configuration parameters
} spi_device_interface_config_t;2
3
4
5
6
7
8
9
11.7.3.3 Data Transmission
- spi_device_transmit() Sends a data transmission operation to the SPI device and waits for the operation to complete. This function handles both the data transmitted to the device and the data received from the device.
esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans);- Parameters:
handle: The device handle.trans: Pointer to thespi_transaction_tstructure, describing the transaction details to send.
- spi_transaction_t Structure describing an SPI transaction.
typedef struct {
uint32_t length; // Transfer length, in bits
uint32_t rxlength; // If non-zero, specifies the receive length; otherwise equal to length
const void *tx_buffer; // Pointer to the transmit data buffer
void *rx_buffer; // Pointer to the receive data buffer
... // Other fields, such as user-definable callbacks, etc.
} spi_transaction_t;2
3
4
5
6
7
These API functions are the basics of using SPI communication. By configuring and using these functions reasonably, efficient and reliable communication with various SPI devices can be achieved.
11.8 SPI Verification
Create two new files, bsp_spi.c and bsp_spi.h. Write the following content: bsp_spi.c
#include "bsp_spi.h"
/**********************************************************
* Function Name: w25q64_init_config
* Description: w25q64 initialization
* Parameters: None
* Return: None
* Note: None
**********************************************************/
esp_err_t w25q64_init_config(spi_device_handle_t* handle)
{
//00 Define the error flag
esp_err_t e;
//01 Configure the bus initialization structure
static spi_bus_config_t bus_cfg; // Bus configuration structure
bus_cfg.miso_io_num = Flash_SPI_MISO; // miso
bus_cfg.mosi_io_num = Flash_SPI_MOSI; // mosi
bus_cfg.sclk_io_num = Flash_SPI_SCLK; // sclk
bus_cfg.quadhd_io_num = Flash_SPI_HD; // HD
bus_cfg.quadwp_io_num = Flash_SPI_WP; // WP
bus_cfg.max_transfer_sz = 4092; // Non-DMA max 64 bytes, DMA max 4092 bytes
//bus_cfg.intr_flags = 0; // This is used to set the interrupt priority, 0 is default
bus_cfg.flags = SPICOMMON_BUSFLAG_MASTER;
// This is used to set which options to check during initialization. For example, here it is set to check whether the spi is initialized successfully in master mode. The check result is returned through the return value of the spi_bus_initialize function. If the initialization to master mode is successful, it will return esp_ok
//02 Initialize the bus configuration structure
e = spi_bus_initialize(Flash_SPI, &bus_cfg, SPI_DMA_CH_AUTO);
if (e != ESP_OK)
{
printf("bus initialize failed!\n");
return e;
}
//03 Configure the device structure
static spi_device_interface_config_t interface_cfg; // Device configuration structure
interface_cfg.address_bits = Flash_Address_Bits; // Configure the address bit length
//(1) If set to 0, the address bit will not be sent during communication.
//(2) If a non-zero value is set, the address data of the specified length will be sent during the address sending phase of spi communication.
// If a non-zero value is set and the addr value is not defined in the subsequent data sending structure, the specified length of 0 value will be sent by default.
//(3) We will use the spi_transaction_t structure to send data later. This structure will use the length of address, command, and dummy defined in spi_device_interface_config_t.
// If you want to use a non-fixed length, you need to use the spi_transaction_ext_t structure. This structure includes four parts: a spi_transaction_t and the lengths of address, command, and dummy.
// What we need to do is to set SPI_TRANS_VARIABLE_ADDR/CMD/DUMMY in spi_transaction_ext_t.base.flags
// Then define the length of these three parts of data, and then use the pointer of spi_transaction_ext_t.base instead of the pointer of spi_transaction_t
interface_cfg.command_bits = Flash_Command_Bits; // Configure the command bit length
// Same as address_bits
interface_cfg.dummy_bits = Flash_Dummy_Bits; // Configure the dummy length
// The configuration method here is the same as address_bits. But let me emphasize the meaning of this configuration, which will be mentioned again later.
//(1) dummy_bits is used to compensate for input delay.
//(2) It is inserted before the read phase starts. Under the dummy_bits clock, no data reading work is done.
// It is equivalent to the clocks sent during this period being dummy clocks with no function. When the maximum allowable input delay time is not enough, you can configure it in this way, so that
// the system can work at a higher clock frequency.
//(3) If the master device only performs write operations, you can set SPI_DEVICE_NO_DUMMY in flags to disable the sending of dummy bits. For write-only operations, even if the gpio exchange matrix is used, the clock cycle can work at 80 MHz
//interface_cfg.input_delay_ns = 0; // Configure the allowable range of input delay
// There is a delay from the clock sending the signal to the miso input. This parameter is used to configure the maximum allowable delay time.
// If the master receives the slave clock but does not receive the input signal from miso after this time, it will return a communication failure.
// Even if this time is set to 0, it can work normally, but it is best to estimate it through the manual or logic analyzer. Better communication can be achieved.
// Communications exceeding 8M should set this number carefully
interface_cfg.clock_speed_hz = Flash_CLK_SPEED; // Configure the clock frequency
// Configure the communication clock frequency.
// This frequency is limited by io_mux and input_delay_ns.
// If it is directly connected to io, the clock upper limit is 80 MHz. If it is connected through the gpio exchange matrix, the clock upper limit is 40 MHz.
// If it is full-duplex, the clock upper limit is 26 MHz. And the input delay must also be considered. Under the same input delay conditions, using the gpio exchange matrix will have a smaller maximum allowable clock frequency than using the io mux. You can use
// spi_get_freq_limit() to calculate the maximum allowable clock frequency.
// The issue of SPI communication clock limits and configuration will be discussed in detail later.
interface_cfg.mode = 0; // Set the phase characteristics and sampling edge of SPI communication. Includes mode0-3. You need to check which mode the slave device can use
interface_cfg.spics_io_num = Flash_SPI_CS; // Configure the chip select line
interface_cfg.duty_cycle_pos = 0; // Configure the duty cycle
// Set the duty cycle of the clock. The ratio is pos*1/256, default is 0, that is, 50% duty cycle
//interface_cfg.cs_ena_pretrans; // How many clocks the chip select line should remain active before transmission. Only needed for full-duplex
//interface_cfg.cs_ena_posttrans; // How many clocks the chip select line should remain active after transmission. Only needed for full-duplex
interface_cfg.queue_size = 6; // The length of the transmission queue, indicating how many spi communications can be suspended during communication. In interrupt communication mode, the current spi communication process will be suspended into the queue
//interface_cfg.flags; // Configure some parameters related to the slave, such as MSB or LSB, whether to use 3-wire SPI
//interface_cfg.pre_cb;
// Configure the pre-communication interrupt. For example, if the cs chip select line is not configured here, the chip select line is controlled independently, and pulling the chip select line low is placed in the pre-communication interrupt
//interface_cfg.post_cb;
// Configure the post-communication interrupt. For example, if the cs chip select line is not configured here, the chip select line is controlled independently, and pulling the chip select line high is placed in the post-communication interrupt
//04 Device initialization
e = spi_bus_add_device(Flash_SPI, &interface_cfg, handle);
if (e != ESP_OK)
{
printf("device config error\n");
return e;
}
return ESP_OK;
}
uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle)
{
//00 Define the error flag
esp_err_t e;
//01 Empty command sent when receiving data
uint8_t data[3];
data[0] = Dummy_Byte;
data[1] = Dummy_Byte;
data[2] = Dummy_Byte;
//02 Define the data used to return
uint32_t Temp;
//03 Define the data send/receive structure
spi_transaction_ext_t ext; // Because the instruction structure for reading the device ID is different from the previously defined default, the bit length needs to be changed
memset(&ext, 0, sizeof(ext)); // Initialize the structure
ext.command_bits = 8; // Instruction bit length is 8
ext.address_bits = 0; // Address bit length is 0
ext.base.cmd = W25X_JedecDeviceID; // Device ID
ext.base.length = 3 * 8; // Length of data to send
ext.base.tx_buffer = data; // Content of data to send
ext.base.rx_buffer = NULL; // Receive data buffer uses the one built into the structure
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA;
//04 Data send/receive
e=spi_device_polling_transmit(handle, &ext.base);
if (e != ESP_OK)
{
printf("get ID error!\n");
return 0;
}
//05 Return the obtained data ID
uint8_t temp0 = ext.base.rx_data[0];
uint8_t temp1 = ext.base.rx_data[1];
uint8_t temp2 = ext.base.rx_data[2];
Temp = (temp0 << 16) | (temp1 << 8) | temp2;
return Temp;
}
/**
* @breif Flash write enable. A page write must be performed once before executing page write and erase commands
* @param[in] handle: Provides the SPI operation handle
* @retval None
**/
void bsp_spi_flash_WriteEnable(spi_device_handle_t handle)
{
esp_err_t e; // Error flag bit
// Define the data send/receive structure
spi_transaction_ext_t ext; // The length of write enable is different from the default and needs to be modified
memset(&ext, 0, sizeof(ext)); // Initialize the structure
ext.command_bits = 8; // Instruction bit length is 8
ext.address_bits = 0; // Address bit length is 0
ext.base.cmd = W25X_WriteEnable; // Write enable
ext.base.length = 0; // Length of data to send. No data needs to be sent here
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
// Send instruction
e = spi_device_polling_transmit(handle, &ext.base);
if (e != ESP_OK)
{
printf("write enable failed!\n");
}
}
/**
* @breif Wait for flash to complete the current operation
* @param[in] handle: Provides the SPI operation handle
* @retval None
**/
void bsp_spi_flash_WaitForWriteEnd(spi_device_handle_t handle)
{
// Define the data send/receive structure
spi_transaction_ext_t ext; // The length of write enable is different from the default and needs to be modified
memset(&ext, 0, sizeof(ext)); // Initialize the structure
ext.command_bits = 8; // Instruction bit length is 8
ext.address_bits = 0; // Address bit length is 0
ext.base.cmd = W25X_ReadStatusReg; // Read status register
ext.base.length = 1 * 8; // Length of data to send. No data needs to be sent here
ext.base.rx_buffer = NULL; // Do not use external data
ext.base.tx_buffer = NULL; // Do not use external data
ext.base.tx_data[0] = Dummy_Byte; // Send data
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;
do
{
// Send instruction
spi_device_polling_transmit(handle, &ext.base);
}
while ( (ext.base.rx_data[0] & WIP_Flag )== WIP_SET);
}
/**
* @breif Sector erase
* @param[in] handle: Provides the SPI operation handle
* @param[in] SectorAddr: The starting sector address to erase
* @retval None
**/
void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr)
{
bsp_spi_flash_WriteEnable(handle);
bsp_spi_flash_WaitForWriteEnd(handle);
// Define the data send/receive structure
spi_transaction_t t; // The configuration bits are the same as the default and do not need to be modified
memset(&t, 0, sizeof(t)); // Initialize the structure
t.cmd = W25X_SectorErase; // Erase instruction
t.addr = SectorAddr; // Erase address
t.length = 0; // No additional data needed
// Send instruction
spi_device_polling_transmit(handle, &t);
// Wait for erasure to complete
bsp_spi_flash_WaitForWriteEnd(handle);
}
/**
* @breif Page write
* @param[in] handle: Provides the SPI operation handle
* @param[in] pBuffer: The address of the data to write
* @param[in] WriteAddr: The address to write to
* @param[in] NumByteToWrite: The length to write
* @retval None
**/
void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
bsp_spi_flash_WriteEnable(handle);
// Define the data send/receive structure
spi_transaction_t t; // The configuration bits are the same as the default and do not need to be modified
memset(&t, 0, sizeof(t)); // Initialize the structure
t.cmd = W25X_PageProgram; // Page write
t.addr = WriteAddr; // Erase address
t.length = 8*NumByteToWrite; // Write length
t.tx_buffer = pBuffer; // Data to write
t.rx_buffer = NULL; // No need to read data
if (NumByteToWrite > SPI_Flash_PageSize)
{
printf("length is too long!\n");
return ;
}
// Send instruction
spi_device_polling_transmit(handle, &t);
// Wait for erasure to complete
bsp_spi_flash_WaitForWriteEnd(handle);
}
/**
* @breif Variable-length data write
* @param[in] handle: Provides the SPI operation handle
* @param[in] pBuffer: The address of the data to write
* @param[in] WriteAddr: The address to write to
* @param[in] NumByteToWrite: The length to write
* @retval None
**/
void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
// Perform modulo operation to check whether page alignment is performed
Addr = WriteAddr % SPI_Flash_PageSize;
// count data values are needed to achieve page alignment
count = SPI_Flash_PageSize - Addr;
// Calculate how many complete pages to write
NumOfPage = NumByteToWrite / SPI_Flash_PageSize;
// Calculate how many bytes are left that do not fill 1 page
NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;
// If Addr=0, that is, page alignment is performed
if (Addr == 0)
{
// If less than 1 page is written
if (NumOfPage == 0)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
}
else
{
// If more than 1 page, write the full pages first
while (NumOfPage--)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);
WriteAddr += SPI_Flash_PageSize;
pBuffer += SPI_Flash_PageSize;
}
// Write the remaining less than 1 page
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);
}
}
// If page alignment is not performed
else
{
if (NumOfPage == 0)
{
// If the remaining count positions of the current page are smaller than NumOfSingle, 1 page is not enough
if (NumOfSingle > count)
{
// Write the remaining of this page first
temp = NumOfSingle - count;
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
// Write the extra
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, temp);
}
else
{
// If the remaining space is large enough, write directly
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
}
}
// If more than 1 page
else
{
// Write the misaligned bytes first
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_Flash_PageSize;
NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr,count);
// Repeat the case of address alignment
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);
pBuffer += SPI_Flash_PageSize;
WriteAddr += SPI_Flash_PageSize;
}
if (NumOfSingle != 0)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/**
* @breif Data read
* @param[in] handle: Provides the SPI operation handle
* @param[out] pBuffer: The data buffer address to read
* @param[in] WriteAddr: The address to write to
* @param[in] NumByteToWrite: The length to write
* @retval None
**/
void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
bsp_spi_flash_WriteEnable(handle);
// Define the data send/receive structure
spi_transaction_t t; // The configuration bits are the same as the default and do not need to be modified
memset(&t, 0, sizeof(t)); // Initialize the structure
t.cmd = W25X_ReadData; // Read data
t.addr = WriteAddr; // Erase address
t.length = 8 * NumByteToWrite; // Read length
t.tx_buffer = NULL; // No need to write data
t.rx_buffer = pBuffer; // Read data
// Send instruction
spi_device_polling_transmit(handle, &t);
// Wait for erasure to complete
bsp_spi_flash_WaitForWriteEnd(handle);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
bsp_spi.h
#ifndef _BSP_SPI_H__
#define _BSP_SPI_H__
#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "hal/gpio_types.h"
#include <string.h>
// Define the pins required for the flash experiment
#define Flash_SPI SPI2_HOST
#define Flash_SPI_MISO GPIO_NUM_12
#define Flash_SPI_MOSI GPIO_NUM_13
#define Flash_SPI_SCLK GPIO_NUM_14
#define Flash_SPI_CS GPIO_NUM_15
#define Flash_SPI_WP -1
#define Flash_SPI_HD -1
#define Flash_SPI_DMA SPI_DMA_CH1
// Define device parameters
#define Flash_CLK_SPEED 6 * 1000 * 1000 // 6M clock
#define Flash_Address_Bits 3*8 // Address bit length
#define Flash_Command_Bits 1*8 // Command bit length
#define Flash_Dummy_Bits 0*8 // dummy bit length
#define SPI_Flash_PageSize 256 // Page write maximum
// Define command instructions
#define W25X_JedecDeviceID 0x9F // Instruction to get flash ID
#define W25X_WriteEnable 0x06 // Write enable
#define W25X_WriteDisable 0x04 // Disable write
#define W25X_ReadStatusReg 0x05 // Read status register
#define W25X_SectorErase 0x20 // Sector erase
#define W25X_BlockErase 0xD8 // Block erase
#define W25X_ChipErase 0xC7 // Chip erase
#define W25X_PageProgram 0x02 // Page write
#define W25X_ReadData 0x03 // Data read
#define Dummy_Byte 0xFF // Empty instruction, used to fill the send buffer
#define WIP_Flag 0x01 // Flash busy flag bit
#define WIP_SET 1
esp_err_t w25q64_init_config(spi_device_handle_t* handle);
uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle);
void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr);
void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
#endif2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Import their file paths into the project, and write the following verification code in main.c:
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "bsp_spi.h"
#include <esp_log.h>
spi_device_handle_t spi2_handle;
static const char * TAG = "Task";
void app_main(void)
{
uint8_t pBuffer[11] = { 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0x00 };
uint8_t rBuffer[11] = {0};
int id=0;
// Configure W25Q64
w25q64_init_config(&spi2_handle);
// Read device ID
id=bsp_spi_flash_ReadID(spi2_handle);
ESP_LOGI(TAG,"id = %X\r\n",id);
// Write 10 bytes of data to address 0 of W25Q64
bsp_spi_flash_SectorErase(spi2_handle, 0);
ESP_LOGI(TAG,"Sector erase successfully\r\n");
bsp_spi_flash_BufferWrite(spi2_handle, pBuffer, 0, 10);
ESP_LOGI(TAG,"Write successfully\r\n");
// Read 10 bytes of data from address 0 of W25Q64
bsp_spi_flash_BufferRead(spi2_handle, rBuffer, 0, 10);
for (int i = 0; i < 10; i++)
{
ESP_LOGI(TAG,"0x%x ", rBuffer[i]);
}
ESP_LOGI(TAG,".");
while(1)
{
ESP_LOGI(TAG,".");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Physical operation effect