WinBond W25Q64JV SPI Flash指定地址擦除与覆写方法咨询
解决W25Q64JV SPI Flash覆写与指定地址擦除问题
咱们一步步拆解你的问题并解决——SPI Flash的写入和擦除有特定的硬件规则,你现在刚好漏掉了几个关键步骤,导致出现无法覆写、误擦除全片的情况。
先搞懂SPI Flash的核心特性
SPI Flash的存储单元只能从1变为0,无法直接从0变为1。所以要覆写旧数据,必须先把目标区域擦除(擦除会把该区域所有位变回1),之后才能写入新数据。这是你无法直接覆写的根本原因。
另外,W25Q64JV的擦除命令分好几种,你可能搞混了:
0x20:扇区擦除,只擦除4KB的扇区(比如地址0x1000-0x1FFF就是一个扇区)0x52:32KB块擦除0xD8:64KB块擦除0xC7/0x60:芯片擦除,会清空整个Flash的所有数据!
你说用0x20命令会全清,大概率是误发了0xC7/0x60命令,或者擦除前没做写使能导致操作异常,后续误触发了全片擦除。
关键错误分析
看你的代码,擦除操作有个致命问题:擦除前没有发送写使能命令(0x06)!和写入操作一样,擦除必须先让Flash进入可写/可擦除状态,否则擦除命令根本不会执行,甚至可能引发异常操作。
另外,你没有等待擦除/写入完成——SPI Flash的擦除、写入都是耗时操作,不能发完命令就立刻做其他操作,必须等待芯片的忙状态解除。
完整的覆写+指定扇区擦除流程
下面是针对你需求的完整代码步骤,以覆写地址0x1014为例:
1. 先写一个等待Flash就绪的工具函数
这个函数会读取Flash的状态寄存器,直到忙状态解除:
uint8_t wait_flash_ready(void) { uint8_t status; do { // 拉低片选 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x05; // 读取状态寄存器1的命令 HAL_SPI_Transmit(&hspi2, Spi_data, 1, 100); HAL_SPI_Receive(&hspi2, &status, 1, 100); // 拉高片选 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); } while((status & 0x01) != 0); // 等待BUSY位(第0位)变为0 return status; }
2. 完整的覆写操作代码
// -------------------------- 步骤1:擦除目标地址所在的扇区 -------------------------- // 1.1 发送写使能命令 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x06; // Write Enable HAL_SPI_Transmit(&hspi2, Spi_data, 1, 1000); HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); // 1.2 发送扇区擦除命令(0x20),指定目标地址0x001014 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x20; // 扇区擦除命令 Spi_data[1] = 0x00; // 地址高位(A23-A16) Spi_data[2] = 0x10; // 地址中位(A15-A8) Spi_data[3] = 0x14; // 地址低位(A7-A0) HAL_SPI_Transmit(&hspi2, Spi_data, 4, 1000); HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); // 1.3 等待擦除完成(必须等,否则后续写入会失败) wait_flash_ready(); // -------------------------- 步骤2:写入新数据到目标地址 -------------------------- // 2.1 再次发送写使能(擦除操作会自动清除写使能位) HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x06; // Write Enable HAL_SPI_Transmit(&hspi2, Spi_data, 1, 1000); HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); // 2.2 发送页编程命令,写入新数据到0x001014 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x02; // Page Program命令 Spi_data[1] = 0x00; Spi_data[2] = 0x10; Spi_data[3] = 0x14; Spi_data[4] = 0xAA; // 这里替换成你要写入的新数据 HAL_SPI_Transmit(&hspi2, Spi_data, 5, 10000); HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); // 2.3 等待写入完成 wait_flash_ready(); // -------------------------- 步骤3:读取数据验证 -------------------------- HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_RESET); Spi_data[0] = 0x03; // Read Data命令 Spi_data[1] = 0x00; Spi_data[2] = 0x10; Spi_data[3] = 0x14; HAL_SPI_Transmit(&hspi2, Spi_data, 4, 100); HAL_SPI_Receive(&hspi2, GSdata, 1, 100); // 读取1字节数据 HAL_GPIO_WritePin(CHIP_SELECT_GPIO_Port, CHIP_SELECT_Pin, GPIO_PIN_SET); // 现在GSdata[0]应该是你写入的新数据0xAA
额外注意事项
- 扇区擦除会擦除整个4KB扇区,如果你只需要修改几个字节,这是SPI Flash的必经之路,没有更小粒度的擦除(比如字节擦除)。
- 页编程命令(0x02)只能写入同一256字节页内的数据,如果你的写入地址跨页,需要分多次写入。
- 每次写/擦除操作前都必须发送写使能,擦除后要重新发送,因为擦除会自动关闭写使能。
- 绝对不要误发
0xC7/0x60命令,这会清空整个Flash的所有数据。
内容的提问来源于stack exchange,提问作者Sathish




