本文主要是介绍STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
随言:
为后面的QSPI内存映射铺垫。
为芯片内执行 (XIP) 执行代码。
参考例程:
C:\Users\admin\STM32Cube\Repository\STM32Cube_FW_F7_V1.16.0\Projects\STM32F723E-Discovery\Examples\QSPI\QSPI_ReadWrite
源码链接:
H743_QSPI_W25Q64.rar-嵌入式文档类资源-CSDN下载
QSPI介绍:
下面内容摘自《STM32H7xx参考手册中文版.PDF》
QSPI控制Flash W25Q64芯片用间接模式。
指令阶段
这一阶段,将在 QUADSPI_CCR[7:0] 寄存器的 INSTRUCTION 字段中配置的一条 8 位指令 发送到 FLASH,指定待执行操作的类型。
尽管大多数 FLASH 从 IO0/SO 信号(单线 SPI 模式)只能以一次 1 位的方式接收指令,但指 令阶段可选择一次发送 2 位(在双线 SPI 模式中通过 IO0/IO1)或一次发送 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[9:8] 寄存器中的 IMODE[1:0] 字 段进行配置。
若 IMODE = 00,则跳过指令阶段,命令序列从地址阶段(如果存在)开始。
地址阶段
在地址阶段,将 1-4 字节发送到 FLASH,指示操作地址。待发送的地址字节数在
QUADSPI_CCR[13:12] 寄存器的 ADSIZE[1:0] 字段中进行配置。在间接模式和自动轮询模 式下,待发送的地址字节在 QUADSPI_AR 寄存器的 ADDRESS[31:0] 中指定。在内存映射 模式下,则通过 AXI(来自于 Cortex® 或 DMA)直接给出地址。
地址阶段可一次发送 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[11:10] 寄存器中的 ADMODE[1:0] 字段进行配置。
若 ADMODE = 00,则跳过地址阶段,命令序列直接进入下一阶段(如果存在)。
交替字节阶段
不常用,直接设置跳过。
ABMODE = 00,则跳过交替字节阶段,命令序列直接进入下一阶段(如果存在)。
空指令周期阶段
在空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高的时钟频 率时,给 FLASH 留出准备数据阶段的时间。这一阶段中给定的周期数在 QUADSPI_CCR[22:18] 寄存器的 DCYC[4:0] 字段中指定。在 SDR 和 DDR 模式下,持续时间被指定为一定个数的全 时钟周期。
若 DCYC 为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
空指令周期阶段的操作模式由 DMODE 确定。
为确保数据信号从输出模式转变为输入模式有足够的“周转”时间,使用双线和四线模式从FLASH 接收数据时,至少需要指定一个空指令周期。
数据阶段
在数据阶段,可从 FLASH 接收或向其发送任意数量的字节。
在间接模式和自动轮询模式下,待发送/接收的字节数在 QUADSPI_DLR 寄存器中指定。
在间接写入模式下,发送到 FLASH 的数据必须写入 QUADSPI_DR 寄存器。在间接读取模 式下,通过读取 QUADSPI_DR 寄存器获得从 FLASH 接收的数据。
在内存映射模式下,读取的数据通过 AXI 直接发送回 Cortex 或 DMA。
数据阶段可一次发送/接收 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段进行配置。
若 DMODE = 00,则跳过数据阶段,命令序列在拉高 nCS 时立即完成。这一配置仅可用于 仅间接写入模式。
STM32CubeIDE:
硬件:STM32H743IIT6 + W25Q64
1、配置时钟树,QSPI时钟是240MHz:
2、设置QSPI参数。
Clock Prescaler:时钟分频系数。内部计算默认+1,FCLK(qspi) = Fquadspi_ker_ck / (Clock Prescaler + 1)。
上面时钟树设置的QSPI时钟是240MHz,而W25Q64芯片最大支持133MHz,我想2分频成120MHz。由于内部计算默认加1了,故分频240MHz / (2 - 1) = 120MHz.
Fifo Threshold:FIFO 阈值级别。设置FIFO阈值为4,即如果 FIFO 中存在 5 个以上空闲字节可供读或写,则 FTF 置 1。
Sample Shifting:采样移位。默认情况下,QUADSPI 在 FLASH 驱动数据后过半个 CLK 周期开始采集数据。使用该
位,可考虑外部信号延迟,推迟数据采集。0:不发生移位 ;1:移位半个周期。
Flash Size:Flash芯片大小。FSIZE+1 是对 FLASH 寻址所需的地址位数。
注:在间接模式下,FLASH 容量最高可达4GB(使用 32 位进行寻址),但在内存映射模式下的可寻址空间限制为 256MB。
W25Q64大小是64Mbit = 8MByte = 8388608Byte 即 2的23次方。但是内部默认+1,故填写23 - 1.
Chip select high time:片选高电平时间,根据W25Q64芯片手册的tSHSL参数设置,内部默认+1个周期。
由于W25Q64的tSHSL最小是50ns,故1s / 120MHz = 8.3ns。那么Chip select high time >= (50 / 8.3) - 1 , 考虑走线等因素结果向上加1.
Clock Mode:nCS 为高电平(片选释放)时,CLK 必须保持电平。对应的就是SPI协议的模式 0/模式 3 (Mode 0 / mode 3)。
根据自己芯片选择,W25Q64兼容两种时序,故选择高低电平都可行。
Flash ID:FLASH 选择。挂载在Bank1就叫 Flash ID1,只有在双QSPI芯片时操作单闪存时才有用。
Dual-flash mode:双闪存模式。该位激活双闪存模式,同时使用两个外部 FLASH 以将吞吐量和容量扩大一倍。
最后生成代码。
编程:
编程前,先讲一下编程思路。
1、W25Q64芯片在上电默认通讯方式是SPI,模式1或者模式3.
2、即如果我们要使用QSPI方式通讯,那么一定要先用SPI方式设置芯片下一次通讯方式为QSPI.
对此ST的HAL库代码需要用户自己先配置QSPI通讯的方式,支持单线,双线和四线通讯。
首先编程
第一步先把HAL_QSPI_Command()配置指令、地址、交替字节、空指令周期数和数据,以几线方式通讯。对应的就是下图。
第二步才是收发数据HAL_QSPI_Transmit()或HAL_QSPI_Receive();
看看发送指令函数HAL_StatusTypeDef HAL_QSPI_Command()参数中的命令结构体QSPI_CommandTypeDef。
把结构体成员整理了一下顺序,和上图的四线模式时序一样的顺序。
其实就是上图时序几个步骤设置几个数据IO通讯或者跳过。
typedef struct
{uint32_t InstructionMode; // 指令模式,可设置使用 单线 双线 四线通讯,设0则跳过发送指令。uint32_t Instruction; // 指令值uint32_t AddressMode; // 地址模式,可设置使用 单线 双线 四线通讯,设0则跳过发送地址。uint32_t AddressSize; // 地址位数,可8bit 16bit 24bit 32bit uint32_t Address; // 地址值uint32_t AlternateByteMode; // 交替模式,常见设0则跳过即可。uint32_t AlternateBytesSize; // 交替字节位数,可8bit 16bit 24bit 32bit uint32_t AlternateBytes; // 交替字节 uint32_t DummyCycles; // 空指令周期uint32_t DataMode; // 数据模式,可设置使用 单线 双线 四线通讯,设0则跳过发送数据。uint32_t NbData; // 发送或接收数据长度uint32_t DdrMode; // DDR(双倍数据速率)模式,设0禁止。uint32_t DdrHoldHalfCycle; // DDR模式下数据输出延时uint32_t SIOOMode; // 在每个事务中发送指令或者只发一次指令
}QSPI_CommandTypeDef;
下一步就是调用QSPI收发数据函数
HAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout);
HAL_StatusTypeDef HAL_QSPI_Receive (QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout);
可以看到没有写接收的字节长度,因为长度是在HAL_QSPI_Command()参数中QSPI_CommandTypeDef的NbData已指定。
1、QSPI单线读取ID
QSPI单线即标准SPI协议,且W25Q64上电默认是标准的SPI协议,故无需额外设置。
// 以标准的SPI协议测试读ID指令
uint16_t W25Qx_SPI_ReadID(QSPI_HandleTypeDef *hqspi)
{uint8_t temp[2] = {0};QSPI_CommandTypeDef s_command;s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; // 指令单线传输s_command.Instruction = MANUFACTURER_DEVICE_ID; // 读ID指令0x90s_command.AddressMode = QSPI_ADDRESS_1_LINE; // 地址单线传输s_command.Address = 0; // 地址 0s_command.AddressSize = QSPI_ADDRESS_24_BITS; // 地址 24bits_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 跳过交替字节模式s_command.DummyCycles = 0; // 空指令周期数s_command.DataMode = QSPI_DATA_1_LINE; // 数据单线传输s_command.NbData = 2; // 数据接收长度s_command.DdrMode = QSPI_DDR_MODE_DISABLE; // 禁止DDR模式s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; // 禁止DDR延时s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 在每个事务中发送指令/* Send the command */if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK){return HAL_ERROR;}HAL_QSPI_Receive(hqspi, temp, 1000);printf("ID = %X %X\r\n", temp[0], temp[1]);return HAL_OK;
}
打印出“ID = EF 16”, W25Q64读取ID正确。
2、QSPI四线读取ID
1、先用QSPI单线(标准SPI)协议把W25Q64通讯设成四线QSPI。
先检查一下寄存器2的QE标志位是否为1,然后发送QPI 模式(0x38)指令启动QSPI。
我的芯片默认寄存器2的QE标志位是为1,故直接设置QPI.再读ID.
uint16_t W25Qx_QSPI_ReadID( QSPI_HandleTypeDef *hqspi )
{uint8_t temp[2] = {0};QSPI_CommandTypeDef s_command = {0};// SPI 启动QPI模式s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; // 指令QSPI单线传输s_command.Instruction = ENTER_QPI_MODE; // 指令,0x38使能QPI模式s_command.AddressMode = QSPI_ADDRESS_NONE; // 地址跳过s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 交替字节模式跳过s_command.DummyCycles = 0; // 空指令周期数s_command.DataMode = QSPI_DATA_NONE; // 数据模式跳过s_command.DdrMode = QSPI_DDR_MODE_DISABLE; // 禁止DDR模式s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; // 禁止DDR模式数据延时s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 在每个事务中发送指令/* Send the command */if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK){return HAL_ERROR;}// QSPI 读IDs_command.InstructionMode = QSPI_INSTRUCTION_4_LINES; // 指令QSPI四线传输s_command.Instruction = MANUFACTURER_DEVICE_ID; // 指令,0x90读IDs_command.AddressMode = QSPI_ADDRESS_4_LINES; // 地址QSPI四线传输s_command.Address = 0; // 地址 0s_command.AddressSize = QSPI_ADDRESS_24_BITS; // 地址 24bits_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 交替字节模式跳过s_command.DummyCycles = 0; // 空指令周期数s_command.DataMode = QSPI_DATA_4_LINES; // 数据QSPI四线传输s_command.NbData = 2; // 数据长度 1s_command.DdrMode = QSPI_DDR_MODE_DISABLE; // 禁止DDR模式s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; // 禁止DDR模式数据延时s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 在每个事务中发送指令/* Send the command */if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK){return HAL_ERROR;}HAL_QSPI_Receive(hqspi, temp, 1000);printf("QSPI, ID = %X %X\r\n", temp[0], temp[1]);return HAL_OK;
}
打印出“ QSPI, = EF 16 ”, W25Q64读取ID正确。
至于后面的后面的读写和标准SPI编程一样,微微改动即可,就不写了,直接上代码了。
3、状态轮询模式
状态轮询模式实际上就是读取某一W25Q64寄存器的值,进行and 或者 or 运算,与匹配值对比。
若一致运算结果与结果一致就退出,HAL_QSPI_AutoPolling()返回0表正确。
下面是官方例程代码。
typedef struct
{uint32_t Match; // 匹配值 uint32_t Mask; // 掩码 uint32_t Interval; // 两次读操作间CLK周期数。uint32_t StatusBytesSize; // 指定接收到的状态字节的大小uint32_t MatchMode; // 用于确定匹配项的方法,AND或者OR运算 uint32_t AutomaticStop; // 指定匹配后是否停止自动轮询
}QSPI_AutoPollingTypeDef;
/* Configure automatic polling mode to wait the QUADEN bit=1 and WIP bit=0 */s_config.Match = QSPI_SR_QUADEN;s_config.Mask = QSPI_SR_QUADEN|QSPI_SR_WIP;s_config.MatchMode = QSPI_MATCH_MODE_AND;s_config.StatusBytesSize = 1;s_config.Interval = 0x10;s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;s_command.Instruction = READ_STATUS_REG_CMD;s_command.DataMode = QSPI_DATA_4_LINES;if (HAL_QSPI_AutoPolling(hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return HAL_ERROR;}return HAL_OK;
过程大概是:
s_command写好读取的命令和协议,发送。
读取到的W25Q64某一寄存器的值,与Mask进行运算,运算方式由MatchMode指定,
若运算结果和Match一致则匹配成功。
4、看手册写驱动
看手册会发现有很多读指令。每个读指令用法都不一样,看红框圈出来的内容,
如(1-4-4)代表的是传输的三个步骤使用几线传输数据,指令用单线传输,地址和数据都是四线传输。
现在看读指令Fast Read Quad I/O (EBh),在地址传输完成后,要等3个字节,每个字节传输需要2个时钟周期,即6个时钟周期才能接收到数据。
下面是完整的驱动代码,我移植野火的QSPI驱动。
/** w25qxx_qspi.c** Created on: 2020年10月19日* Author: sudaroot*/
#include <stdio.h>
#include <w25qx_qspi.h>
#include "stm32h7xx_hal_qspi.h"extern QSPI_HandleTypeDef hqspi;static void W25Qx_QSPI_Delay(uint32_t ms);
static uint8_t W25Qx_QSPI_Addr_Mode_Init(void);
static uint8_t W25Qx_QSPI_ResetMemory (void);
static uint8_t W25Qx_QSPI_WriteEnable (void);
static uint8_t W25Qx_QSPI_AutoPollingMemReady (uint32_t Timeout);/*** @brief 初始化QSPI存储器* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_Init(void)
{QSPI_CommandTypeDef s_command;uint8_t value = W25QxJV_FSR_QE;/* QSPI存储器复位 */if (W25Qx_QSPI_ResetMemory() != QSPI_OK){return QSPI_NOT_SUPPORTED;}/* 使能写操作 */if (W25Qx_QSPI_WriteEnable() != QSPI_OK){return QSPI_ERROR;}/* 设置四路使能的状态寄存器,使能四通道IO2和IO3引脚 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = WRITE_STATUS_REG2_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.NbData = 1;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 传输数据 */if (HAL_QSPI_Transmit(&hqspi, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 自动轮询模式等待存储器就绪 */if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){return QSPI_ERROR;}/* 配置地址模式为 4 字节 , 非W25Q256直接跳过*/if (sFLASH_ID != 0XEF4019)return QSPI_OK;if (W25Qx_QSPI_Addr_Mode_Init() != QSPI_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 检查地址模式不是4字节地址,配置为4字节* @retval QSPI存储器状态*/
static uint8_t W25Qx_QSPI_Addr_Mode_Init(void)
{QSPI_CommandTypeDef s_command;uint8_t reg;/* 初始化读取状态寄存器命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_STATUS_REG3_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.NbData = 1;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 接收数据 */if (HAL_QSPI_Receive(&hqspi, ®, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 检查寄存器的值 */if ((reg & W25Q256FV_FSR_4ByteAddrMode) == 1) // 4字节模式{return QSPI_OK;}else // 3字节模式{/* 配置进入 4 字节地址模式命令 */s_command.Instruction = Enter_4Byte_Addr_Mode_CMD;s_command.DataMode = QSPI_DATA_NONE;/* 配置并发送命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 自动轮询模式等待存储器就绪 */if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){return QSPI_ERROR;}return QSPI_OK;}
}/*** @brief 从QSPI存储器中读取大量数据.* @param pData: 指向要读取的数据的指针* @param ReadAddr: 读取起始地址* @param Size: 要读取的数据大小* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_FastRead(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{QSPI_CommandTypeDef s_command;if(Size == 0) return QSPI_OK;/* 初始化读命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;s_command.AddressMode = QSPI_ADDRESS_4_LINES;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.Address = ReadAddr;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_4_LINES;s_command.DummyCycles = 0;s_command.NbData = Size;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 接收数据 */if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 从QSPI存储器中读取大量数据.* @note 改指令只能使用在50MHz一下,本配置下不好用* @param pData: 指向要读取的数据的指针* @param ReadAddr: 读取起始地址* @param Size: 要读取的数据大小* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{QSPI_CommandTypeDef s_command;/* 初始化读命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_CMD; //READ_CMD;s_command.AddressMode = QSPI_ADDRESS_1_LINE;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.Address = ReadAddr;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.NbData = Size;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 接收数据 */if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 将大量数据写入QSPI存储器* @param pData: 指向要写入数据的指针* @param WriteAddr: 写起始地址* @param Size: 要写入的数据大小* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{QSPI_CommandTypeDef s_command;uint32_t end_addr, current_size, current_addr;/* 计算写入地址和页面末尾之间的大小 */current_addr = 0;while (current_addr <= WriteAddr){current_addr += W25QxJV_PAGE_SIZE;}current_size = current_addr - WriteAddr;/* 检查数据的大小是否小于页面中的剩余位置 */if (current_size > Size){current_size = Size;}/* 初始化地址变量 */current_addr = WriteAddr;end_addr = WriteAddr + Size;/* 初始化程序命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = QUAD_INPUT_PAGE_PROG_CMD;s_command.AddressMode = QSPI_ADDRESS_1_LINE;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_4_LINES;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 逐页执行写入 */do{s_command.Address = current_addr;s_command.NbData = current_size;/* 启用写操作 */if (W25Qx_QSPI_WriteEnable() != QSPI_OK){return QSPI_ERROR;}/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 传输数据 */if (HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 配置自动轮询模式等待程序结束 */if (W25Qx_QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK){return QSPI_ERROR;}/* 更新下一页编程的地址和大小变量 */current_addr += current_size;pData += current_size;current_size =((current_addr + W25QxJV_PAGE_SIZE) > end_addr) ?(end_addr - current_addr) : W25QxJV_PAGE_SIZE;} while (current_addr < end_addr);return QSPI_OK;
}/*** @brief 擦除QSPI存储器的指定块* @param BlockAddress: 需要擦除的块地址* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_Erase_Block(uint32_t BlockAddress)
{QSPI_CommandTypeDef s_command;/* 初始化擦除命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = SECTOR_ERASE_CMD;s_command.AddressMode = QSPI_ADDRESS_1_LINE;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.Address = BlockAddress;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_NONE;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 启用写操作 */if (W25Qx_QSPI_WriteEnable() != QSPI_OK){return QSPI_ERROR;}/* 发送命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 配置自动轮询模式等待擦除结束 */if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 擦除整个QSPI存储器* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_Erase_Chip(void)
{QSPI_CommandTypeDef s_command;/* 初始化擦除命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = CHIP_ERASE_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_NONE;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 启用写操作 */if (W25Qx_QSPI_WriteEnable() != QSPI_OK){return QSPI_ERROR;}/* 发送命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 配置自动轮询模式等待擦除结束 */if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_BULK_ERASE_MAX_TIME) != QSPI_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 读取QSPI存储器的当前状态* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_GetStatus(void)
{QSPI_CommandTypeDef s_command;uint8_t reg;/* 初始化读取状态寄存器命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_STATUS_REG1_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.NbData = 1;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 配置命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 接收数据 */if (HAL_QSPI_Receive(&hqspi, ®, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 检查寄存器的值 */if ((reg & W25QxJV_FSR_BUSY) != 0){return QSPI_BUSY;}else{return QSPI_OK;}
}/*** @brief 返回QSPI存储器的配置* @param pInfo: 在配置结构上的指针* @retval QSPI存储器状态*/
uint8_t W25Qx_QSPI_GetInfo(QSPI_Info *pInfo)
{/* 配置存储器配置结构 */pInfo->FlashSize = W25QxJV_FLASH_SIZE;pInfo->EraseSectorSize = W25QxJV_SUBSECTOR_SIZE;pInfo->EraseSectorsNumber = (W25QxJV_FLASH_SIZE / W25QxJV_SUBSECTOR_SIZE);pInfo->ProgPageSize = W25QxJV_PAGE_SIZE;pInfo->ProgPagesNumber = (W25QxJV_FLASH_SIZE / W25QxJV_PAGE_SIZE);return QSPI_OK;
}/*** @brief 复位QSPI存储器。* @param hqspi: QSPI句柄* @retval 无*/
static uint8_t W25Qx_QSPI_ResetMemory()
{QSPI_CommandTypeDef s_command;/* 初始化复位使能命令 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = RESET_ENABLE_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_NONE;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* 发送命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return QSPI_ERROR;}/* 发送复位存储器命令 */s_command.Instruction = RESET_MEMORY_CMD;if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return QSPI_ERROR;}s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;s_command.Instruction = RESET_ENABLE_CMD;/* 发送命令 */if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return QSPI_ERROR;}/* 发送复位存储器命令 */s_command.Instruction = RESET_MEMORY_CMD;if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return QSPI_ERROR;}W25Qx_QSPI_Delay(1);/* 配置自动轮询模式等待存储器就绪 */if (W25Qx_QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 发送写入使能,等待它有效.* @param hqspi: QSPI句柄* @retval 无*/
static uint8_t W25Qx_QSPI_WriteEnable()
{QSPI_CommandTypeDef s_command;QSPI_AutoPollingTypeDef s_config;/* 启用写操作 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = WRITE_ENABLE_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_NONE;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return QSPI_ERROR;}/* 配置自动轮询模式等待写启用 */s_config.Match = W25QxJV_FSR_WREN;s_config.Mask = W25QxJV_FSR_WREN;s_config.MatchMode = QSPI_MATCH_MODE_AND;s_config.StatusBytesSize = 1;s_config.Interval = 0x10;s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;s_command.Instruction = READ_STATUS_REG1_CMD;s_command.DataMode = QSPI_DATA_1_LINE;s_command.NbData = 1;if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 读取存储器的SR并等待EOP* @param hqspi: QSPI句柄* @param Timeout 超时* @retval 无*/
static uint8_t W25Qx_QSPI_AutoPollingMemReady(uint32_t Timeout)
{QSPI_CommandTypeDef s_command;QSPI_AutoPollingTypeDef s_config;/* 配置自动轮询模式等待存储器准备就绪 */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_STATUS_REG1_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;s_config.Match = 0x00;s_config.Mask = W25QxJV_FSR_BUSY;s_config.MatchMode = QSPI_MATCH_MODE_AND;s_config.StatusBytesSize = 1;s_config.Interval = 0x10;s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, Timeout) != HAL_OK){return QSPI_ERROR;}return QSPI_OK;
}/*** @brief 读取FLASH ID* @param 无* @retval FLASH ID*/
uint32_t W25Qx_QSPI_FLASH_ReadID(void)
{QSPI_CommandTypeDef s_command;uint32_t Temp = 0;uint8_t pData[3];/* 读取JEDEC ID */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_JEDEC_ID_CMD;s_command.AddressMode = QSPI_ADDRESS_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DummyCycles = 0;s_command.NbData = 3;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){printf("QSPI_FLASH_ReadID ERROR!!!....\r\n");/* 用户可以在这里添加一些代码来处理这个错误 */while (1){}}if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){printf("QSPI_FLASH_ReadID ERROR!!!....\r\n");/* 用户可以在这里添加一些代码来处理这个错误 */while (1){}}Temp = (pData[2] | pData[1] << 8) | (pData[0] << 16);return Temp;
}/*** @brief 读取FLASH Device ID* @param 无* @retval FLASH Device ID*/
uint32_t W25Qx_QSPI_FLASH_ReadDeviceID(void)
{QSPI_CommandTypeDef s_command;uint32_t Temp = 0;uint8_t pData[3];/*##-2-读取设备ID测试 ###########################################*//* 读取制造/设备 ID */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = READ_ID_CMD;s_command.AddressMode = QSPI_ADDRESS_1_LINE;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.Address = 0x000000;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_1_LINE;s_command.DummyCycles = 0;s_command.NbData = 2;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){printf("QSPI_FLASH_ReadDeviceID ERROR!!!....\r\n");/* 用户可以在这里添加一些代码来处理这个错误 */while (1){}}if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){printf("QSPI_FLASH_ReadDeviceID ERROR!!! ....\r\n");/* 用户可以在这里添加一些代码来处理这个错误 */while (1){}}Temp = pData[1] | (pData[0] << 8);return Temp;
}static void W25Qx_QSPI_Delay(uint32_t ms)
{HAL_Delay(ms);
}
/** w25qxx_qspi.h** Created on: 2020年10月19日* Author: sudaroot*/#ifndef INC_W25QX_QSPI_H_
#define INC_W25QX_QSPI_H_#include "main.h"/* Private typedef -----------------------------------------------------------*/
//#define sFLASH_ID 0xEF3015 //W25X16
//#define sFLASH_ID 0xEF4015 //W25Q16
#define sFLASH_ID 0XEF4017 //W25Q64
//#define sFLASH_ID 0XEF4018 //W25Q128
//#define sFLASH_ID 0XEF4019 //W25Q256/* QSPI Error codes */
#define QSPI_OK ((uint8_t)0x00)
#define QSPI_ERROR ((uint8_t)0x01)
#define QSPI_BUSY ((uint8_t)0x02)
#define QSPI_NOT_SUPPORTED ((uint8_t)0x04)
#define QSPI_SUSPENDED ((uint8_t)0x08)/* W25QxJV Micron memory */
/* Size of the flash */
#define QSPI_FLASH_SIZE 24 /* 地址总线宽度访问整个内存空间 */
#define QSPI_PAGE_SIZE 256/* QSPI Info */
typedef struct {uint32_t FlashSize; /*!< 闪存大小 */uint32_t EraseSectorSize; /*!< 擦除操作的扇区大小 */uint32_t EraseSectorsNumber; /*!< 擦除操作的扇区数 */uint32_t ProgPageSize; /*!< 编程操作的页面大小 */uint32_t ProgPagesNumber; /*!< 编程操作的页面数 */
} QSPI_Info;/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
/*** @brief W25QxJV配置*/
#define W25QxJV_FLASH_SIZE 0x800000 /* 64 MBits => 8MBytes */
#define W25QxJV_SECTOR_SIZE 0x10000 /* 128 sectors of 64KBytes */
#define W25QxJV_SUBSECTOR_SIZE 0x1000 /* 2048 subsectors of 4kBytes */
#define W25QxJV_PAGE_SIZE 0x100 /* 65536 pages of 256 bytes */#define W25QxJV_DUMMY_CYCLES_READ 4
#define W25QxJV_DUMMY_CYCLES_READ_QUAD 10#define W25QxJV_BULK_ERASE_MAX_TIME 250000
#define W25QxJV_SECTOR_ERASE_MAX_TIME 3000
#define W25QxJV_SUBSECTOR_ERASE_MAX_TIME 800/*** @brief W25QxJV 指令*/
/* 复位操作 */
#define RESET_ENABLE_CMD 0x66
#define RESET_MEMORY_CMD 0x99#define ENTER_QPI_MODE_CMD 0x38
#define EXIT_QPI_MODE_CMD 0xFF/* 识别操作 */
#define READ_ID_CMD 0x90
#define DUAL_READ_ID_CMD 0x92
#define QUAD_READ_ID_CMD 0x94
#define READ_JEDEC_ID_CMD 0x9F/* 读操作 */
#define READ_CMD 0x03
#define FAST_READ_CMD 0x0B
#define DUAL_OUT_FAST_READ_CMD 0x3B
#define DUAL_INOUT_FAST_READ_CMD 0xBB
#define QUAD_OUT_FAST_READ_CMD 0x6B
#define QUAD_INOUT_FAST_READ_CMD 0xEB/* 写操作 */
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04/* 寄存器操作 */
#define READ_STATUS_REG1_CMD 0x05
#define READ_STATUS_REG2_CMD 0x35
#define READ_STATUS_REG3_CMD 0x15#define WRITE_STATUS_REG1_CMD 0x01
#define WRITE_STATUS_REG2_CMD 0x31
#define WRITE_STATUS_REG3_CMD 0x11/* 编程操作 */
#define PAGE_PROG_CMD 0x02
#define QUAD_INPUT_PAGE_PROG_CMD 0x32
#define EXT_QUAD_IN_FAST_PROG_CMD 0x12
#define Enter_4Byte_Addr_Mode_CMD 0xB7/* 擦除操作 */
#define SECTOR_ERASE_CMD 0x20 //0xD8擦:64K 0x52擦:32K 0x20擦:4K
#define CHIP_ERASE_CMD 0xC7#define PROG_ERASE_RESUME_CMD 0x7A
#define PROG_ERASE_SUSPEND_CMD 0x75/* 状态寄存器标志 */
#define W25QxJV_FSR_BUSY ((uint8_t)0x01) /*!< busy */
#define W25QxJV_FSR_WREN ((uint8_t)0x02) /*!< write enable */
#define W25QxJV_FSR_QE ((uint8_t)0x02) /*!< quad enable */
#define W25Q256FV_FSR_4ByteAddrMode ((uint8_t)0x01) /*!< 4字节地址模式 *//*命令定义-结尾*******************************/uint8_t W25Qx_QSPI_Init(void);
uint8_t W25Qx_QSPI_Erase_Block(uint32_t BlockAddress);
uint8_t W25Qx_QSPI_FastRead(uint8_t* pData, uint32_t ReadAddr, uint32_t Size);
uint8_t W25Qx_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size);
uint8_t W25Qx_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size);uint32_t W25Qx_QSPI_FLASH_ReadDeviceID(void);
uint32_t W25Qx_QSPI_FLASH_ReadID(void);#endif /* INC_W25QX_QSPI_H_ */
进入内存映射模式:
手册中提醒了,QSPI FLASH寻址空间不能大于256MB,但是QSPI FLASH芯片可以大于256MB。
QSPI Flash映射到内存地址是0x9000 0000,芯片内部flash地址是0x0800 0000,别搞混了。HAL库有定义:
内存映射需要调用HAL的HAL_QSPI_MemoryMapped()函数,配置函数,调用。
只要你访问的地址是0x9000 0000,那么芯片自动去QSPI flash读取0地址的数据。
下面函数参数s_command配置的是使用W25Q64 qspi 的读指令,这个指令一般会使用读取速度最快的,所以是四线读取指令。
而s_mem_mapped_cfg参数,设置是用CS超时值和超时是否释放CS片选。
/*** @brief Configure the QSPI in memory-mapped mode* @retval QSPI memory status*/
static uint32_t QSPI_EnableMemoryMappedMode(QSPI_HandleTypeDef *QSPIHandle)
{QSPI_CommandTypeDef s_command;QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;/* Configure the command for the read instruction */s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;s_command.AddressMode = QSPI_ADDRESS_4_LINES;s_command.AddressSize = QSPI_ADDRESS_24_BITS;s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;s_command.DataMode = QSPI_DATA_4_LINES;s_command.DummyCycles = 6;s_command.DdrMode = QSPI_DDR_MODE_DISABLE;s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_HALF_CLK_DELAY;s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;/* Configure the memory mapped mode */s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;s_mem_mapped_cfg.TimeOutPeriod = 0;return HAL_QSPI_MemoryMapped(QSPIHandle, &s_command, &s_mem_mapped_cfg);
}
测试程序。main函数
int main(void)
{uint8_t temp1[50] = "hello sudaroot\r\n";uint8_t temp2[50] = {0};uint8_t *temp3 = (uint8_t*)QSPI_BASE;SCB_EnableICache();SCB_EnableDCache();HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_QUADSPI_Init();MX_USART1_UART_Init();W25Qx_QSPI_Init();W25Qx_QSPI_Erase_Block(0);W25Qx_QSPI_Write(temp1, 0, 15);W25Qx_QSPI_FastRead(temp2, 0, 15);printf("1: %s\r\n", temp2);QSPI_EnableMemoryMappedMode(&hqspi);memset(temp2, 0, 50);memcpy(temp2, temp3, 15);printf("2: %s\r\n", temp2);while (1){}
}
现象:
退出内存映射模式:
ST官网有篇帖子说了怎么退出,不过我没测试。
https://community.st.com/s/question/0D50X00009XkaJuSAJ/stm32f7-qspi-exit-memory-mapped-mode
全篇完。
本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解,记录成长笔记。
笔记是以最简单的方式,只展示最核心的原理。
若有与 大神大大 见解有歧义,我绝对坚信 大神大大 见解是对的,我的是错的。
若无积分等无法下载源码,可加入QQ群657407920下载交流经验。感谢~!
这篇关于STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!