0
点赞
收藏
分享

微信扫一扫

SPI应用—W25Q128串行FLASH

一只1994 2022-03-30 阅读 79

一.FLASH存储器介绍

FLASH存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于EEPROM,现在基本取代了它的地位。在存储控制上,最主要的区别是FLASH芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
W25Q128是一种使用SPI通讯协议的NOR FLASH存储器,它的CS/CLK/DIO/DO引脚分别连接到了 STM32对应的SDI引脚NSS/SCK/MOSI/MISO上,其中STM32的NSS引脚是一个普通的GPIO,不是SPI的专用NSS引脚,所以程序要使用软件控制片选的方式。

二.编程要点

1.初始化通讯使用的目标引脚、使能端口时钟及SPI外设的时钟

①使用GPIO_InitTypeDef定义GPIO初始化结构体变量,以便下面用于存储GPIO配置;

②调用库函数SPIx_SCK_GPIO_CLK_ENABLE(),SPIx_MISO_GPIO_CLK_ENA BLE()等完成SPI相关引脚的时钟使能。调用库函数SPIx_CLK_ENABLE()完成SPI外设的使能。

③向GPIO初始化结构体赋值,把SCK/MOSI/MISO引脚初始化成复用 推挽模式。而CS(NSS)引脚由于使用软件控制,我们把它配置为普通的推挽输出模式。

④使用以上初始化结构体的配置,调用HAL_GPIO_Init函数向分别寄存器写入参数,完成GPIO的初始化。

 /**
   * @brief SPI MSP 初始化
   *       此函数配置此示例中使用的硬件资源:
   *           - 外设时钟使能
   *           - 外设引脚配置
   * @param hspi: SPI句柄指针
   * @retval 无
   */
 void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
 {
     GPIO_InitTypeDef  GPIO_InitStruct;

     /*##-1- Enable peripherals and GPIO Clocks */
     /* Enable GPIO TX/RX clock */
     SPIx_SCK_GPIO_CLK_ENABLE();
     SPIx_MISO_GPIO_CLK_ENABLE();
     SPIx_MOSI_GPIO_CLK_ENABLE();
     SPIx_CS_GPIO_CLK_ENABLE();
     
     /* Enable SPI clock */
     SPIx_CLK_ENABLE();

     /*##-2- Configure peripheral GPIO */
     /* SPI SCK GPIO pin configuration  */
     GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
     GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;//复用推挽输出
     GPIO_InitStruct.Pull      = GPIO_PULLUP;
     GPIO_InitStruct.Speed     = GPIO_SPEED_FAST;
     GPIO_InitStruct.Alternate = SPIx_SCK_AF;
     HAL_GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStruct);

     /* SPI MISO GPIO pin configuration  */
     GPIO_InitStruct.Pin = SPIx_MISO_PIN;
     GPIO_InitStruct.Alternate = SPIx_MISO_AF;
     HAL_GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStruct);

     /* SPI MOSI GPIO pin configuration  */
     GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
     GPIO_InitStruct.Alternate = SPIx_MOSI_AF;
     HAL_GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStruct);

     GPIO_InitStruct.Pin = FLASH_CS_PIN ;
     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
     HAL_GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStruct);
 }

2.配置SPI外设的模式、地址、速率等参数并使能SPI外设

在配置STM32的SPI模式前,要先了解从机端的SPI模式。通过查阅FLASH数据手册《W25Q128》可知,W25Q128支持SPI模式0及模式3,支持双线全双工, 使用MSB先行模式,支持最高通讯时钟为104MHz,数据帧长度为8位。STM32的SPI外设中的这些参数配置需要与从机端一致。

void SPI_FLASH_Init(void)
 {
     /* Set the SPI parameters */
     SpiHandle.Instance               = SPIx;
     SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
     SpiHandle.Init.Direction         = SPI_DIRECTION_2LINES;
     SpiHandle.Init.CLKPhase          = SPI_PHASE_2EDGE;
     SpiHandle.Init.CLKPolarity       = SPI_POLARITY_HIGH;
     SpiHandle.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;
     SpiHandle.Init.CRCPolynomial     = 7;
     SpiHandle.Init.DataSize          = SPI_DATASIZE_8BIT;
     SpiHandle.Init.FirstBit          = SPI_FIRSTBIT_MSB;
     SpiHandle.Init.NSS               = SPI_NSS_SOFT;
     SpiHandle.Init.TIMode            = SPI_TIMODE_DISABLE;

     SpiHandle.Init.Mode = SPI_MODE_MASTER;

     HAL_SPI_Init(&SpiHandle);

     __HAL_SPI_ENABLE(&SpiHandle);
 }

STM32的SPI外设配置为主机端,双线全双工模式,数据帧长度为8位,使用SPI模式3(CLKPolarity =1,CLKPhase =1), NSS引脚由软件控制以及MSB先行模式。由于与FLASH芯片通信不需要CRC校验,并没有使能SPI的CRC功能, 这时CRC计算式的成员值是无效的。 赋值结束后调用库函数HAL_SPI_Init把这些配置写入寄存器,并调用__HAL_SPI_ENABLE函数使能外设。

3.编写基本SPI按字节收发的函数

 /**
  * @brief  使用SPI发送一个字节的数据
  * @param  byte:要发送的数据
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待发送缓冲区为空,TXE事件 置1为非空*/
  while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_TXE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
   }

  /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
  WRITE_REG(SpiHandle.Instance->DR, byte);

  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待接收缓冲区非空,RXNE事件 */
  while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_RXNE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
   }

  /* 读取数据寄存器,获取接收缓冲区数据 */
  return READ_REG(SpiHandle.Instance->DR);
}

 /**
  * @brief  使用SPI读取一个字节的数据
  * @param  无
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_ReadByte(void)
{
  return (SPI_FLASH_SendByte(Dummy_Byte));
}

(1)该函数中不包含SPI起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作;

(2)对SPITimeout变量赋值为宏SPIT_FLAG_TIMEOUT。这个SPITimeout变量在下面的while中每次循环减1,该循环通过调用库函数SPI_I2S_GetFlagStatus检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测SPIT_FLAG_TIMEOUT次都还没等待到事件则认为通讯失败,调用的SPI_TIMEOUT_UserCallback输出调试信息,并退出通讯;

(3)通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;

(4)等待至发送缓冲区为空后,调用函数

WRITE_REG(SpiHandle.Instance->DR, byte);

把要发送的数据“byte”写入到SPI的数据寄存器DR,写入SPI数据寄存器的数据会存储到发送缓冲区,由SPI外设发送出去;

(5)写入完毕后等待RXNE事件,即接收缓冲区非空事件。由于SPI双线全双工模式下MOSI与MISO数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;

(6)等待至接收缓冲区非空时,通过调用函数

READ_REG(SpiHandle.Instance->DR);

读取SPI的数据寄存器DR,就可以获取接收缓冲区中的新数据。代码中使用关键字“return”把接收到的这个数据作为SPI_FLASH_SendByte函数的返回值,所以可以看到在下面定义的SPI接收数据函数SPI_FLASH_ReadByte,它只是简单地调用了SPI_FLASH_SendByte函数发送数据“Dummy_Byte”,然后获取其返回值(因为接收字节函数不关注发送的数据而关注接收的数据,所以此时的输入参数“Dummy_Byte”可以为任意值)。可以这样做的原因是SPI的接收过程和发送过程实质是一样的,收发同步进行,关键在于上层应用中关注的是发送还是接收的数据。

4.编写对FLASH擦除及读写操作的函数

FLASH芯片自定义了很多指令,通过控制STM32利用SPI总线向FLASH芯片发送指令,FLASH芯片收到后就会执行相应的操作。
这些指令对主机端(STM32)来说,只是它遵守最基本的SPI通讯协议发送出的数据,但在设备端(FLASH芯片)把这些数据解释成不同的意义,所以才成为指令。查看FLASH芯片的数据手册《W25Q128》,可了解各种指令的功能及指令格式。
在这里插入图片描述
该表中的第一列为指令名,第二列为指令编码,第三至第N列的具体内容根据指令的不同而有不同的含义。其中带括号的字节参数,方向为FLASH向主机传输,即命令响应;不带括号的为主机向FLASH传输。
“ A0~A23” 指FLASH芯片 内部存储器组织的地址;
“ M0~M7” 为厂商号(MANUFACTURER ID);
“ID0-ID15”为FLASH芯片的ID;
“dummy”指该处可为任意数据;
“ D0~D7” 为FLASH内部存储矩阵的内容。
在FLASH芯片内部,存储有固定的厂商编号(M7-M0)和不同类型FLASH芯片独有的编号(ID15-ID0)

FLASH型号厂商号FLASH型号(ID15-ID0)
W25Q64EF h4017 h
W25Q128EF h4018 h

通过指令表中的读ID指令“JEDEC ID”可以获取这两个编号,该指令编码为“9F h”,其中“9F h”是指16进制数“9F” (相当于C语言中的0x9F)。紧跟指令编码的三个字节分别为FLASH芯片输出的“生产厂商(M7-M0)”、“存储器类型(ID15-ID8)”及“容量(ID7-ID0)” 。常见的应用是主机端通过读取设备ID来测试硬件是否连接正常,或用于识别设备。
在这里插入图片描述

编写测试程序,对读写数据进行校验。

举报

相关推荐

0 条评论