/*! \file qspi_hdl.h \brief firmware functions to manage qspi \version 2024-04-15, V1.0.0 \author tbj */ #ifndef QSPI_HDL_H #define QSPI_HDL_H #include xqspips.h //QSPI器件ID #define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID //flash操作指令 #define READ_CMD 0x03 //读指令 #define WRITE_CMD 0x02 //写指令 #define READ_STATUS_CMD 0x05 //读状态指令 #define WRITE_STATUS_CMD 0x01 //写状态指令 #define WRITE_ENABLE_CMD 0x06 //写使能指令 #define WRITE_DISABLE_CMD 0x04 //禁止写使能指令 #define FAST_READ_CMD 0x0B //单通道读取 #define DUAL_READ_CMD 0x3B //双通道读取-半双工 #define QUAD_READ_CMD 0x6B //四通道读取-半双工 #define BULK_ERASE_CMD 0xC7 //擦除整片flash-全部写1 #define SEC_ERASE_CMD 0xD8 //擦除一个扇区-全部写1 #define READ_ID 0x9F //读取flash ID指令 //定义flash操作指令、地址、数据等在读写buffer中的位置 #define COMMAND_OFFSET 0 //flash操作指令在写buffer中的位置第0个字节 #define ADDRESS_1_OFFSET 1 //操作flash数据起始地址的高字节在写buffer中的位置第1个字节 #define ADDRESS_2_OFFSET 2 //操作flash数据起始地址的中字节在写buffer中的位置第2个字节 #define ADDRESS_3_OFFSET 3 //操作flash数据起始地址的低字节在写buffer中的位置第3个字节 #define DATA_OFFSET 4 //操作flash的数据读取或写入的数据在写buffer中的位置第4个字节开始是纯数据区 #define DUMMY_OFFSET 4 //空闲字节的位置当使用快速、双线、四线模式读取数据时空闲字节占读buffer的第4个字节纯数据区从第5个字节开始 //定义各种操作所需字节长度 #define DUMMY_SIZE 1 //空闲字节大小占1个字节当使用快速、双线、四线模式读取数据时存在dummy byte #define RD_ID_SIZE 4 //读取flash ID占字节数其中第0个字节为读取ID指令号后3个字节为读取到的flash ID号 #define BULK_ERASE_SIZE 1 //清空整片flash指令占字节数只需要一个清空整片flash的指令号 #define SEC_ERASE_SIZE 4 //按扇区清空flash指令占字节数其中第0字节为按扇区清空flash的指令号后3个字节是起始清空的flash地址 #define OVERHEAD_SIZE 4 //定义读写buffer头部数据长度包括指令号1字节和操作地址3字节 //定义flash的参数数据 #define SECTOR_SIZE 0x10000 //定义单个扇区大小-64KB根据自己flash芯片手册确定 #define NUM_SECTORS 0x200 //定义扇区数量-256个根据自己flash芯片手册确定 #define NUM_PAGES 0x20000 //定义页数量-65536个根据自己flash芯片手册确定 #define PAGE_SIZE 256 //定义每页字节数-256个字节根据自己flash芯片手册确定 //定义实际读写操作的范围和数据大小 #define PAGE_COUNT 16 //定义需要操作读写的页数 #define TEST_ADDRESS 0x01FF0000//0x00055000 //定义读写操作的起始地址 #define UNIQUE_VALUE 0x05 //定义读写操作的起始值 #define MAX_DATA (PAGE_COUNT * PAGE_SIZE) //定义读写操作的最大数据量 //定义QSPI操作结构体对象 extern XQspiPs QspiInstance; #ifdef __cplusplus extern C { #endif //初始化QSPI控制器 int Qspi_Init(XQspiPs *QspiInstancePtr); //通过QSPI将数据写入flash中 void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command); //通过QSPI读取flash中的数据 void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command); //擦除flash void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount); //读取flash ID int FlashReadID(void); //使能四线模式 void FlashQuadEnable(XQspiPs *QspiPtr); #ifdef __cplusplus } #endif #endif /* QSPI_HDL_H *//*! \file qspi_hdl.c \brief firmware functions to manage qspi \version 2024-04-15, V1.0.0 \author tbj */ #include qspi_hdl.h XQspiPs QspiInstance; //QSPI读写flash使用的buffer内部使用 static u8 FlashReadBuffer[MAX_DATA DATA_OFFSET DUMMY_SIZE]; static u8 FlashWriteBuffer[PAGE_SIZE DATA_OFFSET]; /* 功能初始化QSPI控制器 * 入参1QSPI控制器实例化对象指针 */ int Qspi_Init(XQspiPs *QspiInstancePtr){ int Status; XQspiPs_Config *QspiConfig; //初始化QSPI控制器 QspiConfig XQspiPs_LookupConfig(QSPI_DEVICE_ID); if (QspiConfig NULL) { return XST_FAILURE; } Status XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig, QspiConfig-BaseAddress); if (Status ! XST_SUCCESS) { return XST_FAILURE; } //QSPI控制器自检保证初始化成功 Status XQspiPs_SelfTest(QspiInstancePtr); if (Status ! XST_SUCCESS) { return XST_FAILURE; } //清空读写操作的buffer memset(FlashWriteBuffer, 0x00, sizeof(FlashWriteBuffer)); memset(FlashReadBuffer, 0x00, sizeof(FlashReadBuffer)); //将flash配置为手动启动、手动片选模式将hold(reset)引脚配置为高电平hold低电平暂停收发数据高电平恢复收发数据 Status | XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION | XQSPIPS_FORCE_SSELECT_OPTION | XQSPIPS_HOLD_B_DRIVE_OPTION); //设置QSPI预分频系数 Status | XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8); //将片选信号置为有效 Status | XQspiPs_SetSlaveSelect(QspiInstancePtr); //读取flash ID Status | FlashReadID(); //使能QSPI Quad模式 FlashQuadEnable(QspiInstancePtr); return Status; } /** * brief 通过QSPI将数据写入flash中 * param QSPI结构体指针 * param 要写入数据的起始地址 * param 要写入数据的数量-按字节 * param 写数据指令 * return 无 * note 无 * */ void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command) { u8 WriteEnableCmd { WRITE_ENABLE_CMD }; u8 ReadStatusCmd[] { READ_STATUS_CMD, 0 }; /* must send 2 bytes */ u8 FlashStatus[2]; //发送写使能指令 XQspiPs_PolledTransfer(QspiPtr, WriteEnableCmd, NULL, sizeof(WriteEnableCmd)); //将写操作指令以及数据地址写入对应待发送buffer的前4个字节第五个字节开始才是写入的数据 FlashWriteBuffer[COMMAND_OFFSET] Command; FlashWriteBuffer[ADDRESS_1_OFFSET] (u8)((Address 0xFF0000) 16); FlashWriteBuffer[ADDRESS_2_OFFSET] (u8)((Address 0xFF00) 8); FlashWriteBuffer[ADDRESS_3_OFFSET] (u8)(Address 0xFF); //将要写入的纯数据写入到flash写操作buffer中flash写操作buffer包括写指令、地址、数据等内容 memcpy(FlashWriteBuffer 4, WriteBuf, ByteCount); //将写指令、写起始地址信息、写入数据内容写到flash中 XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL, ByteCount OVERHEAD_SIZE); //等待数据写入完毕 while (1) { //通过发送读状态指令获取是否已经将数据写完如果为可读状态则数据写入完毕 XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus, sizeof(ReadStatusCmd)); //如果读取的状态值是0xff则证明数据还未写完 FlashStatus[1] | FlashStatus[0]; if ((FlashStatus[1] 0x01) 0) { break; } } } /** * brief 通过QSPI读取flash中的数据 * param QSPI结构体指针 * param 读取数据的起始地址 * param 读取数据的数量-按字节 * param 读取数据指令-普通读取、快速、双线、四线读取等 * return 无 * note 无 * */ void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command) { //将读指令和读数据首地址写入到要发送的buffer中它们占4个字节 FlashWriteBuffer[COMMAND_OFFSET] Command; FlashWriteBuffer[ADDRESS_1_OFFSET] (u8)((Address 0xFF0000) 16); FlashWriteBuffer[ADDRESS_2_OFFSET] (u8)((Address 0xFF00) 8); FlashWriteBuffer[ADDRESS_3_OFFSET] (u8)(Address 0xFF); //如果是快速读取、双线读取和四线读取则需要增加一个空闲字节的长度DUMMY_SIZE if ((Command FAST_READ_CMD) || (Command DUAL_READ_CMD) || (Command QUAD_READ_CMD)) { ByteCount DUMMY_SIZE; } //将读指令和读地址发送到通过QSPI发送到flash等待数据读取至ReadBuffer中 XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, FlashReadBuffer, ByteCount OVERHEAD_SIZE); //如果是快速读取、双线读取和四线读取则需要增加一个虚拟字节的长度DUMMY_SIZE if ((Command FAST_READ_CMD) || (Command DUAL_READ_CMD) || (Command QUAD_READ_CMD)) { //非普通模式读取有空闲字节从第5个字节开始是纯数据 memcpy(ReadBuf, FlashReadBuffer 5, ByteCount - 1); }else{ //普通模式读取无空闲字节从第4个字节开始是纯数据 memcpy(ReadBuf, FlashReadBuffer 4, ByteCount); } } /** * brief 擦除flash * param QSPI结构体指针 * param 擦除的起始地址 * param 擦除数据的数量-按字节 * return 无 * note 无 * */ void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount) { u8 WriteEnableCmd { WRITE_ENABLE_CMD }; u8 ReadStatusCmd[] { READ_STATUS_CMD, 0 }; /* must send 2 bytes */ u8 FlashStatus[2]; int Sector; //如果是擦除整片flash则使用整片擦除指令chip erase if (ByteCount (NUM_SECTORS * SECTOR_SIZE)) { //发送写使能指令 XQspiPs_PolledTransfer(QspiPtr, WriteEnableCmd, NULL, sizeof(WriteEnableCmd)); //将整片擦除指令写入到发送buffer的首个字节的位置 FlashWriteBuffer[COMMAND_OFFSET] BULK_ERASE_CMD; //将整片擦除指令发送到flash XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL, BULK_ERASE_SIZE); //等待擦除完成 while (1) { //通过发送读状态指令获取是否已经将数据写完如果为可读状态则数据写入完毕 XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus, sizeof(ReadStatusCmd)); //如果读取的状态值是0xff则证明数据还未写完 FlashStatus[1] | FlashStatus[0]; if ((FlashStatus[1] 0x01) 0) { break; } } return; } //如果是部分擦除则使用扇区sector擦除的指令进行擦除操作 for (Sector 0; Sector ((ByteCount / SECTOR_SIZE) 1); Sector) { //发送写使能指令 XQspiPs_PolledTransfer(QspiPtr, WriteEnableCmd, NULL, sizeof(WriteEnableCmd)); //将扇区擦除指令以及开始擦除首地址写入到发送buffer的前四个字节 FlashWriteBuffer[COMMAND_OFFSET] SEC_ERASE_CMD; FlashWriteBuffer[ADDRESS_1_OFFSET] (u8)(Address 16); FlashWriteBuffer[ADDRESS_2_OFFSET] (u8)(Address 8); FlashWriteBuffer[ADDRESS_3_OFFSET] (u8)(Address 0xFF); //将扇区擦除指令以及开始擦除首地址发送到flash XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL, SEC_ERASE_SIZE); //等待擦除完成 while (1) { //通过发送读状态指令获取是否已经将数据写完如果为可读状态则数据写入完毕 XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus, sizeof(ReadStatusCmd)); //如果读取的状态值是0xff则证明数据还未写完 FlashStatus[1] | FlashStatus[0]; if ((FlashStatus[1] 0x01) 0) { break; } } Address SECTOR_SIZE; } } /** * brief 读取flash ID * param 无 * return 无 * note 无 * */ int FlashReadID(void) { int Status; //读取ID指令后三个字节是空闲字节填不填都行填什么也无所谓 FlashWriteBuffer[COMMAND_OFFSET] READ_ID; FlashWriteBuffer[ADDRESS_1_OFFSET] 0x23; FlashWriteBuffer[ADDRESS_2_OFFSET] 0x08; FlashWriteBuffer[ADDRESS_3_OFFSET] 0x09; //将读ID指令发送到flash Status XQspiPs_PolledTransfer(QspiInstance, FlashWriteBuffer, FlashReadBuffer, RD_ID_SIZE); if (Status ! XST_SUCCESS) { return XST_FAILURE; } xil_printf(FlashID0x%x 0x%x 0x%x\n\r, FlashReadBuffer[1], FlashReadBuffer[2], FlashReadBuffer[3]); return XST_SUCCESS; } /** * brief 使能四线模式 * param QSPI结构体指针 * return 无 * note 无 * */ void FlashQuadEnable(XQspiPs *QspiPtr) { u8 WriteEnableCmd {WRITE_ENABLE_CMD}; u8 ReadStatusCmd[] {READ_STATUS_CMD, 0}; u8 QuadEnableCmd[] {WRITE_STATUS_CMD, 0}; u8 FlashStatus[2]; //判断读取的flash ID是否正确不加这个判断也行 if (FlashReadBuffer[1] 0xEF) { //获取读状态 XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus, sizeof(ReadStatusCmd)); QuadEnableCmd[1] FlashStatus[1] | 1 6; //发送写使能指令 XQspiPs_PolledTransfer(QspiPtr, WriteEnableCmd, NULL, sizeof(WriteEnableCmd)); //发送Quad配置指令 XQspiPs_PolledTransfer(QspiPtr, QuadEnableCmd, NULL, sizeof(QuadEnableCmd)); while (1) { //获取读状态等待指令写入完毕 XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus, sizeof(ReadStatusCmd)); /* * 第6it置1第0bit置0则Quad模式设置成功、且设备状态准备就绪 */ if ((FlashStatus[0] 0x40) (FlashStatus[1] 0x40)) { break; } } } }/****************************************************************************** * * Copyright (C) 2009 - 2014 Xilinx, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * Use of the Software is limited solely to applications: * (a) running on a Xilinx device, or * (b) that interact with a Xilinx device through a bus or interconnect. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the Xilinx shall not be used * in advertising or otherwise to promote the sale, use or other dealings in * this Software without prior written authorization from Xilinx. * ******************************************************************************/ /* * helloworld.c: simple test application * * This application configures UART 16550 to baud rate 9600. * PS7 UART (Zynq) is not initialized by this application, since * bootrom/bsp configures it to baud rate 115200 * * ------------------------------------------------ * | UART TYPE BAUD RATE | * ------------------------------------------------ * uartns550 9600 * uartlite Configurable only in HW design * ps7_uart 115200 (configured by bootrom/bsp) */ #include stdio.h #include platform.h #include xil_printf.h #include xstatus.h #include qspi_hdl.h //读写buffer数据长度 #define test_buf_len 255 //QSPI读写flash测试 void QSPI_Flash_Opt(){ u8 nRet XST_SUCCESS; u8 write_buf[test_buf_len] {0}; u8 read_buf[test_buf_len] {0}; //初始化QSPI控制器 Qspi_Init(QspiInstance); //write buffer填写数据 for(int i 0; i test_buf_len; i){ write_buf[i] i 1; } //清除要写入的flash区域 FlashErase(QspiInstance, TEST_ADDRESS, test_buf_len); //将write buffer数据写入flash中 FlashWrite(QspiInstance, TEST_ADDRESS, write_buf, test_buf_len, WRITE_CMD); //将写入flash中的数据再进行读取 // FlashRead(QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, READ_CMD); // FlashRead(QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, FAST_READ_CMD); // FlashRead(QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, DUAL_READ_CMD); FlashRead(QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, QUAD_READ_CMD); //打印写buffer区数据 for(int i 0; i test_buf_len; i){ printf(%d , write_buf[i]); if(i test_buf_len - 1) printf(\n); } //打印读buffer区数据 for(int i 0; i test_buf_len; i){ printf(%d , read_buf[i]); if(i test_buf_len - 1) printf(\n); } //对比写入和读出的数据是否一致 for(int i 0; i test_buf_len; i){ if(read_buf[i] ! write_buf[i]){ nRet XST_FAILURE; } } if(nRet XST_SUCCESS){ printf(QSPI Operate flash successful!\n); }else{ printf(QSPI Operate flash failed!\n); } } int main() { init_platform(); print(Hello World\n\r); print(Successfully ran Hello World application); cleanup_platform(); QSPI_Flash_Opt(); return 0; }把代码放里面了这里有报错https://blog.csdn.net/qq_38584212/article/details/137965232本人参考链接这文章老师可以根据这文章详细讲讲操作flash的流程吗代码讲讲注释哈对应底层如何运行的谢谢啦老师别着急这个multiple definition of QspiInstance的报错是C语言里非常经典的问题在Vitis里也很常见。 先解决报错多个定义 (Multiple Definition)问题根源在C语言中变量的“定义”分配内存应该放在.c文件里而.h文件里只应该做“声明”告诉编译器有这么一个变量但不分配内存。你的qspi_hdl.h第58行直接写了XQspiPs QspiInstance;这既是声明也是定义。当main.c和qspi_hdl.c都包含这个头文件时编译器就看到了两次定义于是报错。解决方法修改头文件qspi_hdl.h把第58行改成extern XQspiPs QspiInstance;只做声明。修改源文件qspi_hdl.c在文件开头包含头文件之后加上一行XQspiPs QspiInstance;这里才是真正的定义。这样修改后整个工程里只有qspi_hdl.c真正定义了QspiInstance其他文件通过extern引用它就不会再冲突了。️ QSPI Flash 操作全流程以代码为例整个过程就是软件通过Zynq的QSPI控制器严格按照Flash芯片的“命令手册”与它对话。第一步初始化 (Qspi_Init)XQspiPs_LookupConfigXQspiPs_CfgInitialize查找并初始化QSPI控制器的硬件配置基地址、设备ID等。XQspiPs_SetOptions设置手动模式这是为了精确控制片选和传输时序。FlashReadID读取Flash的ID0x9F指令。这一步很重要它确认了芯片通信正常也为后面判断芯片厂商如Winbond的ID是0xEF提供了依据。FlashQuadEnable使能四线Quad模式。默认是单线四线可以同时用4根数据线传输大幅提升读写速度。它通过读取状态寄存器把第6位QE位置1来实现。第二步擦除 (FlashErase)Flash存储器的特性是写数据前必须先擦除把对应区域全部置为1即0xFF。擦除指令代码支持两种模式。全片擦除发送0xC7指令。扇区擦除发送0xD8指令。代码会根据地址计算出需要擦除多少个扇区然后循环发送指令和地址。等待完成发送擦除指令后循环发送0x05读状态寄存器指令直到状态寄存器的第0位变为0表示不再“忙”擦除才真正结束。第三步写入 (FlashWrite)写使能 (0x06)在每次写/擦除操作前必须先发送0x06指令告诉Flash“我要开始写了”这是一个安全机制。页编程 (0x02)这是写入数据的核心指令。代码先构造一个缓冲区FlashWriteBuffer依次放入0x02指令 - 3字节地址 - 你要写入的数据。然后通过XQspiPs_PolledTransfer一次性发送出去。等待完成同样发送完数据后也要轮询状态寄存器等待Flash内部编程完成。第四步读取 (FlashRead)读指令代码支持多种读指令。0x03普通读。0x0B快速读。0x3B双线读。0x6B四线读。DUMMY字节使用快速/双线/四线读指令时在发送完指令和地址后需要额外插入1个DUMMY字节空闲字节给Flash内部电路一个准备数据的时间。数据对齐因为发送缓冲区前4个字节被指令和地址占用所以读取回来的数据要从FlashReadBuffer的第5个字节FlashReadBuffer 5开始才是真正的用户数据。 底层原理Zynq QSPI 控制器如何工作你代码里调用的XQspiPs_PolledTransfer函数底层其实是在操作Zynq芯片内部QSPI控制器的一系列寄存器配置寄存器设置时钟分频XQspiPs_SetClkPrescaler、传输模式等。命令寄存器把你的“指令字节”和“地址字节”写入特定寄存器控制器会自动将它们打包成符合SPI协议的波形从引脚发送出去。数据寄存器把要发送的“纯数据”写入FIFO或者从接收FIFO中读出收到的数据。状态寄存器控制器有“忙”标志位PolledTransfer函数就是通过不断查询这个标志来知道一次传输是否完成。整个过程就是CPU通过配置和控制QSPI控制器间接地操控着外部SPI Flash芯片的引脚电平。先按上面的方法把报错解决了代码能跑起来后再对着流程看代码会清晰很多。如果修改后还有别的报错随时发我修改后可以跑通谢谢啦上面代码已按照deepseek两处修改好可以使用参考链接https://blog.csdn.net/AlibabaApple/article/details/169951375?spm1001.2014.3001.5501 老师这篇文章是关于flash操作的作为c语言小白阅读代码初始化QSPI控制器QSPI控制器实例化对象指针什么是实例指针这些基本c语言的基本概念可以给小白扫盲一下嘛谢谢啦弄懂了这个基础然后把这份代码重新讲解一遍每一个函数是用来干嘛的对应到底层是如何运行的谢谢啦看到这一大串代码和“实例化”、“指针”这些词觉得懵是很正常的。我们先把这些C语言概念弄明白再回头看代码就会清晰很多。 第一步C语言概念扫盲“实例”与“指针”在嵌入式开发中这两个概念的核心逻辑是通过“指针”找到并操作“实例”。实例 (Instance)可以理解为根据设计图纸结构体定义建造出来的一个实体对象。在代码中XQspiPs是一个“结构体”类型它就像一个图纸规定了QSPI控制器需要哪些数据比如基地址、配置信息等。XQspiPs QspiInstance;这行代码就是根据图纸在内存中真的建造了一个对象并给它取名叫QspiInstance。这个对象里就保存了当前QSPI控制器运行时的所有状态信息。指针 (Pointer)可以理解为一张写着实体地址的“名片”。QspiInstance就是获取这个实体的地址而XQspiPs *QspiInstancePtr就是一个专门用来存放这张“名片”的变量。函数通过这张“名片”就能找到并操作那个实体。所以Qspi_Init(QspiInstance)的意思就是把QspiInstance这个实体的“名片”地址交给Qspi_Init函数函数拿到名片后就能找到这个对象并完成初始化了。️ 第二步QSPI Flash 操作全流程代码精讲现在来看整个Flash操作的全流程代码是如何一步步实现的。第一步初始化 (Qspi_Init)XQspiPs_LookupConfig根据设备ID找到QSPI控制器的硬件配置信息。XQspiPs_CfgInitialize用找到的配置信息初始化我们前面创建的QspiInstance实例。XQspiPs_SetOptions设置控制器为手动模式。这是为了精确控制片选和传输时序。FlashReadID发送0x9F指令读取Flash的ID。这一步能确认通信正常并识别芯片厂商如Winbond的ID是0xEF。FlashQuadEnable使能四线Quad模式。默认是单线四线可以同时用4根数据线传输大幅提升读写速度。它通过读取状态寄存器把第6位QE位置1来实现。第二步擦除 (FlashErase)Flash存储器的特性是写数据前必须先擦除把对应区域全部置为1即0xFF。全片擦除发送0xC7指令将整个芯片擦除。扇区擦除发送0xD8指令。代码会根据地址计算出需要擦除多少个扇区然后循环发送指令和地址。等待完成发送擦除指令后循环发送0x05读状态寄存器指令直到状态寄存器的第0位变为0表示不再“忙”擦除才真正结束。第三步写入 (FlashWrite)写使能 (0x06)在每次写/擦除操作前必须先发送0x06指令告诉Flash“我要开始写了”这是一个安全机制。页编程 (0x02)这是写入数据的核心指令。代码先构造一个缓冲区FlashWriteBuffer依次放入0x02指令 - 3字节地址 - 你要写入的数据。然后通过XQspiPs_PolledTransfer一次性发送出去。等待完成同样发送完数据后也要轮询状态寄存器等待Flash内部编程完成。第四步读取 (FlashRead)读指令代码支持多种读指令包括普通读0x03、快速读0x0B、双线读0x3B和四线读0x6B。DUMMY字节使用快速/双线/四线读指令时在发送完指令和地址后需要额外插入1个DUMMY字节空闲字节给Flash内部电路一个准备数据的时间。数据对齐因为发送缓冲区前4个字节被指令和地址占用所以读取回来的数据要从FlashReadBuffer的第5个字节FlashReadBuffer 5开始才是真正的用户数据。 第三步底层运行原理Zynq QSPI控制器你代码里调用的XQspiPs_PolledTransfer函数底层其实是在操作Zynq芯片内部QSPI控制器的一系列寄存器配置寄存器设置时钟分频XQspiPs_SetClkPrescaler、传输模式等。命令寄存器把你的“指令字节”和“地址字节”写入特定寄存器控制器会自动将它们打包成符合SPI协议的波形从引脚发送出去。数据寄存器FIFO把要发送的“纯数据”写入FIFO或者从接收FIFO中读出收到的数据。状态寄存器控制器有“忙”标志位PolledTransfer函数就是通过不断查询这个标志来知道一次传输是否完成。整个过程就是CPU通过配置和控制QSPI控制器间接地操控着外部SPI Flash芯片的引脚电平。 总结C语言概念实例是内存中的实体对象指针是访问它的地址凭证。代码流程初始化控制器 - 擦除目标扇区 - 写入数据 - 读取数据验证。底层原理CPU通过读写QSPI控制器的寄存器间接控制SPI总线上Flash芯片的引脚电平从而完成通信。//将写操作指令以及数据地址写入对应待发送buffer的前4个字节第五个字节开始才是写入的数据 FlashWriteBuffer[COMMAND_OFFSET] Command; FlashWriteBuffer[ADDRESS_1_OFFSET] (u8)((Address 0xFF0000) 16); FlashWriteBuffer[ADDRESS_2_OFFSET] (u8)((Address 0xFF00) 8); FlashWriteBuffer[ADDRESS_3_OFFSET] (u8)(Address 0xFF); 老师这几句可以给小白从语法含义各方面解释一下嘛谢谢啦这几行代码可以说是嵌入式C语言里处理“协议封包”最经典、最教科书级别的操作了。我们用寄快递来打个比方芯片CPU要发送一封信给Flash芯片信的内容Data比较长但必须先填写“快递单”前4个字节。这4行代码就是在手写这张快递单。我们先明确几个前提Address是一个32位4字节的变量比如0x01FF0000。FlashWriteBuffer是一个字节数组u8类型。因为 SPI 通信是一根线只能一个字节一个字节地发。所以哪怕地址是 32 位的我们也必须把它“拆”成 3 个 8 位的字节分开放。宏定义COMMAND_OFFSET是 0ADDRESS_1_OFFSET是 1以此类推。第一句填收件人指令cFlashWriteBuffer[COMMAND_OFFSET] Command;语法把Command比如写指令0x02放到数组的第0个位置。含义告诉 Flash 芯片“我要开始给你下命令了接下来我要执行Command这个动作”。这是快递单上的“业务类型”。第二句填地址高位比如 0x01cFlashWriteBuffer[ADDRESS_1_OFFSET] (u8)((Address 0xFF0000) 16);含义提取 32 位地址的最高位字节第 16 到 23 位放到数组的第 1 个位置。语法拆解洋葱剥皮法Address 0xFF0000位运算按位与。0xFF0000就像个“面具”只保留高位的 2 个字节红色的部分把低 16 位全变成 0。比如0x01FF0000 0xFF00000x01000000。 16位移运算。把结果向右移动 16 位把高位的0x01挪到最低 8 位。比如0x01000000 160x00000001。(u8)强制类型转换。把一个大整数硬生生截断成 8 位1个字节多余的高位丢掉。比如最终变成0x01。第三句填地址中位比如 0xFFcFlashWriteBuffer[ADDRESS_2_OFFSET] (u8)((Address 0xFF00) 8);含义提取 32 位地址的中间字节第 8 到 15 位放到数组的第 2 个位置。语法拆解Address 0xFF00保留中间两个字节。比如0x01FF0000 0xFF000x0000FF00。 8向右移动 8 位。比如0x0000FF00 80x000000FF。(u8)截断变成0xFF。第四句填地址低位比如 0x00cFlashWriteBuffer[ADDRESS_3_OFFSET] (u8)(Address 0xFF);含义提取 32 位地址的最低位字节第 0 到 7 位放到数组的第 3 个位置。语法拆解Address 0xFF0xFF就是0000 0000 0000 0000 0000 0000 1111 1111直接把最低 8 位保留高位全清零。不需要位移了因为已经在最低位了。(u8)截断。比如0x01FF0000 0xFF0x00000000最终变成0x00。 底层逻辑为什么非要拆成 3 个字节在 SPI 通信协议中比如操作 Flash硬件底层规定发完 1 个字节的指令后紧接着必须连续发 3 个字节的地址。这 3 个字节必须是高位在前大端模式。如果你直接发一个 32 位的Address比如0x01FF0000在内存里因为小端模式它存的是00 00 FF 01如果直接发出去就全乱套了。所以这段代码用“掩码 移位”的方法强行把一个大地址按顺序拆成了01,FF,00三个字节依次塞进数组。最后通过函数XQspiPs_PolledTransfer把数组发出去时物理引脚上就会精准地输出01-FF-00的波形。这就像把一个大包裹拆分成三个小快递按顺序发给对方。对方收到后再重新拼起来。这也是嵌入式底层开发必须掌握的基本功