SPI编程——读写Flash芯片(W25Q64JV)
FLASH芯片介绍——W25Q64JV
W25Q64JV的芯片手册是英文的,用软件翻译了一下,有些地方翻译得不准确,大概了解一下即可,例如芯片的工作电压在2.7V到3.6V的电源上,电流消耗地至断电1uA,每个页面256字节,一次最多可编程256字节,通信接口有SPI
W25Q64JV常用指令集
32单片机通过SPI总线与W25Q64JV芯片通信,可通过发送指令控制W25Q64JV的相关操作,例如读写Flash存储器,清除存储内容等
指令 | 作用 |
---|
0x06 | 写使能 | 0x04 | 写禁止 | 0x05 | 读状态寄存器1,可判断芯片是否准备接收下一条指令 | 0x03 | 读数据 | 0x02 | 页编程 | 0x20 | 扇区擦除 | 0xC7 | 芯片擦除 | 0x9F | 读设备ID信息 |
指令详细作用要看数据手册
页编程指令——0x02
说明:
第1段:一次写入字节不能超过256字节(一页),在写入之前要先执行0xFF指令擦除内存;发送任何指令之前,都要先拉低CS引脚
第2段:这一段就说明一页最多256个字节,如果前几次写入的字节长度小于256字节,则剩下的空间还能继续存数据,这对前面存入的数据没影响;如果某一次写入的字节长度大于剩余的空间,256个字节已经装满数据了,则多出来的部分就会将开头的数据覆盖掉,造成开头页面数据丢失
第3段:在写入一个字节的最后一位后,必须将CS引脚拉高,这里需要注意,STM32自带有硬件SPI接口,但硬件的SPI接口CS引脚在传输完数据之后并不会自动拉高,一直是低电平,这不符合W25Q64JV芯片的时序要求,所以在初始化时,不使用硬件SPI的CS引脚,使用普通的GPIO口功能驱动W25Q64JV的CS引脚,通过编程拉低或者拉高CS引脚,达到芯片的时序要求,W25Q64JV的CS引脚是接到32单片机的SPI3_NSS引脚的,只是不使用这个NSS功能,用普通IO口;
读状态寄存器指令0x05可以判断数据是否已经完全写入Flash芯片,没写完时BUSY位是1,写完后BUSY位变为0,就可以发送下一条指令
页编程时序图
通过看时序图可以知道,W25Q64JV芯片支持SPI通信的模式3和模式0,如果用模式1和模式2的话,就会出错
这里使用的是模式0,在CLK的上升沿采集数据,数据是先发高位再发低位的
读数据指令——0x03
读取Flash数据就比较简单,也是要先将CS引脚拉低,发送0x03指令,再发送要读取的地址,在主机接受完数据后,要将CS引脚拉高
读取一个地址的数据后,地址指针会自动增加到下一个地址,所以可以连续读取数据
要注意读数据指令要在BUSY位为0时发送,否则会被芯片自动忽略
读数据时序图
CubeMX配置
GPIO配置
与Flash芯片CS引脚连接的单片机引脚为PA15,可复用为SPI3_NSS,这次配置不开启SPI3_NSS功能,用普通IO口PA15来输出高低电平
PA15配置为推挽输出,默认输出电平为高电平
SPI3配置
根据硬件电路图,选择SPI3
在模式中选择全双工主机,因为上边GPIO引脚没有复用为NSS,所以第二个选择不启用
其中模式选择有多种,一般32单片机作为主机,需要根据通信双方来进行选择
参数配置
CPOL和CPHA的选择可以组合成4种模式,CPOL可以选择0或者1,CPHA的1Edge表示奇数边采集数据,2Edge表示偶数边采集数据,对应了SPI通信总线介绍的4种不同模式;
因为W25Q64JV芯片是只支持模式3和模式0的,所以32也要配置为模式3或者模式0,其他模式会通信失败
CPOL = Low,CPHA = 1 Edge 时表示模式0
CPOL = High,CPHA = 2 Edge 时表示模式3
程序
SPI_Flash.h
头文件中定义CS引脚,根据W25Q64JV芯片的数据手册编写指令宏定义
#define SET_SPI_Flash_CS HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port,SPI_Flash_CS_Pin,GPIO_PIN_SET)
#define CLR_SPI_Flash_CS HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port,SPI_Flash_CS_Pin,GPIO_PIN_RESET)
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusRg1 0x05
#define W25X_ReadData 0x03
#define W25X_PageProgram 0x02
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_ReadJedecID 0x9F
#define SPI_FLASH_PageSize 256
#define Flash_Status1_BUSY BIT0
#define Dummy_Byte 0xFF
SPI_Flash.c
Flash写入一个字节函数
static void SPI_Flash_WriteByte(uint8_t Byte)
{
uint8_t SendByte = Byte;
HAL_SPI_Transmit(&hspi3,&SendByte,1,0x0A);
}
Flash读取一个字节函数
static uint8_t SPI_Flash_ReadByte()
{
uint8_t ReceiveByte;
if(HAL_SPI_Receive(&hspi3,&ReceiveByte,1,0x0A) != HAL_OK)
{
ReceiveByte = Dummy_Byte;
}
return ReceiveByte;
}
Flash写使能,在写入数据之前,要先调用该函数使能写操作
static void SPI_Flash_WriteEnable()
{
CLR_SPI_Flash_CS;
SPI_Flash_WriteByte(W25X_WriteEnable);
SET_SPI_Flash_CS;
}
等待SPI数据写入完成,是通过判断读状态寄存器1的BUSY位,当芯片在读数据或者写数据时,BUSY位是1,当读完数据或者写完数据后,BUSY位是0
static void SPI_Flash_WaitForWriteEnd()
{
uint8_t Flash_Status = 0;
CLR_SPI_Flash_CS;
SPI_Flash_WriteByte(W25X_ReadStatusRg1);
Timer6.usDelay_Timer = 0;
do
{
Flash_Status = SPI_Flash_ReadByte();
if(Timer6.usDelay_Timer >= TIMER_10s)
{
break;
}
} while((Flash_Status&Flash_Status1_BUSY) == Flash_Status1_BUSY);
SET_SPI_Flash_CS;
}
扇区擦除函数,在写入数据之前,要先执行擦除操作
static void SPI_Flash_EraseSector(uint32_t SectorAddr)
{
SPI_Flash_WaitForWriteEnd();
SPI_Flash_WriteEnable();
CLR_SPI_Flash_CS;
SPI_Flash_WriteByte(W25X_SectorErase);
SPI_Flash_WriteByte((SectorAddr & 0xFF0000) >> 16);
SPI_Flash_WriteByte((SectorAddr & 0x00FF00) >> 8);
SPI_Flash_WriteByte((SectorAddr & 0x0000FF));
SET_SPI_Flash_CS;
SPI_Flash_WaitForWriteEnd();
printf("扇区擦除成功!\r\n");
}
写入页数据,一个页面存储的数据最多256个字节
因此该函数使用有风险
因为每一页最多只能写256个字节,如果某一次写入数据后,页里的字节超过了256,则超过的部分会将这一页最开始的地址数据覆盖,造成数据丢失
static void SPI_Flash_WritePage(uint8_t* pWriteBuffer,uint32_t WriteAddr,uint16_t WriteLength)
{
SPI_Flash_WaitForWriteEnd();
SPI_Flash_WriteEnable();
CLR_SPI_Flash_CS;
SPI_Flash_WriteByte(W25X_PageProgram);
SPI_Flash_WriteByte((WriteAddr & 0xFF0000) >> 16);
SPI_Flash_WriteByte((WriteAddr & 0x00FF00) >> 8);
SPI_Flash_WriteByte((WriteAddr & 0x0000FF));
if(WriteLength > SPI_FLASH_PageSize)
{
WriteLength = SPI_FLASH_PageSize;
printf("Flash每次写入的数据长度不能超过256个字节");
}
while(WriteLength--)
{
SPI_Flash_WriteByte(*pWriteBuffer);
pWriteBuffer++;
}
SET_SPI_Flash_CS;
SPI_Flash_WaitForWriteEnd();
}
读取不固定长度数据,还有个写入不固定长度数据函数,代码篇幅太长,所以不展示
static void SPI_Flash_ReadUnfixed(uint8_t* pReadBuffer,uint32_t ReadAddr,uint32_t ReadLength)
{
SPI_Flash_WaitForWriteEnd();
CLR_SPI_Flash_CS;
SPI_Flash_WriteByte(W25X_ReadData);
SPI_Flash_WriteByte((ReadAddr & 0xFF0000) >> 16);
SPI_Flash_WriteByte((ReadAddr & 0x00FF00) >> 8);
SPI_Flash_WriteByte((ReadAddr & 0x0000FF));
while(ReadLength--)
{
*pReadBuffer = SPI_Flash_ReadByte();
pReadBuffer++;
}
SET_SPI_Flash_CS;
}
系统运行函数,调用SPI的相关函数,完成写入不定长数据和读取不定长数据的功能
如果写入数据的地址和读取数据的地址不一致则会读不到写入的数据
static void Run()
{
uint8_t i;
uint8_t CMP_Flag = TRUE;
uint8_t Tx_Buffer[] = "SPI通信——Flash芯片读写测试";
const uint8_t BufferSize = sizeof(Tx_Buffer)/sizeof(Tx_Buffer[0]);
uint8_t Re_Buffer[BufferSize] = {0};
SPI_Flash.SPI_Flash_EraseSector(0x00000000);
SPI_Flash.SPI_Flash_WriteUnfixed(Tx_Buffer,0x00000088,BufferSize);
printf("写入的数据为:%s\r\n",Tx_Buffer);
SPI_Flash.SPI_Flash_ReadUnfixed(Re_Buffer,0x00000088,BufferSize);
printf("读取的数据为:%s\r\n",Re_Buffer);
for(i=0;i<BufferSize;i++)
{
if(Tx_Buffer[i] != Re_Buffer[i])
{
CMP_Flag = FALSE;
break;
}
}
if(CMP_Flag == TRUE)
{
printf("Flash芯片读写数据测试成功!\r\n");
}
else
{
printf("Flash芯片读写数据测试失败!\r\n");
}
HAL_Delay(1000);
}
实验效果
|