你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F7QSPI学习笔记——读写N25Q128

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍

QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:

 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。

 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)

 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器

采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。

内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。

内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。

二、 STM32QSPI 功能框图

20191006091636349.png


STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。

三、QUADSPI  命令序列

QUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
nCS 在每条指令开始前下降,在每条指令完成后再次上升。

20191006092242666.png


上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.

交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);

空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高
的时钟频率时,给 Flash 留出准备数据阶段的时间。
若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。

此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。

四、QUADSPI  信号接口协议模式

下面提到的几线模式都是指数据线的根数

单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。
通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可
对不同的命令阶段分别进行配置,以使用此单个位模式。
在每个已配置为单线模式的阶段中:
 IO0 (SO) 处于输出模式
 IO1 (SI) 处于输入模式(高阻抗)
 IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)

双线 SPI  模式:
在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。
在每个已配置为双线模式的阶段中:
 IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出
 IO2 处于输出模式并强制置“0”
 IO3 处于输出模式并强制置“1”
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。


四线 SPI  模式:
在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可
对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高
阻态(输入),在其他情况下为输出。
在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
IO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI
激活,对应 IO2 和 IO3 的引脚也可用于其他功能。


SDR  模式(默认工作模式)
默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
的下降沿发生转变。
在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。


DDR  模式
在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
后(下一个反向边沿)对信号采样。

五、QSPI 配置(HAL库)

1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟

20191006121236849.png


  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
  2. {

  3.   GPIO_InitTypeDef GPIO_InitStruct = {0};
  4.   if(qspiHandle->Instance==QUADSPI)
  5.   {
  6.   /* USER CODE BEGIN QUADSPI_MspInit 0 */

  7.   /* USER CODE END QUADSPI_MspInit 0 */
  8.     /* QUADSPI clock enable */
  9.     __HAL_RCC_QSPI_CLK_ENABLE();

  10.     __HAL_RCC_GPIOE_CLK_ENABLE();
  11.     __HAL_RCC_GPIOB_CLK_ENABLE();
  12.     __HAL_RCC_GPIOD_CLK_ENABLE();
  13.     /**QUADSPI GPIO Configuration   
  14.     PE2     ------> QUADSPI_BK1_IO2
  15.     PB6     ------> QUADSPI_BK1_NCS
  16.     PB2     ------> QUADSPI_CLK
  17.     PD12     ------> QUADSPI_BK1_IO1
  18.     PD13     ------> QUADSPI_BK1_IO3
  19.     PD11     ------> QUADSPI_BK1_IO0
  20.     */
  21.     GPIO_InitStruct.Pin = GPIO_PIN_2;
  22.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  23.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  24.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  25.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
  26.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  27.     GPIO_InitStruct.Pin = GPIO_PIN_6;
  28.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  29.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  30.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  31.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
  32.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  33.     GPIO_InitStruct.Pin = GPIO_PIN_2;
  34.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  35.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  36.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  37.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
  38.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  39.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
  40.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  41.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  42.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  43.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
  44.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  45.   /* USER CODE BEGIN QUADSPI_MspInit 1 */

  46.   /* USER CODE END QUADSPI_MspInit 1 */
  47.   }
  48. }
复制代码

2.接着初始化SPI外设

  1. QSPI_HandleTypeDef hqspi;

  2. /* QUADSPI init function */
  3. void MX_QUADSPI_Init(void)
  4. {
  5.   hqspi.Instance = QUADSPI;//QSPI外设
  6.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
  7.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
  8.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
  9.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
  10.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数
  11.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0
  12.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
  13.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式
  14.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设
  15.   {
  16.     Error_Handler();
  17.   }
  18. }
复制代码

3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。

我们首先需要配置下面的命令参数结构体:

20191006122437823.png


Instruction:指令类型(读、写、擦除等等)

Address:需要读取或写入数据的起始地址

NbData:需要读取或写入数据的长度

DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。

这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!

4.读取芯片JEDEC ID

首先查看手册上读取JEDEC ID的时序:

20191006131644114.png


可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以

InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式

DataMode = QSPI_DATA_1_LINE;//数据单线传输模式

Address和AddressMode 这两个参数的值都置0,表示无地址。

NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3

其他值和下面代码保持一致即可。

然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成

最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID

  1. /**
  2. * @brief  读取FLASH ID
  3. * @param   无
  4. * @retval FLASH ID
  5. */
  6. uint32_t QSPI_FLASH_ReadID(void)
  7. {
  8.      QSPI_CommandTypeDef s_command;

  9.      uint32_t Temp = 0;

  10.      uint8_t pData[3];

  11.      /* 读取JEDEC ID */

  12.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。

  13.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令

  14.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度

  15.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。

  16.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。
  17.          
  18.      s_command.Address           = 0;   
  19.         
  20.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度

  21.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。

  22.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。

  23.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。

  24.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
  25.                  
  26.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。

  27.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  28.                  {
  29.          #if  QSPI_DEBUG
  30.                      printf("send read flash ID command error!\r\n");
  31.                #endif
  32.          /* 用户可以在这里添加一些代码来处理这个错误 */
  33.          while (1) {}
  34.      }
  35.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)
  36.                  {
  37.          #if  QSPI_DEBUG
  38.                      printf("read qspi flash ID error!\r\n");
  39.                #endif
  40.          /* 用户可以在这里添加一些代码来处理这个错误 */
  41.          while (1) {}
  42.      }
  43.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );
  44.      return Temp;
  45. }
复制代码

5.FALSH 写使能

在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):

类似的,需要查看芯片手册,写使能的时序

20191006134057767.png


可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0

  1. /**
  2.   * @brief  QSPI FALSH写使能
  3.   * @param  None
  4.   * @retval HAL_OK
  5. */
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)
  7. {
  8.   QSPI_CommandTypeDef     sCommand;
  9.   QSPI_AutoPollingTypeDef sConfig;

  10.   /* Enable write operations ------------------------------------------ */
  11.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  12.   sCommand.Instruction       = WRITE_ENABLE_CMD;
  13.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;
  14.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  15.   sCommand.DataMode          = QSPI_DATA_NONE;
  16.   sCommand.DummyCycles       = 0;
  17.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
  18.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  19.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

  20.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  21.   {
  22.     return HAL_ERROR;
  23.   }

  24.   /* Configure automatic polling mode to wait for write enabling ---- */  
  25.   sConfig.Match           = 0x02;
  26.   sConfig.Mask            = 0x02;
  27.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
  28.   sConfig.StatusBytesSize = 1;
  29.   sConfig.Interval        = 0x10;
  30.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

  31.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
  32.   sCommand.DataMode       = QSPI_DATA_1_LINE;

  33.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  34.   {
  35.                 return HAL_ERROR;        
  36.   }
  37.         return HAL_OK;
  38. }
复制代码

这里需要讲下 QUADSPI  状态标志轮询模式:
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内
容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。
如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
访问。
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数
据读取后清零。

简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。

6.读FALSH

这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序

20191006135244693.png


从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:

  1. /**
  2. * @brief  从QSPI存储器中读取大量数据.
  3. * @param  pData: 指向要读取的数据的指针
  4. * @param  ReadAddr: 读取起始地址
  5. * @param  Size: 要读取的数据大小
  6. * @retval QSPI存储器状态
  7. */
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
  9. {
  10.      QSPI_CommandTypeDef s_command;

  11.      /* 初始化读命令 */

  12.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

  13.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;

  14.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;

  15.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

  16.      s_command.Address           = ReadAddr;

  17.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

  18.      s_command.DataMode          = QSPI_DATA_4_LINES;

  19.      s_command.DummyCycles       = 10;

  20.      s_command.NbData            = Size;

  21.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

  22.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

  23.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  24.      /* 配置命令 */

  25.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  26.          {
  27.                 #if  QSPI_DEBUG
  28.                 printf("R-send read command error!\r\n");
  29.           #endif
  30.     return HAL_ERROR;
  31.    }  
  32.    /* 接收数据 */
  33.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)
  34.          {
  35.                 #if  QSPI_DEBUG
  36.                 printf("R-read data error!\r\n");
  37.           #endif
  38.     return HAL_ERROR;
  39.    }
  40.    return HAL_OK;
  41. }
复制代码

本函数实现了从FALSH指定地址读取指定数量字节的功能

7.写FALSH

同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:

20191006135836784.png


根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期

  1. /**
  2. * @brief  将大量数据写入QSPI存储器
  3. * @param  pData: 指向要写入数据的指针
  4. * @param  WriteAddr: 写起始地址
  5. * @param  Size: 要写入的数据大小
  6. * @retval QSPI存储器状态
  7. */
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
  9. {
  10.                         QSPI_CommandTypeDef s_command;
  11.                         uint32_t end_addr, current_size, current_addr;
  12.             unsigned char sector_start,sector_end,i;
  13.         
  14.                         /* 计算写入地址和页面末尾之间的大小 */
  15.                         current_addr = 0;
  16.         
  17.                         while (current_addr <= WriteAddr)
  18.                         {
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;
  20.                         }
  21.                   current_size = current_addr - WriteAddr;
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */
  23.      if (current_size > Size)
  24.                  {
  25.          current_size = Size;
  26.      }
  27.      /* 初始化地址变量 */
  28.      current_addr = WriteAddr;
  29.      end_addr = WriteAddr + Size;
  30.                  
  31.                  sector_start = current_addr >> 16;
  32.                  sector_end = end_addr >> 16;
  33.                  
  34.                  for( i = sector_start; i<=sector_end ;i++)
  35.                  {
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
  37.                  }
  38.                  
  39.      /* 初始化程序命令 */
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

  41.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;

  42.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

  43.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

  44.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

  45.      s_command.DataMode          = QSPI_DATA_4_LINES;

  46.      s_command.DummyCycles       = 0;

  47.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

  48.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

  49.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

  50.      /* 逐页执行写入 */
  51.      do {

  52.          s_command.Address = current_addr;
  53.          s_command.NbData  = current_size;
  54.          /* 启用写操作 */

  55.          if (QSPI_WriteEnable() != HAL_OK)
  56.                                  {
  57.                                                 #if  QSPI_DEBUG
  58.                                                 printf("W-Qspi write enable error!\r\n");
  59.                                                 #endif
  60.             return HAL_ERROR;
  61.          }
  62.          /* 配置命令 */
  63.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  64.                 {                                 
  65.                                           #if  QSPI_DEBUG
  66.                                                 printf("W-Qspi send Command error!\r\n");
  67.                                                 #endif
  68.             return HAL_ERROR;
  69.          }
  70.          /* 传输数据 */
  71.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  72.                                  {
  73.                                           #if  QSPI_DEBUG
  74.                                                 printf("W-Qspi Transmit data error!\r\n");
  75.                                                 #endif
  76.             return HAL_ERROR;
  77.          }
  78.          /* 配置自动轮询模式等待程序结束 */

  79.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  80.                                  {                                         
  81.                                                 #if  QSPI_DEBUG
  82.                                                 printf("W-AutoPollingMemReady error!\r\n");
  83.                                                 #endif
  84.             return HAL_ERROR;
  85.          }
  86.          /* 更新下一页编程的地址和大小变量 */

  87.          current_addr += current_size;

  88.          pData += current_size;

  89.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :

  90.                         N25Q128FV_PAGE_SIZE;

  91.      } while (current_addr < end_addr);
  92.      return HAL_OK;
  93. }
复制代码

该函数实现从指定地址写入指定数量的字节。

8.擦除FALSH

第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)

20191006140420479.png


参数设置为1线指令 ,1线地址,24bit地址,无数据阶段

  1. /**
  2. * @brief  擦除QSPI存储器的指定扇区
  3. * @param  BlockAddress: 需要擦除的扇区地址
  4. * @retval QSPI存储器状态
  5. */
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)
  7. {
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址
  9.         
  10.      QSPI_CommandTypeDef s_command;
  11.      /* 初始化擦除命令 */
  12.         
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

  14.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除

  15.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

  16.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

  17.      s_command.Address           = SectorAddress;

  18.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

  19.      s_command.DataMode          = QSPI_DATA_NONE;

  20.      s_command.DummyCycles       = 0;

  21.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

  22.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

  23.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

  24.      /* 启用写操作 */

  25.      if (QSPI_WriteEnable() != HAL_OK)
  26.                  {
  27.          return HAL_ERROR;
  28.      }
  29.      /* 发送命令 */
  30.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  31.      {
  32.          return HAL_ERROR;
  33.      }
  34.      /* 配置自动轮询模式等待擦除结束 */

  35.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  36.      {
  37.                                 #if  QSPI_DEBUG
  38.                                 printf("erase sector error!\r\n");
  39.                                 #endif
  40.         return HAL_ERROR;
  41.      }
  42.     return HAL_OK;
  43. }
复制代码

第二种情况是全片擦除,时序如下:

20191006140919805.png


全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。

  1. /**
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s
  3.   * @retval QSPI memory status
  4.   */
  5. uint8_t BSP_QSPI_Erase_Chip(void)
  6. {
  7.   QSPI_CommandTypeDef s_command;

  8.   /* Initialize the erase command */
  9.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  10.   s_command.Instruction       = CHIP_ERASE_CMD;
  11.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
  12.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  13.   s_command.DataMode          = QSPI_DATA_NONE;
  14.   s_command.DummyCycles       = 0;
  15.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  16.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  17.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

  18.   /* Enable write operations */
  19.   if (QSPI_WriteEnable() != HAL_OK)
  20.   {
  21.     return HAL_ERROR;
  22.   }

  23.   /* Send the command */
  24.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  25.   {
  26.     return HAL_ERROR;
  27.   }

  28.   /* Configure automatic polling mode to wait for end of erase */  
  29.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
  30.   {
  31.     return HAL_ERROR;
  32.   }

  33.   return HAL_OK;
  34. }
复制代码

9.测试程序

测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。

  1. unsigned char write[4096];
  2. unsigned char read[4096];   
  3. void N25Qxx_Test(void)
  4. {
  5.   uint32_t i = 0;

  6.         for(i = 0;i<sizeof(write);i++)
  7.         {
  8.     write=i;
  9.         }
  10.         
  11.         printf("write data:\r\n");        
  12.   for(i = 0;i<sizeof(write);i++)
  13.         {
  14.                 printf("%d ",write);
  15.         }
  16.   printf("\r\n");
  17.         printf("\r\n");
  18.         printf("\r\n");
  19.         
  20.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
  21.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        
  22.         
  23.         printf("Read from QSPI:\r\n");
  24.         
  25.   for(i = 0;i<sizeof(read);i++)
  26.         {
  27.                 printf("%d ",read);
  28.         }
  29.   printf("\r\n");
  30. }
复制代码

写入的数据(部分截图):

20191006141636369.png


读取的数据(部分截图):

20191006141537697.png


截取相同的部分,两次数据完全一致,写入和读取成功。

最后,讲几点需要注意的地方:

1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。

2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。

3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:
 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。

4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。

5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。

20200316162249870.jpg


6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。

7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!



收藏 评论0 发布时间:2021-12-16 21:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版