11. SPI
11.1 什么是SPI
SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器和外部设备之间进行数据传输。它由一个主设备(通常是微控制器MCU)和一个或多个从设备组成,即一主多从模式。它通常用于短距离、高速、全双工的通信,它在许多嵌入式系统和电子设备中被广泛应用。
11.2 SPI的硬件接口
SPI主要使用4根线,时钟线(SCLK),主输出从输入线(MOSI),主输入从输出线(MISO)和片选线(CS)。
- 主设备通过MOSI线向从设备发送数据。在每个时钟周期中,主设备将一个位发送到MOSI线上,从设备在下一个时钟周期中读取该位。
- 从设备通过MISO线向主设备发送数据。在每个时钟周期中,从设备将一个位发送到MISO线上,主设备在下一个时钟周期中读取该位。
- 数据传输可以是全双工的,即主设备和从设备可以同时发送和接收数据。
- 数据传输的长度可以是可变的,通常以字节为单位。
- 数据传输可以是单向的,即主设备只发送数据或只接收数据。
- 数据传输可以是多主设备的,即多个主设备可以与多个从设备进行通信。
主设备是通过片选线选择要与之通信的从设备。每个从设备都有一个片选线,当片选线为低电平时,表示该从设备被选中。(也有一些设备以高电平有效,需要根据其数据手册确定)。主设备通过控制时钟线的电平来同步数据传输。时钟线的上升沿和下降沿用于控制数据的传输和采样。
SPI的主从接线方式与串口类似,需要发送与接收交叉连接。
11.3 SPI的模式选择
SPI协议定义了多种传输模式,也称为SPI模式或时序模式,用于控制数据在时钟信号下的传输顺序和数据采样方式。SPI的传输模式主要由两个参数决定:时钟极性 (CKPL) 和相位 (CKPH)。时钟极性 (CKPL):时钟极性定义了时钟信号在空闲状态时的电平。
CKPL = 0:时钟信号在空闲状态时为低电平。
CKPL = 1:时钟信号在空闲状态时为高电平。
时钟相位 (CKPH):相位定义了数据采样和更新发生在时钟信号的哪个边沿上。
CKPH = 0:数据采样发生在时钟的第一个边沿,数据更新发生在第二个边沿。
CKPH = 1:数据采样发生在时钟的第二个边沿,数据更新发生在第一个边沿。
以下是常见的SPI模式:
模式0(CKPL=0,CKPH=0):
- 时钟极性(Clock Polarity)为0,表示时钟空闲状态为低电平。
- 时钟相位(Clock Phase)为0,表示数据在时钟信号的第一个边沿(时钟上升沿)进行采样和稳定。
模式1(CKPL=0,CKPH=1):
- 时钟极性为0,时钟空闲状态为低电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟下降沿)进行采样和稳定。
模式2(CKPL=1,CKPH=0):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为0,数据在时钟信号的第一个边沿(时钟下降沿)进行采样和稳定。
模式3(CKPL=1,CKPH=1):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟上升沿)进行采样和稳定。
选择SPI模式的决策通常取决于从设备的规格要求和通信协议。不同的设备可能采用不同的模式,所以在与特定从设备通信之前,必须了解从设备所需的SPI模式。如果没有明确指定SPI模式,通常可以根据从设备的规格手册或通信协议选择最常见的模式0或模式3进行尝试。此外,还需要注意SPI模式时钟的频率限制,以确保主设备和从设备之间的时钟频率匹配。
11.4 SPI的基本参数
SPI协议定义了一组参数,这些参数对于正确设置通信参数并实现SPI通信非常重要。
SPI的基本参数包括:
- 时钟极性(CPOL):指定时钟空闲状态时信号线的电平,有两种状态:空闲时为高电平(CPOL=1)或空闲时为低电平(CPOL=0);
- 时钟相位(CPHA):指定数据采样的时刻,有两种状态:在时钟上升边沿之后采样数据(CPHA=0)或在时钟下降边沿之前采样数据(CPHA=1);
- 数据位数:指定每个SPI数据包包含的位数(通常为8位),也可以设置为较小或较大的值;
- 传输模式:确定数据如何在SPI总线上传输(如全双工、半双工或单向模式);
- 时钟速率:指定SPI总线的时钟速率,以bits per second (bps)为单位;
- 主/从模式:确定设备是SPI总线上的主机还是从机;
- 传输顺序:指定数据的 bit 传输顺序,MSB(most significant bit)优先或LSB(least significant bit)优先。
这些参数可以通过配置SPI控制寄存器来设置,以确保SPI设备之间的正确通信。在确定这些参数时,应该考虑实际硬件设置和通信需求。如果需要使用SPI进行通信,请确保正确设置这些参数。
11.5 软件SPI与硬件SPI
SPI与IIC类似,都分有软件SPI和硬件SPI,软件SPI部分不再讲解,本章节着重讲解硬件SP部分。
ESP32-S3芯片集成了四个 SPI 控制器:
• SPI0 • SPI1 • 通用 SPI2,即 GP-SPI2 • 和通用 SPI3,即 GP-SPI3 SPI0 和 SPI1 控制器主要供内部使用以访问外部 flash 及 PSRAM。我们只能使用SPI2和SPI3。
硬件SPI支持以下特性:
11.6 SPI协议的优劣势
优势
简单易实现 :SPI协议的硬件和软件实现相对简单,通信过程清晰明了,易于理解和调试。这使得SPI协议在嵌入式系统中得到广泛应用。
高速传输 :SPI协议可以实现高速的数据传输。由于SPI是点对点的通信协议,数据直接在主设备和从设备之间传输,无需地址和冲突检测,发送与接收分别在不同的信号线上,因此具有较高的传输速度。
灵活性强 :SPI协议允许主设备与多个从设备进行通信。主设备可以通过选择从设备的片选信号来与特定设备通信,从而实现多设备的串行通信。
劣势
信号线长度有限 :由于SPI协议没有特定的规范限制信号线的长度,较长的信号线可能会引入稳定性和传输速度方面的问题。在长距离通信时需要考虑信号传输的可靠性。
信号线数量多 :SPI协议在每个从设备之间都需要一条单独的控制线,因此对于连接大量设备的系统来说,所需的信号线数量会相对较多。
无手握通信 :SPI协议没有提供像I2C协议那样的“握手”机制,即从设备无法主动向主设备发送数据请求。因此,对于需要主动主设备交互的应用场景来说,SPI可能不太适合。
11.7 SPI FLASH应用
11.7.1 W25Q64介绍
W25Q64是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q64芯片容量为64 Mbit(8 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,比如W25Q16、W25Q32、W25Q128等。通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。
W25Q64闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB。
11.7.2 硬件接口
使用市场上的常用模块,W25Q64,其引脚的说明,见下表。
在本案例中与ESP32S3R8N8开发板的连接如下:
11.7.3 API介绍
11.7.3.1 初始化和配置
- spi_bus_initialize()
初始化SPI总线,并配置其I/O引脚和主模式下的时钟等参数。
esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);
- 参数:
host
:指定SPI总线的主机设备。bus_config
:指向spi_bus_config_t
结构体的指针,用于配置SPI总线的SCLK、MISO、MOSI等引脚以及其他参数。dma_chan
:指定使用哪个DMA通道。有效值为SPI_DMA_CH_AUTO
,SPI_DMA_DISABLED
或1至2之间的数字。
spi_bus_config_t
配置SPI总线的结构体,包括用于SCLK、MISO、MOSI的引脚,以及可选的quad模式引脚等。
typedef struct {
int miso_io_num; // MISO引脚号
int mosi_io_num; // MOSI引脚号
int sclk_io_num; // 时钟引脚号
int quadwp_io_num; // 用于Quad模式的WP引脚号,未使用时设置为-1
int quadhd_io_num; // 用于Quad模式的HD引脚号,未使用时设置为-1
int max_transfer_sz; // 最大传输大小
} spi_bus_config_t;
2
3
4
5
6
7
8
11.7.3.2 设备配置
- spi_bus_add_device()
在指定的SPI总线上添加一个SPI设备。
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);
- 参数:
host
:指定SPI总线的主机设备。dev_config
:指向spi_device_interface_config_t
结构体的指针,用于配置SPI设备的通信参数,如时钟速率、SPI模式等。handle
:返回创建的设备句柄。
spi_device_interface_config_t
配置SPI设备接口的结构体。
typedef struct {
uint32_t command_bits; // 命令阶段的位数
uint32_t address_bits; // 地址阶段的位数
uint32_t dummy_bits; // 虚拟阶段的位数
int clock_speed_hz; // 时钟速率
uint32_t mode; // SPI模式(0-3)
int spics_io_num; // CS引脚号
... // 其他设备特定的配置参数
} spi_device_interface_config_t;
2
3
4
5
6
7
8
9
11.7.3.3 数据传输
- spi_device_transmit() 向SPI设备发送一个数据传输操作,并等待操作完成。这个函数同时处理传输到设备的数据和从设备接收的数据。
esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans);
- 参数:
handle
:设备的句柄。trans
:指向spi_transaction_t
结构体的指针,描述了要发送的事务详情。
- spi_transaction_t 描述一个SPI事务的结构体。
typedef struct {
uint32_t length; // 传输长度,单位位(bit)
uint32_t rxlength; // 如果非零,指定接收长度,否则等于lengthconst
void *tx_buffer; // 指向发送数据缓冲区的指针
void *rx_buffer; // 指向接收数据缓冲区的指针
... // 其他字段,如用户可定义的回调等
} spi_transaction_t;
2
3
4
5
6
7
这些API函数是使用SPI通信时的基础。通过合理配置和使用这些函数,可以实现与各种SPI设备的高效、可靠通信。
11.8 SPI验证
新建两个文件,bsp_spi.c 和 bsp_spi.h。编写以下内容:
bsp_spi.c
#include "bsp_spi.h"
/**********************************************************
* 函 数 名 称:w25q64_init_config
* 函 数 功 能:w25q64初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 备 注:无
**********************************************************/
esp_err_t w25q64_init_config(spi_device_handle_t* handle)
{
//00 定义错误标志
esp_err_t e;
//01 配置总线初始化结构体
static spi_bus_config_t bus_cfg; //总线配置结构体
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; //非DMA最大64bytes,DMA最大4092bytes
//bus_cfg.intr_flags = 0; //这个用于设置中断优先级的,0是默认
bus_cfg.flags = SPICOMMON_BUSFLAG_MASTER;
//这个用于设置初始化的时候要检测哪些选项。比如这里设置的是spi初始化为主机模式是否成功。检测结果通过spi_bus_initialize函数的
//返回值进行返回。如果初始化为主机模式成功,就会返回esp_ok
//02 初始化总线配置结构体
e = spi_bus_initialize(Flash_SPI, &bus_cfg, SPI_DMA_CH_AUTO);
if (e != ESP_OK)
{
printf("bus initialize failed!\n");
return e;
}
//03 配置设备结构体
static spi_device_interface_config_t interface_cfg; //设备配置结构体
interface_cfg.address_bits = Flash_Address_Bits; //配置地址位长度
//(1)如果设置为0,在通讯的时候就不会发送地址位。
//(2)如果设置了非零值,就会在spi通讯的地址发送阶段发送指定长度的address数据。
//如果设置了非零值并且在后面数据发送结构体中没有定义addr的值,会默认发送指定长度0值
//(3)我们后面发送数据会使用到spi_transaction_t结构体,这个结构体会使用spi_device_interface_config_t中定义好address、command和dummy的长度
//如果想使用非固定长度,就要使用spi_transaction_ext_t结构体了。这个结构体包括了四个部分,包含了一个spi_transaction_t和address、command、dummy的长度。
//我们要做的就是在spi_transaction_ext_t.base.flags中设置SPI_TRANS_VARIABLE_ADDR/CMD/DUMMY
//然后定义好这三部分数据的长度,然后用spi_transaction_ext_t.base的指针代替spi_transaction_t的指针即可
interface_cfg.command_bits = Flash_Command_Bits; //配置命令位长度
//与address_bits是一样的
interface_cfg.dummy_bits = Flash_Dummy_Bits; //配置dummy长度
//这里的配置方法与address_bits是一样的。但是要着重说一下这个配置的意义,后面会再说一遍
//(1)dummy_bits是用来用来补偿输入延迟。
//(2)在read phase开始阶段之前被插入进去。在dummy_bits的时钟下,并不进行数据读取的工作
//相当于这段时间发送的clock都是虚拟的时钟,并没有功能。在输入延迟最大允许时间不够的时候,可以通过这种方法进行配置,从而
//能够使得系统工作在更高的时钟频率下。
//(3)如果主机设备只进行write操作,可以在flags中设置SPI_DEVICE_NO_DUMMY,关闭dummy bits的发送。只有写操作的话,即使使用了gpio交换矩阵,时钟周期也可以工作在80MHZ
//interface_cfg.input_delay_ns = 0; //配置输入延时的允许范围
//时钟发出信号到miso进行输入直接会有延迟,这个参数就是配置这个允许的最大延迟时间。
//如果主机接收到从机时钟,但是超过这个时间没有收到miso发来的输入信号,就会返回通讯失败。
//这个时间即使设置为0,也能正常工作,但是最好通过手册或逻辑分析仪进行估算。能够实现更好的通讯。
//超过8M的通讯都应该认真设置这个数字
interface_cfg.clock_speed_hz = Flash_CLK_SPEED; //配置时钟频率
//配置通讯的时钟频率。
//这个频率受到io_mux和input_delay_ns限制。
//如果是io直连的,时钟上限是80MHZ,如果是gpio交换矩阵连接进来的,时钟上限是40MHZ。
//如果是全双工,时钟上限是26MHZ。并且还要考虑输入延时。在相同输入延时的条件下,使用gpio交换矩阵会比使用io mux最大允许的时钟频率小。可以通过
//spi_get_freq_limit()来计算能够允许的最大时钟频率是多少
//有关SPI通讯时钟极限和配置的问题,后面会详细说一下。
interface_cfg.mode = 0; //设置SPI通讯的相位特性和采样边沿。包括了mode0-3四种。要看从设备能够使用哪种模式
interface_cfg.spics_io_num = Flash_SPI_CS; //配置片选线
interface_cfg.duty_cycle_pos = 0; //配置占空比
//设置时钟的占空比,比例是 pos*1/256,默认为0,也就是50%占空比
//interface_cfg.cs_ena_pretrans; //在传输之前,片选线应该保持激活状态多少个时钟,只有全双工的时候才需要配置
//interface_cfg.cs_ena_posttrans; //在传输之后,片选线应该保持激活状态多少个时钟,只有全双工的时候才需要配置
interface_cfg.queue_size = 6; //传输队列的长度,表示可以在通讯的时候挂起多少个spi通讯。在中断通讯模式的时候会把当前spi通讯进程挂起到队列中
//interface_cfg.flags; //配置与从机有关的一些参数,比如MSB还是LSB,使不使用三线SPI
//interface_cfg.pre_cb;
//配置通讯前中断。比如不在这里配置cs片选线,把片选线作为自行控制的线,把片选线拉低放在通讯前中断中
//interface_cfg.post_cb;
//配置通讯后中断。比如不在这里配置cs片选线,把片选线作为自行控制的线,把片选线拉高放在通讯前中断中
//04 设备初始化
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 定义错误标志
esp_err_t e;
//01 接收数据的时候发送的空指令
uint8_t data[3];
data[0] = Dummy_Byte;
data[1] = Dummy_Byte;
data[2] = Dummy_Byte;
//02 定义用于返回的数据
uint32_t Temp;
//03 定义数据发送接收结构体
spi_transaction_ext_t ext; //因为读取设备ID的指令结构与前面定义的默认的不一样,所以需要更改位长
memset(&ext, 0, sizeof(ext)); //初始化结构体
ext.command_bits = 8; //指令位长度为8
ext.address_bits = 0; //地址位长度为0
ext.base.cmd = W25X_JedecDeviceID; //设备ID
ext.base.length = 3 * 8; //要发送数据的长度
ext.base.tx_buffer = data; //要发送数据的内容
ext.base.rx_buffer = NULL; //接收数据buffer使用结构体内部带的
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA;
//04 数据收发
e=spi_device_polling_transmit(handle, &ext.base);
if (e != ESP_OK)
{
printf("get ID error!\n");
return 0;
}
//05 返回获取的数据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写使能。在执行页写入和擦除命令之前,都必须执行一次页写入
* @param[in] handle: 提供SPI的操作句柄
* @retval 无
**/
void bsp_spi_flash_WriteEnable(spi_device_handle_t handle)
{
esp_err_t e; //错误标志位
// 定义数据发送接收结构体
spi_transaction_ext_t ext; //写使能的长度与默认的不同,需要修改
memset(&ext, 0, sizeof(ext)); //初始化结构体
ext.command_bits = 8; //指令位长度为8
ext.address_bits = 0; //地址位长度为0
ext.base.cmd = W25X_WriteEnable; //写使能
ext.base.length = 0; //要发送数据的长度,这里不需要发送数据
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
//发送指令
e = spi_device_polling_transmit(handle, &ext.base);
if (e != ESP_OK)
{
printf("write enable failed!\n");
}
}
/**
* @breif 等待flash完成当前操作
* @param[in] handle: 提供SPI的操作句柄
* @retval 无
**/
void bsp_spi_flash_WaitForWriteEnd(spi_device_handle_t handle)
{
// 定义数据发送接收结构体
spi_transaction_ext_t ext; //写使能的长度与默认的不同,需要修改
memset(&ext, 0, sizeof(ext)); //初始化结构体
ext.command_bits = 8; //指令位长度为8
ext.address_bits = 0; //地址位长度为0
ext.base.cmd = W25X_ReadStatusReg; //读取状态寄存器
ext.base.length = 1 * 8; //要发送数据的长度,这里不需要发送数据
ext.base.rx_buffer = NULL; //不使用外部数据
ext.base.tx_buffer = NULL; //不使用外部数据
ext.base.tx_data[0] = Dummy_Byte; //发送数据
ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;
do
{
//发送指令
spi_device_polling_transmit(handle, &ext.base);
}
while ( (ext.base.rx_data[0] & WIP_Flag )== WIP_SET);
}
/**
* @breif 扇区擦除
* @param[in] handle: 提供SPI的操作句柄
* @param[in] SectorAddr: 要擦除的起始扇区地址
* @retval 无
**/
void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr)
{
bsp_spi_flash_WriteEnable(handle);
bsp_spi_flash_WaitForWriteEnd(handle);
// 定义数据发送接收结构体
spi_transaction_t t; //配置位与默认一致,不需要修改
memset(&t, 0, sizeof(t)); //初始化结构体
t.cmd = W25X_SectorErase; //擦除指令
t.addr = SectorAddr; //擦除地址
t.length = 0; //不需要额外数据了
//发送指令
spi_device_polling_transmit(handle, &t);
//等待擦除完毕
bsp_spi_flash_WaitForWriteEnd(handle);
}
/**
* @breif 页写入
* @param[in] handle: 提供SPI的操作句柄
* @param[in] pBuffer:要写入的数据地址
* @param[in] WriteAddr:要写入的地址
* @param[in] NumByteToWrite: 要写入的长度
* @retval 无
**/
void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
bsp_spi_flash_WriteEnable(handle);
// 定义数据发送接收结构体
spi_transaction_t t; //配置位与默认一致,不需要修改
memset(&t, 0, sizeof(t)); //初始化结构体
t.cmd = W25X_PageProgram; //页写入
t.addr = WriteAddr; //擦除地址
t.length = 8*NumByteToWrite; //写入长度
t.tx_buffer = pBuffer; //写入的数据
t.rx_buffer = NULL; //不需要读取数据
if (NumByteToWrite > SPI_Flash_PageSize)
{
printf("length is too long!\n");
return ;
}
//发送指令
spi_device_polling_transmit(handle, &t);
//等待擦除完毕
bsp_spi_flash_WaitForWriteEnd(handle);
}
/**
* @breif 不定量数据写入
* @param[in] handle: 提供SPI的操作句柄
* @param[in] pBuffer:要写入的数据地址
* @param[in] WriteAddr:要写入的地址
* @param[in] NumByteToWrite: 要写入的长度
* @retval 无
**/
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;
//进行取余运算,查看是否进行了页对齐
Addr = WriteAddr % SPI_Flash_PageSize;
//差count个数据值可以进行页对齐
count = SPI_Flash_PageSize - Addr;
//计算要写多少个完整的页
NumOfPage = NumByteToWrite / SPI_Flash_PageSize;
//计算剩余多少字节不满1页
NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;
//如果Addr=0,也就是进行了页对齐
if (Addr == 0)
{
//如果写不满1页
if (NumOfPage == 0)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
}
else
{
//如果超过1页,先把满的写了
while (NumOfPage--)
{
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);
WriteAddr += SPI_Flash_PageSize;
pBuffer += SPI_Flash_PageSize;
}
//不满的1页再写
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);
}
}
//如果没有进行页对齐
else
{
if (NumOfPage == 0)
{
//如果当前页剩下的count个位置比NumOfSingle小,1页写不完
if (NumOfSingle > count)
{
//先把这页剩下的写了
temp = NumOfSingle - count;
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
//再把多了的写了
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, temp);
}
else
{
//如果剩下的空间足够大,就直接写
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
}
}
//如果不止1页
else
{
//先把对不齐的字节写了
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_Flash_PageSize;
NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;
bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr,count);
//重复地址对齐的情况
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 数据读取
* @param[in] handle: 提供SPI的操作句柄
* @param[out] pBuffer:要读取的数据buffer地址
* @param[in] WriteAddr:要写入的地址
* @param[in] NumByteToWrite: 要写入的长度
* @retval 无
**/
void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
bsp_spi_flash_WriteEnable(handle);
// 定义数据发送接收结构体
spi_transaction_t t; //配置位与默认一致,不需要修改
memset(&t, 0, sizeof(t)); //初始化结构体
t.cmd = W25X_ReadData; //读取数据
t.addr = WriteAddr; //擦除地址
t.length = 8 * NumByteToWrite; //读取长度
t.tx_buffer = NULL; //不需要写入数据
t.rx_buffer = pBuffer; //读取数据
//发送指令
spi_device_polling_transmit(handle, &t);
//等待擦除完毕
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>
//定义Flash实验所需要的引脚
#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 Flash_CLK_SPEED 6 * 1000 * 1000 //6M的时钟
#define Flash_Address_Bits 3*8 //地址位长度
#define Flash_Command_Bits 1*8 //命令位长度
#define Flash_Dummy_Bits 0*8 //dummy位长度
#define SPI_Flash_PageSize 256 //页写入最大值
//定义命令指令
#define W25X_JedecDeviceID 0x9F //获取flashID的指令
#define W25X_WriteEnable 0x06 //写入使能
#define W25X_WriteDisable 0x04 //禁止写入
#define W25X_ReadStatusReg 0x05 //读取状态寄存器
#define W25X_SectorErase 0x20 //扇区擦除
#define W25X_BlockErase 0xD8 //块擦除
#define W25X_ChipErase 0xC7 //芯片擦除
#define W25X_PageProgram 0x02 //页写入
#define W25X_ReadData 0x03 //数据读取
#define Dummy_Byte 0xFF //空指令,用于填充发送缓冲区
#define WIP_Flag 0x01 //flash忙碌标志位
#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);
#endif
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
将它们的文件路径导入工程,在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;
//配置W25Q64
w25q64_init_config(&spi2_handle);
//读取器件ID
id=bsp_spi_flash_ReadID(spi2_handle);
ESP_LOGI(TAG,"id = %X\r\n",id);
//向W25Q64的地址0写入10个数据
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");
//向W25Q64的地址0读取10个数据
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
实物操作效果