STM32之CubeL4(三)--- SPI + QSPI + HAL

一、SPI简介

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。

1.1 SPI物理层

SPI 一般使用 4 根线通信,如下图所示:
全双工单个主和单个从设备通信

  • MISO –主机输入 / 从机输出数据线(SPI Bus Master Input / Slave Output);
  • MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output / Slave Input);
  • SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备,用于通信数据同步,同时决定了SPI的通信速率;
  • NSS(CS) –从设备选择线 (NO. Slave Select / Chip select),由主设备控制,当NSS为低电平则选中从器件。

SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。

如下为主器件与多个从器件通信,其中SCK,MOSI,MISO是接在一起的,NSS分别接到不同的IO管脚控制。主器件要和从器件通信就先拉低对应从器件的NSS管脚使能。默认状态IO1,IO2,IO3全为高电平,当主器件和从器件1通信时,拉低IO1管脚使能从器件1。而从器件2,3不使能,不作响应:
主设备与三个独立的从设备通信

1.2 SPI协议层

每次开始传输时,主器件先拉低从器件的片选信号线NSS,选中要传输的从器件。SCK时钟线发送一个时钟周期就传输一位数据。MOSI为主出从入,数据由主器件控制发送,从器件接收。MISO的数据由从器件控制发送,主器件接收。所以SPI传输一个字节就相当于主器件和从器件交换一个字节。

SPI只有主模式和从模式之分,没有读和写的说法,因为实质上每次SPI是主从设备在交换数据。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

根据时钟极性(CPOL: Clock Polarity)及相位(CPHA: Clock Phase)不同,SPI有四种工作模式:
SPI四种工作模式
SPI四种工作模式对应的工作时序图如下:
SPI工作时序图
上图中的nSS信号由高电平变为低电平即为SPI通讯的起始信号,反过来,nSS信号由低电平变为高电平即为SPI通讯的结束信号。

SPI通信数据的采集跟其工作模式有关,当时钟相位CPHA=0则在时钟的第一个跳变沿进行数据采样,当CPHA=1则在时钟的第二个跳变沿(也即延迟半个时钟周期)进行数据采样。时钟极性CPOL则定义了时钟空闲状态的电平高低,即CPOL=0则时钟空闲状态为低电平,CPOL=1则时钟空闲状态为高电平。

1.3 QSPI简介

QSPI 是 Queued SPI 的简写,是 Motorola 公司推出的 SPI 接口的扩展,比 SPI 应用更加广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。使用该接口,用户可以一次性传输包含多达 16 个 8 位或 16 位数据的传输队列。一旦传输启动,直到传输结束,都不需要 CPU 干预,极大的提高了传输效率。与 SPI 相比,QSPI 的最大结构特点是以 80 字节的 RAM 代替了 SPI 的发送和接收数据寄存器。

QSPI 是一种专用的通信接口,连接单、双或四(条数据线) SPI Flash 存储介质。根据使用数据线数的不同,可将SPI Flash分为以下三种类型(在相同时钟下,线数越多传输速率越高):

  • SPI Flash: 前面介绍的标准SPI全双工模式,虽然有两条数据线,但均为单向数据传输,这样一个时钟周期内只能传输1 个bit数据;
  • Dual SPI Flash: 对于 SPI Flash 而言全双工并不常用,可以发送一个命令字节进入 Dual 模式,让它工作在半双工模式,用以加倍数据传输。这样 MOSI 变成 SIO0(serial io 0),MISO 变成 SIO1(serial io 1),这样一个时钟周期内就能传输 2 个 bit 数据,加倍了数据传输;
  • Quad SPI Flash: 与 Dual SPI 类似,Quad SPI Flash增加了两根 I/O 线(SIO2,SIO3),目的是一个时钟内传输 4 个 bit 数据。

QSPI通信中最常用的是Quad-SPI即四线SPI,其数据线比标准的SPI接口要多,最多支持四条数据线同时传输。Quad-SPI总共有6根控制线:CS为片选,CLK为时钟信号线,IO0~IO3为数据线,可以发送数据也可以接收数据,QSPI的功能框图如下:
QSPI功能框图
上图中QSPI通信涉及的各信号引脚名称类型及三种不同SPI接口的信号映射关系如下表所示:
QSPI信号引脚及接口类型
QSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五个阶段 任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。nCS 在每条指令开始前下降,在每条指令完成后再次上升。先看看QSPI四线模式下的读命令时序:
QSPI四线模式读命令时序

  • 指令阶段:这一阶段将发送一条8位指令到flash,指定待执行的类型。指令可以单线,双线或四线传输;
  • 地址阶段:在地址阶段,将1-4字节发送到flash,指示操作地址,地址阶段可一次发送 1 位(在单线 SPI 模式中通过 IO0)、2 位(在双线 SPI 模式中通过 IO0/IO1 )或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3);
  • 交替字节阶段:在交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式,适用于支持现场执行(直接从外部存储器执行程序代码的方法)的所有写入和读取命令。可以通过可以单线,双线或四线传输;
  • 空指令周期阶段:在空指令周期阶段,给定的 1-31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给 Flash 留出准备数据阶段的时间;
  • 数据阶段:在数据阶段,可从 Flash 接收或向其发送任意数量的字节。数据阶段如果发送数据则IO口切换为输出,如果是接收数据则IO口切换为输入,一次可发送 1 位(在单线 SPI 模式中通过 IO0)、2 位(在双线 SPI 模式中通过 IO0/IO1 )或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。

QSPI接口协议除了上面介绍的按照数据线条数划分的单线标准SPI、双线Dual-SPI、四线Quad-SPI三种模式外,还可根据一个时钟周期一条数据线上传输的数据位数划分为SDR(Single Data Rate)模式和DDR(Double Data Rate)模式,前面介绍的都是SDR模式,在DDR模式下将在每个时钟的上升沿和下降沿分别发生1 bit数据,也即每个时钟周期每条数据线发送2 bit数据。

QSPI除了支持DDR双倍数据速率模式,还支持双闪存模式,即使用两个外部四线 SPI Flash(FLASH 1 和 FLASH 2),在每个周期中发送/接收 8 位(在 DDR 模式下为16 位),能够有效地将吞吐量和容量扩大一倍。每个 Flash 使用同一个 CLK 并可选择使用同一个 nCS 信号,但其 IO0、IO1、IO2 和 IO3 信号是各自独立的。双闪存模式可与单比特模式、双比特模式以及四比特模式结合使用,也可与 SDR 或 DDR 模式相结合。

QSPI更多参考信息:https://www.cypress.com/file/405966/download

二、SPI在HAL中的配置

2.1 SPI配置

STM32 HAL库提供了SPI初始化结构体、句柄结构体及相关函数来配置SPI外设,了解这些结构体定义及其操作函数后,我们就能对SPI外设运用自如了。

以STM32L475芯片的HAL库文件为例,SPI的初始化结构体定义如下:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_spi.h

/**
  * @brief  SPI Configuration Structure definition
  */
typedef struct
{
  uint32_t Mode;                /*!< Specifies the SPI operating mode.
                                     This parameter can be a value of @ref SPI_Mode */

  uint32_t Direction;           /*!< Specifies the SPI bidirectional mode state.
                                     This parameter can be a value of @ref SPI_Direction */

  uint32_t DataSize;            /*!< Specifies the SPI data size.
                                     This parameter can be a value of @ref SPI_Data_Size */

  uint32_t CLKPolarity;         /*!< Specifies the serial clock steady state.
                                     This parameter can be a value of @ref SPI_Clock_Polarity */

  uint32_t CLKPhase;            /*!< Specifies the clock active edge for the bit capture.
                                     This parameter can be a value of @ref SPI_Clock_Phase */

  uint32_t NSS;                 /*!< Specifies whether the NSS signal is managed by
                                     hardware (NSS pin) or by software using the SSI bit.
                                     This parameter can be a value of @ref SPI_Slave_Select_management */

  uint32_t BaudRatePrescaler;   /*!< Specifies the Baud Rate prescaler value which will be
                                     used to configure the transmit and receive SCK clock.
                                     This parameter can be a value of @ref SPI_BaudRate_Prescaler
                                     @note The communication clock is derived from the master
                                     clock. The slave clock does not need to be set. */

  uint32_t FirstBit;            /*!< Specifies whether data transfers start from MSB or LSB bit.
                                     This parameter can be a value of @ref SPI_MSB_LSB_transmission */

  uint32_t TIMode;              /*!< Specifies if the TI mode is enabled or not.
                                     This parameter can be a value of @ref SPI_TI_mode */

  uint32_t CRCCalculation;      /*!< Specifies if the CRC calculation is enabled or not.
                                     This parameter can be a value of @ref SPI_CRC_Calculation */

  uint32_t CRCPolynomial;       /*!< Specifies the polynomial used for the CRC calculation.
                                     This parameter must be an odd number between Min_Data = 1 and Max_Data = 65535 */

  uint32_t CRCLength;           /*!< Specifies the CRC Length used for the CRC calculation.
                                     CRC Length is only used with Data8 and Data16, not other data size
                                     This parameter can be a value of @ref SPI_CRC_length */

  uint32_t NSSPMode;            /*!< Specifies whether the NSSP signal is enabled or not .
                                     This parameter can be a value of @ref SPI_NSSP_Mode
                                     This mode is activated by the NSSP bit in the SPIx_CR2 register and
                                     it takes effect only if the SPI interface is configured as Motorola SPI
                                     master (FRF=0) with capture on the first edge (SPIx_CR1 CPHA = 0,
                                     CPOL setting is ignored).. */
} SPI_InitTypeDef;

/** @defgroup SPI_Mode SPI Mode
  */
#define SPI_MODE_SLAVE                  (0x00000000U)
#define SPI_MODE_MASTER                 (SPI_CR1_MSTR | SPI_CR1_SSI)

/** @defgroup SPI_Direction SPI Direction Mode
  */
#define SPI_DIRECTION_2LINES            (0x00000000U)
#define SPI_DIRECTION_2LINES_RXONLY     SPI_CR1_RXONLY
#define SPI_DIRECTION_1LINE             SPI_CR1_BIDIMODE

/** @defgroup SPI_Data_Size SPI Data Size
  */
#define SPI_DATASIZE_4BIT               (0x00000300U)
#define SPI_DATASIZE_5BIT               (0x00000400U)
......
#define SPI_DATASIZE_16BIT              (0x00000F00U)

/** @defgroup SPI_Clock_Polarity SPI Clock Polarity
  */
#define SPI_POLARITY_LOW                (0x00000000U)
#define SPI_POLARITY_HIGH               SPI_CR1_CPOL

/** @defgroup SPI_Clock_Phase SPI Clock Phase
  */
#define SPI_PHASE_1EDGE                 (0x00000000U)
#define SPI_PHASE_2EDGE                 SPI_CR1_CPHA

/** @defgroup SPI_Slave_Select_management SPI Slave Select Management
  */
#define SPI_NSS_SOFT                    SPI_CR1_SSM
#define SPI_NSS_HARD_INPUT              (0x00000000U)
#define SPI_NSS_HARD_OUTPUT             (SPI_CR2_SSOE << 16U)

/** @defgroup SPI_BaudRate_Prescaler SPI BaudRate Prescaler
  */
#define SPI_BAUDRATEPRESCALER_2         (0x00000000U)
#define SPI_BAUDRATEPRESCALER_4         (SPI_CR1_BR_0)
......
#define SPI_BAUDRATEPRESCALER_256       (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)

/** @defgroup SPI_MSB_LSB_transmission SPI MSB LSB Transmission
  */
#define SPI_FIRSTBIT_MSB                (0x00000000U)
#define SPI_FIRSTBIT_LSB                SPI_CR1_LSBFIRST

/** @defgroup SPI_TI_mode SPI TI Mode
  */
#define SPI_TIMODE_DISABLE              (0x00000000U)
#define SPI_TIMODE_ENABLE               SPI_CR2_FRF

/** @defgroup SPI_CRC_Calculation SPI CRC Calculation
  */
#define SPI_CRCCALCULATION_DISABLE      (0x00000000U)
#define SPI_CRCCALCULATION_ENABLE       SPI_CR1_CRCEN

/** @defgroup SPI_CRC_length SPI CRC Length
  * This parameter can be one of the following values:
  *     SPI_CRC_LENGTH_DATASIZE: aligned with the data size
  *     SPI_CRC_LENGTH_8BIT    : CRC 8bit
  *     SPI_CRC_LENGTH_16BIT   : CRC 16bit
  */
#define SPI_CRC_LENGTH_DATASIZE         (0x00000000U)
#define SPI_CRC_LENGTH_8BIT             (0x00000001U)
#define SPI_CRC_LENGTH_16BIT            (0x00000002U)

/** @defgroup SPI_NSSP_Mode SPI NSS Pulse Mode
  */
#define SPI_NSS_PULSE_ENABLE            SPI_CR2_NSSP
#define SPI_NSS_PULSE_DISABLE           (0x00000000U)

SPI初始化结构体SPI_InitTypeDef中的成员配置在CubeMX配置中会涉及,SPI_InitTypeDef结构体各成员有哪些配置选项,各配置选项有何意义,可参考上面的宏定义及其注释理解。

SPI的句柄结构体定义及其操作函数声明如下:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_spi.h

/**
  * @brief  SPI handle Structure definition
  */
typedef struct __SPI_HandleTypeDef
{
  SPI_TypeDef                *Instance;      /*!< SPI registers base address               */

  SPI_InitTypeDef            Init;           /*!< SPI communication parameters             */

  uint8_t                    *pTxBuffPtr;    /*!< Pointer to SPI Tx transfer Buffer        */

  uint16_t                   TxXferSize;     /*!< SPI Tx Transfer size                     */

  __IO uint16_t              TxXferCount;    /*!< SPI Tx Transfer Counter                  */

  uint8_t                    *pRxBuffPtr;    /*!< Pointer to SPI Rx transfer Buffer        */

  uint16_t                   RxXferSize;     /*!< SPI Rx Transfer size                     */

  __IO uint16_t              RxXferCount;    /*!< SPI Rx Transfer Counter                  */

  uint32_t                   CRCSize;        /*!< SPI CRC size used for the transfer       */

  void (*RxISR)(struct __SPI_HandleTypeDef *hspi);   /*!< function pointer on Rx ISR       */

  void (*TxISR)(struct __SPI_HandleTypeDef *hspi);   /*!< function pointer on Tx ISR       */

  DMA_HandleTypeDef          *hdmatx;        /*!< SPI Tx DMA Handle parameters             */

  DMA_HandleTypeDef          *hdmarx;        /*!< SPI Rx DMA Handle parameters             */

  HAL_LockTypeDef            Lock;           /*!< Locking object                           */

  __IO HAL_SPI_StateTypeDef  State;          /*!< SPI communication state                  */

  __IO uint32_t              ErrorCode;      /*!< SPI Error code                           */

#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
  void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi);             /*!< SPI Tx Completed callback          */
  void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi);             /*!< SPI Rx Completed callback          */
  void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi);           /*!< SPI TxRx Completed callback        */
  void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);         /*!< SPI Tx Half Completed callback     */
  void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);         /*!< SPI Rx Half Completed callback     */
  void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);       /*!< SPI TxRx Half Completed callback   */
  void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi);              /*!< SPI Error callback                 */
  void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi);          /*!< SPI Abort callback                 */
  void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi);            /*!< SPI Msp Init callback              */
  void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi);          /*!< SPI Msp DeInit callback            */

#endif  /* USE_HAL_SPI_REGISTER_CALLBACKS */
} SPI_HandleTypeDef;


/* Initialization/de-initialization functions  ********************************/
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi);
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);
void HAL_SPI_MspDeInit(SPI_HandleTypeDef *hspi);

/* Callbacks Register/UnRegister functions  ***********************************/
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
HAL_StatusTypeDef HAL_SPI_RegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID, pSPI_CallbackTypeDef pCallback);
HAL_StatusTypeDef HAL_SPI_UnRegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID);
#endif /* USE_HAL_SPI_REGISTER_CALLBACKS */

/* I/O operation functions  ***************************************************/
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
                                          uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                             uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size);
HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi);
/* Transfer Abort functions */
HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_Abort_IT(SPI_HandleTypeDef *hspi);

void HAL_SPI_IRQHandler(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi);

/* Peripheral State and Error functions ***************************************/
HAL_SPI_StateTypeDef HAL_SPI_GetState(SPI_HandleTypeDef *hspi);
uint32_t             HAL_SPI_GetError(SPI_HandleTypeDef *hspi);

SPI的HAL API函数跟前面介绍UART API或HAL库详解时的分类类似,主要也可分为初始化/反初始化函数、I/O操作函数、回调函数、状态与错误函数。

2.2 QSPI配置

STM32 HAL库同样提供了QSPI初始化结构体、句柄结构体及相关操作函数来配置QSPI外设,下面先看看QSPI初始化结构体定义:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_qspi.h

/**
  * @brief  QSPI Init structure definition
  */
typedef struct
{
  uint32_t ClockPrescaler;     /* Specifies the prescaler factor for generating clock based on the AHB clock.
                                  This parameter can be a number between 0 and 255 */
  uint32_t FifoThreshold;      /* Specifies the threshold number of bytes in the FIFO (used only in indirect mode)
                                  This parameter can be a value between 1 and 16 */
  uint32_t SampleShifting;     /* Specifies the Sample Shift. The data is sampled 1/2 clock cycle delay later to
                                  take in account external signal delays. (It should be QSPI_SAMPLE_SHIFTING_NONE in DDR mode)
                                  This parameter can be a value of @ref QSPI_SampleShifting */
  uint32_t FlashSize;          /* Specifies the Flash Size. FlashSize+1 is effectively the number of address bits
                                  required to address the flash memory. The flash capacity can be up to 4GB
                                  (addressed using 32 bits) in indirect mode, but the addressable space in
                                  memory-mapped mode is limited to 256MB
                                  This parameter can be a number between 0 and 31 */
  uint32_t ChipSelectHighTime; /* Specifies the Chip Select High Time. ChipSelectHighTime+1 defines the minimum number
                                  of clock cycles which the chip select must remain high between commands.
                                  This parameter can be a value of @ref QSPI_ChipSelectHighTime */
  uint32_t ClockMode;          /* Specifies the Clock Mode. It indicates the level that clock takes between commands.
                                  This parameter can be a value of @ref QSPI_ClockMode */
#if defined(QUADSPI_CR_DFM)
  uint32_t FlashID;            /* Specifies the Flash which will be used,
                                  This parameter can be a value of @ref QSPI_Flash_Select */
  uint32_t DualFlash;          /* Specifies the Dual Flash Mode State
                                  This parameter can be a value of @ref QSPI_DualFlash_Mode */
#endif
}QSPI_InitTypeDef;

/** @defgroup QSPI_SampleShifting QSPI Sample Shifting
  */
#define QSPI_SAMPLE_SHIFTING_NONE      0x00000000U                   /*!<No clock cycle shift to sample data*/
#define QSPI_SAMPLE_SHIFTING_HALFCYCLE ((uint32_t)QUADSPI_CR_SSHIFT) /*!<1/2 clock cycle shift to sample data*/

/** @defgroup QSPI_ChipSelectHighTime QSPI ChipSelect High Time
  */
#define QSPI_CS_HIGH_TIME_1_CYCLE      0x00000000U                                         /*!<nCS stay high for at least 1 clock cycle between commands*/
#define QSPI_CS_HIGH_TIME_2_CYCLE      ((uint32_t)QUADSPI_DCR_CSHT_0)                      /*!<nCS stay high for at least 2 clock cycles between commands*/
......
#define QSPI_CS_HIGH_TIME_8_CYCLE      ((uint32_t)QUADSPI_DCR_CSHT)                        /*!<nCS stay high for at least 8 clock cycles between commands*/

/** @defgroup QSPI_ClockMode QSPI Clock Mode
  */
#define QSPI_CLOCK_MODE_0              0x00000000U                    /*!<Clk stays low while nCS is released*/
#define QSPI_CLOCK_MODE_3              ((uint32_t)QUADSPI_DCR_CKMODE) /*!<Clk goes high while nCS is released*/

#if defined(QUADSPI_CR_DFM)
/** @defgroup QSPI_Flash_Select QSPI Flash Select
  */
#define QSPI_FLASH_ID_1                0x00000000U                 /*!<FLASH 1 selected*/
#define QSPI_FLASH_ID_2                ((uint32_t)QUADSPI_CR_FSEL) /*!<FLASH 2 selected*/

  /** @defgroup QSPI_DualFlash_Mode QSPI Dual Flash Mode
  */
#define QSPI_DUALFLASH_ENABLE          ((uint32_t)QUADSPI_CR_DFM) /*!<Dual-flash mode enabled*/
#define QSPI_DUALFLASH_DISABLE         0x00000000U                /*!<Dual-flash mode disabled*/
#endif

QSPI初始化结构体QSPI_InitTypeDef中的成员配置在CubeMX配置中会涉及,QSPI_InitTypeDef结构体各成员有哪些配置选项,各配置选项有何意义,可参考上面的宏定义及其注释理解,最后两个成员是针对QSPI双闪存模式的。

宏定义未给出的结构体成员配置在注释中可获知其配置值,比如时钟预分频值ClockPrescaler的取值范围是0-255,可以实现1-256级别的分频;传输队列阈值FifoThreshold的取值范围是1-16,仅用于间接模式(QSPI除间接模式外,还有自动轮询模式和内存映射模式);外部存储器大小FlashSize指的是寻址位数,其取值范围是0-31(表示1-32位寻址),在间接模式下FlashSize可设到31(即32位寻址4GB容量),但在内存映射模式下最高设到27(即28位寻址256MB容量)。

前面介绍了QSPI的命令序列,QSPI通过命令与Flash通信,每条命令包括指令、地址、交替字节、空指令和数据这五个阶段,HAL库为QSPI命令序列定义了命令结构体QSPI_CommandTypeDef如下:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_qspi.h

/**
  * @brief  QSPI Command structure definition
  */
typedef struct
{
  uint32_t Instruction;        /* Specifies the Instruction to be sent
                                  This parameter can be a value (8-bit) between 0x00 and 0xFF */
  uint32_t Address;            /* Specifies the Address to be sent (Size from 1 to 4 bytes according AddressSize)
                                  This parameter can be a value (32-bits) between 0x0 and 0xFFFFFFFF */
  uint32_t AlternateBytes;     /* Specifies the Alternate Bytes to be sent (Size from 1 to 4 bytes according AlternateBytesSize)
                                  This parameter can be a value (32-bits) between 0x0 and 0xFFFFFFFF */
  uint32_t AddressSize;        /* Specifies the Address Size
                                  This parameter can be a value of @ref QSPI_AddressSize */
  uint32_t AlternateBytesSize; /* Specifies the Alternate Bytes Size
                                  This parameter can be a value of @ref QSPI_AlternateBytesSize */
  uint32_t DummyCycles;        /* Specifies the Number of Dummy Cycles.
                                  This parameter can be a number between 0 and 31 */
  uint32_t InstructionMode;    /* Specifies the Instruction Mode
                                  This parameter can be a value of @ref QSPI_InstructionMode */
  uint32_t AddressMode;        /* Specifies the Address Mode
                                  This parameter can be a value of @ref QSPI_AddressMode */
  uint32_t AlternateByteMode;  /* Specifies the Alternate Bytes Mode
                                  This parameter can be a value of @ref QSPI_AlternateBytesMode */
  uint32_t DataMode;           /* Specifies the Data Mode (used for dummy cycles and data phases)
                                  This parameter can be a value of @ref QSPI_DataMode */
  uint32_t NbData;             /* Specifies the number of data to transfer. (This is the number of bytes)
                                  This parameter can be any value between 0 and 0xFFFFFFFF (0 means undefined length
                                  until end of memory)*/
  uint32_t DdrMode;            /* Specifies the double data rate mode for address, alternate byte and data phase
                                  This parameter can be a value of @ref QSPI_DdrMode */
  uint32_t DdrHoldHalfCycle;   /* Specifies if the DDR hold is enabled. When enabled it delays the data
                                  output by one half of system clock in DDR mode.
                                  Not available on all devices.
                                  This parameter can be a value of @ref QSPI_DdrHoldHalfCycle */
  uint32_t SIOOMode;           /* Specifies the send instruction only once mode
                                  This parameter can be a value of @ref QSPI_SIOOMode */
}QSPI_CommandTypeDef;

/** @defgroup QSPI_AddressSize QSPI Address Size
  */
#define QSPI_ADDRESS_8_BITS            0x00000000U                      /*!<8-bit address*/
#define QSPI_ADDRESS_16_BITS           ((uint32_t)QUADSPI_CCR_ADSIZE_0) /*!<16-bit address*/
#define QSPI_ADDRESS_24_BITS           ((uint32_t)QUADSPI_CCR_ADSIZE_1) /*!<24-bit address*/
#define QSPI_ADDRESS_32_BITS           ((uint32_t)QUADSPI_CCR_ADSIZE)   /*!<32-bit address*/

/** @defgroup QSPI_AlternateBytesSize QSPI Alternate Bytes Size
  */
#define QSPI_ALTERNATE_BYTES_8_BITS    0x00000000U                      /*!<8-bit alternate bytes*/
#define QSPI_ALTERNATE_BYTES_16_BITS   ((uint32_t)QUADSPI_CCR_ABSIZE_0) /*!<16-bit alternate bytes*/
#define QSPI_ALTERNATE_BYTES_24_BITS   ((uint32_t)QUADSPI_CCR_ABSIZE_1) /*!<24-bit alternate bytes*/
#define QSPI_ALTERNATE_BYTES_32_BITS   ((uint32_t)QUADSPI_CCR_ABSIZE)   /*!<32-bit alternate bytes*/

/** @defgroup QSPI_InstructionMode QSPI Instruction Mode
*/
#define QSPI_INSTRUCTION_NONE          0x00000000U                     /*!<No instruction*/
#define QSPI_INSTRUCTION_1_LINE        ((uint32_t)QUADSPI_CCR_IMODE_0) /*!<Instruction on a single line*/
#define QSPI_INSTRUCTION_2_LINES       ((uint32_t)QUADSPI_CCR_IMODE_1) /*!<Instruction on two lines*/
#define QSPI_INSTRUCTION_4_LINES       ((uint32_t)QUADSPI_CCR_IMODE)   /*!<Instruction on four lines*/

/** @defgroup QSPI_AddressMode QSPI Address Mode
*/
#define QSPI_ADDRESS_NONE              0x00000000U                      /*!<No address*/
#define QSPI_ADDRESS_1_LINE            ((uint32_t)QUADSPI_CCR_ADMODE_0) /*!<Address on a single line*/
#define QSPI_ADDRESS_2_LINES           ((uint32_t)QUADSPI_CCR_ADMODE_1) /*!<Address on two lines*/
#define QSPI_ADDRESS_4_LINES           ((uint32_t)QUADSPI_CCR_ADMODE)   /*!<Address on four lines*/

/** @defgroup QSPI_AlternateBytesMode QSPI Alternate Bytes Mode
*/
#define QSPI_ALTERNATE_BYTES_NONE      0x00000000U                      /*!<No alternate bytes*/
#define QSPI_ALTERNATE_BYTES_1_LINE    ((uint32_t)QUADSPI_CCR_ABMODE_0) /*!<Alternate bytes on a single line*/
#define QSPI_ALTERNATE_BYTES_2_LINES   ((uint32_t)QUADSPI_CCR_ABMODE_1) /*!<Alternate bytes on two lines*/
#define QSPI_ALTERNATE_BYTES_4_LINES   ((uint32_t)QUADSPI_CCR_ABMODE)   /*!<Alternate bytes on four lines*/

/** @defgroup QSPI_DataMode QSPI Data Mode
  */
#define QSPI_DATA_NONE                 0x00000000U                     /*!<No data*/
#define QSPI_DATA_1_LINE               ((uint32_t)QUADSPI_CCR_DMODE_0) /*!<Data on a single line*/
#define QSPI_DATA_2_LINES              ((uint32_t)QUADSPI_CCR_DMODE_1) /*!<Data on two lines*/
#define QSPI_DATA_4_LINES              ((uint32_t)QUADSPI_CCR_DMODE)   /*!<Data on four lines*/

/** @defgroup QSPI_DdrMode QSPI DDR Mode
  */
#define QSPI_DDR_MODE_DISABLE          0x00000000U                  /*!<Double data rate mode disabled*/
#define QSPI_DDR_MODE_ENABLE           ((uint32_t)QUADSPI_CCR_DDRM) /*!<Double data rate mode enabled*/

/** @defgroup QSPI_DdrHoldHalfCycle QSPI DDR Data Output Delay
  */
#define QSPI_DDR_HHC_ANALOG_DELAY      0x00000000U                  /*!<Delay the data output using analog delay in DDR mode*/
#if defined(QUADSPI_CCR_DHHC)
#define QSPI_DDR_HHC_HALF_CLK_DELAY    ((uint32_t)QUADSPI_CCR_DHHC) /*!<Delay the data output by one half of system clock in DDR mode*/
#endif

/** @defgroup QSPI_SIOOMode QSPI Send Instruction Mode
  */
#define QSPI_SIOO_INST_EVERY_CMD       0x00000000U                  /*!<Send instruction on every transaction*/
#define QSPI_SIOO_INST_ONLY_FIRST_CMD  ((uint32_t)QUADSPI_CCR_SIOO) /*!<Send instruction only for the first command*/

QSPI命令结构体QSPI_CommandTypeDef成员包含了QSPI命令序列五个阶段的线数、位宽、模式等信息,在使用QSPI与Flash通信时需要配置该命令结构体成员,并通过HAL库提供的函数HAL_QSPI_Command向Flash设备发送命令。

QSPI句柄结构体定义及相关操作函数声明如下:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_qspi.h

/**
  * @brief  QSPI Handle Structure definition
  */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
typedef struct __QSPI_HandleTypeDef
#else
typedef struct
#endif
{
  QUADSPI_TypeDef            *Instance;        /* QSPI registers base address        */
  QSPI_InitTypeDef           Init;             /* QSPI communication parameters      */
  uint8_t                    *pTxBuffPtr;      /* Pointer to QSPI Tx transfer Buffer */
  __IO uint32_t              TxXferSize;       /* QSPI Tx Transfer size              */
  __IO uint32_t              TxXferCount;      /* QSPI Tx Transfer Counter           */
  uint8_t                    *pRxBuffPtr;      /* Pointer to QSPI Rx transfer Buffer */
  __IO uint32_t              RxXferSize;       /* QSPI Rx Transfer size              */
  __IO uint32_t              RxXferCount;      /* QSPI Rx Transfer Counter           */
  DMA_HandleTypeDef          *hdma;            /* QSPI Rx/Tx DMA Handle parameters   */
  __IO HAL_LockTypeDef       Lock;             /* Locking object                     */
  __IO HAL_QSPI_StateTypeDef State;            /* QSPI communication state           */
  __IO uint32_t              ErrorCode;        /* QSPI Error code                    */
  uint32_t                   Timeout;          /* Timeout for the QSPI memory access */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
  void (* ErrorCallback)        (struct __QSPI_HandleTypeDef *hqspi);
  void (* AbortCpltCallback)    (struct __QSPI_HandleTypeDef *hqspi);
  void (* FifoThresholdCallback)(struct __QSPI_HandleTypeDef *hqspi);
  void (* CmdCpltCallback)      (struct __QSPI_HandleTypeDef *hqspi);
  void (* RxCpltCallback)       (struct __QSPI_HandleTypeDef *hqspi);
  void (* TxCpltCallback)       (struct __QSPI_HandleTypeDef *hqspi);
  void (* RxHalfCpltCallback)   (struct __QSPI_HandleTypeDef *hqspi);
  void (* TxHalfCpltCallback)   (struct __QSPI_HandleTypeDef *hqspi);
  void (* StatusMatchCallback)  (struct __QSPI_HandleTypeDef *hqspi);
  void (* TimeOutCallback)      (struct __QSPI_HandleTypeDef *hqspi);

  void (* MspInitCallback)      (struct __QSPI_HandleTypeDef *hqspi);
  void (* MspDeInitCallback)    (struct __QSPI_HandleTypeDef *hqspi);
#endif
}QSPI_HandleTypeDef;


/* Initialization/de-initialization functions  ********************************/
HAL_StatusTypeDef     HAL_QSPI_Init     (QSPI_HandleTypeDef *hqspi);
HAL_StatusTypeDef     HAL_QSPI_DeInit   (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_MspInit  (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_MspDeInit(QSPI_HandleTypeDef *hqspi);

/* IO operation functions *****************************************************/
/* QSPI IRQ handler method */
void                  HAL_QSPI_IRQHandler(QSPI_HandleTypeDef *hqspi);

/* QSPI indirect mode */
HAL_StatusTypeDef     HAL_QSPI_Command      (QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, uint32_t Timeout);
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_StatusTypeDef     HAL_QSPI_Command_IT   (QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd);
HAL_StatusTypeDef     HAL_QSPI_Transmit_IT  (QSPI_HandleTypeDef *hqspi, uint8_t *pData);
HAL_StatusTypeDef     HAL_QSPI_Receive_IT   (QSPI_HandleTypeDef *hqspi, uint8_t *pData);
HAL_StatusTypeDef     HAL_QSPI_Transmit_DMA (QSPI_HandleTypeDef *hqspi, uint8_t *pData);
HAL_StatusTypeDef     HAL_QSPI_Receive_DMA  (QSPI_HandleTypeDef *hqspi, uint8_t *pData);

/* Callback functions in non-blocking modes ***********************************/
void                  HAL_QSPI_ErrorCallback        (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_AbortCpltCallback    (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_FifoThresholdCallback(QSPI_HandleTypeDef *hqspi);

/* QSPI indirect mode */
void                  HAL_QSPI_CmdCpltCallback      (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_RxCpltCallback       (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_TxCpltCallback       (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_RxHalfCpltCallback   (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_TxHalfCpltCallback   (QSPI_HandleTypeDef *hqspi);

#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
/* QSPI callback registering/unregistering */
HAL_StatusTypeDef     HAL_QSPI_RegisterCallback     (QSPI_HandleTypeDef *hqspi, HAL_QSPI_CallbackIDTypeDef CallbackId, pQSPI_CallbackTypeDef pCallback);
HAL_StatusTypeDef     HAL_QSPI_UnRegisterCallback   (QSPI_HandleTypeDef *hqspi, HAL_QSPI_CallbackIDTypeDef CallbackId);
#endif

/* Peripheral Control and State functions  ************************************/
HAL_QSPI_StateTypeDef HAL_QSPI_GetState        (QSPI_HandleTypeDef *hqspi);
uint32_t              HAL_QSPI_GetError        (QSPI_HandleTypeDef *hqspi);
HAL_StatusTypeDef     HAL_QSPI_Abort           (QSPI_HandleTypeDef *hqspi);
HAL_StatusTypeDef     HAL_QSPI_Abort_IT        (QSPI_HandleTypeDef *hqspi);
void                  HAL_QSPI_SetTimeout      (QSPI_HandleTypeDef *hqspi, uint32_t Timeout);
HAL_StatusTypeDef     HAL_QSPI_SetFifoThreshold(QSPI_HandleTypeDef *hqspi, uint32_t Threshold);
uint32_t              HAL_QSPI_GetFifoThreshold(QSPI_HandleTypeDef *hqspi);
#if defined(QUADSPI_CR_DFM)
HAL_StatusTypeDef     HAL_QSPI_SetFlashID      (QSPI_HandleTypeDef *hqspi, uint32_t FlashID);
#endif

QSPI API函数依然可以分为初始化/反初始化函数、I/O操作函数、回调函数、控制函数、状态与错误函数等五大类。

QSPI协议可以在以下三种模式下工作:

  • 间接模式(indirect mode):使用 QSPI 寄存器执行全部操作;
  • 状态轮询模式(status flag polling mode):周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断);
  • 内存映射模式(memory-mapped mode):外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器。

前面介绍的QSPI主要指工作在间接模式下的结构体定义和操作,针对后两种模式(状态轮询模式和内存映射模式)HAL库给出了额外的结构体定义和操作函数,QSPI后两种工作模式的相关结构体定义和函数声明如下:

// .\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_qspi.h

/**
  * @brief  QSPI Auto Polling mode configuration structure definition
  */
typedef struct
{
  uint32_t Match;              /* Specifies the value to be compared with the masked status register to get a match.
                                  This parameter can be any value between 0 and 0xFFFFFFFF */
  uint32_t Mask;               /* Specifies the mask to be applied to the status bytes received.
                                  This parameter can be any value between 0 and 0xFFFFFFFF */
  uint32_t Interval;           /* Specifies the number of clock cycles between two read during automatic polling phases.
                                  This parameter can be any value between 0 and 0xFFFF */
  uint32_t StatusBytesSize;    /* Specifies the size of the status bytes received.
                                  This parameter can be any value between 1 and 4 */
  uint32_t MatchMode;          /* Specifies the method used for determining a match.
                                  This parameter can be a value of @ref QSPI_MatchMode */
  uint32_t AutomaticStop;      /* Specifies if automatic polling is stopped after a match.
                                  This parameter can be a value of @ref QSPI_AutomaticStop */
}QSPI_AutoPollingTypeDef;

/**
  * @brief  QSPI Memory Mapped mode configuration structure definition
  */
typedef struct
{
  uint32_t TimeOutPeriod;      /* Specifies the number of clock to wait when the FIFO is full before to release the chip select.
                                  This parameter can be any value between 0 and 0xFFFF */
  uint32_t TimeOutActivation;  /* Specifies if the timeout counter is enabled to release the chip select.
                                  This parameter can be a value of @ref QSPI_TimeOutActivation */
}QSPI_MemoryMappedTypeDef;

/** @defgroup QSPI_MatchMode QSPI Match Mode
  */
#define QSPI_MATCH_MODE_AND            0x00000000U                /*!<AND match mode between unmasked bits*/
#define QSPI_MATCH_MODE_OR             ((uint32_t)QUADSPI_CR_PMM) /*!<OR match mode between unmasked bits*/

/** @defgroup QSPI_AutomaticStop QSPI Automatic Stop
  */
#define QSPI_AUTOMATIC_STOP_DISABLE    0x00000000U                 /*!<AutoPolling stops only with abort or QSPI disabling*/
#define QSPI_AUTOMATIC_STOP_ENABLE     ((uint32_t)QUADSPI_CR_APMS) /*!<AutoPolling stops as soon as there is a match*/

/** @defgroup QSPI_TimeOutActivation QSPI Timeout Activation
  */
#define QSPI_TIMEOUT_COUNTER_DISABLE   0x00000000U                 /*!<Timeout counter disabled, nCS remains active*/
#define QSPI_TIMEOUT_COUNTER_ENABLE    ((uint32_t)QUADSPI_CR_TCEN) /*!<Timeout counter enabled, nCS released when timeout expires*/


/* IO operation functions *****************************************************/
/* QSPI status flag polling mode */
HAL_StatusTypeDef     HAL_QSPI_AutoPolling   (QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, QSPI_AutoPollingTypeDef *cfg, uint32_t Timeout);
HAL_StatusTypeDef     HAL_QSPI_AutoPolling_IT(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, QSPI_AutoPollingTypeDef *cfg);

/* QSPI memory-mapped mode */
HAL_StatusTypeDef     HAL_QSPI_MemoryMapped(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, QSPI_MemoryMappedTypeDef *cfg);

/* Callback functions in non-blocking modes ***********************************/
/* QSPI status flag polling mode */
void                  HAL_QSPI_StatusMatchCallback  (QSPI_HandleTypeDef *hqspi);

/* QSPI memory-mapped mode */
void                  HAL_QSPI_TimeOutCallback      (QSPI_HandleTypeDef *hqspi);

三、QSPI读写Serial Flash示例

FLSAH存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于EEPROM,现在基本取代了它的地位。我们生活中常用的U盘、SD卡、SSD固态硬盘以及我们STM32芯片内部用于存储程序的设备,都是FLASH类型的存储器。在存储控制上,最主要的区别是FLASH芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。

下面以STM32L475潘多拉开发板上自带的W25Q128 Flash芯片作为QSPI读写的串行Flash外设。

3.1 W25Q128简介

W25Q128是华邦公司推出的大容量 SPI FLASH 产品,W25Q128 容量为 128Mb(也即 16M 字节),该系列还有 W25Q80/16/32/64 等型号。

W25Q128 将 16M 的容量分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为16 个扇区(Sector),每个扇区 4K 个字节。W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

W25Q128 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80MHz(双输出时相当于 160MHz,四输出时相当于 320M)。

W25Q128芯片的引脚定义如下:
W25Q128引脚图示
W26Q128引脚定义
W25Q128的功能框图如下:
W25Q128功能框图
W25Q128 Flash写入数据和EEPROM类似,每次最多只能写入一页(也即256个字节),不能跨页写入,因为W25Q128必须在写满一页后等待Flash将数据从缓存搬移到非易失区,才能再次往里写下一页数据。

W25Q128 Flash还有一个特点就是只能写0,不能写1,要想将0写成1必须首先进行擦除操作,因此通常要改写某部分空间的数据,必须首先对该部分物理存储空间进行擦除,最小的擦除单位为一个扇区sector(也即4K个字节),扇区擦除就是将这整个扇区每个字节全部变成 0xFF。

W25Q128 Serial Flash操作框图如下:
W25Q128串行flash操作框图
W25Q128内部有一个“SPI Command & Control Logic”,可以通过 SPI 接口向其发送指令,从而执行相应操作。指令的长度是不定的,有单字节的,也有多字节的,W25Qxx 一共具有 34 个操作指令,下面只列举常用的 12 个:
W25Q128常用操作指令
下面以Read ID命令为例,展示W25Q128的命令序列图如下:
W25Q128 Read ID命令SPI模式
通过QSPI接口发送给W25Q128 Flash的命令结构体QSPI_CommandTypeDef的成员配置主要就是靠上图所示的W25Q128命令序列图,限于篇幅就不一一展示了,W25Q128更多命令序列图参考:W25Q128FV Datasheet

3.2 QSPI读写W25Q128工程示例

本示例使用STM32L475潘多拉开发板及其板载的W25Q128 Flash芯片,二者默认使用QSPI接口进行通信,其连接原理图如下:
W25Q128与STM32L475连接原理图

3.2.1 CubeMX配置QUADSPI

首先使用CubeMX配置QSPI外设,由于本示例需要使用串口USART1与开发板交互,故在前篇《STM32之CubeL4(二)—USART + DMA + HAL》示例工程USART1_IT_Printf的基础上继续增加QSPI驱动。如果想保留USART1的DMA配置,也可在示例工程USART1_DMA的基础上继续增加QSPI驱动,这里我们选择带DMA的示例工程作为基础。

新建文件夹QSPI_W25Q128,从USART1_DMA工程复制USART1_DMA.ioc文件到文件夹QSPI_W25Q128,并将该文件重命名为QSPI_W25Q128.ioc,打开QSPI_W25Q128.ioc文件,QSPI外设配置如下图所示:
STM32L475 QSPI配置
STM32L475芯片选择QUADSPI外设–>QUAD SPI Line,默认的引脚与我们接线原理图上的不一致,需要在相应的引脚通过鼠标左键选择相应的QUADSPI引脚功能,修改后的引脚配置如上图所示。

QUADSPI引脚配置好后,需要配置QSPI的初始化参数,常见的QSPI初始化参数成员见前面介绍的QSPI初始化结构体QSPI_InitTypeDef,这里将时钟预分频值配置为0也即QSPI设置最高速率80 Mbit/s(= 80 / (0 + 1) );这里使用SDR单倍数据速率,采用移位半个时钟周期;W25Q128容量为128 Mbit也即16 MByte,需要24位寻址,故FlashSize配置为23(= 24 - 1)。

时钟树与工程管理不需要修改,直接点击GENERATE CODE生成工程代码,使用MDK5打开工程文件QSPI_W25Q128.uvprojx编译无错误。把示例工程USART1_DMA中的usart和gpio部分的代码复制过来,主要复制支持printf函数功能的I / O重定向代码,QSPI_W25Q128工程新增usart1和gpio部分代码后重新编译无报错。

3.2.2 新增W25Q128驱动接口

HAL库中QSPI发送 / 接收函数参数中并没有待传输数据长度参数,只有待传输的数据缓冲区首地址,查看HAL库HAL_QSPI_Transmit() / Receive()函数源码可知,待传输数据长度由命令结构体成员QSPI_CommandTypeDef.NbData指定。

从前面介绍的QSPI API函数看,STM32L475想要通过QSPI向W25Q128发送命令需要构建命令结构体QSPI_CommandTypeDef,每次构建命令结构体比较麻烦,我们新增一个函数专门实现构建命令结构体的功能,函数实现代码如下:

// Core\Src\w25qxx.c

/**
 * @brief	QSPI发送命令
 *
 * @param   instruction		要发送的指令
 * @param   address			发送到的目的地址
 * @param   dummyCycles		空指令周期数
 * @param   addressMode		地址模式; QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINES,QSPI_ADDRESS_4_LINES
 * @param   dataMode		数据模式; QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINES,QSPI_DATA_4_LINES
 * @param   dataSize        待传输的数据长度
 *
 * @return  uint8_t			QSPI_OK:正常
 *                      QSPI_ERROR:错误
 */
uint8_t QSPI_Send_CMD(uint32_t instruction, uint32_t address,uint32_t dummyCycles, 
                    uint32_t addressMode, uint32_t dataMode, uint32_t dataSize)
{
    QSPI_CommandTypeDef Cmdhandler;

    Cmdhandler.Instruction        = instruction;                 	
    Cmdhandler.Address            = address;
    Cmdhandler.AlternateBytes     = 0x00;
    Cmdhandler.AddressSize        = QSPI_ADDRESS_24_BITS;
    Cmdhandler.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;                             
    Cmdhandler.DummyCycles        = dummyCycles;                   
    Cmdhandler.InstructionMode    = QSPI_INSTRUCTION_1_LINE;				
    Cmdhandler.AddressMode        = addressMode;
    Cmdhandler.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;    					      				
    Cmdhandler.DataMode           = dataMode;
    Cmdhandler.NbData             = dataSize;            				              
    Cmdhandler.DdrMode            = QSPI_DDR_MODE_DISABLE;           	
    Cmdhandler.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;
    Cmdhandler.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;

    if(HAL_QSPI_Command(&hqspi, &Cmdhandler, 5000) != HAL_OK)
      return QSPI_ERROR;

    return QSPI_OK;
}

上面的函数为了减少参数个数,部分QSPI_CommandTypeDef成员设置为了默认值,当然损失了部分通用性,读者可以留出更多的成员由传入参数赋值。

STM32L475通过QSPI命令访问W25Q128,需要把W25Q128常用的命令及ID以宏定义的形式给出,代码如下:

// Core\Inc\w25qxx.h

//W25QX chip ID
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17
#define W25Q256 0XEF18

//W25X instruction list
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg1		0x05 
#define W25X_ReadStatusReg2		0x35 
#define W25X_ReadStatusReg3		0x15 
#define W25X_WriteStatusReg1    0x01 
#define W25X_WriteStatusReg2    0x31 
#define W25X_WriteStatusReg3    0x11 
#define W25X_ReadData			0x03 
#define W25X_FastReadData		0x0B 
#define W25X_FastReadDual		0x3B
#define W25X_FastReadQuad       0x6B 
#define W25X_PageProgram		0x02
#define W25X_QuadPageProgram    0x32 
#define W25X_BlockErase			0xD8 
#define W25X_SectorErase		0x20 
#define W25X_ChipErase			0xC7 
#define W25X_PowerDown			0xB9 
#define W25X_ReleasePowerDown	0xAB 
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 
#define W25X_Enable4ByteAddr    0xB7
#define W25X_Exit4ByteAddr      0xE9
#define W25X_SetReadParam		0xC0 
#define W25X_EnterQPIMode       0x38
#define W25X_ExitQPIMode        0xFF

//W25QXX Error Code
#define W25QXX_OK     0
#define W25QXX_ERROR  1
#define QSPI_OK     0
#define QSPI_ERROR  1

// W25QXX API function declaration
uint8_t QSPI_Send_CMD(uint32_t instruction, uint32_t address,uint32_t dummyCycles, uint32_t addressMode, uint32_t dataMode, uint32_t dataSize);

对W25Q128的操作主要有读取设备ID、读取数据、擦除扇区、写入数据等,对W25Q128的初始化已经由MX_QUADSPI_Init实现了。由于读写数据量可能会比较大,所以本示例对读取 / 写入数据的操作采用QUAD SPI模式,其余操作则采用Standard SPI模式。

W25Q128读取数据在Standard SPI模式和QUAD SPI模式下的命令序列对比如下:
W25Q128标准SPI读取数据命令序列
W25Q128 QUAD SPI模式读取数据命令序列
W25Q128写入数据在Standard SPI模式和QUAD SPI模式下的命令序列对比如下:
W25Q128标准SPI页写入数据命令序列
W25Q128 QUAD SPI模式也写入数据命令序列
从上面的命令序列图可以获知QSPI命令结构体QSPI_CommandTypeDef的成员如何配置,也即前面我们实现的函数QSPI_Send_CMD应该传入什么参数值。

了解了W25Q128的命令序列图,接下来就可以实现操作函数了,实现的函数代码如下:

// Core\Src\w25qxx.c

/**
 * W25Q128容量为16M字节,共有256个Block,4096个Sector
 * 1 Block = 16 Sectors
 * 1 Sector = 16 Pages = 4096 Bytes
 * 1 Pages = 256 Bytes
*/
uint16_t W25QXX_TYPE = W25Q128;
uint8_t W25QXX_BUFFER[4096];

/**
 * @brief	读取W25QXX芯片ID
 *
 * @param   void
 *
 * @return  uint16_t    返回值如下
 * 				0XEF13,表示芯片型号为W25Q80
 * 				0XEF14,表示芯片型号为W25Q16
 * 				0XEF15,表示芯片型号为W25Q32
 * 				0XEF16,表示芯片型号为W25Q64
 * 				0XEF17,表示芯片型号为W25Q128
 * 				0XEF18,表示芯片型号为W25Q256
 */
uint16_t W25QXX_ReadID(void)
{
    uint8_t ID[2];
    uint16_t deviceID;

    QSPI_Send_CMD(W25X_ManufactDeviceID, 0x00, 0, QSPI_ADDRESS_1_LINE, QSPI_DATA_1_LINE, sizeof(ID));
    HAL_QSPI_Receive(&hqspi, ID, 5000);

    deviceID = (ID[0] << 8) | ID[1];

    return deviceID;
}

/**
 * @brief	读取SPI FLASH数据
 *
 * @param   pBuf			数据存储区
 * @param   ReadAddr		开始读取的地址(最大32bit)
 * @param   ReadSize	    要读取的字节数(最大32bit)
 *
 * @return  void
 */
uint8_t W25QXX_Read(uint8_t *pBuf, uint32_t ReadAddr, uint32_t ReadSize)
{
	//QSPI_Send_CMD(W25X_ReadData, ReadAddr, 0, QSPI_ADDRESS_1_LINE, QSPI_DATA_1_LINE, ReadSize);
    QSPI_Send_CMD(W25X_FastReadQuad, ReadAddr, 8, QSPI_ADDRESS_1_LINE, QSPI_DATA_4_LINES, ReadSize);

    if(HAL_QSPI_Receive(&hqspi, pBuf, 5000) != HAL_OK)
        return W25QXX_ERROR;

    return W25QXX_OK;
}

/**
 * @brief	等待空闲
 *
 * @param   void
 *
 * @return  void
 */
void W25QXX_Wait_Busy(void)
{
    uint8_t status = 1;

    while((status & 0x01) == 0x01){
        QSPI_Send_CMD(W25X_ReadStatusReg1, 0x00, 0, QSPI_ADDRESS_NONE, QSPI_DATA_1_LINE, 1);
        HAL_QSPI_Receive(&hqspi, &status, 5000);
    }
}

/**
 * @brief	擦除一个扇区
 *
 * @param   EraseAddr 要擦除的扇区地址
 *
 * @return  void
 */
void W25QXX_Erase_Sector(uint32_t EraseAddr)
{
    W25QXX_Write_Enable();
    W25QXX_Wait_Busy();

    QSPI_Send_CMD(W25X_SectorErase, EraseAddr, 0, QSPI_ADDRESS_1_LINE, QSPI_DATA_NONE, 1);
    W25QXX_Wait_Busy();
}

/**
 * @brief	在指定地址开始写入最大一页的数据
 *
 * @param   pBuf			数据存储区
 * @param   WriteAddr		开始写入的地址(最大32bit)
 * @param   WriteSize	    要写入的字节数(最大1 Page = 256 Bytes),该数不应该超过该页的剩余字节数
 *
 * @return  void
 */
void W25QXX_Write_Page(uint8_t *pBuf, uint32_t WriteAddr, uint32_t WriteSize)
{
    if(WriteSize > W25X_PAGE_SIZE)
        return;
    
    W25QXX_Write_Enable();
    W25QXX_Wait_Busy();

    //QSPI_Send_CMD(W25X_PageProgram, WriteAddr, 0, QSPI_ADDRESS_1_LINE, QSPI_DATA_1_LINE, WriteSize);
    QSPI_Send_CMD(W25X_QuadPageProgram, WriteAddr, 0, QSPI_ADDRESS_1_LINE, QSPI_DATA_4_LINES, WriteSize);
    HAL_QSPI_Transmit(&hqspi, pBuf, 5000);
    W25QXX_Wait_Busy();
}

/**
 * @brief	无检验写SPI FLASH
 * 			必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 * 			具有自动换页功能
 * 			在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 *
 * @param   pBuf			数据存储区
 * @param   WriteAddr		开始写入的地址(最大32bit)
 * @param   WriteSize	    要写入的字节数(最大32bit)
 *
 * @return  void
 */
void W25QXX_Write_NoCheck(uint8_t *pBuf, uint32_t WriteAddr, uint32_t WriteSize)
{
    uint32_t pageremain = W25X_PAGE_SIZE - WriteAddr % W25X_PAGE_SIZE; //单页剩余的字节数

    if(WriteSize <= pageremain)
        pageremain = WriteSize;

    while(1)
    {
        W25QXX_Write_Page(pBuf, WriteAddr, pageremain);

        if(WriteSize == pageremain)
            break;              //写入结束了
        else                    //WriteSize > pageremain
        {
            pBuf += pageremain;
            WriteAddr += pageremain;
            WriteSize -= pageremain;

            pageremain = (WriteSize > W25X_PAGE_SIZE) ? W25X_PAGE_SIZE : WriteSize;
        }
    }
}


/**
 * @brief	写SPI FLASH
 * 			在指定地址开始写入指定长度的数据
 * 			该函数带擦除操作!
 *
 * @param   pBuf			数据存储区
 * @param   WriteAddr		开始写入的地址(最大32bit)
 * @param   WriteSize	    要写入的字节数(最大32bit)
 *
 * @return  void
 */
void W25QXX_Write(uint8_t *pBuf, uint32_t WriteAddr, uint32_t WriteSize)
{
    uint32_t secpos = WriteAddr / W25X_SECTOR_SIZE; //扇区地址
    uint32_t secoff = WriteAddr % W25X_SECTOR_SIZE; //在扇区内的偏移
    uint32_t secremain = W25X_SECTOR_SIZE - secoff; //扇区剩余空间大小
    uint32_t i;
    uint8_t * W25QXX_BUF = W25QXX_BUFFER;

    if(WriteSize <= secremain)
        secremain = WriteSize; //不大于4096个字节

    while(1)
    {
        W25QXX_Read(W25QXX_BUF, secpos * W25X_SECTOR_SIZE, W25X_SECTOR_SIZE); //读出整个扇区的内容

        for(i = 0; i < secremain; i++) //校验数据
        {
            if(W25QXX_BUF[secoff + i] != 0XFF)
                break; //需要擦除
        }

        if(i < secremain) //需要擦除
        {
            W25QXX_Erase_Sector(secpos * W25X_SECTOR_SIZE);//擦除这个扇区

            for(i = 0; i < secremain; i++)	 //复制
            {
                W25QXX_BUF[i + secoff] = pBuf[i];
            }

            W25QXX_Write_NoCheck(W25QXX_BUF, secpos * W25X_SECTOR_SIZE, W25X_SECTOR_SIZE); //写入整个扇区

        }
        else
            W25QXX_Write_NoCheck(pBuf, WriteAddr, secremain); //写已经擦除了的,直接写入扇区剩余区间.

        if(WriteSize == secremain)
            break; //写入结束了
        else//写入未结束
        {
            secpos++;       //扇区地址增1
            secoff = 0;     //偏移位置为0

            pBuf += secremain;          //指针偏移
            WriteAddr += secremain;     //写地址偏移
            WriteSize -= secremain;		//字节数递减

            secremain = (WriteSize > W25X_SECTOR_SIZE) ? W25X_SECTOR_SIZE : WriteSize;
        }
    }
}


// Core\Inc\w25qxx.h

extern uint16_t W25QXX_TYPE;

// W25Q128 Page size and Sector size
#define W25X_PAGE_SIZE      256
#define W25X_SECTOR_SIZE    4096

// W25QXX API function declaration
#define W25QXX_Write_Enable()   QSPI_Send_CMD(W25X_WriteEnable, 0x00, 0, QSPI_ADDRESS_NONE, QSPI_DATA_NONE, 1)
#define W25QXX_Write_Disable()  QSPI_Send_CMD(W25X_WriteDisable, 0x00, 0, QSPI_ADDRESS_NONE, QSPI_DATA_NONE, 1)

uint16_t W25QXX_ReadID(void);
void W25QXX_Wait_Busy(void);
void W25QXX_Erase_Sector(uint32_t EraseAddr);
uint8_t W25QXX_Read(uint8_t *pBuf, uint32_t ReadAddr, uint32_t ReadSize);
void W25QXX_Write(uint8_t *pBuf, uint32_t WriteAddr, uint32_t WriteSize);

W25Q128的驱动API函数定义及声明文件w25qxx.c和w25qxx.h需要添加到MDK5工程QSPI_W25Q128.uvprojx中,重新编译工程无报错。

3.2.3 QSPI读写W25Q128

前面准备好了W25Q128 Flash的接口函数,下面在main函数中通过调用W25Q128 API实现对W25Q128 Flash的读取、擦除、写入操作,最后通过比较写入数据与读取数据是否一致,判断对W25Q128 Flash的访问测试是否成功。

在main.c中新增的W25Q128 Flash访问代码如下:

// Core\Src\main.c

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define TestAddr  0x007F0000			//测试数据存储地址
#define TestSize  0x100           //测试数据大小

/* USER CODE END PD */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

uint8_t wData[TestSize];
uint8_t rData[TestSize];

/* USER CODE END PV */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  uint32_t i = 0;

  /* USER CODE END 1 */
  ......
  /* USER CODE BEGIN 2 */

  printf(" W25Q128FV QuadSPI Test ....\r\n\r\n");
  
  /* 1- Read W25QXX Flash ID */
  W25QXX_TYPE = W25QXX_ReadID();

  printf(" W25Q128FV QuadSPI Device ID : 0x%4X\r\n",W25QXX_TYPE);
  if(W25QXX_TYPE == W25Q128)
    printf(" W25Q128FV QuadSPI Device name : W25Q128\r\n");

  /* 2-QSPI Erase/Write/Read Test */
  for(i = 0; i < TestSize; i++){
    wData[i] = i;
    rData[i] = 0;
  }

  W25QXX_Write(wData, TestAddr, TestSize);
  printf(" QSPI Write ok\r\n");

  W25QXX_Read(rData, TestAddr, TestSize);
  printf(" QSPI Read ok\r\n");

  printf("QSPI Read Data : \r\n");
  for(i = 0; i < TestSize; i++)
    printf("0x%02X  ", rData[i]);
  printf("\r\n\r\n");

  /* 3-check data */
  if(memcmp(wData, rData, TestSize) == 0)
    printf(" W25Q128FV QuadSPI Test OK\r\n");
  else
    printf(" W25Q128FV QuadSPI Test Failed\r\n");

  /* USER CODE END 2 */
	......
}


// Core\Inc\main.h

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include "dma.h"
#include "quadspi.h"
#include "usart.h"
#include "gpio.h"
#include "w25qxx.h"
#include <stdio.h>
#include <string.h>

/* USER CODE END Includes */

这里把main.c中包含的部分头文件转移到main.h中了,方便外设比如w25qxx.h中只包含main.h即可访问其余外设及C头文件,头文件中都使用了条件宏,所以也不会出现重复预编译的情况。

本示例代码已全部实现,在MDK5中编译无报错,烧录到我们的STM32L475潘多拉开发板中,通过串口工具查看运行结果如下:
QSPI访问W25Q128运行结果
本示例工程代码下载地址:https://github.com/StreamAI/STM32L4/tree/master/QSPI_W25Q128

更多文章:

发布了65 篇原创文章 · 获赞 35 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/m0_37621078/article/details/101395150
今日推荐