FPGA—ZYNQ学习spi(六)
完成对SPiflash的读写
功能:1.flash初始化2.flash擦除3.flash写入数据4.flash读数据
1可以选择是ps的mio引脚也可以是emio的PL侧引出来的引脚
1.1mio

1.2emio



2综合完后,分配引脚
还是一样的需要导出然后设置顶层,最后进行综合,分配引脚,生成bit打开SDK

3打开SDK,在进行创建工层


4.打开示例工程发现一些指令

指令
基本读写操作指令
这是最核心、最常用的一组指令。
指令宏 | 操作码 | 功能描述 |
|---|---|---|
|
| 读取数据。主控制器发送此命令后,再发送要读取的内存地址,Flash芯片就会从该地址开始返回数据。 |
|
| 快速读取。比普通读取更快,通常在命令和地址后会有一个“哑元”字节,以支持更高的SPI时钟频率。 |
|
| 页编程。用于向Flash写入数据。主控制器先发送此命令,再发送目标地址,然后是待写入的数据。写入前必须先擦除。 |
|
| 自动地址增量写入。某些品牌(如SST)Flash的特色功能,可以连续写入多个字而无需重复发送写命令和地址,提高了写入效率。 |
🗑️ 擦除操作指令
Flash存储器的重要特性是:在写入(编程)之前,必须先进行擦除(将位从“0”变为“1”)。擦除操作的单位不同。
指令宏 | 操作码 | 功能描述 |
|---|---|---|
|
| 整片擦除。擦除整个Flash芯片的所有数据,操作需极其谨慎,因为会清空所有内容。 |
|
| 批量擦除,通常是 |
|
| 块擦除。擦除一个指定地址开始的、大小为64KB的存储块。这是更精细的擦除操作。 |
⚙️ 状态与控制指令
用于管理写操作、查询芯片状态和配置。
指令宏 | 操作码 | 功能描述 |
|---|---|---|
|
| 写使能。在执行任何写或擦除操作前,必须首先发送此命令,将芯片置为可写状态。 |
|
| 写禁止。用于取消写使能状态,保护数据。 |
|
| 读状态寄存器。Flash芯片内部有一个状态寄存器,通过此命令可以读取其值,关键目的是检查bit0的“忙”位,以判断写/擦除操作是否完成。 |
|
| 写状态寄存器。用于配置写保护区域等参数。 |
🔍 识别与保护指令
用于识别芯片型号和管理硬件保护功能。
指令宏 | 操作码 | 功能描述 |
|---|---|---|
|
| 读ID。读取制造商标识和设备标识。 |
|
| SST读ID。另一种标准的读ID命令,JEDEC标准命令,绝大多数SPI Flash都支持。 |
|
| 注释说明是SST闪存的ID值,可能用于识别。 |
|
| 全局块保护解锁。用于解除SST Flash的硬件写保护功能。 |
💎 总结与工作流程
这些指令共同构成了操作SPI Flash的完整流程。一个典型的写入流程如下:
-
发送
WRITE_ENABLE_CMD。 -
发送擦除指令(如
BLOCK_ERASE_64K_CMD)擦除目标区域。 -
轮询
READ_STATUS_CMD直到芯片就绪。 -
再次发送
WRITE_ENABLE_CMD。 -
发送
WRITE_CMD或AAI_WRITE_CMD进行数据写入。 -
轮询
READ_STATUS_CMD直到写入完成。
4找到spi.h库文件看看函数有哪些



4.2模式函数作用
根据是哪个spi进行操作
XSpiPs Setoptlons(SpiInstancePtr, XSPIPS MANUAL START OPTION
XSPIPS MASTER OPTIONXSPIPS FORCE SSELECT OPTION);XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MANUAL_START_OPTION | XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION);
这行代码的意思是:对 SpiInstancePtr所指向的SPI控制器实例,同时启用“手动启动”、“主模式”和“强制片选”这三个选项。
🔧 三种模式详解
下面的表格清晰地展示了代码中设置的三种模式及其作用:
模式选项(宏定义) | 含义 | 作用与解释 |
|---|---|---|
| 主模式 | 将当前SPI设备设置为主设备。主设备控制通信,提供时钟信号(SCLK),并决定何时开始和结束传输。这是最常见的设置,因为CPU/FPGA通常作为主设备去控制Flash、传感器等从设备。 |
| 强制片选模式 | 强制SPI控制器的片选信号(SS或CS)在整个数据传输期间始终保持有效(低电平)。这确保了从设备在接收一整个完整的数据帧期间不会被意外中断,是通信稳定性的关键。 |
| 手动启动模式 | 将传输的控制权交给程序员。启用后,调用 |
4.3时钟分频函数作用
![]()
spi控制器是连接总线的,axi总线的时钟比较快,所以必须进行分频一般在25mhz以下
分频的参数

4.4FLASH读ID 指令

在手册中找到读ID指令

需要给FLASH写入一个字节的90h,然后在发送三个字节,
第五个字节返回厂商的字节以及他的设备id

根据自己的设备ID 读出来了进行核对
根据自己的器件手册把指令定义
根据是对读id进行6个字节的操作

设置6个字节的写操作

4.5传输指令
spi控制器以及发送的地址和接收的地址,没有接收就不接收等等spi是包括读和写的

byteconunt是写和读的总共字节数

在传输的时候,如果对设备进行读ID,需要先发送4个字节,然后接收的是2个字节,就是得从第5个字节和第6个字节进行接收

4.6片擦除指令
spi控制器最少发送两个字节

查看状态寄存器的busy最低位是不是0 0 表示擦除结束


int FlashChipErase(XSpiPs *SpiPs)
{u8 WriteBuf[2];u8 ReadBuf[2];WriteBuf[0] = ChipEraseCmd;WriteBuf[1] = 0;XSpiPs_PolledTransfer(SpiPs,WriteBuf,NULL,2); 不关心接收数据可以填写null空指针while(1) //判断擦除有没有结束查看状态寄存器的busy最低位是不是0 0 表示擦除结束{WriteBuf[0] = ReadStatus1Cmd;WriteBuf[1] = 0;XSpiPs_PolledTransfer(SpiPs,WriteBuf,ReadBuf,2);//if(ReadBuf[1]&0x01 == 0)break; //如果最低位是1与0x01就是看最低位 是否为10}return XST_SUCCESS;
}4.7FLASH写操作


因为是32位的地址只用了24为 在给地址的时候需要把第三位与ff相与 在左移16位放在最低位。以此类推
4.8FLASH读操作
int FlashRead(XSpiPs *SpiPs,u32 Address,u8 *ReadBuf,u32 Readlen)
{u8 *WriteBuf = (u8 *)malloc(Readlen * sizeof(u8));WriteBuf[0] = ReadDataCmd;WriteBuf[1] = (Address & 0xFF0000) >> 16;WriteBuf[2] = (Address & 0xFF00) >> 8;WriteBuf[3] = (Address & 0xFF);XSpiPs_PolledTransfer(SpiPs,WriteBuf,ReadBuf,Readlen);return XST_SUCCESS;5,完整代码
5.1 flash.driver.c
/** flash_drive.c* 功能:1.flash初始化 2.flash擦除 3.flash写入数据 4.flash读数据*/#include "flash_drive.h"int FlashInit(XSpiPs *SpiPs,u16 DeviceId)
{int Status;XSpiPs_Config *SpiConfig;u8 ManID;u8 DeviceID;SpiConfig = XSpiPs_LookupConfig(DeviceId);if (NULL == SpiConfig) {return XST_FAILURE;}Status = XSpiPs_CfgInitialize(SpiPs, SpiConfig,SpiConfig->BaseAddress);if (Status != XST_SUCCESS) {return XST_FAILURE;}XSpiPs_SetOptions(SpiPs, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION);XSpiPs_SetClkPrescaler(SpiPs,XSPIPS_CLK_PRESCALE_32);if(FlashID(SpiPs,&ManID,&DeviceID) == XST_FAILURE)return XST_FAILURE;return XST_SUCCESS;
}int FlashID(XSpiPs *SpiPs,u8 *ManID,u8 *DeviceID)
{u8 WriteBuf[6];u8 ReadBuf[6];WriteBuf[0] = ReadIDCmd;WriteBuf[1] = 0;WriteBuf[2] = 0;WriteBuf[3] = 0;if(XSpiPs_PolledTransfer(SpiPs,WriteBuf,ReadBuf,6) == XST_FAILURE)return XST_FAILURE;//ReadBuf[5] Man ID//ReadBuf[6] Device ID*ManID = ReadBuf[5];*DeviceID = ReadBuf[6];return XST_SUCCESS;
}int FlashChipErase(XSpiPs *SpiPs)
{u8 WriteBuf[2];u8 ReadBuf[2];WriteBuf[0] = ChipEraseCmd;WriteBuf[1] = 0;XSpiPs_PolledTransfer(SpiPs,WriteBuf,NULL,2);while(1){WriteBuf[0] = ReadStatus1Cmd;WriteBuf[1] = 0;XSpiPs_PolledTransfer(SpiPs,WriteBuf,ReadBuf,2);if(ReadBuf[1]&0x01 == 0)break;}return XST_SUCCESS;
}int FlashWrite(XSpiPs *SpiPs,u32 Address,u8 *Num,u32 Numlen)
{u8 WriteDataBuf[5];u32 i;for(i = 0 ;i < Numlen ;i++){WriteDataBuf[0] = WriteDataCmd;WriteDataBuf[1] = (Address & 0xFF0000) >> 16;WriteDataBuf[2] = (Address & 0xFF00) >> 8;WriteDataBuf[3] = (Address & 0xFF);WriteDataBuf[4] = *(Num + i);XSpiPs_PolledTransfer(SpiPs,WriteDataBuf,NULL,5);}return XST_SUCCESS;
}int FlashRead(XSpiPs *SpiPs,u32 Address,u8 *ReadBuf,u32 Readlen)
{u8 *WriteBuf = (u8 *)malloc(Readlen * sizeof(u8));WriteBuf[0] = ReadDataCmd;WriteBuf[1] = (Address & 0xFF0000) >> 16;WriteBuf[2] = (Address & 0xFF00) >> 8;WriteBuf[3] = (Address & 0xFF);XSpiPs_PolledTransfer(SpiPs,WriteBuf,ReadBuf,Readlen);return XST_SUCCESS;
}5.2 .h
/**/#ifndef SRC_FLASH_DRIVE_H_
#define SRC_FLASH_DRIVE_H_#include "xparameters.h" /* SDK generated parameters */
#include "xplatform_info.h"
#include "xspips.h" /* SPI device driver */
#include "xil_printf.h"
#include "xstatus.h"
#include "stdlib.h"#define WriteEnableCmd 0x16
#define WriteDisEnableCmd 0x04
#define ReadStatus1Cmd 0x05
#define ReadStatus2Cmd 0x35
#define WriteStatusCmd 0x01
#define WriteDataCmd 0x02
#define BlockErase64Cmd 0xd8
#define BlockErase32Cmd 0x52
#define SectorEraseCmd 0x20
#define ChipEraseCmd 0xc7
#define ReadIDCmd 0x90
#define ReadDataCmd 0x03#endif /* SRC_FLASH_DRIVE_H_ */
遗漏问题:
1.对spi的写id时候为什么写3个字节,读的字节是要跟写字节后面的
2.对于写操作怎么计算和读操作 u8 *WriteBuf = (u8 *)malloc(Readlen * sizeof(u8));
WriteDataBuf[1] = (Address & 0xFF0000) >> 16;
WriteDataBuf[2] = (Address & 0xFF00) >> 8;
WriteDataBuf[3] = (Address & 0xFF);
WriteDataBuf[4] = *(Num + i);
XSpiPs_PolledTransfer(SpiPs,WriteDataBuf,NULL,5);
这两个为什么放到的是最低位发送流程是什么





