一: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的硬件通讯调试过程的“坑”-电子产品世界论坛
测试代码如下: