main.c
FLASH:掉电后数据不丢失,U 盘、SD 卡、SSD 固态硬盘、STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。FLASH芯片(W25Q64)是一种使用 SPI 通讯协议的 NOR FLASH 存储器。 STM32 的 NSS 引脚是一个普通的 GPIO,程序中要使用软件控制的方式。
结果:
里面的FlashID是Flash芯片ID,就是W25Q64的厂商号和FLASH型号。通过Flash型号对比来看看W25Q64是否连接正常。
然后擦除FLASH扇区,往FLASH里面写数据,读FLASH里面的数据。检查写入的数据与读出的数据是否相等,相等的话亮绿灯。
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;/* 获取缓冲区的长度 */
#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[] = "jymjymjym123456789\r\n";
uint8_t Rx_Buffer[BufferSize];__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;// 函数原型声明
void Delay(__IO uint32_t nCount);
TestStatus Buffercmp(uint8_t* pBuffer1,uint8_t* pBuffer2, uint16_t BufferLength);/** 函数名:main* 描述 :主函数* 输入 :无* 输出 :无*/
int main(void)
{ LED_GPIO_Config();LED_BLUE;/* 配置串口为:115200 8-N-1 */USART_Config();printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");/* 8M串行flash W25Q64初始化 */SPI_FLASH_Init();/* 获取 Flash Device ID */DeviceID = SPI_FLASH_ReadDeviceID(); Delay( 200 );/* 获取 SPI Flash ID */FlashID = SPI_FLASH_ReadID(); printf("\r\n FlashID is 0x%X,\Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);/* 检验 SPI Flash ID */if (FlashID == sFLASH_ID){ printf("\r\n 检测到串行flash W25Q64 !\r\n");/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */// 这里擦除4K,即一个扇区,擦除的最小单位是扇区SPI_FLASH_SectorErase(FLASH_SectorToErase); /* 将发送缓冲区的数据写到flash中 */// 这里写一页,一页的大小为256个字节SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize); printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);/* 将刚刚写入的数据读出来放到接收缓冲区中 */SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);/* 检查写入的数据与读出的数据是否相等 */TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);if( PASSED == TransferStatus1 ){ LED_GREEN;printf("\r\n 8M串行flash(W25Q64)测试成功!\n\r");}else{ LED_RED;printf("\r\n 8M串行flash(W25Q64)测试失败!\n\r");}}// if (FlashID == sFLASH_ID)else// if (FlashID == sFLASH_ID){ LED_RED;printf("\r\n 获取不到 W25Q64 ID!\n\r");}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;
}void Delay(__IO uint32_t nCount)
{for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/
spi_flash.h
里面写了SPI 硬件相关的宏定义。根据硬件连接,把FLASH用的SPI号、GPIO用宏封装。定义控制 CS(NSS)引脚输出电平的宏,用来控制产生起始和停止信号。
里面还定义了 FLASH 指令编码表。
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H#include "stm32f10x.h"
#include <stdio.h>//#define sFLASH_ID 0xEF3015 //W25X16
//#define sFLASH_ID 0xEF4015 //W25Q16
//#define sFLASH_ID 0XEF4018 //W25Q128
#define sFLASH_ID 0XEF4017 //W25Q64#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256/*命令定义-开头*******************************/
#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/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag 0x01
#define Dummy_Byte 0xFF
/*命令定义-结尾*******************************//*SPI接口定义-开头****************************/
#define FLASH_SPIx SPI1
#define FLASH_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK RCC_APB2Periph_SPI1//CS(NSS)引脚 片选选普通GPIO即可
#define FLASH_SPI_CS_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK RCC_APB2Periph_GPIOC
#define FLASH_SPI_CS_PORT GPIOC
#define FLASH_SPI_CS_PIN GPIO_Pin_0//SCK引脚
#define FLASH_SPI_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_PIN GPIO_Pin_5
//MISO引脚
#define FLASH_SPI_MISO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MISO_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_PIN GPIO_Pin_6
//MOSI引脚
#define FLASH_SPI_MOSI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MOSI_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_PIN GPIO_Pin_7#define SPI_FLASH_CS_LOW() GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define SPI_FLASH_CS_HIGH() GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )/*SPI接口定义-结尾****************************//*等待超时时间*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))/*信息输出*/
#define FLASH_DEBUG_ON 1#define FLASH_INFO(fmt,arg...) printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...) printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...) do{\if(FLASH_DEBUG_ON)\printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\}while(0)void SPI_FLASH_Init(void);
void SPI_FLASH_SectorErase(u32 SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);
u32 SPI_FLASH_ReadID(void);
u32 SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(u32 ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);u8 SPI_FLASH_ReadByte(void);
u8 SPI_FLASH_SendByte(u8 byte);
u16 SPI_FLASH_SendHalfWord(u16 HalfWord);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);#endif /* __SPI_FLASH_H */
spi_flash.c
下面这个函数,用于初始化 SPI 的 GPIO(配置 SPI 使用的引脚);配置 SPI 的模式。
void SPI_FLASH_Init(void)
下面这两个函数分别是使用SPI发送、接收一个字节的数据。函数中不包含 SPI 起始和停止信号,只是收发的主要过程。主要就是通过SPI_I2S_GetFlagStatus函数检测事件。
首先检测 TXE 标志,获取发送缓冲区的状态。等待至发送缓冲区为空后,通过SPI_I2S_SendData函数把要写入的数据写入数据寄存器DR。
然后检测 RXNE 标志,获取接收缓冲区的状态。等待至接收缓冲区为非空,通过SPI_I2S_ReceiveData函数读取 SPI 的数据寄存器 DR。
u8 SPI_FLASH_SendByte(u8 byte)
u8 SPI_FLASH_ReadByte(void)
对 FLASH 芯片进行操作:控制 STM32 利用 SPI 总线向 FLASH 芯片发送指令,FLASH 芯片收到指令后就会执行相应的操作。
W25Q64定义的各种指令的功能及指令格式如下。根据这些指令的格式要求, 使用通讯协议向设备发送指令,从而控制设备。
定义FLASH 指令编码表:可以把 FLASH 芯片的常用指令编码使用宏来封装起来,可以在发送指令编码时直接使用这些宏,FLASH 指令编码表在上面的spi_flash.h里面写了。
读取Flash芯片ID:
对于W25Q64芯片,厂商号(M7-M0):EF h ;FLASH 型号(ID15-ID0):4017 h。
根据下面的时序可以编写函数:这里面发送JEDEC指令用的是SPI_FLASH_SendByte函数,这个函数就是根据SPI通信协议编写的,那么这个指令就能够传到W25Q64芯片里面(相当于使用通讯协议向设备发送指令)。由于SPI_FLASH_SendByte返回接收到的数据,那么继续调用它,相当于接收W25Q64芯片对JEDEC指令的响应。
最后可以把该返回值与定义的宏sFLASH_ID对比,判断 FLASH 芯片是否正常。
#define sFLASH_ID 0XEF4017 //W25Q64
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;
}
FLASH写使能:下面这个函数实现FLASH写使能,也就是,使用SPI协议向FLASH里面发了一个WriteEnable指令。
向Flash芯片存储矩阵写入数据之前,要进行写使能。
void SPI_FLASH_WriteEnable(void)
读取FLASH当前状态:FLASH内部有一个状态寄存器,这个状态寄存器第0位(busy位)为1,说明FLASH芯片可能正在对内部的存储矩阵进行擦除或数据写入操作。在写操作后需要确认 FLASH 芯片空闲时才能进行再次写入。
下面这个函数实现了读取FLASH当前状态功能。发了一个ReadStatusReg指令来读FLASH的状态寄存器,在while 循环里持续获得状态寄存器的内容并检验它的busy位,一直等待到busy位为0(FLASH芯片已经写入完毕),才退出循环。
void SPI_FLASH_WaitForWriteEnd(void)
FLASH扇区擦除:向FLASH写入数据前,要对目标存储区进行擦除操作,这是因为FLASH存储器只能把1数据位改成0,那么如果要存储1,但如果目标存储区的数据位是0,那就没法存储1了。所以擦除操作,要对目标存储区中的数据位全部擦成1。
FLASH扇区擦除函数如下,其实也就是对FLASH发送几个指令,指令后边要加上SPI_FLASH_WaitForWriteEnd函数,用来等待扇区操作完成。发送发送擦除地址时高位在前;调用扇区擦除指令时输入的地址要对齐到 4KB。下图是FLASH芯片的扇区(Sector 4KB)和块(Block 64KB)的存储结构。
/*** @brief 擦除FLASH扇区* @param SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}
FLASH页写入:
FLASH芯片有页写入命令,使用页写入命令最多可以一次向FLASH传输 256 个字节的数据(这个单位为页大小)。时序图如下。
由图可知,发送完写指令,再发送写地址(从高位到低位发),再发要写入的内容(一个字节一个字节的发,最多256个字节)。发送完,结束通信,然后等待Flash内部写入结束。下面这个是页写入函数。(调用这个函数写入数据前需要先擦除扇区)
/*** @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* @retval 无*/
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
下面这个函数是不定量数据写入。
/*** @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区* @param pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param NumByteToWrite,写入数据长度* @retval 无*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
从Flash读数据:使用读取指令ReadData。
/*** @brief 读取FLASH数据* @param pBuffer,存储读出数据的指针* @param ReadAddr,读取地址* @param NumByteToRead,读取数据长度* @retval 无*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
函数首先发送读指令,再发送读地址的高位到地位,再读数据,读完就发送停止信号。
全部代码:
#include "./flash/bsp_spi_flash.h"static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);/*** @brief SPI_FLASH初始化* @param 无* @retval 无*/
void SPI_FLASH_Init(void)
{SPI_InitTypeDef SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;/* 使能SPI时钟 */FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );/* 使能SPI引脚相关的时钟 */FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );/* 配置SPI的 CS引脚,普通IO即可 */GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);/* 配置SPI的 SCK引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI的 MISO引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI的 MOSI引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);/* 停止信号 FLASH: CS引脚高电平*/SPI_FLASH_CS_HIGH();/* SPI 模式配置 */// FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHASPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(FLASH_SPIx , &SPI_InitStructure);/* 使能 SPI */SPI_Cmd(FLASH_SPIx , ENABLE);}/*** @brief 擦除FLASH扇区* @param SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief 擦除FLASH扇区,整片擦除* @param 无* @retval 无*/
void SPI_FLASH_BulkErase(void)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 整块 Erase *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送整块擦除指令*/SPI_FLASH_SendByte(W25X_ChipErase);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* @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();
}/*** @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;/* 先写完count个数据,为的是让下一次要写的地址对齐 */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);}}}
}/*** @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--) /* while there is data to be read */{/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();
}/*** @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;
}/*** @brief 读取FLASH Device ID* @param 无* @retval FLASH Device ID*/
u32 SPI_FLASH_ReadDeviceID(void)
{u32 Temp = 0;/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "RDID " instruction */SPI_FLASH_SendByte(W25X_DeviceID);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);/* Read a byte from the FLASH */Temp = SPI_FLASH_SendByte(Dummy_Byte);/* Deselect the FLASH: Chip Select high */SPI_FLASH_CS_HIGH();return Temp;
}
/*******************************************************************************
* Function Name : SPI_FLASH_StartReadSequence
* Description : Initiates a read data byte (READ) sequence from the Flash.
* This is done by driving the /CS line low to select the device,
* then the READ instruction is transmitted followed by 3 bytes
* address. This function exit and keep the /CS line low, so the
* Flash still being selected. With this technique the whole
* content of the Flash is read with a single READ instruction.
* Input : - ReadAddr : FLASH's internal address to read from.
* Output : None
* Return : None
*******************************************************************************/
void SPI_FLASH_StartReadSequence(u32 ReadAddr)
{/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "Read from Memory " instruction */SPI_FLASH_SendByte(W25X_ReadData);/* Send the 24-bit address of the address to read from -----------------------*//* Send ReadAddr high nibble address byte */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* Send ReadAddr medium nibble address byte */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* Send ReadAddr low nibble address byte */SPI_FLASH_SendByte(ReadAddr & 0xFF);
}/*** @brief 使用SPI读取一个字节的数据* @param 无* @retval 返回接收到的数据*/
u8 SPI_FLASH_ReadByte(void)
{return (SPI_FLASH_SendByte(Dummy_Byte));
}/*** @brief 使用SPI发送一个字节的数据* @param byte:要发送的数据* @retval 返回接收到的数据*/
u8 SPI_FLASH_SendByte(u8 byte)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , byte);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @brief 使用SPI发送两个字节的数据* @param byte:要发送的数据* @retval 返回接收到的数据*/
u16 SPI_FLASH_SendHalfWord(u16 HalfWord)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , HalfWord);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @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();
}/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag 0x01/*** @brief 等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕* @param none* @retval none*/
void SPI_FLASH_WaitForWriteEnd(void)
{u8 FLASH_Status = 0;/* 选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 读状态寄存器 命令 */SPI_FLASH_SendByte(W25X_ReadStatusReg);/* 若FLASH忙碌,则等待 */do{/* 读取FLASH芯片的状态寄存器 */FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte); }while ((FLASH_Status & WIP_Flag) == SET); /* 正在写入标志 *//* 停止信号 FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}//进入掉电模式
void SPI_Flash_PowerDown(void)
{ /* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送 掉电 命令 */SPI_FLASH_SendByte(W25X_PowerDown);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();
} //唤醒
void SPI_Flash_WAKEUP(void)
{/*选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 上电 命令 */SPI_FLASH_SendByte(W25X_ReleasePowerDown);/* 停止信号 FLASH: CS 高 */SPI_FLASH_CS_HIGH();
} /*** @brief 等待超时回调函数* @param None.* @retval None.*/
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{/* 等待超时后的处理,输出错误信息 */FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);return 0;
}/*********************************************END OF FILE**********************/