一:W25Q128芯片介绍: W25Q128 是一款常见的串行 Flash 存储器芯片,由华邦(Winbond)公司生产。它的存储容量为 128Mbit(16MB),采用 SPI(串行外设接口)进行通信,常用于嵌入式系统中存储代码或数据
二:W25Q128操作注意事项:
W25Q128 的基本操作遵循 SPI 协议,一般流程是:片选拉低 → 发送命令字 → 发送地址(如需)→ 交换数据 → 片选拉高。
2.1:写使能(Write Enable, 06h):在执行任何编程或擦除操作前,必须先发送写使能指令。该指令会将状态寄存器中的写使能锁存(WEL)
2.2:页编程(Page Program, 02h):向指定地址写入数据。一次最多写入256字节(一页),且不能跨页写入。如果写入数据地址会跨页,超出页首地址部分会从当前页的开头覆写8。写入前必须确保目标区域已被擦除。
2.3:扇区擦除(Sector Erase, 20h):擦除指定4KB扇区。擦除操作会将所有位变为1。擦除前需写使能。
2.4:块擦除(Block Erase):提供32KB (52h) 和64KB (D8h) 块擦除选项。
2.5:芯片擦除(Chip Erase, C7h / 60h):擦除整个芯片。谨慎使用。
2.6:读数据(Read Data, 03h):从指定地址开始读取数据。读取操作没有页的限制8。
2.7:读状态寄存器(Read Status Register, 05h):主要用于检查 BUSY 位(S0)和 WEL 位(S1)。在编程、擦除或写状态寄存器期间,BUSY位会置1,此时芯片仅响应读状态寄存器和擦除暂停等少数指令。
读设备ID:如 Read JEDEC ID (9Fh) 可用于识别器件。
三:STM32cube MX软件配置如下所示:


这里需要配置一下 SPI的片选引脚,PB引脚,配置工作模式为开漏输出模式
GPIO_InitStruct.Pin = SPI2_CS_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(SPI2_CS_GPIO_Port, &GPIO_InitStruct);
四:软件代码如下所示:
4.1 修改硬件底层驱动函数:
//SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1, HAL_MAX_DELAY);
return Rxdata; //返回收到的数据
}4.2 写入函数如下所示:
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t * W25QXX_BUF;
W25QXX_BUF=W25QXX_BUFFER;
secpos=WriteAddr/4096;//扇区地址
secoff=WriteAddr%4096;//在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
while(1)
{
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
for(i=0;i<secremain;i++)//校验数据
{
if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
W25QXX_Erase_Sector(secpos);//擦除这个扇区
for(i=0;i<secremain;i++) //复制
{
W25QXX_BUF[i+secoff]=pBuffer[i];
}
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.
if(NumByteToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++;//扇区地址增1
secoff=0;//偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain;//写地址偏移
NumByteToWrite-=secremain; //字节数递减
if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完
else secremain=NumByteToWrite; //下一个扇区可以写完了
}
};
}4.3 读取函数如下所示:
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
uint16_t i;
FLASH_CS_LOW() ; //使能器件
SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位
{
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24));
}
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));
SPI2_ReadWriteByte((uint8_t)ReadAddr);
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循环读数
}
FLASH_CS_HIGH();
}4.4 测试代码如下所示:
uint8_t WriteBuffer[] ={"EEPW STM32F103 W25Q128 test !!"};
uint8_t ReadBuffer[256] ;
void Test_SPI_Funtion(void)
{
W25QXX_Write(&WriteBuffer[0],0,sizeof(WriteBuffer));
HAL_Delay(20);
W25QXX_Read(&ReadBuffer[0],0,sizeof(WriteBuffer)) ;
HAL_UART_Transmit(&huart1, &ReadBuffer[0], sizeof(WriteBuffer), 100);
}五:测试图片如下所示:

对于SPI调试的经验,大家可以参考下,我之前的帖子
【我踩过的那些坑】STM32的硬件通讯调试过程的“坑”-电子产品世界论坛
测试代码如下:
30

