打印
[其他ST产品]

STM32的QSPI通信(学习笔记)

[复制链接]
楼主: kqh11a
手机看帖
扫描二维码
随时随地手机跟帖
21
kqh11a|  楼主 | 2023-7-30 15:42 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
(4) AddressSize

本成员定义地址长度,可以是8位,16位,24位或者32位。

使用特权

评论回复
22
kqh11a|  楼主 | 2023-7-30 15:42 | 只看该作者
(5) AlternateBytesSize

本成员定义交替字节长度,可以是8位,16位,24位或者32位。

使用特权

评论回复
23
kqh11a|  楼主 | 2023-7-30 15:42 | 只看该作者
(6) DummyCycles

本成员定义空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。

使用特权

评论回复
24
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(7) InstructionMode

本成员定义指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。

使用特权

评论回复
25
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(8) AddressMode

本成员定义地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。

使用特权

评论回复
26
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(9) AlternateByteMode

本成员定义交替字节阶段的操作模式00:无交替字节;01:单线传输交替字节;10:双线传输交替字节;11:四线传输交替字节。

使用特权

评论回复
27
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(10) DataMode

本成员定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。该字段还定义空指令阶段的操作模式。

使用特权

评论回复
28
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(11) NbData

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

使用特权

评论回复
29
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(12) DdrMode

本成员为地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。

使用特权

评论回复
30
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(13) DdrHoldHalfCycle

本成员设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。仅在 DDR 模式下激活。

使用特权

评论回复
31
kqh11a|  楼主 | 2023-7-30 15:43 | 只看该作者
(14) SIOOMode

本成员设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。

使用特权

评论回复
32
kqh11a|  楼主 | 2023-7-30 15:44 | 只看该作者
三、QSPI—读写串行FLASH实验

使用特权

评论回复
33
kqh11a|  楼主 | 2023-7-30 15:44 | 只看该作者
(1) 初始化通讯使用的目标引脚及端口时钟;
      GPIO_InitTypeDef GPIO_InitStruct;

     /* 使能 QSPI 及 GPIO 时钟 */
     QSPI_FLASH_CLK_ENABLE();
     QSPI_FLASH_CLK_GPIO_ENABLE();
     QSPI_FLASH_BK1_IO0_CLK_ENABLE();
     QSPI_FLASH_BK1_IO1_CLK_ENABLE();
     QSPI_FLASH_BK1_IO2_CLK_ENABLE();
     QSPI_FLASH_BK1_IO3_CLK_ENABLE();
     QSPI_FLASH_CS_GPIO_CLK_ENABLE();

使用特权

评论回复
34
kqh11a|  楼主 | 2023-7-30 15:44 | 只看该作者
(2) 配置SPI外设的模式、地址、速率等参数并使能SPI外设;


     /* QSPI_FLASH 模式配置 */
     QSPIHandle.Instance = QUADSPI;
     QSPIHandle.Init.ClockPrescaler = 2;
     QSPIHandle.Init.FifoThreshold = 4;
     QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
     QSPIHandle.Init.FlashSize = 23;
     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE;
     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
     HAL_QSPI_Init(&QSPIHandle);

使用特权

评论回复
35
kqh11a|  楼主 | 2023-7-30 15:44 | 只看该作者
(3)初始化QSPI接口;
uint8_t BSP_QSPI_Init(void)

  {

      QSPI_CommandTypeDef s_command;

      uint8_t value = W25Q128FV_FSR_QE;

     /* QSPI存储器复位 */

     if (QSPI_ResetMemory() != QSPI_OK) {

         return QSPI_NOT_SUPPORTED;

     }

     /* 使能写操作 */

     if (QSPI_WriteEnable() != QSPI_OK) {

         return QSPI_ERROR;

     }

     /* 设置四路使能的状态寄存器,使能四通道IO2和IO3引脚 */

     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

     s_command.Instruction       = WRITE_STATUS_REG2_CMD;

     s_command.AddressMode       = QSPI_ADDRESS_NONE;

     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

     s_command.DataMode          = QSPI_DATA_1_LINE;

     s_command.DummyCycles       = 0;

     s_command.NbData            = 1;

     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

     /* 配置命令 */

if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

         return QSPI_ERROR;

     }

     /* 传输数据 */

if (HAL_QSPI_Transmit(&QSPIHandle, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

         return QSPI_ERROR;

     }

    /* 自动轮询模式等待存储器就绪 */

     if (QSPI_AutoPollingMemReady(W25Q128FV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) {

         return QSPI_ERROR;

     }
     return QSPI_OK;
}

使用特权

评论回复
36
kqh11a|  楼主 | 2023-7-30 15:44 | 只看该作者
(4) 编写基本SPI按字节收发的函数;
 /**接收函数
    * [url=home.php?mod=space&uid=247401]@brief[/url]  从QSPI存储器中读取大量数据.
    * @param  pData: 指向要读取的数据的指针
    * @param  ReadAddr: 读取起始地址
    * @param  Size: 要读取的数据大小
    * @retval QSPI存储器状态
    */
uint8_t BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
  {
     QSPI_CommandTypeDef s_command;
     /* 初始化读命令 */
     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
     s_command.Instruction       = READ_CMD;
     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
     s_command.Address           = ReadAddr;
     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
     s_command.DataMode          = QSPI_DATA_1_LINE;
     s_command.DummyCycles       = 0;
     s_command.NbData            = Size;
     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

     /* 配置命令 */
if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
         return QSPI_ERROR;
     }

     /* 接收数据 */
     if(HAL_QSPI_Receive(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
         return QSPI_ERROR;
     }
     return QSPI_OK;
}



发送函数
/**
   * @brief  将大量数据写入QSPI存储器
   * @param  pData: 指向要写入数据的指针
   * @param  WriteAddr: 写起始地址
   * @param  Size: 要写入的数据大小
   * @retval QSPI存储器状态
   */

uint8_t BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)

{
     QSPI_CommandTypeDef s_command;

     uint32_t end_addr, current_size, current_addr;

     /* 计算写入地址和页面末尾之间的大小 */

     current_addr = 0;

     while (current_addr <= WriteAddr) {

         current_addr += W25Q128FV_PAGE_SIZE;

     }

     current_size = current_addr - WriteAddr;

     /* 检查数据的大小是否小于页面中的剩余位置 */

     if (current_size > Size) {

         current_size = Size;

     }

     /* 初始化地址变量 */

     current_addr = WriteAddr;

     end_addr = WriteAddr + Size;

     /* 初始化程序命令 */

     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

     s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;

     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

     s_command.DataMode          = QSPI_DATA_4_LINES;

     s_command.DummyCycles       = 0;

     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

     /* 逐页执行写入 */

     do {

         s_command.Address = current_addr;

         s_command.NbData  = current_size;

         /* 启用写操作 */

         if (QSPI_WriteEnable() != QSPI_OK) {

             return QSPI_ERROR;
         }

         /* 配置命令 */

if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

             return QSPI_ERROR;

         }

        /* 传输数据 */

if(HAL_QSPI_Transmit(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

             return QSPI_ERROR;
         }

         /* 配置自动轮询模式等待程序结束 */

         if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) {

             return QSPI_ERROR;

         }

         /* 更新下一页编程的地址和大小变量 */

         current_addr += current_size;

         pData += current_size;

         current_size = ((current_addr + W25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) : W25Q128FV_PAGE_SIZE;

     } while (current_addr < end_addr);

     return QSPI_OK;
}

使用特权

评论回复
37
kqh11a|  楼主 | 2023-7-30 15:45 | 只看该作者
(5) 编写对FLASH擦除及读写操作的的函数;

由于FLASH存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念,在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

使用特权

评论回复
38
kqh11a|  楼主 | 2023-7-30 15:45 | 只看该作者
通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例子中的FLASH芯片支持“扇区擦除”、“块擦除”以及“整片擦除”,

使用特权

评论回复
39
kqh11a|  楼主 | 2023-7-30 15:46 | 只看该作者
扇区擦除指令的第一个字节为指令编码,紧接着发送的3个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。
/**
    * @brief  擦除QSPI存储器的指定块
    * @param  BlockAddress: 需要擦除的块地址
    * @retval QSPI存储器状态
    */
uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress)
{
      QSPI_CommandTypeDef s_command;
      /* 初始化擦除命令 */
     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
     s_command.Instruction       = SECTOR_ERASE_CMD;
     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
     s_command.Address           = BlockAddress;
     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
     s_command.DataMode          = QSPI_DATA_NONE;
     s_command.DummyCycles       = 0;
     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
     /* 启用写操作 */
     if (QSPI_WriteEnable() != QSPI_OK) {
         return QSPI_ERROR;
     }

     /* 发送命令 */
if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
         return QSPI_ERROR;
     }

     /* 配置自动轮询模式等待擦除结束 */
     if (QSPI_AutoPollingMemReady(W25Q128FV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) {
         return QSPI_ERROR;
     }
     return QSPI_OK;
}

使用特权

评论回复
40
cemaj| | 2023-8-4 13:27 | 只看该作者
STM32 QSPI读取时每一个字节后都有延时

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则