STM32 SPI方式读写Flash

一.flash 介绍

1.flash简介

Flash 是常见的用于存储数据的半导体器件,它具有容量大、可重复擦写、按"扇区/块"擦除、掉电后数据可继续保存的特性。常见的Flash主要有NOR Flash和Nand Flash两种类型,NOR 和 NAND 是两种数字门电路,可以简单地认为 Flash 内部存储单元使用哪种门作存储单元就是哪类型的Flash。U 盘,SSD,eMMC 等为 NAND 型,而 NOR Flash 则根据设计需要灵活应用于各类 PCB 上,如 BIOS,手机等。

NOR 与 NAND 在数据写入前都需要有擦除操作,但实际上 NOR Flash 的一个 bit 可以从 1变成 0,而要从 0 变 1 就要擦除后再写入,NAND Flash 这两种情况都需要擦除。擦除操作的最小单位为"扇区/块",这意味着有时候即使只写一字节的数据,则这个"扇区/块"上之前的数据都可能会被擦除。

2.NOR flash与Nand flash区别

NOR 的地址线和数据线分开,它可以按"字节"读写数据,符合 CPU 的指令译码执行要求,所以假如 NOR 上存储了代码指令,CPU 给 NOR 一个地址,NOR 就能向 CPU 返回一个数据让CPU执行,中间不需要额外的处理操作。因此可以用 NOR Flash 直接作为嵌入式 MCU 的程序存储空间。

NAND 的数据和地址线共用,只能按"块"来读写数据,假如 NAND 上存储了代码指令,CPU 给 NAND 地址后,它无法直接返回该地址的数据,所以不符合指令译码要求。若代码存储在 NAND 上,可以把它先加载到 RAM 存储器上,再由 CPU 执行。所以在功能上可以认为 NOR 是一种断电后数据不丢失的 RAM,但它的擦除单位与 RAM 有区别,且读写速度比 RAM 要慢得多。

W25Q128 为例:

3.flash缺点

一是 Flash 的使用寿命,另一个是可能的位反转。

****使用寿命体现在:****读写上是 FLASH 的擦除次数都是有限的(NOR Flash 普遍是 10 万次左

右),当它的使用接近寿命的时候,可能会出现写操作失败。由于 NAND 通常是整块擦写,块内有一位失效整个块就会失效,这被称为坏块。使用 NAND Flash 最好通过算法扫描介质找出坏块并标记为不可用,因为坏块上的数据是不准确的。

位反转是数据位写入时为 1,但经过一定时间的环境变化后可能实际变为 0 的情况,反之

亦然。位反转的原因很多,可能是器件特性也可能与环境、干扰有关,由于位反转的的问题可能存在,所以 FLASH 存储器需要"探测/错误更正(EDC/ECC)"算法来确保数据的正确性。

4.常用flash型号

本文只介绍NOR FLASH 芯片,如有:W25Q128、W25Q256、BY25Q128、NM25Q128,它们是来自不同的厂商的同种规格的 NOR FLASH 芯片;

W25Q128为128Mbit,16MByte;

W25Q256为256Mbit,32MByte;

二.spi读写flash代码示例

本程序以STM32F429读写W25Q128为示例。

1.flash.h

#ifndef __SPI_FLASH_H

#define __SPI_FLASH_H

#include "stm32f4xx.h"

#include <stdio.h>

/* Private typedef -----------------------------------------------------------*/

//#define sFLASH_ID 0xEF3015 //W25X16

//#define sFLASH_ID 0xEF4015 //W25Q16

//#define sFLASH_ID 0XEF4017 //W25Q64

#define sFLASH_ID 0XEF4018 //W25Q128 //128Mbit, 16MByte

//#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

/*命令定义-结尾*******************************/

#define SPIx SPI5

#define SPIx_CLK_ENABLE() __HAL_RCC_SPI5_CLK_ENABLE()

#define SPIx_SCK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()

#define SPIx_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()

#define SPIx_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()

#define SPIx_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()

#define SPIx_FORCE_RESET() __HAL_RCC_SPI5_FORCE_RESET()

#define SPIx_RELEASE_RESET() __HAL_RCC_SPI5_RELEASE_RESET()

/* Definition for SPIx Pins */

#define SPIx_SCK_PIN GPIO_PIN_7

#define SPIx_SCK_GPIO_PORT GPIOF

#define SPIx_SCK_AF GPIO_AF5_SPI5

#define SPIx_MISO_PIN GPIO_PIN_8

#define SPIx_MISO_GPIO_PORT GPIOF

#define SPIx_MISO_AF GPIO_AF5_SPI5

#define SPIx_MOSI_PIN GPIO_PIN_9

#define SPIx_MOSI_GPIO_PORT GPIOF

#define SPIx_MOSI_AF GPIO_AF5_SPI5

#define FLASH_CS_PIN GPIO_PIN_6

#define FLASH_CS_GPIO_PORT GPIOF

#define digitalHi(p,i) {p->BSRR=i;} //设置为高电平

#define digitalLo(p,i) {p->BSRR=(uint32_t)i << 16;} //输出低电平

#define SPI_FLASH_CS_LOW() digitalLo(FLASH_CS_GPIO_PORT,FLASH_CS_PIN )

#define SPI_FLASH_CS_HIGH() digitalHi(FLASH_CS_GPIO_PORT,FLASH_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(uint32_t SectorAddr);

void SPI_FLASH_BlockErase(uint32_t BlockAddr);

void SPI_FLASH_BulkErase(void);

void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);

void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);

void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);

uint32_t SPI_FLASH_ReadID(void);

uint32_t SPI_FLASH_ReadDeviceID(void);

void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);

void SPI_Flash_PowerDown(void);

void SPI_Flash_WAKEUP(void);

void SPI_FLASH_Mode_Init(void);

uint8_t SPI_FLASH_ReadByte(void);

uint8_t SPI_FLASH_SendByte(uint8_t byte);

uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord);

void SPI_FLASH_WriteEnable(void);

void SPI_FLASH_WaitForWriteEnd(void);

#endif /* __SPI_FLASH_H */

2.flsh.c

/**

******************************************************************************

* @file bsp_spi_flash.c

* @author fire

* @version V1.0

* @date 2015-xx-xx

* @brief spi flash 底层应用函数bsp

******************************************************************************

* @attention

*

* 实验平台:野火STM32 F429 开发板

* 论坛 :http://www.firebbs.cn

* 淘宝 :https://fire-stm32.taobao.com

*

******************************************************************************

*/

#include "./flash/bsp_spi_flash.h"

SPI_HandleTypeDef SpiHandle;

static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;

static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);

/**

* @brief SPI MSP Initialization

* This function configures the hardware resources used in this example:

* - Peripheral's clock enable

* - Peripheral's GPIO Configuration

* @param hspi: SPI handle pointer

* @retval None

*/

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);

}

void SPI_FLASH_Init(void)

{

/*##-1- Configure the SPI peripheral #######################################*/

/* 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);

/* 使 SPI_FLASH 进入 4 字节地址模式 */

SPI_FLASH_Mode_Init();

}

/**

* @brief 擦除FLASH扇区

* @param SectorAddr:要擦除的扇区地址

* @retval 无

*/

void SPI_FLASH_SectorErase(uint32_t SectorAddr) //SectorAddr = n*4096 //n --- 扇区数

{

/* 发送FLASH写使能命令 */

SPI_FLASH_WriteEnable();

SPI_FLASH_WaitForWriteEnd();

/* 擦除扇区 */

/* 选择FLASH: CS低电平 */

SPI_FLASH_CS_LOW();

/* 发送扇区擦除指令*/

SPI_FLASH_SendByte(W25X_SectorErase); //一次4KByte

/*发送擦除扇区地址的高8位*/

// SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24); //打开后擦除无效2024.9.9

/*发送擦除扇区地址的中前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();

}

/**

* @brief 擦除FLASH块

* @param SectorAddr:要擦除的块地址

* @retval 无

*/

void SPI_FLASH_BlockErase(uint32_t BlockAddr) //SectorAddr = n*64*1024 //n --- 块数

{

/* 发送FLASH写使能命令 */

SPI_FLASH_WriteEnable();

SPI_FLASH_WaitForWriteEnd();

/* 擦除扇区 */

/* 选择FLASH: CS低电平 */

SPI_FLASH_CS_LOW();

/* 发送扇区擦除指令*/

SPI_FLASH_SendByte(W25X_BlockErase); //一次64KByte

/*发送擦除扇区地址的高8位*/

// SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24);

/*发送擦除扇区地址的中前8位*/

SPI_FLASH_SendByte((BlockAddr & 0xFF0000) >> 16);

/* 发送擦除扇区地址的中后8位 */

SPI_FLASH_SendByte((BlockAddr & 0xFF00) >> 8);

/* 发送擦除扇区地址的低8位 */

SPI_FLASH_SendByte(BlockAddr & 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(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)

{

/* 发送FLASH写使能命令 */

SPI_FLASH_WriteEnable();

/* 选择FLASH: CS低电平 */

SPI_FLASH_CS_LOW();

/* 写页写指令*/

SPI_FLASH_SendByte(W25X_PageProgram);

/*发送写地址的高8位*/

SPI_FLASH_SendByte((WriteAddr & 0xFF000000) >> 24);

/*发送写地址的中前8位*/

SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);

/*发送写地址的中后8位*/

SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);

/*发送写地址的低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(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)

{

uint8_t 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);

}

}

}

}

/**

* @brief 读取FLASH数据

* @param pBuffer,存储读出数据的指针

* @param ReadAddr,读取地址

* @param NumByteToRead,读取数据长度

* @retval 无

*/

void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)

{

/* 选择FLASH: CS低电平 */

SPI_FLASH_CS_LOW();

/* 发送 读 指令 */

SPI_FLASH_SendByte(W25X_ReadData);

/* 发送 读 地址高8位 */

SPI_FLASH_SendByte((ReadAddr & 0xFF000000) >> 24);

/* 发送 读 地址中前8位 */

SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);

/* 发送 读 地址中后8位 */

SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);

/* 发送 读 地址低8位 */

SPI_FLASH_SendByte(ReadAddr & 0xFF);

/* 读取数据 */

while (NumByteToRead--)

{

/* 读取一个字节*/

*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);

/* 指向下一个字节缓冲区 */

pBuffer++;

}

/* 停止信号 FLASH: CS 高电平 */

SPI_FLASH_CS_HIGH();

}

/**

* @brief 读取FLASH ID

* @param 无

* @retval FLASH ID

*/

uint32_t SPI_FLASH_ReadID(void)

{

uint32_t 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

*/

uint32_t SPI_FLASH_ReadDeviceID(void)

{

uint32_t 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(uint32_t 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 返回接收到的数据

*/

uint8_t SPI_FLASH_ReadByte(void)

{

return (SPI_FLASH_SendByte(Dummy_Byte));

}

/**

* @brief 使用SPI发送一个字节的数据

* @param byte:要发送的数据

* @retval 返回接收到的数据

*/

uint8_t SPI_FLASH_SendByte(uint8_t byte)

{

SPITimeout = SPIT_FLAG_TIMEOUT;

/* 等待发送缓冲区为空,TXE事件 */

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);

}

/*******************************************************************************

* Function Name : SPI_FLASH_SendHalfWord

* Description : Sends a Half Word through the SPI interface and return the

* Half Word received from the SPI bus.

* Input : Half Word : Half Word to send.

* Output : None

* Return : The value of the received Half Word.

*******************************************************************************/

uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord)

{

SPITimeout = SPIT_FLAG_TIMEOUT;

/* Loop while DR register in not emplty */

while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_TXE ) == RESET)

{

if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);

}

/* Send Half Word through the SPIx peripheral */

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

SPITimeout = SPIT_FLAG_TIMEOUT;

/* Wait to receive a Half Word */

while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_RXNE ) == RESET)

{

if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);

}

/* Return the Half Word read from the SPI bus */

return READ_REG(SpiHandle.Instance->DR);

}

/**

* @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();

}

/**

* @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();

}

//进入掉电模式

void SPI_Flash_PowerDown(void)

{

/* 选择 FLASH: CS 低 */

SPI_FLASH_CS_LOW();

/* 发送 掉电 命令 */

SPI_FLASH_SendByte(W25X_PowerDown);

/* 停止信号 FLASH: 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(); //等待TRES1

}

/**

* @brief 使 SPI_FLASH 进入 4 字节地址模式

* @param none

* @retval none

*/

void SPI_FLASH_Mode_Init(void)

{

uint8_t Temp;

/*选择 FLASH: CS 低 */

SPI_FLASH_CS_LOW();

/* 发送状态寄存器 3 命令 */

SPI_FLASH_SendByte(W25X_ReadStatusRegister3);

Temp = SPI_FLASH_SendByte(Dummy_Byte);

/* 停止信号 FLASH: CS 高 */

SPI_FLASH_CS_HIGH();

if((Temp&0x01) == 0)

{

/*选择 FLASH: CS 低 */

SPI_FLASH_CS_LOW();

/* 进入4字节模式 */

SPI_FLASH_SendByte(W25X_Enter4ByteMode);

/* 停止信号 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;

}

3.main.c

#include "main.h"

#include "stm32f4xx.h"

#include "./usart/bsp_debug_usart.h"

#include "./led/bsp_led.h"

#include "./flash/bsp_spi_flash.h"

#include <string.h>

typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;

/* 获取缓冲区的长度 */

#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\nhttp://fire-stm32.taobao.com";

uint8_t Tx_Buffer[] = "45345";

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);

TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);

/*

* 函数名:main

* 描述 :主函数

* 输入 :无

* 输出 :无

*/

int main(void)

{

/* 设定系统时钟为180MHz */

SystemClock_Config();

LED_GPIO_Config();

LED_BLUE;

/* 配置串口1为:115200 8-N-1 */

DEBUG_USART_Config();

printf("\r\n这是一个16M串行flash(W25Q128)实验(QSPI驱动) \r\n");

/* 16M串行flash W25Q128初始化 */

SPI_FLASH_Init();

/* 获取 Flash Device ID */

DeviceID = SPI_FLASH_ReadDeviceID();

Delay( 200 );

/* 获取 SPI Flash ID */

FlashID = SPI_FLASH_ReadID();

printf("\r\nFlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);

/* 检验 SPI Flash ID */

if (FlashID == sFLASH_ID)

{

printf("\r\n检测到SPI FLASH W25Q128 !\r\n");

/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */

SPI_FLASH_SectorErase(0);

// SPI_FLASH_BlockErase(0);

// SPI_FLASH_BulkErase();

/* 将发送缓冲区的数据写到flash中 */

SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);

printf("\r\n写入的数据为:\r\n%s ;%d", Tx_Buffer, BufferSize);

/* 将刚刚写入的数据读出来放到接收缓冲区中 */

memset(Rx_Buffer,'\0',sizeof(Rx_Buffer));

SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);

printf("\r\n读出的数据为:\r\n");

for(int i=0; i< 5; i++)

{

printf("\r\n数据为:%d,%d ", Rx_Buffer[i],i);

}

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;

}

void Delay(__IO uint32_t nCount)

{

for(; nCount != 0; nCount--);

}

相关推荐
敲敲敲-敲代码8 分钟前
游戏设计:推箱子【easyx图形界面/c语言】
c语言·开发语言·游戏
simple_ssn30 分钟前
【C语言刷力扣】1502.判断能否形成等差数列
c语言·算法·leetcode
ahadee1 小时前
蓝桥杯每日真题 - 第10天
c语言·vscode·算法·蓝桥杯
好想有猫猫1 小时前
【51单片机】LCD1602液晶显示屏
c语言·单片机·嵌入式硬件·51单片机·1024程序员节
军训猫猫头1 小时前
35.矩阵格式的一到一百数字 C语言
c语言·算法
Mr_Xuhhh2 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
爱吃生蚝的于勒4 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
失落的香蕉7 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
追梦少年时9 小时前
STM32-Flash闪存
stm32·单片机·嵌入式硬件·51单片机
ChoSeitaku10 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表