STM32 QSPI Flash 退出内存映射模式的方法

最近在折腾自己做的板子上的QSPI FLASH,型号为MT25QL256ABA1EW9,容量为32MB,使用四线SPI进行读写。
使用野火的代码野火-QSPI—读写串行FLASH,稍作修改就完成了Flash的读写测试。
但测试读取速度时发现,才只有9223KB/s,速度不理想。遂上网搜集资料,由硬汉大佬给出的代码,以及该帖子,得知可以开启内存映射模式,但其只能在读取状态下进行,开启了就写入不了了。
后来在ST论坛上看到这个帖子https://community.st.com/t5/stm32-mcus-products/stm32f7-qspi-exit-memory-mapped-mode/m-p/452589,当中提到清除繁忙位并重新初始化Flash即可,如下图所示。
在这里插入图片描述
因此编写代码,给读取操作增加了内存映射标志位判定。

完整的flash_qspi.c如下

#include "flash_qspi.h"
#include "quadspi.h"
#include <stdio.h>
#include <string.h>

uint8_t memory_map_mode_flag = 0; // 内存映射标志位,用于加速读取

/*
*********************************************************************************************************
*	函 数 名: sfTestReadSpeed
*	功能说明: 测试串行Flash读速度。读取整个串行Flash的数据,最后打印结果
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
ALIGN_32BYTES(uint8_t SpeedTestbuf[16 * 1024]); /* 仅用于读速度测试目的 */
void sfTestReadSpeed(void)
{
    uint32_t i;
    int32_t iTime1, iTime2;
    uint32_t uiAddr;

    iTime1 = HAL_GetTick(); /* 记下开始时间 */
    uiAddr = 0;
    for (i = 0; i < FLASH_SIZE_32MB / (16 * 1024); i++, uiAddr += 16 * 1024)
    {
        BSP_QSPI_Read(SpeedTestbuf, uiAddr, 16 * 1024);
    }
    iTime2 = HAL_GetTick(); /* 记下结束时间 */

    /* 打印读速度 */
    printf("数据长度: %d字节, 读耗时: %dms, 读速度: %d KB/s\r\n", FLASH_SIZE_32MB, iTime2 - iTime1, FLASH_SIZE_32MB / (iTime2 - iTime1));
}

/*
*********************************************************************************************************
*	函 数 名: sfWriteTest
*	功能说明: 写串行Flash测试
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
#define TEST_SIZE 8 * 1024 //  8KB
#define TEST_ADDR 0x00
ALIGN_32BYTES(uint8_t buf[TEST_SIZE]);
void sfWriteTest(void)
{
    uint32_t i;
    int32_t iTime1, iTime2;

    /* 填充测试缓冲区 */
    for (i = 0; i < TEST_SIZE; i++)
    {
        buf[i] = i;
    }

    BSP_QSPI_Erase_Block(TEST_ADDR);

    iTime1 = HAL_GetTick(); /* 记下开始时间 */
    for (i = 0; i < TEST_SIZE; i += QSPI_PAGE_SIZE)
    {
        if (BSP_QSPI_Write(buf, TEST_ADDR + i, QSPI_PAGE_SIZE) != QSPI_OK)
        {
            printf("写串行Flash出错!\r\n");
            return;
        }
    }

    iTime2 = HAL_GetTick(); /* 记下结束时间 */
    printf("写串行Flash成功!\r\n");

    /* 打印读速度 */
    printf("数据长度: %d字节, 写耗时: %dms, 写速度: %dB/s\r\n", TEST_SIZE, iTime2 - iTime1, (TEST_SIZE * 1000) / (iTime2 - iTime1));
}

/**
 * @brief  擦除QSPI存储器的指定块
 * @param  BlockAddress: 需要擦除的块地址
 * @retval QSPI存储器状态
 */
uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress)
{
    // 如果执行过内存映射读取操作,则在下次擦除前重新初始化QSPI FLASH
    if (memory_map_mode_flag == 1)
    {
        HAL_QSPI_Abort(&hqspi);
        BSP_QSPI_Init();
        memory_map_mode_flag = 0;
    }
    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_32_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 (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 (QSPI_AutoPollingMemReady(SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
    {
        return QSPI_ERROR;
    }
    return QSPI_OK;
}

static uint8_t QSPI_AutoPollingMemReady(uint32_t Timeout);
/**
 * @brief  复位QSPI存储器。
 * @param  hqspi: QSPI句柄
 * @retval 无
 */
static uint8_t 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;
    }

    /* 配置自动轮询模式等待存储器就绪 */
    if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
    {
        return QSPI_ERROR;
    }
    return QSPI_OK;
}

/**
 * @brief  读取存储器的SR并等待EOP
 * @param  hqspi: QSPI句柄
 * @param  Timeout 超时
 * @retval 无
 */
static uint8_t 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 = 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  发送写入使能,等待它有效.
 * @param  hqspi: QSPI句柄
 * @retval 无
 */
static uint8_t 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 = FSR_WREN;
    s_config.Mask = 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  设置QSPI存储器为4字节地址模式。
 * @param  无
 * @retval 无
 */
static uint8_t BSP_QSPI_4BYTE_ADDR_MOD(void)
{
    QSPI_CommandTypeDef s_command;
    /* 初始化复位使能命令 */
    s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction = ENTER_4_BYTE_ADDR_MODE_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;
    }

    /* 配置自动轮询模式等待存储器就绪 */
    if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
        QSPI_OK)
    {
        return QSPI_ERROR;
    }
    return QSPI_OK;
}

/**
 * @brief  从QSPI存储器中读取大量数据.
 * @param  pData: 指向要读取的数据的指针
 * @param  ReadAddr: 读取起始地址
 * @param  Size: 要读取的数据大小
 * @retval QSPI存储器状态
 */
uint8_t BSP_QSPI_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
    uint8_t *temp = (uint8_t *)(QSPI_BASE + ReadAddr);

    if (memory_map_mode_flag == 0) // 如果没有初始化内存映射 则先初始化
    {
        QSPI_CommandTypeDef s_command;
        /* 初始化读命令 */
        s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;
        s_command.AddressMode = QSPI_ADDRESS_4_LINES;
        s_command.AddressSize = QSPI_ADDRESS_32_BITS;
        s_command.Address = ReadAddr;
        s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
        s_command.DataMode = QSPI_DATA_4_LINES;
        s_command.DummyCycles = 10;
        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;

        QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;
        s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
        s_mem_mapped_cfg.TimeOutPeriod = 0;

        if (HAL_QSPI_MemoryMapped(&hqspi, &s_command, &s_mem_mapped_cfg) != HAL_OK)
        {
            printf("wrong!");
            return QSPI_ERROR;
        }
        memory_map_mode_flag = 1; // 设置内存映射标志位为1 后面要是有写入操作需要置为0并重新初始化QSPI
    }

    memcpy(pData, temp, Size);
    return QSPI_OK;
}

/**
 * @brief  将大量数据写入QSPI存储器
 * @param  pData: 指向要写入数据的指针
 * @param  WriteAddr: 写起始地址
 * @param  Size: 要写入的数据大小
 * @retval QSPI存储器状态
 */
uint8_t BSP_QSPI_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{
    // 如果执行过内存映射读取操作,则在下次写入前重新初始化QSPI FLASH
    if (memory_map_mode_flag == 1)
    {
        HAL_QSPI_Abort(&hqspi);
        BSP_QSPI_Init();
        memory_map_mode_flag = 0;
    }
    QSPI_CommandTypeDef s_command;
    uint32_t end_addr, current_size, current_addr;
    /* 计算写入地址和页面末尾之间的大小 */
    current_addr = 0;

    while (current_addr <= WriteAddr)
    {
        current_addr += QSPI_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_32_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 (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 (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
        {
            return QSPI_ERROR;
        }

        /* 更新下一页编程的地址和大小变量 */
        current_addr += current_size;
        pData += current_size;
        current_size = ((current_addr + QSPI_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : QSPI_PAGE_SIZE;
    } while (current_addr < end_addr);
    return QSPI_OK;
}

/**
 * @brief  初始化QSPI存储器
 * @retval QSPI存储器状态
 */
uint8_t BSP_QSPI_Init(void)
{
    QSPI_CommandTypeDef s_command;
    uint8_t value = FSR_QE;

    /* QSPI存储器复位 */
    if (QSPI_ResetMemory() != QSPI_OK)
    {
        return QSPI_NOT_SUPPORTED;
    }

    /* 设置QSPI存储器为4字节地址模式 */
    if (BSP_QSPI_4BYTE_ADDR_MOD() != QSPI_OK)
    {
        return QSPI_ERROR;
    }

    /* 使能写操作 */
    if (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 (QSPI_AutoPollingMemReady(SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
    {
        return QSPI_ERROR;
    }
    return QSPI_OK;
}

/**
 * @brief  读取FLASH ID
 * @param   无
 * @retval FLASH ID
 */
uint32_t 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_1_LINE;
    s_command.AddressSize = QSPI_ADDRESS_24_BITS;
    s_command.DataMode = QSPI_DATA_1_LINE;
    s_command.AddressMode = QSPI_ADDRESS_NONE;
    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("something wrong ....\r\n");
        /* 用户可以在这里添加一些代码来处理这个错误 */
        while (1)
        {
        }
    }
    if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        printf("something wrong ....\r\n");
        /* 用户可以在这里添加一些代码来处理这个错误 */
        while (1)
        {
        }
    }

    Temp = (pData[2] | pData[1] << 8) | (pData[0] << 16);

    return Temp;
}

完整的qspi_flash.h代码如下

#ifndef _FLASH_QSPI_H_
#define _FLASH_QSPI_H_
#include "main.h"

/* 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)

/* W25Q256JV 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;

/*命令定义-开头*******************************/
/**
 * @brief  Flash 配置
 */
#define FLASH_SIZE_32MB 0x2000000 /* 256 MBits => 32MBytes */
#define SECTOR_SIZE 0x10000       /* 256 sectors of 64KBytes */
#define SUBSECTOR_SIZE 0x1000     /* 4096 subsectors of 4kBytes */
#define PAGE_SIZE 0x100           /* 65536 pages of 256 bytes */

#define DUMMY_CYCLES_READ 4
#define DUMMY_CYCLES_READ_QUAD 10

#define BULK_ERASE_MAX_TIME 250000
#define SECTOR_ERASE_MAX_TIME 3000
#define SUBSECTOR_ERASE_MAX_TIME 800

/**
 * @brief  QSPI指令
 */
/* 复位操作 */
#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 SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xC7

#define PROG_ERASE_RESUME_CMD 0x7A
#define PROG_ERASE_SUSPEND_CMD 0x75

/* 4字节地址模式操作 */
#define ENTER_4_BYTE_ADDR_MODE_CMD 0xB7
#define EXIT_4_BYTE_ADDR_MODE_CMD 0xE9

/* 状态寄存器标志 */
#define FSR_BUSY ((uint8_t)0x01) /*!< busy */
#define FSR_WREN ((uint8_t)0x02) /*!< write enable */
#define FSR_QE ((uint8_t)0x02)   /*!< quad enable */

uint8_t BSP_QSPI_Init(void);
uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress);
uint8_t BSP_QSPI_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size);
uint8_t BSP_QSPI_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size);
static uint8_t BSP_QSPI_4BYTE_ADDR_MOD(void);

static uint8_t QSPI_ResetMemory(void);
static uint8_t QSPI_WriteEnable(void);
static uint8_t QSPI_AutoPollingMemReady(uint32_t Timeout);
uint32_t QSPI_FLASH_ReadID(void);
// 速度测试
void sfWriteTest(void);
void sfTestReadSpeed(void);
#endif

在主函数中调用

sfTestReadSpeed();
sfWriteTest();

在串口可得到输出,读取速度是未开启内存映射时的4倍多,写入前512KB区域测试,也一切正常
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值