W25Q64简介
•W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器(数据掉电不丢失),常应用于数据存储、字库存储、固件程序存储等场景
•存储介质:Nor Flash(闪存)
•时钟频率:80MHz / 160MHz (Dual SPI双重SPI) / 320MHz (Quad SPI四重SPI)
•存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
硬件电路


| 引脚 | 功能 |
|---|---|
| VCC、GND | 电源(2.7~3.6V) |
| CS(SS) | SPI片选 |
| CLK(SCK) | SPI时钟 |
| DI(MOSI) | SPI主机输出从机输入 |
| DO(MISO) | SPI主机输入从机输出 |
| WP | 写保护 |
| HOLD | 数据保持 |
W25Q64框图

右上角为存储器的规划示意图,以64KB为基本单元,分为若干个块。
Flash操作注意事项
写入操作时:
•写入操作前,必须先进行写使能(使用SPI发送写使能的指令)
•每个数据位只能由1改写为0,不能由0改写为1
•写入数据前必须先擦除,擦除后,所有数据位变为1
•擦除必须按最小擦除单元进行
•连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
•写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
•直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
软件SPI读写W25Q64
MySPI.c
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 | void MySPI_W_CS(uint8_t BitValue)//写CS引脚{ GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);} void MySPI_W_SCK(uint8_t BitValue)//写SCK引脚{ GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);} void MySPI_W_MOSI(uint8_t BitValue)//写MOSI引脚{ GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);} uint8_t MySPI_R_MISO(void)//读MISO引脚{ return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);} void MySPI_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //初始化DI、CLK、CS为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7); //初始化DO为上拉输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_6); MySPI_W_CS(1); MySPI_W_SCK(0);} void MySPI_Start(void)//起始信号,只需要把CS置低电平即可{ MySPI_W_CS(0);} void MySPI_Stop(void)//终止信号,只需要把CS置高电平即可{ MySPI_W_CS(1);}uint8_t MySPI_SwapByte(uint8_t ByteSend)//交换字节{// uint8_t i,ByteReceive = 0x00;//接收到的字节// for(i = 0; i < 8 ;i ++)// {// MySPI_W_MOSI(ByteSend & (0x80 >> i));// MySPI_W_SCK(1);// if (MySPI_R_MISO() == 1 ) // {// ByteReceive |= (0x80 >> i);// }// MySPI_W_SCK(0);// } uint8_t i; for(i = 0; i < 8 ;i ++) { MySPI_W_MOSI(ByteSend & 0x80); ByteSend <<= 1; MySPI_W_SCK(1); if (MySPI_R_MISO() == 1 ) { ByteSend |= 0x01; } MySPI_W_SCK(0); } return ByteSend;} |
W25Q64.c
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 | void W25Q64_Init(void)//初始化W25Q64{ MySPI_Init();} void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)//读取ID{ MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_JEDEC_ID);//发送0x9F,交换到厂商号 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收厂商号,发送的0xFF是为了交换到返回的厂商号 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收返回值的高8位 *DID <<= 8;//左移8位 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收返回值的低8位 MySPI_Stop();} void W25Q64_WriteEnable(void)//写使能{ MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送指令码,写使能 MySPI_Stop();//发送停止信号结束} void W25Q64_WaitBusy(void){ uint32_t Timeout; MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//发送指令码,读状态寄存器1 Timeout = 100000; while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) { Timeout -- ; if(Timeout == 0) { break; } } MySPI_Stop();} void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count){ W25Q64_WriteEnable(); uint16_t i; MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//发送指令码 MySPI_SwapByte(Address >> 16);//发送地址最高位 MySPI_SwapByte(Address >> 8);//发送地址中间位 MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了 for(i = 0; i < Count; i ++) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy();} void W25Q64_SectorErase(uint32_t Address){ W25Q64_WriteEnable(); MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//发送指令码,扇区擦除 MySPI_SwapByte(Address >> 16);//发送地址最高位 MySPI_SwapByte(Address >> 8);//发送地址中间位 MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了 MySPI_Stop(); W25Q64_WaitBusy();//事后等待} void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count){ uint32_t i; MySPI_Start();//先发送起始信号 MySPI_SwapByte(W25Q64_READ_DATA);//发送指令码,读数据 MySPI_SwapByte(Address >> 16);//发送地址最高位 MySPI_SwapByte(Address >> 8);//发送地址中间位 MySPI_SwapByte(Address);//发送地址最低位,高位被舍弃了 for(i = 0; i < Count; i ++) { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//发送0xFF,换回有用的数据 } MySPI_Stop();} |
main.c
123456789101112131415161718192021222324252627282930313233343536373839 | uint8_t MID;uint16_t DID; uint8_t ArrayWrite[] = {0x55,0x66,0x77,0x88};uint8_t ArrayRead[4]; int main(void){ OLED_Init(); W25Q64_Init(); OLED_ShowString(1,1,"MID: DID:"); OLED_ShowString(2,1,"W:"); OLED_ShowString(3,1,"R:"); W25Q64_ReadID(&MID,&DID); OLED_ShowHexNum(1,5,MID,2); OLED_ShowHexNum(1,12,DID,4); // W25Q64_SectorErase(0x000000); W25Q64_PageProgram(0x000000,ArrayWrite,4); W25Q64_ReadData(0x000000,ArrayRead,4); OLED_ShowHexNum(2,3,ArrayWrite[0],2); OLED_ShowHexNum(2,6,ArrayWrite[1],2); OLED_ShowHexNum(2,9,ArrayWrite[2],2); OLED_ShowHexNum(2,12,ArrayWrite[3],2); OLED_ShowHexNum(3,3,ArrayRead[0],2); OLED_ShowHexNum(3,6,ArrayRead[1],2); OLED_ShowHexNum(3,9,ArrayRead[2],2); OLED_ShowHexNum(3,12,ArrayRead[3],2);} |