赞
踩
二、QSPI相关寄存器介绍
三、QSPI相关HAL库驱动介绍
四、QSPI基本使用步骤
五、SPI FLASH简介
六、SPI FLASH基本使用步骤
七、编程实战
八、总结
SPI(Serial Peripheral Interface)根据其数据传输能力和硬件接口的不同,可以分为以下几个类别:
Standard SPI(标准SPI)
Dual SPI(双线SPI)
Quad SPI(四线SPI)
总的来说,SPI的这几个变种主要是为了提高数据传输效率,同时在一定程度上减小接口引脚数量,但也会带来一些限制,如在高数据速率下只能进行半双工通信。而在实际应用中,尤其是在与闪存设备交互时,SPI模式的选择需要根据系统的性能需求、空间占用以及功耗预算等因素综合考虑。
QSPI(Queued Serial Peripheral Interface)是SPI接口的一种高级扩展形式,由Motorola公司推出,后来在各类微控制器中广泛应用,特别是在处理高速数据传输和与外部高性能Quad-SPI存储器(如Flash)交互时表现出色。
QSPI的主要特点包括:
简单来说,QSPI是一种高度优化和强化的SPI接口,尤其适用于高效地驱动和管理高性能的SPI Flash存储器,提供更大的带宽和更低延迟的访问体验。通过先进的硬件支持和灵活的操作模式,QSPI极大地提升了与SPI Flash等外部设备的通信效率。
QSPI(Quad Serial Peripheral Interface)在功能结构上相较于标准SPI增加了更多的数据线,用于实现更高的数据传输速率。在双闪存模式禁止的情况下,其功能结构主要包括:
时钟线CLK:
片选线BK1_nCS:
数据线BK1_IO0~IO3:
根据不同模式,这些引脚的功能有所不同:
在双闪存模式禁止时,这意味着QSPI控制器不会同时处理两个独立的闪存设备,而是专注于单一的外部闪存设备。通过调整QSPI控制器的配置寄存器,可以灵活地在这几种模式间切换,以适应不同的应用场景和性能需求。
在STM32H7系列微控制器中,QSPI接口的时钟输入和输出信号说明如下:
时钟输入:
QSPI输出信号:
请注意,以上信号名称并非官方文档中STM32H7系列的标准命名,但它们代表了QSPI接口常见的时钟输入和输出信号类型。在实际使用时,请参考STM32H7系列的官方技术参考手册(TRM)以获得准确的信号名称和功能描述。
在STM32F7和STM32H7系列微控制器中,QSPI时钟源的选择可以根据系统设计需求进行配置。
STM32F7系列:
STM32H7系列(例如STM32H7 MINI PRO H750开发板):
在实际项目开发中,你需要根据微控制器的规格书、参考手册和应用需求来配置QSPI的时钟源。通过查阅STM32CubeMX工具或者直接编程配置RCC寄存器,可以设置合适的时钟源。
间接模式是STM32H7系列微控制器QSPI接口中的一种操作模式,主要用于执行读写和擦除操作。在此模式下,QSPI与外部SPI Flash之间的数据传输通过内部FIFO(First-In-First-Out)缓冲区来进行。
间接写入模式:
间接读取模式:
数据阶段的控制:
读/写字节数的设置:
启动传输:
传输完成的标志:
内存映射模式是STM32H7系列微控制器中QSPI接口的另一种工作模式,主要适用于以下场景:
读取操作:
扩展内部存储器:
执行代码(XIP,Execute-In-Place):
在STM32H7系列中,内存映射模式下,Quad-SPI接口可以管理的最大地址范围是从0x9000 0000到0x9FFF FFFF,总计256MB的内存空间。这意味着在这个地址范围内,处理器可以直接读取外部Quad-SPI Flash的内容,实现无缝的数据访问和代码执行。在实际应用中,需要根据具体的Quad-SPI Flash容量和实际需求来配置映射的地址区间。
在STM32H7系列微控制器的QSPI接口中,无论是间接模式还是内存映射模式,对SPI Flash进行数据读写操作时,都需要构建和发送一个命令序列。这个命令序列通常由五个可配置阶段构成:
指令阶段:
地址阶段:
交替字节阶段(Optional):
空周期阶段(Dummy Cycle Phase,Optional):
数据阶段:
在配置命令序列时,开发人员可以灵活地控制每个阶段是否启动、每个阶段的长度以及数据是在单线、双线还是四线模式下传输。这些配置通常通过QSPI相关的控制寄存器(如QUADSPI_CR、QUADSPI_DCR、QUADSPI_AR、QUADSPI_ABR、QUADSPI_DDRAR等)来完成。在进行数据读写操作时,命令序列的具体构成和配置需遵循SPI Flash器件的数据手册。
在STM32H7系列微控制器中,配置QSPI与外部SPI Flash通信时,需要根据所使用的FLASH芯片型号在QUADSPI_DCR(QuadSPI Device Configuration Register)寄存器中设置相应的参数:
外部存储器大小设置:
时钟模式设置:
片选高电平时间设置:
在内存映射模式下,虽然QSPI可以直接访问高达256MB的外部存储器空间,但在间接模式下,如果使用32位寻址,最大可寻址空间可以达到4GB。在配置QSPI接口时,请务必查阅STM32H7系列微控制器的数据手册和所使用的SPI Flash的数据手册,以确保正确的参数设置和兼容性。
在STM32H7系列微控制器的QSPI接口中,支持多种类型的中断,这些中断在不同操作模式下有不同的触发条件:
超时中断:
状态匹配中断(状态轮询模式下):
FIFO达到阈值中断(间接模式下):
传输完成中断(间接模式下DLR指定字节数的数据已经发送完成):
传输错误中断(间接模式下地址越界或其他错误):
这些中断可以通过配置QSPI相关的中断使能寄存器和状态寄存器来管理和响应。在实际应用中,合理利用中断能够显著提高系统实时性和任务处理效率。
以下是STM32H7系列微控制器中QSPI接口相关寄存器的详细说明:
QUADSPI_CR(QuadSPI Control Register)
QUADSPI_DCR(QuadSPI Device Configuration Register)
QUADSPI_CCR(QuadSPI Communication Configuration Register)
QUADSPI_SR(QuadSPI Status Register)
QUADSPI_FCR(QuadSPI Flag Clear Register)
QUADSPI_DLR(QuadSPI Data Length Register)
QUADSPI_AR(QuadSPI Address Register)
QUADSPI_DR(QuadSPI Data Register)
在STM32 HAL库中,QSPI相关的驱动函数与寄存器的关系及功能描述如下:
__HAL_RCC_QSPI_CLK_ENABLE:
HAL_QSPI_Init:
HAL_QSPI_MspInit:
HAL_QSPI_Command:
HAL_QSPI_Receive 和 HAL_QSPI_Transmit:
QSPI相关的结构体:
QSPI_HandleTypeDef
:包含了QSPI外设的所有句柄信息,包括指向各种寄存器的指针、缓冲区指针、传输长度等。QSPI_InitTypeDef
:用于配置QSPI的基本工作参数,如时钟模式、数据线数等。QSPI_CommandTypeDef
:用于配置和描述QSPI的命令结构,包括命令字、地址、数据长度、交替字节等信息。
QSPI(Quad Serial Peripheral Interface)在STM32上的基本使用步骤可以总结为:
QSPI相关GPIO口配置
示例代码片段(伪代码):
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_...; // 设置对应QSPI引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 通常不用上下拉电阻,视具体情况而定
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 设置为高速模式
GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI; // 设置为QSPI功能
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); // 初始化GPIO
设置QSPI相关参数及时钟
QSPI_HandleTypeDef
结构体,设置QSPI的工作模式、数据线数、时钟速率等参数。HAL_RCCEx_GetPeriphCLKFreq()
获取QSPI时钟频率。HAL_QSPI_Init()
函数进行初始化,传入上述配置好的QSPI_HandleTypeDef
结构体。示例代码片段:
QSPI_HandleTypeDef hqspi;
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = ...; // 设置时钟预分频
hqspi.Init.FifoThreshold = ...; // 设置FIFO阈值
hqspi.Init.SampleShifting = ...; // 是否启用样本位移
...
HAL_QSPI_Init(&hqspi);
使能QSPI中断及设置MPU(Memory Protection Unit,内存保护单元)(可选)
编写QSPI基本通信接口
HAL_QSPI_Command(&hqspi, ..., ...)
HAL_QSPI_Receive(&hqspi, ..., ...)
HAL_QSPI_Transmit(&hqspi, ..., ...)
在实际应用中,还需要结合具体的应用场景和闪存芯片的数据手册进行详细配置和操作。例如,在进行读写操作前,可能需要先发送特定的读写命令,并根据需要擦除或写入扇区地址。在完成操作后,可能需要轮询状态寄存器或等待中断来确认操作完成。
W25Q128是一款16MB(16,777,216字节)容量的SPI(Serial Peripheral Interface)接口的NOR型闪存芯片,具备高速读写性能和出色的耐用性,支持多次重复擦写且在断电后仍能保持数据完整性,数据保存期限长达20年。
在基本操作方面,W25Q128支持以下操作:
擦除:W25Q128的最小擦除单位是一个扇区,也就是4KB(4096字节)。这意味着用户无法单独擦除某个字节或字节组,而必须按照扇区为单位进行擦除操作。
写入:写入操作通常以页为单位进行,每个扇区包含16个页,每个页大小为256字节。不过在写入之前,所写的扇区必须先被擦除。
读取:支持随机读取任意位置的数据,不受擦除或写入操作的限制。
W25Q128内部存储空间组织结构如下:
因此,在对W25Q128进行编程或应用开发时,应按照块、扇区和页的层级结构进行数据管理,确保符合器件的擦写和读取规则,以提高数据操作效率和延长闪存寿命。
W25Q128JV这款SPI闪存芯片支持多种SPI接口模式,以适应不同的应用需求和提高数据传输速率:
在高速模式下,W25Q128JV的最高时钟频率可以达到133MHz。在双线SPI模式下,由于数据线翻倍,理论上的等效传输速率将达到266MHz;在四线SPI模式下,四条数据线并行工作,理论上其等效传输速率将进一步提高至532MHz。这种高速特性使得W25Q128JV在处理大量数据和需要快速读取/写入的应用中表现优秀。不过,实际应用中,设备的实际工作时钟频率应根据微控制器的SPI接口性能和系统稳定性综合考虑,并在器件数据手册规定的范围内进行设置。
SPI FLASH(比如W25Q128)的基本操作指令集:
指令(HEX) | 名称 | 作用 |
---|---|---|
0x06 | 写使能(Write Enable) | 在执行写入数据或擦除操作之前,必须先发送此指令,以使SPI Flash进入可写状态。 |
0x05 | 读状态寄存器1(Read Status Register 1) | 用于检测SPI Flash是否处于空闲状态,是否准备好接受新的擦除或写入操作。 |
0x03 | 读数据(Read Data) | 常规读取SPI Flash中的数据,不是快速读取。 |
0xEB | 快速读取数据(Fast Read) | 用于更快地读取SPI Flash数据,可能需要配合地址和dummy cycles(空闲时钟周期)来提高数据传输速度。 |
0x32 | 页写(Page Program) | 用于向SPI Flash写入数据,每次操作最多写入256字节(一页)的数据。 |
0x20 | 扇区擦除(Sector Erase) | 对SPI Flash执行最小擦除单位操作,即擦除一个扇区(通常为4096字节)的数据。 |
关于状态寄存器(Status Register, SR)相关的额外命令:
指令(HEX) | 名称 | 作用 |
---|---|---|
0x35 | 读状态寄存器2(Read Status Register 2) | 用于读取SR2中的内容,其中包括QE(Quad Enable)位,用于启用四线SPI模式(Quad SPI)。 |
0x31 | 写状态寄存器2(Write Status Register 2) | 用于设置SR2中的QE位,使能四线SPI模式。 |
0x15 | 读状态寄存器3(Read Status Register 3) | 在某些SPI Flash中用于判断地址模式(例如4字节地址模式)是否被启用。 |
0x11 | 写状态寄存器3(Write Status Register 3) | 用于在上电时设置SPI Flash的工作模式,例如启用4字节地址模式。 |
0xB7 | 使能4字节地址模式(Enter 4-byte Address Mode) | 某些SPI Flash需要发送特定命令来切换到4字节地址模式,以便访问更大的存储空间。 |
请注意,不同的SPI Flash芯片可能存在略微不同的指令集和功能,具体操作请参阅各自的数据手册以获取准确信息。
SPI Flash W25Q128的基本使用步骤可以简化描述如下:
HAL_QSPI_Init()
对QSPI外设进行初始化。// 设置地址和数据长度
uint32_t read_address = ...;
uint32_t data_length = ...;
// 构造读取命令包
qspi_command_packet.command = 0xEB; // 快速读取指令
qspi_command_packet.address = read_address;
qspi_command_packet.dummy_cycles = ...; // 根据器件手册配置
qspi_command_packet.data_length = data_length;
// 发送读取命令并接收数据
HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet);
HAL_QSPI_Receive_IT(&hqspi, receive_buffer, data_length);
// 设置要擦除的扇区地址
uint32_t erase_address = ...;
// 构造擦除命令包
qspi_command_packet.command = 0x20; // 扇区擦除指令
qspi_command_packet.address = erase_address;
qspi_command_packet.data_length = 0; // 擦除操作无数据传输
// 发送擦除命令
HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet);
// 等待擦除完成(通常通过读取状态寄存器或中断实现)
// 确认擦除(如果需要)
// ...
// 设置写入地址和数据
uint32_t write_address = ...;
uint8_t* write_data = ...;
uint32_t write_length = ...;
// 构造写入命令包
qspi_command_packet.command = 0x32; // 页写入指令(四线模式)
qspi_command_packet.address = write_address;
qspi_command_packet.data_length = write_length;
qspi_command_packet.pData = write_data;
// 发送写入命令并发送数据
HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet);
// 等待写入完成(同样通过读取状态寄存器或中断实现)
SPI Flash驱动注意事项:
qspi.c
#include "./BSP/QSPI/qspi.h"
QSPI_HandleTypeDef g_qspi_handle; /* QSPI句柄 */
/**
* @brief 等待状态标志
* @param flag : 需要等待的标志位
* @param sta : 需要等待的状态
* @param wtime: 等待时间
* @retval 0, 等待成功; 1, 等待失败.
*/
uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime)
{
uint8_t flagsta = 0;
while (wtime)
{
flagsta = (QUADSPI->SR & flag) ? 1 : 0; /* 获取状态标志 */
if (flagsta == sta)
{
wtime--;
}
break;
}
if (wtime)
{
return 0;
}
else
{
return 1;
}
}
/**
* @brief 初始化QSPI接口
* @param 无
* @retval 0, 成功; 1, 失败.
*/
uint8_t qspi_init(void)
{
g_qspi_handle.Instance = QUADSPI; /* QSPI */
g_qspi_handle.Init.ClockPrescaler = 1; /* QSPI分频比,BY25Q128最大频率为108M,
所以此处应该为2,QSPI频率就为220/(1+1)=110MHZ
稍微有点超频,可以正常就好,不行就只能降低频率 */
g_qspi_handle.Init.FifoThreshold = 4; /* FIFO阈值为4个字节 */
g_qspi_handle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; /* 采样移位半个周期(DDR模式下,必须设置为0) */
g_qspi_handle.Init.FlashSize = 25 - 1; /* SPI FLASH大小,BY25Q128大小为32M字节,2^25,所以取权值25 - 1 = 24 */
g_qspi_handle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE; /* 片选高电平时间为3个时钟(9.1 * 3 = 27.3ns),即手册里面的tSHSL参数 */
g_qspi_handle.Init.ClockMode = QSPI_CLOCK_MODE_3; /* 模式3 */
g_qspi_handle.Init.FlashID = QSPI_FLASH_ID_1; /* 第一片flash */
g_qspi_handle.Init.DualFlash = QSPI_DUALFLASH_DISABLE; /* 禁止双闪存模式 */
if (HAL_QSPI_Init(&g_qspi_handle) == HAL_OK)
{
return 0; /* QSPI初始化成功 */
}
else
{
return 1;
}
}
/**
* @brief QSPI底层驱动,引脚配置,时钟使能
* @param hqspi:QSPI句柄
* @note 此函数会被HAL_QSPI_Init()调用
* @retval 0, 成功; 1, 失败.
*/
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_QSPI_CLK_ENABLE(); /* 使能QSPI时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE(); /* GPIOB时钟使能 */
__HAL_RCC_GPIOD_CLK_ENABLE(); /* GPIOD时钟使能 */
__HAL_RCC_GPIOE_CLK_ENABLE(); /* GPIOE时钟使能 */
gpio_init_struct.Pin = QSPI_BK1_NCS_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF10_QUADSPI; /* 复用为QSPI */
HAL_GPIO_Init(QSPI_BK1_NCS_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_NCS引脚 */
gpio_init_struct.Pin = QSPI_BK1_CLK_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF9_QUADSPI; /* 复用为QSPI */
HAL_GPIO_Init(QSPI_BK1_CLK_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_CLK引脚 */
gpio_init_struct.Pin = QSPI_BK1_IO0_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO0_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO0引脚 */
gpio_init_struct.Pin = QSPI_BK1_IO1_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO1_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO1引脚 */
gpio_init_struct.Pin = QSPI_BK1_IO2_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO2_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO2引脚 */
gpio_init_struct.Pin = QSPI_BK1_IO3_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO3_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO3引脚 */
}
/**
* @brief QSPI发送命令
* @param cmd : 要发送的指令
* @param addr: 发送到的目的地址
* @param mode: 模式,详细位定义如下:
* @arg mode[1:0]: 指令模式; 00,无指令; 01,单线传输指令; 10,双线传输指令; 11,四线传输指令.
* @arg mode[3:2]: 地址模式; 00,无地址; 01,单线传输地址; 10,双线传输地址; 11,四线传输地址.
* @arg mode[5:4]: 地址长度; 00,8位地址; 01,16位地址; 10,24位地址; 11,32位地址.
* @arg mode[7:6]: 数据模式; 00,无数据; 01,单线传输数据; 10,双线传输数据; 11,四线传输数据.
* @param dmcycle: 空指令周期数
* @retval 无
*/
void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle)
{
QSPI_CommandTypeDef qspi_command_init;
qspi_command_init.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次都发送指令 */
qspi_command_init.DdrMode = QSPI_DDR_MODE_DISABLE; /* 关闭DDR模式,使用SDR模式 */
qspi_command_init.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式下,用于设置延迟半个时钟周期再数据输出 */
/* 指令阶段 */
qspi_command_init.Instruction = cmd; /* 要发送的指令 */
/* 设置指令阶段需要几线模式 */
if (((mode >> 0) & 0x03) == 0)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_NONE; /* 不需要指令阶段 */
if (((mode >> 0) & 0x03) == 1)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 单线模式 */
if (((mode >> 0) & 0x03) == 2)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_2_LINES; /* 双线模式 */
if (((mode >> 0) & 0x03) == 3)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_4_LINES; /* 四线模式 */
/* 地址阶段 */
qspi_command_init.Address = addr; /* 要发送的地址 */
/* 设置地址长度 */
if (((mode >> 4) & 0x03) == 0)
qspi_command_init.AddressSize = QSPI_ADDRESS_8_BITS; /* 8位地址 */
if (((mode >> 4) & 0x03) == 1)
qspi_command_init.AddressSize = QSPI_ADDRESS_16_BITS; /* 16位地址 */
if (((mode >> 4) & 0x03) == 2)
qspi_command_init.AddressSize = QSPI_ADDRESS_24_BITS; /* 24位地址 */
if (((mode >> 4) & 0x03) == 3)
qspi_command_init.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
/* 设置地址阶段需要几线模式 */
if (((mode >> 2) & 0x03) == 0)
qspi_command_init.AddressMode = QSPI_ADDRESS_NONE; /* 不需要地址阶段 */
if (((mode >> 2) & 0x03) == 1)
qspi_command_init.AddressMode = QSPI_ADDRESS_1_LINE; /* 单线模式 */
if (((mode >> 2) & 0x03) == 2)
qspi_command_init.AddressMode = QSPI_ADDRESS_2_LINES; /* 双线模式 */
if (((mode >> 2) & 0x03) == 3)
qspi_command_init.AddressMode = QSPI_ADDRESS_4_LINES; /* 四线模式 */
/* 交替字节阶段 */
qspi_command_init.AlternateBytes = 0; /* 交替字节内容 */
qspi_command_init.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS; /* 交替字节长度 */
qspi_command_init.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 交替字节阶段需要几线模式 */
/* 空指令周期阶段 */
qspi_command_init.DummyCycles = dmcycle; /* 空指令周期数 */
/* 数据阶段 */
/* 不设置NbData成员,在qspi_transmit/receive函数中指定 */
// qspi_command_init.NbData = ; /* 数据长度 */
/* 设置数据阶段需要几线模式 */
if (((mode >> 6) & 0x03) == 0)
qspi_command_init.DataMode = QSPI_DATA_NONE; /* 不需要数据阶段 */
if (((mode >> 6) & 0x03) == 1)
qspi_command_init.DataMode = QSPI_DATA_1_LINE; /* 单线模式 */
if (((mode >> 6) & 0x03) == 2)
qspi_command_init.DataMode = QSPI_DATA_2_LINES; /* 双线模式 */
if (((mode >> 6) & 0x03) == 3)
qspi_command_init.DataMode = QSPI_DATA_4_LINES; /* 四线模式 */
HAL_QSPI_Command(&g_qspi_handle, &qspi_command_init, 5000); /* 用于向QSPI FLASH发送命令 */
}
/**
* @brief QSPI发送指定长度的数据
* @param buf : 发送数据缓冲区首地址
* @param datalen : 要传输的数据长度
* @retval 0, 成功; 其他, 错误代码
*/
uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen)
{
g_qspi_handle.Instance->DLR = datalen - 1; /* 直接使用寄存器赋值的方式设置要发送的数据字节数 */
if (HAL_QSPI_Transmit(&g_qspi_handle, buf, 5000) == HAL_OK)
{
return 0;
}
else
{
return 1;
}
}
/**
* @brief QSPI接收指定长度的数据
* @param buf : 接收数据缓冲区首地址
* @param datalen : 要传输的数据长度
* @retval 0, 成功; 其他, 错误代码.
*/
uint8_t qspi_receive(uint8_t *buf, uint32_t datalen)
{
g_qspi_handle.Instance->DLR = datalen - 1; /* 直接使用寄存器赋值的方式设置要发送的数据字节数 */
if (HAL_QSPI_Receive(&g_qspi_handle, buf, 5000) == HAL_OK)
{
return 0;
}
else
{
return 1;
}
}
qspi.h
#ifndef __QSPI_H
#define __QSPI_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* QSPI 相关 引脚 定义 */
#define QSPI_BK1_CLK_GPIO_PORT GPIOB
#define QSPI_BK1_CLK_GPIO_PIN GPIO_PIN_2
#define QSPI_BK1_CLK_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_CLK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口时钟使能 */
#define QSPI_BK1_NCS_GPIO_PORT GPIOB
#define QSPI_BK1_NCS_GPIO_PIN GPIO_PIN_6
#define QSPI_BK1_NCS_GPIO_AF GPIO_AF10_QUADSPI
#define QSPI_BK1_NCS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口时钟使能 */
#define QSPI_BK1_IO0_GPIO_PORT GPIOD
#define QSPI_BK1_IO0_GPIO_PIN GPIO_PIN_11
#define QSPI_BK1_IO0_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */
#define QSPI_BK1_IO1_GPIO_PORT GPIOD
#define QSPI_BK1_IO1_GPIO_PIN GPIO_PIN_12
#define QSPI_BK1_IO1_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */
#define QSPI_BK1_IO2_GPIO_PORT GPIOD
#define QSPI_BK1_IO2_GPIO_PIN GPIO_PIN_13
#define QSPI_BK1_IO2_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */
#define QSPI_BK1_IO3_GPIO_PORT GPIOE
#define QSPI_BK1_IO3_GPIO_PIN GPIO_PIN_2
#define QSPI_BK1_IO3_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE; }while(0) /* PE口时钟使能 */
/******************************************************************************************/
uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime); /* QSPI等待某个状态 */
uint8_t qspi_init(void); /* 初始化QSPI */
void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle); /* QSPI发送命令 */
uint8_t qspi_receive(uint8_t *buf, uint32_t datalen); /* QSPI接收数据 */
uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen); /* QSPI发送数据 */
#endif
norflash.c
#include "./BSP/QSPI/qspi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"
uint16_t g_norflash_type = W25Q128; /* 默认是W25Q128 */
/* SPI FLASH 地址位宽 */
volatile uint8_t g_norflash_addrw = 2; /* SPI FLASH地址位宽, 在norflash_read_id函数里面被修改
* 2, 表示24bit地址宽度
* 3, 表示32bit地址宽度
*/
/**
* @brief 初始化NOR FLASH
* @param 无
* @retval 无
*/
void norflash_init(void)
{
uint8_t temp;
qspi_init(); /* 初始化QSPI */
norflash_qspi_disable(); /* 退出QPI模式(避免芯片之前进入这个模式,导致下载失败) */
norflash_qe_enable(); /* 使能QE位 */
g_norflash_type = norflash_read_id(); /* 读取FLASH ID. */
if (g_norflash_type == W25Q256) /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */
{
temp = norflash_read_sr(3); /* 读取状态寄存器3,判断地址模式 */
if ((temp & 0X01) == 0) /* 如果不是4字节地址模式,则进入4字节地址模式 */
{
norflash_write_enable(); /* 写使能 */
temp |= 1 << 1; /* ADP=1, 上电4字节地址模式 */
norflash_write_sr(3, temp); /* 写SR3 */
norflash_write_enable(); /* 写使能 */
/* SPI, 使能4字节地址指令, 地址为0, 无数据_8位地址_无地址_单线传输指令, 无空指令周期 */
qspi_send_cmd(FLASH_Enable4ByteAddr, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
}
//printf("ID:%x\r\n", g_norflash_type);
}
/**
* @brief 等待空闲
* @param 无
* @retval 无
*/
static void norflash_wait_busy(void)
{
while ((norflash_read_sr(1) & 0x01) == 0x01); /* 等待BUSY位清空 */
}
/**
* @brief 退出QSPI模式
* @param 无
* @retval 无
*/
static void norflash_qspi_disable(void)
{
/* 退出QPI模式指令, 地址为0, 无数据_8位地址_无地址_4线传输指令, 无空周期 */
qspi_send_cmd(FLASH_ExitQPIMode, 0, (0 << 6) | (0 << 4) | (0 << 2) | (3 << 0), 0);
}
/**
* @brief 使能FLASH QE位,使能IO2/IO3
* @param 无
* @retval 无
*/
static void norflash_qe_enable(void)
{
uint8_t stareg2 = 0;
stareg2 = norflash_read_sr(2); /* 先读出状态寄存器2的原始值 */
//printf("stareg2:%x\r\n", stareg2);
if ((stareg2 & 0X02) == 0) /* QE位未使能 */
{
norflash_write_enable(); /* 写使能 */
stareg2 |= 1 << 1; /* 使能QE位 */
norflash_write_sr(2, stareg2); /* 写状态寄存器2 */
}
}
/**
* @brief 25QXX写使能
* @note 将SR1寄存器的WEL置位
* @param 无
* @retval 无
*/
void norflash_write_enable(void)
{
/* SPI, 写使能指令, 地址为0, 无数据_8位地址_无地址_单线传输指令, 无空周期 */
qspi_send_cmd(FLASH_WriteEnable, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
/**
* @brief 25QXX写禁止
* @note 将S1寄存器的WEL清零
* @param 无
* @retval 无
*/
void norflash_write_disable(void)
{
/* SPI, 写禁止指令, 地址为0, 无数据_8位地址_无地址_单线传输指令, 无空周期 */
qspi_send_cmd(FLASH_WriteDisable, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
/**
* @brief 读取25QXX的状态寄存器,25QXX一共有3个状态寄存器
* @note 状态寄存器1:
* BIT7 6 5 4 3 2 1 0
* SPR RV TB BP2 BP1 BP0 WEL BUSY
* SPR:默认0,状态寄存器保护位,配合WP使用
* TB,BP2,BP1,BP0:FLASH区域写保护设置
* WEL:写使能锁定
* BUSY:忙标记位(1,忙;0,空闲)
* 默认:0x00
*
* 状态寄存器2:
* BIT7 6 5 4 3 2 1 0
* SUS CMP LB3 LB2 LB1 (R) QE SRP1
*
* 状态寄存器3:
* BIT7 6 5 4 3 2 1 0
* HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
*
* @param regno: 状态寄存器号,范围:1~3
* @retval 状态寄存器值
*/
uint8_t norflash_read_sr(uint8_t regno)
{
uint8_t byte = 0, command = 0;
switch (regno)
{
case 1:
command = FLASH_ReadStatusReg1; /* 读状态寄存器1指令 */
break;
case 2:
command = FLASH_ReadStatusReg2; /* 读状态寄存器2指令 */
break;
case 3:
command = FLASH_ReadStatusReg3; /* 读状态寄存器3指令 */
break;
default:
command = FLASH_ReadStatusReg1;
break;
}
/* SPI, 发送command指令, 地址为0, 单线传输数据_8位地址_无地址_单线传输指令,无空周期 */
qspi_send_cmd(command, 0, (1 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
qspi_receive(&byte, 1); /* 读状态寄存器指令会返回1个字节数据 */
return byte;
}
/**
* @brief 写25QXX状态寄存器
* @note 寄存器说明见norflash_read_sr函数说明
* @param regno: 状态寄存器号,范围:1~3
* @param sr : 要写入状态寄存器的值
* @retval 无
*/
void norflash_write_sr(uint8_t regno, uint8_t sr)
{
uint8_t command = 0;
switch (regno)
{
case 1:
command = FLASH_WriteStatusReg1; /* 写状态寄存器1指令 */
break;
case 2:
command = FLASH_WriteStatusReg2; /* 写状态寄存器2指令 */
break;
case 3:
command = FLASH_WriteStatusReg3; /* 写状态寄存器3指令 */
break;
default:
command = FLASH_WriteStatusReg1;
break;
}
/* SPI, 发送command指令, 地址为0, 单线传输数据_8位地址_无地址_单线传输指令,无空周期,1个字节数据 */
qspi_send_cmd(command, 0, (1 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
qspi_transmit(&sr, 1); /* 写状态寄存器指令需要写入1个字节数据 */
}
/**
* @brief 读取芯片ID
* @param 无
* @retval FLASH芯片ID
* @note 芯片ID列表见: norflash.h, 芯片列表部分
*/
uint16_t norflash_read_id(void)
{
uint8_t temp[2];
uint16_t deviceid;
qspi_init(); /* 进行库函数调用前要先初始化 */
/* SPI, 读id指令, 地址为0, 单线传输数据_24位地址_单线传输地址_单线传输指令, 无空周期 */
qspi_send_cmd(FLASH_ManufactDeviceID, 0, (1 << 6) | (2 << 4) | (1 << 2) | (1 << 0), 0);
qspi_receive(temp, 2); /* 读状态寄存器指令会返回2个字节数据 */
deviceid = (temp[0] << 8) | temp[1];
if (deviceid == W25Q256)
{
g_norflash_addrw = 3; /* 如果是W25Q256, 标记32bit地址宽度 */
}
return deviceid;
}
/**
* @brief 读取SPI FLASH,仅支持QSPI模式
* @note 在指定地址开始读取指定长度的数据
* @param pbuf : 数据存储区
* @param addr : 开始读取的地址(最大32bit)
* @param datalen : 要读取的字节数(最大65535)
* @retval 无
*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
/* QSPI, 快速读数据指令, 地址为addr, 4线传输数据_24/32位地址_4线传输地址_1线传输指令, 6个空指令周期 */
qspi_send_cmd(FLASH_FastReadQuad, addr, (3 << 6) | (g_norflash_addrw << 4) | (3 << 2) | (1 << 0), 6);
qspi_receive(pbuf, datalen); /* 快速读数据指令会返回设置的datalen个字节数据 */
}
/**
* @brief SPI在一页(0~65535)内写入少于256个字节的数据
* @note 在指定地址开始写入最大256字节的数据
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
* @retval 无
*/
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
norflash_write_enable(); /* 写使能 */
/* QSPI, 页写指令, 地址为addr, 4线传输数据_24/32位地址_1线传输地址_1线传输指令, 无空周期 */
qspi_send_cmd(FLASH_PageProgramQuad, addr, (3 << 6) | (g_norflash_addrw << 4) | (1 << 2) | (1 << 0), 0);
qspi_transmit(pbuf, datalen); /* 页写指令会需要发送设置的datalen个字节数据 */
norflash_wait_busy(); /* 等待写入结束 */
}
/**
* @brief 无检验写SPI FLASH
* @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
* 具有自动换页功能
* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; /* 单页剩余的字节数 */
if (datalen <= pageremain) /* 不大于256个字节 */
{
pageremain = datalen;
}
while (1)
{
/* 当写入字节比页内剩余地址还少的时候, 一次性写完
* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
*/
norflash_write_page(pbuf, addr, pageremain);
if (datalen == pageremain) /* 写入结束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */
addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */
datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */
{
pageremain = 256; /* 一次可以写入256个字节 */
}
else /* 剩余数据小于一页,可以一次写完 */
{
pageremain = datalen; /* 不够256个字节了 */
}
}
}
}
/**
* @brief 写SPI FLASH
* @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
* SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
* 擦除的最小单位为Sector.
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *norflash_buf;
norflash_buf = g_norflash_buf;
secpos = addr / 4096; /* 扇区地址 */
secoff = addr % 4096; /* 在扇区内的偏移 */
secremain = 4096 - secoff; /* 扇区剩余空间大小 */
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
if (datalen <= secremain)
{
secremain = datalen; /* 不大于4096个字节 */
}
while (1)
{
norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */
for (i = 0; i < secremain; i++) /* 校验数据 */
{
if (norflash_buf[secoff + i] != 0XFF)
{
break; /* 需要擦除, 直接退出for循环 */
}
}
if (i < secremain) /* 需要擦除 */
{
norflash_erase_sector(secpos); /* 擦除这个扇区 */
for (i = 0; i < secremain; i++) /* 复制 */
{
norflash_buf[i + secoff] = pbuf[i];
}
norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 写入整个扇区 */
}
else /* 写已经擦除了的,直接写入扇区剩余区间. */
{
norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
}
if (datalen == secremain)
{
break; /* 写入结束了 */
}
else /* 写入未结束 */
{
secpos++; /* 扇区地址增1 */
secoff = 0; /* 偏移位置为0 */
pbuf += secremain; /* 指针偏移 */
addr += secremain; /* 写地址偏移 */
datalen -= secremain; /* 字节数递减 */
if (datalen > 4096)
{
secremain = 4096; /* 下一个扇区还是写不完 */
}
else
{
secremain = datalen;/* 下一个扇区可以写完了 */
}
}
}
}
/**
* @brief 擦除整个芯片
* @note 等待时间超长...
* @param 无
* @retval 无
*/
void norflash_erase_chip(void)
{
norflash_write_enable(); /* 写使能 */
norflash_wait_busy(); /* 等待空闲 */
/* SPI, 写全片擦除指令, 地址为0, 无数据_8位地址_无地址_1线传输指令, 无空周期 */
qspi_send_cmd(FLASH_ChipErase, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
norflash_wait_busy(); /* 等待芯片擦除结束 */
}
/**
* @brief 擦除一个扇区
* @note 注意,这里是扇区地址,不是字节地址!!
* 擦除一个扇区的最少时间:150ms
*
* @param saddr : 扇区地址 根据实际容量设置
* @retval 无
*/
void norflash_erase_sector(uint32_t saddr)
{
//printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */
saddr *= 4096;
norflash_write_enable(); /* 写使能 */
norflash_wait_busy(); /* 等待空闲 */
/* SPI, 写扇区擦除指令, 地址为0, 无数据_24/32位地址_1线传输地址_1线传输指令, 无空周期 */
qspi_send_cmd(FLASH_SectorErase, saddr, (0 << 6) | (g_norflash_addrw << 4) | (1 << 2) | (1 << 0), 0);
norflash_wait_busy(); /* 等待擦除完成 */
}
norflash.h
#ifndef __norflash_H
#define __norflash_H
#include "./SYSTEM/sys/sys.h"
/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID */
extern uint16_t norflash_TYPE; /* 定义FLASH芯片型号 */
/* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFF
/* 静态函数 */
static void norflash_wait_busy(void); /* 等待空闲 */
static void norflash_qe_enable(void); /* 使能QE位 */
static void norflash_qspi_disable(void); /* 退出QPI模式 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入page */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 */
/* 普通函数 */
void norflash_init(void); /* 初始化25QXX */
uint16_t norflash_read_id(void); /* 读取FLASH ID */
void norflash_write_enable(void); /* 写使能 */
void norflash_write_disable(void); /* 写保护 */
uint8_t norflash_read_sr(uint8_t regno); /* 读取状态寄存器 */
void norflash_write_sr(uint8_t regno,uint8_t sr); /* 写状态寄存器 */
void norflash_erase_chip(void); /* 整片擦除 */
void norflash_erase_sector(uint32_t saddr); /* 扇区擦除 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 读取flash */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入flash */
#endif
norflash_ex.c
#include "./BSP/QSPI/qspi.h"
#include "./BSP/NORFLASH/norflash.h"
#include "./BSP/NORFLASH/norflash_ex.h"
extern uint8_t g_norflash_addrw; /* 表示当前是24bit/32bit数据位宽, 在norflash.c里面定义 */
/**
* @brief QSPI接口进入内存映射模式
* @note 调用该函数之前务必已经初始化了QSPI接口
* sys_qspi_enable_memmapmode or norflash_init
* @param 无
* @retval 无
*/
static void norflash_ex_enter_mmap(void)
{
uint32_t tempreg = 0;
/* BY/W25QXX 写使能(0X06指令) */
while (QUADSPI->SR & (1 << 5)); /* 等待BUSY位清零 */
QUADSPI->CCR = 0X00000106; /* 发送0X06指令,BY/W25QXX写使能 */
while ((QUADSPI->SR & (1 << 1)) == 0); /* 等待指令发送完成 */
QUADSPI->FCR |= 1 << 1;
if (qspi_wait_flag(1 << 5, 0, 0XFFFF) == 0) /* 等待BUSY空闲 */
{
tempreg = 0XEB; /* INSTRUCTION[7:0]=0XEB,发送0XEB指令(Fast Read QUAD I/O) */
tempreg |= 1 << 8; /* IMODE[1:0]=1,单线传输指令 */
tempreg |= 3 << 10; /* ADDRESS[1:0]=3,四线传输地址 */
tempreg |= (uint32_t)g_norflash_addrw << 12; /* ADSIZE[1:0]=2,24/32位地址长度 */
tempreg |= 3 << 14; /* ABMODE[1:0]=3,四线传输交替字节 */
tempreg |= 0 << 16; /* ABSIZE[1:0]=0,8位交替字节(M0~M7) */
tempreg |= 4 << 18; /* DCYC[4:0]=4,4个dummy周期 */
tempreg |= 3 << 24; /* DMODE[1:0]=3,四线传输数据 */
tempreg |= 3 << 26; /* FMODE[1:0]=3,内存映射模式 */
QUADSPI->CCR = tempreg; /* 设置CCR寄存器 */
}
sys_intx_enable(); /* 开启中断 */
}
/**
* @brief QSPI接口退出内存映射模式
* @note 调用该函数之前务必已经初始化了QSPI接口
* sys_qspi_enable_memmapmode or norflash_init
* @param 无
* @retval 0, OK; 其他, 错误代码
*/
static uint8_t norflash_ex_exit_mmap(void)
{
uint8_t res = 0;
sys_intx_disable(); /* 关闭中断 */
SCB_InvalidateICache(); /* 清空I CACHE */
SCB_InvalidateDCache(); /* 清空D CACHE */
QUADSPI->CR &= ~(1 << 0); /* 关闭 QSPI 接口 */
QUADSPI->CR |= 1 << 1; /* 退出MEMMAPED模式 */
res = qspi_wait_flag(1 << 5, 0, 0XFFFF); /* 等待BUSY空闲 */
if (res == 0)
{
QUADSPI->CCR = 0; /* CCR寄存器清零 */
QUADSPI->CR |= 1 << 0; /* 使能 QSPI 接口 */
}
return res;
}
/**
* @brief 往 QSPI FLASH写入数据
* @note 在指定地址开始写入指定长度的数据
* 该函数带擦除操作!
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 0, OK; 其他, 错误代码
*/
uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出内存映射模式 */
if (res == 0)
{
norflash_write(pbuf, addr, datalen);
}
norflash_ex_enter_mmap(); /* 进入内存映射模式 */
return res;
}
/**
* @brief 从 QSPI FLASH 读取数据
* @note 在指定地址开始读取指定长度的数据(必须处于内存映射模式下,才可以执行)
*
* @param pbuf : 数据存储区
* @param addr : 开始读取的地址(最大32bit)
* @param datalen : 要读取的字节数(最大65535)
* @retval 0, OK; 其他, 错误代码
*/
void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i = 0;
addr += 0X90000000; /* 使用内存映射模式读取,QSPI的基址是0X90000000,所以这里要加上基址 */
sys_intx_disable(); /* 关闭中断 */
for (i = 0; i < datalen; i++)
{
pbuf[i] = *(volatile uint8_t *)(addr + i);
}
sys_intx_enable(); /* 开启中断 */
}
/**
* @brief 读取QSPI FLASH的ID
* @param 无
* @retval NOR FLASH ID
*/
uint16_t norflash_ex_read_id(void)
{
uint8_t res = 0;
uint16_t id = 0;
res = norflash_ex_exit_mmap(); /* 退出内存映射模式 */
if (res == 0)
{
id = norflash_read_id();
}
norflash_ex_enter_mmap(); /* 进入内存映射模式 */
return id;
}
/**
* @brief 擦除QSPI FLASH的某个扇区
* @note 注意,这里是扇区地址,不是字节地址!!
* 擦除一个扇区的最少时间:150ms
*
* @param saddr: 扇区地址
* @retval 无
*/
void norflash_ex_erase_sector(uint32_t addr)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出内存映射模式 */
if (res == 0)
{
norflash_erase_sector(addr);
}
norflash_ex_enter_mmap(); /* 进入内存映射模式 */
}
/**
* @brief 擦除QSPI FLASH整个芯片
* @note 等待时间超长...
*
* @param 无
* @retval 无
*/
void norflash_ex_erase_chip(void)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出内存映射模式 */
if (res == 0)
{
norflash_erase_chip();
}
norflash_ex_enter_mmap(); /* 进入内存映射模式 */
}
norflash_ex.h
#ifndef __NORFLASH_EX_H
#define __NORFLASH_EX_H
#include "./SYSTEM/sys/sys.h"
void norflash_ex_erase_chip(void); /* NOR FLASH 全片擦除 */
uint16_t norflash_ex_read_id(void); /* NOR FLASH读取ID */
void norflash_ex_erase_sector(uint32_t addr); /* NOR FLASH 擦除扇区 */
uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* NOR FLASH写入数据 */
void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* NOR FLASH读取数据 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/MPU/mpu.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/QSPI/qspi.h"
#include "./BSP/NORFLASH/norflash.h"
#include "./BSP/NORFLASH/norflash_ex.h"
#include "string.h"
/* 要写入到FLASH的字符串数组 */
const uint8_t g_text_buf[] = {"MiniPRO H7 QSPI TEST"};
#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */
int main(void)
{
uint8_t key;
uint16_t i = 0;
uint8_t datatemp[TEXT_SIZE + 2];
uint8_t rectemp[TEXT_SIZE + 2];
uint32_t flashsize;
uint16_t id = 0;
sys_cache_enable(); /* 打开L1-Cache */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
delay_init(480); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(240); /* 初始化USMART */
mpu_memory_protection(); /* 保护相关存储区域 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
/*
* 不需要调用norflash_init函数了,因为sys.c里面的sys_qspi_enable_memmapmode函数已
* 经初始化了QSPI接口,如果再调用,则内存映射模式的设置被破坏,导致QSPI代码执行异常!
* 除非不用分散加载,所有代码放内部FLASH,才可以调用该函数!否则将导致异常!
*/
//norflash_init();
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "QSPI TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED); /* 显示提示信息 */
id = norflash_ex_read_id(); /* 读取FLASH ID */
while ((id == 0) || (id == 0XFFFF)) /* 检测不到FLASH芯片 */
{
lcd_show_string(30, 130, 200, 16, 16, "FLASH Check Failed!", RED);
delay_ms(500);
lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
delay_ms(500);
LED0_TOGGLE(); /* LED0闪烁 */
}
lcd_show_string(30, 130, 200, 16, 16, "QSPI FLASH Ready!", BLUE);
flashsize = 16 * 1024 * 1024; /* FLASH 大小为16M字节 */
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES) /* KEY1按下,写入 */
{
lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */
lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", BLUE);
sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
norflash_ex_write((uint8_t *)datatemp, flashsize - 100, TEXT_SIZE + 2); /* 从倒数第100个地址处开始,写入TEXT_SIZE + 2长度的数据 */
lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", BLUE); /* 提示传送完成 */
}
if (key == KEY0_PRES) /* KEY0按下,读取字符串并显示 */
{
lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH... . ", BLUE);
norflash_ex_read(rectemp, flashsize - 100, TEXT_SIZE + 2); /* 从倒数第100个地址处开始,读出TEXT_SIZE + 2个字节 */
lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); /* 提示传送完成 */
lcd_show_string(30, 170, 200, 16, 16, (char *)rectemp, BLUE); /* 显示读到的字符串 */
}
i++;
if (i == 20)
{
LED0_TOGGLE(); /* LED0闪烁 */
i = 0;
}
delay_ms(10);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。