我正在编写 STM32 固件以与 Winbond W25Q01JV 闪存芯片连接。该芯片在两个 512M 位芯片中提供 1GBit 内存,每个芯片都有自己的状态寄存器。闪存芯片用作两个非易失性 FIFO 缓冲区(每个 512M 位芯片上一个)写入和验证 256 字节页面。
虽然大多数命令使用 3 字节寻址,但芯片可以置于 4 字节地址模式,并且存在用于擦除、写入和读取的 4 字节地址命令(无论芯片是 3 字节还是 4 字节)地址模式)。
命令 0x21 应该擦除从给定 4 字节地址开始的 4K 扇区。它前面必须有写使能命令 0x06。相反,我发现命令 0x21 只会擦除 256 字节的页面,无论我如何尝试使用它。
我还发现,当使用擦除、写入、读取和验证循环时,从 0x00000000 到 0x06FFFFFF 的所有 256 字节页面验证都会失败,但 0x07000000 页面会失败。我已经在两个相同的开发板上尝试过这一点。两者都从相同的地址 0x07000000 失败,其中一个失败到地址范围的末尾,另一个失败从 0x07000000 到 0x7000D00。即使在全芯片擦除后尝试写入、读取验证后,这些位置也会失败。
我以为芯片可能被设置了写保护。两个芯片都清除了三个寄存器,除了设置了寄存器 #3 SRV1(输出驱动强度 = 01,默认为 75%)。
我已经进行了广泛的测试、代码修订和数据表的重新阅读。结果保持不变。为了简洁起见,代码示例是作为整体函数给出的最新迭代。
任何人都可以告诉我我做错了什么吗?
/********************************************************************
* NVR - DATA WRITE
********************************************************************
* Write array of up to 256 bytes to memory page specified by Addr.
* Assume that the memory page has already been erased.
* Addr should be the start of a 256-byte memory page.
* Return HAL_OK if success.
*
* Declared above
* const uint8_t NVR_TIMEOUT = 100;
* uint8_t NvrHead[5];
* uint8_t NvrData[252];
*/
HAL_StatusTypeDef nvrDataWrite(uint32_t Addr, const uint8_t * Data, uint16_t DataLen) {
HAL_StatusTypeDef LocalResult = HAL_OK;
uint8_t LocalEraseFlag = 0;
/* Are we starting 4K sector */
if((Addr & 0x00000FFF) == 0) {
LocalEraseFlag = 1;
}
/* Prepare SPI header with reverse address */
NvrBuff[4] = ((Addr >> 24) & 0xFF);
NvrBuff[3] = ((Addr >> 16) & 0xFF);
NvrBuff[2] = ((Addr >> 8) & 0xFF);
NvrBuff[1] = ((Addr >> 0) & 0xFF);
/* Set pin to mark transaction for Logic Analyser */
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_SET);
/**
* Software Die Select
* Explicity select the Die to ensure that the correct status
* register is polled for busy.
*/
if(Addr < 0x04000000) {
NvrBuff[1] = 0;
} else {
NvrBuff[1] = 1;
}
NvrBuff[0] = 0xC2;
CS_LO
HAL_SPI_Transmit(&hspi1, NvrBuff, 2, NVR_TIMEOUT);
CS_HI
/**
* Erase
* This is supposed to erase a 4K sector starting at the given
* address. Test show that only 256-bytes is being erased.
*/
if(LocalEraseFlag > 0) {
/* Wait until memory chip is not busy */
do {
/* Read Status Register-1 (05h) */
NvrBuff[0] = 0x05;
CS_LO;
HAL_SPI_Transmit(&hspi1, NvrBuff, 1, NVR_TIMEOUT);
HAL_SPI_Receive(&hspi1, NvrData, 1, NVR_TIMEOUT);
CS_HI;
} while((NvrData[0] & 0b00000001) > 0);
/* 8.5.1 Write Enable (06h) */
CS_LO;
NvrBuff[0] = CMD_BWRITE_ENABLE;
HAL_SPI_Transmit(&hspi1, NvrBuff, 1, NVR_TIMEOUT);
CS_HI;
/* Erase 4K sector (21h) */
NvrBuff[0] = 0x21;
CS_LO;
LocalResult = HAL_SPI_Transmit(&hspi1, NvrBuff, 5, NVR_TIMEOUT);
CS_HI;
HAL_Delay(63); /* tSE = 50 to 400 ms. Measured at 62ms by logic analyser */
}
/* Wait until memory chip is not busy */
do {
/* 8.5.4 Read Status Register-1 (05h) */
NvrBuff[0] = 0x05;
CS_LO;
HAL_SPI_Transmit(&hspi1, NvrBuff, 1, NVR_TIMEOUT);
HAL_SPI_Receive(&hspi1, NvrData, 1, NVR_TIMEOUT);
CS_HI;
} while((NvrData[0] & 0b00000001) > 0);
/* Write Enable 8.5.1 */
NvrBuff[0] = 0x06;
CS_LO
HAL_SPI_Transmit(&hspi1, NvrBuff, 1, NVR_TIMEOUT);
CS_HI
/* 8.6.1 WRITE or Page Program 1 to 256-bytes with 4-byte address (12h) */
CS_LO;
NvrBuff[0] = 0x12;
HAL_SPI_Transmit(&hspi1, NvrBuff, 5, NVR_TIMEOUT);
HAL_SPI_Transmit(&hspi1, Data, DataLen, NVR_TIMEOUT);
CS_HI;
HAL_Delay(3); /* tPP = 0.7 to 3.5 ms from Datasheet */
/* Wait until memory chip is not busy */
do {
/* 8.5.4 Read Status Register-1 (05h) */
NvrBuff[0] = 0x05;
CS_LO;
HAL_SPI_Transmit(&hspi1, NvrBuff, 1, NVR_TIMEOUT);
HAL_SPI_Receive(&hspi1, NvrData, 1, NVR_TIMEOUT);
CS_HI;
} while((NvrData[0] & 0b00000001) > 0);
/* READ 1 to 256-bytes with 4-byte address (13h) */
NvrBuff[0] = 0x13;
CS_LO;
HAL_SPI_Transmit(&hspi1, NvrBuff, 5, NVR_TIMEOUT);
HAL_SPI_Receive(&hspi1, NvrData, DataLen, NVR_TIMEOUT);
CS_HI;
/* Set pin to mark transaction for Logic Analyser */
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_2, GPIO_PIN_SET);
/* Verify payload and report result */
if(memcmp(Data, NvrData, DataLen) != 0) {
LocalResult = HAL_ERROR;
}
return LocalResult;
}
郑重声明,我使用的是 STM32H733ZGT6 MCU,具有 12.5MBit/S 的常规 SPI,没有增强功能,如 CRC、自动 CS 等。
您在擦除交易中错误地格式化了地址:
/* Prepare SPI header with reverse address */
NvrBuff[4] = ((Addr >> 24) & 0xFF);
NvrBuff[3] = ((Addr >> 16) & 0xFF);
NvrBuff[2] = ((Addr >> 8) & 0xFF);
NvrBuff[1] = ((Addr >> 0) & 0xFF);
↑
│
└──── Errors Here
地址应该以大端格式编码,但您以小端格式编码。
汇编地址时应反转NvrBuff中的索引。地址的 LSB 应位于缓冲区的第 4 个字节,MSB 应位于缓冲区的第 1 个字节。
/* Prepare SPI header */
NvrBuff[1] = ((Addr >> 24) & 0xFF);
NvrBuff[2] = ((Addr >> 16) & 0xFF);
NvrBuff[3] = ((Addr >> 8) & 0xFF);
NvrBuff[4] = ((Addr >> 0) & 0xFF);