前言
在单片机中一般32K FLASH就够用了,但是当我们使用图片或其他大量数据时就不够用了,又因为数据为固定数据,不需要为此而换大容量芯片。因此用外挂存储芯片来解决这个问题。
本篇文章采用SPI软件通信的方式进行讲解,了解存储芯片同时加深SPI通信了解。
环境:
芯片:STM32F103C8T6
Keil:V5.24.2.0
外设:4针OLED屏,W25Q64芯片
关于SPI通信简介看我之前文章:
还包含了读ID指令。本篇就不在赘述。
标黄色的为重要代码,我们应尽量掌握。
各个代码进行宏定义,方便我们使用,也方便我们以后查阅
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF //无用数据(不是无用)
在写入数据时必须进行的操作,防止误操作
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);
Timeout =1000000; //防死循环措施,时间可以加长
while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout --;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
1.地址:指定写入的地址
2.数据:需要写入的数据,根据芯片描述,每页写入数据不大于64K,大于64K需要进行分页写入
3.数组的大小,方便进行循环
/**************************
@:地址
@:要写入的数据
@:数组数
***************************/
void W25Q64_PageProgram(uint32_t Address,uint32_t *DataArray,uint16_t Count)//写入代码
{
uint16_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16); //总共24位地址,右移16后剩高8位
MySPI_SwapByte(Address >> 8); //右移8位后剩高位16,但是只能发送8位,高8位舍弃
MySPI_SwapByte(Address);
for(i = 0;i < Count ;i ++) //对数组进行写入
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy(); //等待芯片空闲,在读写后进行等待
注:因为芯片写入数据需要时间,不是我们MCU发送完那边就全部写入了。因此我们要等待其数据全部存储完,不然后果就是我们的下次写入操作会数据不准确。当然我们也可以把这行代码放在操作函数前。判断芯片是否忙碌状态。
?代码注释较全。
void W25Q64_SectorErase(uint32_t Address) //擦除函数操作
{
W25Q64_WriteEnable();
W25Q64_WaitBusy();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16); //总共24位地址,右移16后剩高8位
MySPI_SwapByte(Address >> 8); //右移8位后剩高位16,但是只能发送8位,高8位舍弃
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy(); //等待芯片空闲,需要在读写后进行等待
}
void W25Q64_ReadData(uint32_t Address,uint32_t *DataArray,uint32_t Count)//读取代码
{
uint32_t i;
W25Q64_WaitBusy();
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16); //总共24位地址,右移16后剩高8位
MySPI_SwapByte(Address >> 8); //右移8位后剩高位16,但是只能发送8位,高8位舍弃
MySPI_SwapByte(Address);
for(i = 0;i < Count ;i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//以此来置换回需要的数据
}
MySPI_Stop();
}
写入和读取
uint32_t ArrayWrite[] = {0x01,0x02,0x03};
uint32_t ArrayRead[3];
int main(void)
{
OLED_Init();
W25Q64_Init();
W25Q64_SectorErase(0x000000); //擦除操作
W25Q64_PageProgram(0x000000,ArrayWrite,3); //写入数据
W25Q64_ReadData(0x000000,ArrayRead,3); 读取数据
OLED_ShowHexNum(2, 1, ArrayWrite[0], 2); //显示写入的三个数据
OLED_ShowHexNum(2, 5, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(3, 1, ArrayRead[0], 2); //显示读取的三个数据
OLED_ShowHexNum(3, 5, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
while (1)
{
}
}
因芯片设计问题,数据在写入时,只能从1转为0,不可以从0转为1,如果要想从0 转为1,就只能进行擦除作业。擦除分整个擦除,扇区,块擦除三个等级,范围大小不一样,看需要进行操作
因此。我们在写入数据前应尽量进行扇区擦除来保证数据的准确性。大家可以屏闭擦除函数进行验证。
W25Q64芯片存储数据还是比较好用,原理比较清楚。但还是有一些问题待解决
1.写入大数据(如200KB)而MCU除了必要的程序数据(不包含要写入W25Q64的数据)外还剩40K数据。我们需要对200K数据进行5次分割才能写入数据,比较麻烦且不方便量产
2.因芯片每页数据最大64K,因此我们还需对数据进行准确分割,不能超过。