一.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外设的时钟

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

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

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

(4)使用以上初始化结构体的配置,调用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擦除及读写操作的函数

(1)指令说明

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)
W25Q64 EF h 4017 h
W25Q128 EF h 4018 h

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

(2)定义FLASH指令编码表

手册中的指令编码使用宏进行封装,方便调用。

/* Private typedef -----------------------------------------------------------*/
//#define  sFLASH_ID                       0xEF3015     //W25X16
//#define  sFLASH_ID                       0xEF4015      //W25Q16
//#define  sFLASH_ID                        0XEF4017     //W25Q64
//#define  sFLASH_ID                       0XEF4018     //W25Q128
#define  sFLASH_ID                       0XEF4019     //W25Q256//#define SPI_FLASH_PageSize               4096
#define SPI_FLASH_PageSize               256
#define SPI_FLASH_PerWritePageSize       256/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
#define W25X_WriteEnable                 0x06
#define W25X_WriteDisable                0x04
#define W25X_ReadStatusReg               0x05
#define W25X_WriteStatusReg              0x01
#define W25X_ReadData                    0x03
#define W25X_FastReadData                0x0B
#define W25X_FastReadDual                0x3B
#define W25X_PageProgram                 0x02
#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_Enter4ByteMode              0xB7
#define W25X_ReadStatusRegister3         0x15#define WIP_Flag                  0x01  /* Write In Progress (WIP) flag */
#define Dummy_Byte                0xFF
/*命令定义-结尾*******************************/

(3)读取FLASH芯片ID

根据“JEDEC”指令的时序把读取FLASH ID的过程编写成一个函数

/** @brief 读取FLASH ID* @param 无* @retval FLASH ID*/u32 SPI_FLASH_ReadID(void){u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;/* 开始通讯:CS低电平 */SPI_FLASH_CS_LOW();/* 发送JEDEC指令,读取ID */SPI_FLASH_SendByte(W25X_JedecDeviceID);/* 读取一个字节数据 */Temp0 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp1 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp2 = SPI_FLASH_SendByte(Dummy_Byte);/* 停止通讯:CS高电平 */SPI_FLASH_CS_HIGH();/*把数据组合起来,作为函数的返回值*/Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;}

发送一个字节的指令编码“W25X_JedecDeviceID”,然后读取3个字节,获取FLASH芯片对该指令的响应,最后把读取到的这3个数据合并到一个变量Temp中,然后作为函数返回值,把该返回值与定义的宏“sFLASH_ID”对比可知FLASH芯片是否正常。

(4)FLASH写使能以及读取当前状态

向FLASH芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令写使能。

 /** @brief 向FLASH发送 写使能 命令* @param none* @retval none*/void SPI_FLASH_WriteEnable(void){/* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送写使能命令*/SPI_FLASH_SendByte(W25X_WriteEnable);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();}

与EEPROM一样,FLASH芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的, 所以在写操作后需要确认FLASH芯片“空闲”时才能进行再次写入。为了表示工作状态, FLASH芯片定义了一个状态寄存器:

状态寄存器的第0位“BUSY”,当这个位为“1”时,表明FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作。
向FLASH芯片发送“Read Status Register”指令05 h,FLASH芯片就会持续向主机返回最新的状态寄存器内容, 直到收到SPI通讯的停止信号。

 /*** @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕* @param  none* @retval none*/
void SPI_FLASH_WaitForWriteEnd(void)
{uint8_t FLASH_Status = 0;/* 选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 读状态寄存器 命令 */SPI_FLASH_SendByte(W25X_ReadStatusReg);SPITimeout = SPIT_FLAG_TIMEOUT;/* 若FLASH忙碌,则等待 */do{/* 读取FLASH芯片的状态寄存器 */FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);  {if((SPITimeout--) == 0) {SPI_TIMEOUT_UserCallback(4);return;}} }while ((FLASH_Status & WIP_Flag) == SET); /* 正在写入标志 *//* 停止信号  FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}

(5)FLASH扇区擦除

由于FLASH存储器的特性决定其只能把原来为1的数据位改写成0,而原来为0的数据位不能直接改写成1。这里涉及数据”擦除“的概念:在写入前必须对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为1,在数据写入时如果要存储数据1就不修改存储矩阵;要存储数据0时,才更改该位。
通常对存储矩阵擦除的基本操作单位都是多个字节进行,W25Q128 FLASH芯片支持“扇区擦除”、“块擦除”以及“整片擦除”。FLASH芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含16个扇区,其内部存储矩阵分布如下。

擦除单位 大小
扇区擦除Sector Erase 4KB
块擦除Block Erase 64KB
整片擦除Chip Erase 整个芯片完全擦除


扇区擦除指令的第一个字节为指令编码,紧接着发送的3个字节用于表示要擦除的24位存储矩阵地址。 要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后, 通过读取寄存器状态等待扇区擦除操作完毕,扇区擦除时序、实现代码如下。

 /*** @brief  擦除FLASH扇区* @param  SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高8位*/SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24);/*发送擦除扇区地址的中前8位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中后8位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低8位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}

注意发送擦除地址时高位在前,调用扇区擦除指令时输入的地址要对齐到4KB。

(6)FLASH的页写入

目标扇区被擦除完毕后,就可以写入数据了。与EEPROM类似,FLASH芯片也有页写入命令, 使用页写入命令最多可以一次向FLASH传输256个字节的数据,称这个单位为页大小,FLASH页写入时序如下。若发送的数据超出256个,则会覆盖前面发送的数据。

与擦除指令不同,页写入指令的地址并不要求按256字节对齐,只要确认目标存储单元是擦除状态即可(即被擦除后没有被写入过)。所以,若对“地址x”执行页写入指令后,发送了200个字节数据后终止通讯,下一次再执行页写入指令,从“地址(x+200)”开始写入200个字节也是没有问题的(小于256均可)。只是在实际应用中由于基本擦除单元是4KB,一般都以扇区为单位进行读写。

 /*** @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param  pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度,必须小于等于页大小* @retval 无*/void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite){/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 写送写指令*/SPI_FLASH_SendByte(W25X_PageProgram);/*发送写地址的高位*/SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*发送写地址的中位*/SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*发送写地址的低位*/SPI_FLASH_SendByte(WriteAddr & 0xFF);if (NumByteToWrite > SPI_FLASH_PerWritePageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;FLASH_ERROR("SPI_FLASH_PageWrite too large!");}/* 写入数据*/while (NumByteToWrite--){/* 发送当前要写入的字节数据 */SPI_FLASH_SendByte(*pBuffer);/* 指向下一字节数据 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待写入完毕*/SPI_FLASH_WaitForWriteEnd();}

(7)不定量数据写入

实际应用中常常要写入不定量的数据,直接调用“页写入”函数并不方便,所以在其原来基础上编写了“不定量数据写入”的函数。

 /*** @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区* @param  pBuffer,要写入数据的指针* @param  WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度* @retval 无*/void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite){u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/Addr = WriteAddr % SPI_FLASH_PageSize;/*差count个数据值,刚好可以对齐到页地址*/count = SPI_FLASH_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;/*mod运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */if (Addr == 0){/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*先把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}/* 若地址与 SPI_FLASH_PageSize 不对齐  */else{/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){/*当前页剩余的count个位置比NumOfSingle小,写不完*/if (NumOfSingle > count){temp = NumOfSingle - count;/*先写满当前页*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;/*再写剩余的数据*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else /*当前页剩余的count个位置能写完NumOfSingle个数据*/{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*地址不对齐多出的count分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;/*把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}}

(8)从FlASH读取数据


发送了指令编码及要读的起始地址后,FLASH芯片就会按地址递增的方式返回存储矩阵的内容,读取的数据量没有限制, 只要没有停止通讯,FLASH芯片就会一直返回数据。

 /*** @brief  读取FLASH数据* @param  pBuffer,存储读出数据的指针* @param   ReadAddr,读取地址* @param   NumByteToRead,读取数据长度* @retval 无*/void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead){/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送 读 指令 */SPI_FLASH_SendByte(W25X_ReadData);/* 发送 读 地址高位 */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* 发送 读 地址低位 */SPI_FLASH_SendByte(ReadAddr & 0xFF);/* 读取数据 */while (NumByteToRead--){/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();}

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

 /* 获取缓冲区的长度 */#define TxBufferSize1   (countof(TxBuffer1) - 1)#define RxBufferSize1   (countof(TxBuffer1) - 1)#define countof(a)      (sizeof(a) / sizeof(*(a)))#define  BufferSize (countof(Tx_Buffer)-1)#define  FLASH_WriteAddress     0x00000#define  FLASH_ReadAddress      FLASH_WriteAddress#define  FLASH_SectorToErase    FLASH_WriteAddress/* 发送缓冲区初始化 */uint8_t Tx_Buffer[] = "感谢您选用野火stm32开发板\r\n";uint8_t Rx_Buffer[BufferSize];//读取的ID存储位置__IO uint32_t DeviceID = 0;__IO uint32_t FlashID = 0;__IO TestStatus TransferStatus1 = FAILED;// 函数原型声明void Delay(__IO uint32_t nCount);/** 函数名:main* 描述  :主函数* 输入  :无* 输出  :无*/int main(void){SystemClock_Config();LED_GPIO_Config();LED_BLUE;/* 配置串口1为:115200 8-N-1 */Debug_USART_Config();printf("\r\n这是一个16M串行flash(W25Q128)实验 \r\n");/* 16M串行flash W25Q128初始化 */SPI_FLASH_Init();Delay( 200 );/* 获取 SPI Flash ID */FlashID = SPI_FLASH_ReadID();/* 检验 SPI Flash ID */if (FlashID == sFLASH_ID){printf("\r\n检测到SPI FLASH W25Q128 !\r\n");/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */SPI_FLASH_SectorErase(FLASH_SectorToErase);/* 将发送缓冲区的数据写到flash中 */SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);printf("\r\n写入的数据为:\r\n%s", Tx_Buffer);/* 将刚刚写入的数据读出来放到接收缓冲区中 */SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("\r\n读出的数据为:\r\n%s", Rx_Buffer);/* 检查写入的数据与读出的数据是否相等 */TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);if ( PASSED == TransferStatus1 ){LED_GREEN;printf("\r\n16M串行flash(W25Q128)测试成功!\n\r");}else{LED_RED;printf("\r\n16M串行flash(W25Q128)测试失败!\n\r");}}// if (FlashID == sFLASH_ID)else{LED_RED;printf("\r\n获取不到 W25Q128 ID!\n\r");}SPI_Flash_PowerDown();while (1);}/** 函数名:Buffercmp* 描述  :比较两个缓冲区中的数据是否相等* 输入  :-pBuffer1     src缓冲区指针*         -pBuffer2     dst缓冲区指针*         -BufferLength 缓冲区长度* 输出  :无* 返回  :-PASSED pBuffer1 等于   pBuffer2*         -FAILED pBuffer1 不同于 pBuffer2*/
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{while(BufferLength--){if(*pBuffer1 != *pBuffer2){return FAILED;}pBuffer1++;pBuffer2++;}return PASSED;
}

初始化LED、串口、SPI外设,然后读取FLASH芯片的ID进行校验,若ID校验通过则向FLASH的特定地址写入测试数据,然后再从该地址读取数据,测试读写是否正常。

SPI应用——W25Q128串行FLASH相关推荐

  1. 下载的内容:MSP430F149利用硬件SPI口读写串行Flash M25P64

    /*****************<SPI_Flash.C>**************************************/ #include "SPI_Flas ...

  2. 第24章 SPI—读写串行FLASH—零死角玩转STM32-F429系列

    第24章     SPI-读写串行FLASH 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...

  3. SPI—读写串行FLASH(时序中的无关项)

    SPI-读写串行FLASH 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 本章 ...

  4. SPI—读写串行FLASH

    1 SPI协议简介 SPI协议是由摩托罗拉公司提出的通讯协议(SerialPeripheralInterface),即串行外围设备接口,是一种高速全双工的通信总线.它被广泛地使用在ADC.LCD等设备 ...

  5. STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs

    预备知识 >>W25Q128是16M spi flash,一共有256个block ,每个Block 64KB. >>一个Block可以分割为16个扇区(small secto ...

  6. 25章 SPI—读写串行FLASH

    SPI 协议简介 3 条总线分别为SCK(时钟).MOSI(主出从入).MISO(主入从出),片选线为SS1.2.3(从设备)(多少设备也就这三条ss) MOSI和MISO能看出是全双工(输入输出两条 ...

  7. 3.3 SPI串行Flash配置模式

     SPI串行Flash配置模式 1.SPI串行配置介绍 串行Flash的特点是占用管脚比较少,作为系统的数据存贮非常合适,一般都是采用串行外设接口(SPI 总线接口).Flash 存贮器与EEPROM ...

  8. 【立创EDA开源推荐】001期 | 基于航顺HK32F030R8串行Flash烧录器

    工程名称: 串行Flash烧录器 工程作者: micespring 工程主页链接:https://oshwhub.com/micespring/hk32_serialflashprogrammer 开 ...

  9. JEDEC标准(JESD216)S FDP对串行Flash在系统中的应用

    摘要:JEDEC标准(JESD216)Serial Flash Discoverable Parameter (SFDP)[1]是在串行Flash中建立一个可供查询的描述串行Flash功能的参数表.文 ...

最新文章

  1. C语言中static的作用及C语言中使用静态函数有何好处
  2. JavaScript基础3——关于运算符
  3. java swing 外观框架_Swing外观框架BeautyEye使用
  4. python反爬虫破解_python中绕过反爬虫的方法总结
  5. [react] react是哪个公司开发的
  6. python经典数据类型
  7. HTTP请求方式中GET和POST的区别?
  8. Preferences偏好设置
  9. 小程序按钮调用扫一扫_他在一个小程序“按钮”上动了个手脚,生意大火,赢得美人归!...
  10. 「知识蒸馏」最新2022研究综述
  11. LAMP兄弟连PHP全民总动员
  12. pjsip 录音生成wav文件声音一直很小
  13. linux版本qq的安装
  14. vega56刷64_Vega56显卡怎么刷Bios VEGA56显卡刷Vega64方法
  15. 微服务究竟是“灵丹”还是“毒药”?
  16. MediaSession框架全解析
  17. Matlab中mod函数使用
  18. Thinkphp 5.0 仿百度糯米开发多商家电商平台
  19. 【有限马尔科夫链状态分解+Kosaraju 算法】基于Kosaraju 算法和可达矩阵的有限马尔科夫链状态分解
  20. SOC的定义、适用性和组成

热门文章

  1. 流星滑落-第13届蓝桥杯Scratch省赛1真题第2题
  2. 触发器(Trigger)
  3. 支持IPv6 DNS64 / NAT64网络
  4. ubuntu 安装图片编辑工具pinta
  5. project2016调配资源冲突
  6. 系统分析师-2021年下午简答题
  7. 性能优化: http 请求的过程及潜在的性能优化点
  8. Jeaf Dean万字长文回顾2020谷歌技术发展(上)
  9. iOS 免越狱修改微信运动步数
  10. 4.1.2. Constants