引言
在嵌入式系统中,外部存储器如串行FLASH在数据存储和管理方面起到了至关重要的作用。本文将介绍如何在STM32平台上通过软件模拟SPI(Soft SPI)完成对W25Q64串行FLASH的控制,并编写相应的驱动程序。这个过程是移植文件系统FatFs的第一步。
准备工作
硬件准备
- STM32开发板:本文使用野火霸道开发板,芯片是STM32F103ZET6。
- W25Q64串行FLASH:一个外部存储芯片。
- 连接:开发板上已经将W25Q64的引脚和STM32的GPIO引脚连接好。
软件准备
- Keil MDK: 用于编写和调试代码。
引脚连接
下面是对应野火霸道开发板的引脚连接方案,具体连接根据自己使用开发板而定。
W25Q64引脚 | STM32引脚 |
CS | PA4 |
DO(对应主机MISO) | PA6 |
WP | 3.3V |
GND | GND |
DI(对应主机MOSI) | PA7 |
CLK | PA5 |
HOLD | 3.3V |
VCC | 3.3V |
软件模拟SPI的实现
GPIO初始化
为了更好的移植性,这里将使用到的GPIO引脚通过宏进行了定义,这样在后续有移植需求的情况下,只需要更改宏即可。
//CS(NSS)引脚 片选选普通GPIO即可
#define FLASH_SPI_CS_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_CS_PORT GPIOA
#define FLASH_SPI_CS_PIN GPIO_Pin_4
//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
/* 控制W25Q64的相关GPIO初始化函数 */
void W25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
FLASH_SPI_CS_APBxClock_FUN(FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE);
/* 配置SPI的 CS引脚 */
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_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
/* 配置SPI的 MOSI引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
/* 配置SPI的 MISO引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
/* 停止信号 FLASH: CS引脚高电平*/
SPI_FLASH_CS_HIGH();
}
SPI基本操作
编写通过SPI来进行读写的函数,因为使用的四线SPI是全双工的协议,发送一个字节数据的同时也会接收到一个字节数据,所以这里将读写一个字节的函数写成一个函数。
//通过下面的宏来完成基本的SPI操作
#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 )
#define SPI_FLASH_SCLK_LOW() GPIO_ResetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_SCLK_HIGH() GPIO_SetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_MOSI_LOW() GPIO_ResetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
#define SPI_FLASH_MOSI_HIGH() GPIO_SetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
/* SPI读写一个字节数据的函数 */
uint8_t SPI1_ReadWriteByte(u8 dat)
{
u8 i, read_data;
SPI_FLASH_SCLK_HIGH(); //时钟线拉高
for (i = 0; i< 8; i++)
{
read_data <<= 1;
if (0x80 == ((dat << i)&0x80))
{
SPI_FLASH_MOSI_HIGH();
}
else
{
SPI_FLASH_MOSI_LOW();
}
SPI_FLASH_SCLK_LOW(); //拉低时钟线,向总线输出数据
delay_us(1);
SPI_FLASH_SCLK_HIGH(); //拉高时钟线,准备读取数据
delay_us(1);
if (GPIO_ReadInputDataBit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN))
{
read_data |= 0x01; //读取到数据位为1
}
}
SPI_FLASH_SCLK_HIGH();
return read_data;
}
W25Q64基础驱动函数
写使能/写禁止函数
查看数据手册,对应指令如下:
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteDisable);
SPI_FLASH_CS_HIGH();
}
读写状态寄存器
查看数据手册,对应指令如下:
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
//读取W25QXX的状态寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t W25QXX_ReadSR(void)
{
u8 byte = 0;
SPI_FLASH_CS_LOW(); /* 选择FLASH: CS低电平 */
SPI1_ReadWriteByte(W25X_ReadStatusReg); //发送读状态寄存器指令
byte = SPI1_ReadWriteByte(0xff); //读取收到的状态寄存器的值
SPI_FLASH_CS_HIGH();
return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteStatusReg); //发送写状态寄存器指令
SPI1_ReadWriteByte(sr); //将值sr写入到状态寄存器
SPI_FLASH_CS_HIGH();
}
读取设备ID
通过读取W25Q64的设备ID可以帮助确认通信是否正常。
通过数据手册可以看出,当发送90h指令的时候,最后获取到的设备ID为0xef16。
#define W25X_ManufactDeviceID 0x90
#define W25Q64 0xef16
u16 W25QXX_ReadID(void)
{
u16 temp;
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ManufactDeviceID); //发送读设备ID指令
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
temp |= (SPI1_ReadWriteByte(0xff)<<8); //数据存放在temp高8位
temp |= SPI1_ReadWriteByte(0xff); //数据存放在temp低8位
SPI_FLASH_CS_HIGH();
return temp;
}
读FLASH内部数据函数
#define W25X_ReadData 0x03
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
u16 i;
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ReadData); //发送读数据指令
SPI1_ReadWriteByte((u8)(ReadAddr>>16)); //发送要读的24bit地址
SPI1_ReadWriteByte((u8)(ReadAddr>>8));
SPI1_ReadWriteByte((u8)ReadAddr);
for (i=0; i<NumByteToRead; i++)
{
pBuffer[i] = SPI1_ReadWriteByte(0xff); //读取数据
}
SPI_FLASH_CS_HIGH();
}
向FLASH内部写入数据函数
#define W25X_PageProgram 0x02
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u16 i;
if (NumByteToWrite > 256) /* 参数检查 */
NumByteToWrite = 256;
W25QXX_Write_Enable(); //写使能
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_PageProgram);
SPI1_ReadWriteByte((u8)(WriteAddr>>16));
SPI1_ReadWriteByte((u8)(WriteAddr>>8));
SPI1_ReadWriteByte((u8)WriteAddr);
for (i=0; i<NumByteToWrite; i++)
{
SPI1_ReadWriteByte(pBuffer[i]);
}
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy(); //等待写入结束
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/* 取余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0 */
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr; //差count个数据值,刚好可以对齐到页地址
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; //计算出要写多少整数页/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; //计算出剩余不满一页的字节数
if (Addr == 0) //WriteAddr刚好按页对齐
{
if (NumOfPage == 0) //不足一页
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
else //数据比一页多
{
while (NumOfPage--) //先把整数页写完
{
W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/* 若有多余的不足一页的数据,将其写完 */
W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
else //WriteAddr没有按页对齐
{
if (NumOfPage == 0) //不足一页
{
/* 当前页可写的数据数不足以将数据写完 */
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
//先将当前页写完
W25QXX_Write_Page(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
//再将剩余的数据写完
W25QXX_Write_Page(pBuffer, WriteAddr, temp);
}
else /* 当前页可写的数据数可以将数据写完 */
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
}
else //要写的数据数大于一页
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先写完count个数据,为的是让下一次要写的地址对齐 */
W25QXX_Write_Page(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0)
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
整片擦除和擦除扇区函数
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
W25QXX_Write_Enable();
W25QXX_Wait_Busy();
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_SectorErase);
SPI1_ReadWriteByte((u8)(Dst_Addr >> 16));
SPI1_ReadWriteByte((u8)(Dst_Addr >> 8));
SPI1_ReadWriteByte((u8)Dst_Addr);
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
/* 发送FLASH写使能命令 */
W25QXX_Write_Enable();
W25QXX_Wait_Busy();
/* 整块 Erase */
SPI_FLASH_CS_LOW(); //选择FLASH: CS低电平
SPI1_ReadWriteByte(W25X_ChipErase); // 发送整块擦除指令
SPI_FLASH_CS_HIGH(); //停止信号 FLASH: CS 高电平
/* 等待擦除完毕*/
W25QXX_Wait_Busy();
}
等待空闲函数
该函数一般用于写操作之后,确保数据写入完成。
//等待空闲
void W25QXX_Wait_Busy(void)
{
while ((W25QXX_ReadSR() & 0x01) == 0x01); // 等待BUSY位清空
}
进入掉电模式/唤醒函数
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
//进入掉电模式
void W25QXX_PowerDown(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_PowerDown);
SPI_FLASH_CS_HIGH();
delay_us(3);
}
//唤醒
void W25QXX_WAKEUP(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ReleasePowerDown);
SPI_FLASH_CS_HIGH();
delay_us(3);
}
通过这些函数就可以实现对W25Q64的基本读写和擦除操作,在main函数中对这些函数进行测试,看能否正确读写数据。
函数测试
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 WriteAddress 0x00000
#define ReadAddress FLASH_WriteAddress
#define SectorToErase FLASH_WriteAddress
#define W25Q64 0xEF16
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = "软件模拟SPI对FLASH进行测试\r\n";
uint8_t Rx_Buffer[BufferSize];
u16 W25QXX_TYPE;
// 函数原型声明
void Delay(__IO uint32_t nCount);
TestStatus Buffercmp(uint8_t* pBuffer1,uint8_t* pBuffer2, uint16_t BufferLength);
/*
* 函数名:main
* 描述 :主函数
* 输入 :无
* 输出 :无
*/
int main(void)
{
/* 配置串口为:115200 8-N-1 */
USART_Config();
printf("\r\n 串行flash(W25Q64)实验 \r\n");
/* W25Q64初始化 */
W25QXX_Init();
/* 获取 Flash Device ID */
W25QXX_TYPE= W25QXX_ReadID();
Delay( 200 );
/* 检验 SPI Flash ID */
if (W25QXX_TYPE == W25Q64)
{
printf("\r\n 检测到 W25Q64 !\r\n");
/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */
// 这里擦除4K,即一个扇区,擦除的最小单位是扇区
W25QXX_Erase_Sector(FLASH_SectorToErase);
/* 将发送缓冲区的数据写到flash中 */
W25QXX_Write(Tx_Buffer, FLASH_WriteAddress, BufferSize);
printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);
/* 将刚刚写入的数据读出来放到接收缓冲区中 */
W25QXX_Read(Rx_Buffer, FLASH_ReadAddress, BufferSize);
printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);
/* 检查写入的数据与读出的数据是否相等 */
TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
if( PASSED == TransferStatus1 )
{
printf("\r\n W25Q64 测试成功!\n\r");
}
else
{
printf("\r\n W25Q64 测试失败!\n\r");
}
}
else
{
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--);
}
完整的W25Q64驱动文件如下
spi_flash.h
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H
#include "stm32f10x.h"
#include <stdio.h>
#define W25Q80 0XEF13
#define W25Q16 0XEF14
#define W25Q32 0XEF15
#define W25Q64 0XEF16
#define W25Q128 0XEF17
#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_GPIOA
#define FLASH_SPI_CS_PORT GPIOA
#define FLASH_SPI_CS_PIN GPIO_Pin_4
//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 )
#define SPI_FLASH_SCLK_LOW() GPIO_ResetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_SCLK_HIGH() GPIO_SetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_MOSI_LOW() GPIO_ResetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
#define SPI_FLASH_MOSI_HIGH() GPIO_SetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
/*SPI接口定义-结尾****************************/
/*函数声明-开头****************************/
void W25QXX_Init(void);
uint8_t SPI1_ReadWriteByte(u8 dat);
uint8_t W25QXX_ReadSR(void);
void W25QXX_Write_SR(u8 sr);
void W25QXX_Write_Enable(void);
void W25QXX_Write_Disable(void);
u16 W25QXX_ReadID(void);
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead);
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void W25QXX_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void W25QXX_Erase_Chip(void);
void W25QXX_Erase_Sector(u32 Dst_Addr);
void W25QXX_Wait_Busy(void);
void W25QXX_PowerDown(void);
void W25QXX_WAKEUP(void);
/*函数声明-结尾****************************/
#endif /* __SPI_FLASH_H */
spi_flash.c
#include "spi_flash.h"
u16 W25QXX_TYPE = W25Q64;
void delay_us(u16 nus)
{
u16 i;
for (i=0; i<nus; i++)
{
}
}
void W25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
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_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
/* 配置SPI的 MOSI引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
/* 配置SPI的 MISO引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
/* 停止信号 FLASH: CS引脚高电平*/
SPI_FLASH_CS_HIGH();
W25QXX_TYPE = W25QXX_ReadID();
if (W25QXX_TYPE == W25Q64)
{
printf("FlashID: %#x\r\n", W25QXX_TYPE);
printf("w25q64 ok\r\n");
}
}
uint8_t SPI1_ReadWriteByte(u8 dat)
{
u8 i, read_data;
SPI_FLASH_SCLK_HIGH(); //时钟线拉高
for (i = 0; i< 8; i++)
{
read_data <<= 1;
if (0x80 == ((dat << i)&0x80))
{
SPI_FLASH_MOSI_HIGH();
}
else
{
SPI_FLASH_MOSI_LOW();
}
SPI_FLASH_SCLK_LOW(); //拉低时钟线,向总线输出数据
delay_us(1);
SPI_FLASH_SCLK_HIGH(); //拉高时钟线,准备读取数据
delay_us(1);
if (GPIO_ReadInputDataBit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN))
{
read_data |= 0x01;
}
}
SPI_FLASH_SCLK_HIGH();
return read_data;
}
//读取W25QXX的状态寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t W25QXX_ReadSR(void)
{
u8 byte = 0;
SPI_FLASH_CS_LOW(); /* 选择FLASH: CS低电平 */
SPI1_ReadWriteByte(W25X_ReadStatusReg);
byte = SPI1_ReadWriteByte(0xff);
SPI_FLASH_CS_HIGH();
return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteStatusReg);
SPI1_ReadWriteByte(sr);
SPI_FLASH_CS_HIGH();
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteDisable);
SPI_FLASH_CS_HIGH();
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
u16 W25QXX_ReadID(void)
{
u16 temp;
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ManufactDeviceID);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
temp |= (SPI1_ReadWriteByte(0xff)<<8);
temp |= SPI1_ReadWriteByte(0xff);
SPI_FLASH_CS_HIGH();
return temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
u16 i;
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ReadData);
SPI1_ReadWriteByte((u8)(ReadAddr>>16));
SPI1_ReadWriteByte((u8)(ReadAddr>>8));
SPI1_ReadWriteByte((u8)ReadAddr);
for (i=0; i<NumByteToRead; i++)
{
pBuffer[i] = SPI1_ReadWriteByte(0xff);
}
SPI_FLASH_CS_HIGH();
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u16 i;
if (NumByteToWrite > 256) /* 参数检查 */
NumByteToWrite = 256;
W25QXX_Write_Enable(); //写使能
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_PageProgram);
SPI1_ReadWriteByte((u8)(WriteAddr>>16));
SPI1_ReadWriteByte((u8)(WriteAddr>>8));
SPI1_ReadWriteByte((u8)WriteAddr);
for (i=0; i<NumByteToWrite; i++)
{
SPI1_ReadWriteByte(pBuffer[i]);
}
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy(); //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u16 pageremain;
pageremain = 256 - WriteAddr%256;
if (NumByteToWrite <= pageremain)
{
pageremain = NumByteToWrite;
}
while (1)
{
W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);
if (pageremain == NumByteToWrite)
{
break;
}
else
{
pBuffer += pageremain;
WriteAddr += pageremain;
NumByteToWrite -= pageremain;
if (NumByteToWrite > 256)
{
pageremain = 256;
}
else
{
pageremain = NumByteToWrite;
}
}
}
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
void W25QXX_Write(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/* 取余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0 */
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr; //差count个数据值,刚好可以对齐到页地址
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; //计算出要写多少整数页/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; //计算出剩余不满一页的字节数
if (Addr == 0) //WriteAddr刚好按页对齐
{
if (NumOfPage == 0) //不足一页
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
else //数据比一页多
{
while (NumOfPage--) //先把整数页写完
{
W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/* 若有多余的不足一页的数据,将其写完 */
W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
else //WriteAddr没有按页对齐
{
if (NumOfPage == 0) //不足一页
{
/* 当前页可写的数据数不足以将数据写完 */
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
//先将当前页写完
W25QXX_Write_Page(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
//再将剩余的数据写完
W25QXX_Write_Page(pBuffer, WriteAddr, temp);
}
else /* 当前页可写的数据数可以将数据写完 */
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
}
else //要写的数据数大于一页
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先写完count个数据,为的是让下一次要写的地址对齐 */
W25QXX_Write_Page(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
W25QXX_Write_Page(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0)
{
W25QXX_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
/* 发送FLASH写使能命令 */
W25QXX_Write_Enable();
W25QXX_Wait_Busy();
/* 整块 Erase */
SPI_FLASH_CS_LOW(); //选择FLASH: CS低电平
SPI1_ReadWriteByte(W25X_ChipErase); // 发送整块擦除指令
SPI_FLASH_CS_HIGH(); //停止信号 FLASH: CS 高电平
/* 等待擦除完毕*/
W25QXX_Wait_Busy();
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{
W25QXX_Write_Enable();
W25QXX_Wait_Busy();
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_SectorErase);
SPI1_ReadWriteByte((u8)(Dst_Addr >> 16));
SPI1_ReadWriteByte((u8)(Dst_Addr >> 8));
SPI1_ReadWriteByte((u8)Dst_Addr);
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
while ((W25QXX_ReadSR() & 0x01) == 0x01); // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_PowerDown);
SPI_FLASH_CS_HIGH();
delay_us(3);
}
//唤醒
void W25QXX_WAKEUP(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_ReleasePowerDown);
SPI_FLASH_CS_HIGH();
delay_us(3);
}
结语
本文介绍了如何在STM32平台上通过软件模拟SPI完成对W25Q64串行FLASH的基本控制。我们实现了SPI基本操作、读取设备ID和扇区擦除等功能。下一步将介绍如何将FatFs文件系统移植到W25Q64上,以便实现更复杂的数据管理功能。
在接下来的文章中,我们将继续深入探讨文件系统的移植和优化,敬请期待!
相关阅读
如果你有任何问题或建议,请在评论区留言。感谢阅读!