目录
1. W25Q32JVSSIQ背景知识
1.1 64个可擦除块
1.2 1024个扇区(每个块有16个扇区)
1.3 页
1. W25Q32JVSSIQ背景知识
W25Q32JV阵列被组织成16,384个可编程页,每页有256字节。一次最多可以编程256个字节。页面可分为16组(4KB扇区清除)、128组(32KB块删除)、256组(64KB块删除)或整个芯片(芯片清除)。W25Q32JV分别有1,024个可擦除扇区和64个可擦除块。小型的4KB扇区允许在需要数据和参数存储的应用程序中具有更大的灵活性。
1.1 64个可擦除块
8M的空间被切割成128块,每块64kb
1.2 1024个扇区(每个块有16个扇区)
每块64kb又每切割成16个扇区,每扇区4kb(16*4=64kb)
1.3 页
一扇区是4KB,划分成16份每份256字节这就是页,而且擦除数据也只能按照扇区或者块来擦除,下图的00FF00H-00FFFFH刚好256字节,000000h-0000ff之间也是256字节
2. Flash操作注意事项
2.1 写入操作
1. 写入操作之前必须先进行写使能;
2. 每个数据只能由1改写为0,不能由0改写为1是因为FLASH芯片自身的限制决定,它没有完全任意修改的能力,所以芯片内部无数据的时候默认为0XFF,表示为空。;
3. 写入数据之前必须先进行擦除,擦除后所有的数据位为1;
4. 进行擦除时必须按照最小擦除单元进行擦除(最小擦除单位为扇区);
5.连续写入多字节数据时,最多写入一页的数据,如果超过一页则会覆盖前面写的内容;
6. 写入操作结束后, 芯片进入忙状态(寄存器中有一个busy位),不影响新的读写操作;
2.2 读操作
进行读操作时,直接调用读取操作时序,无需使能,但是别忘了拉低电平,没有页的限制,读取之后也不会进入到忙状态,但是不能在忙状态时读取数据;
3.代码编写
此次对SPI Flash进行读写操作还是采用SPI1,具体的配置请见上篇文章,在这里不做详细概述。
3.1 spi_flash.c添加代码
/* Flash 写使能 */
static void SPI1_FLASH_WriteEnable(void)
{
cs_low();
SPI_FLASH_SendByte( 0x06 ); //需要查芯片的datasheet来看具体的
cs_high();
}
/* Flash 等待写结束 */
static void SPI1_FLASH_WaitEnd(void)
{
uint8_t state = 0;
cs_low();
SPI_FLASH_SendByte(0x05); //需要查芯片的datasheet来看具体的
do
{
state = SPI1_FLASH_ReadByte();
}
while( (state & 0x01) == SET );
cs_high();
}
// 扇区擦除验证函数
int SPI_FLASH_VerifyErase(uint32_t addr, uint32_t length)
{
uint8_t buffer[16];
uint32_t bytesRead;
for (bytesRead = 0; bytesRead < length; bytesRead += sizeof(buffer))
{
uint32_t toRead = sizeof(buffer);
if (bytesRead + toRead > length) {
toRead = length - bytesRead;
}
// 读取地址开始的若干字节数据
SPI_FLASH_BufferRead(addr + bytesRead, buffer, toRead);
// 检查读取的数据是否为0xFF
for (uint32_t i = 0; i < toRead; i++)
{
if (buffer[i] != 0xFF)
{
return -1; // 如果不是0xFF,返回false,表示擦除失败
}
}
}
return 0; // 如果所有数据都是0xFF,返回true,表示擦除成功
}
/*清空扇区*/
int SPI_FLASH_SectorErase(uint32_t addr)
{
cs_low();
/* 开始的时候要先发送写使能信号 */
SPI1_FLASH_WriteEnable();
/* 发送扇区擦除命令 */
SPI_FLASH_SendByte(0x20);
/* 发送扇区地址,高位先行 */
SPI_FLASH_SendByte( (addr & 0xff0000) >> 16 );
SPI_FLASH_SendByte( (addr & 0xff00) >> 8 );
SPI_FLASH_SendByte( addr & 0xff );
cs_high();
/* 最后等待Flash 处理完这次信号之后退出 */
SPI1_FLASH_WaitEnd();
if (!SPI_FLASH_VerifyErase(addr, 4096)) // 通常扇区大小为4KB
{
printf("Sector erase failed at address 0x%06X\n", addr);
}
else
{
printf("Sector erase successful at address 0x%06X\n", addr);
}
return 0;
}
/* 按页写数据,在写数据之前要先擦除 */
void SPI_FLASH_PageWrite(uint32_t addr, uint8_t *pBuffer, uint8_t size)
{
/* 发送使能信号 */
SPI1_FLASH_WriteEnable();
cs_low();
/* 发送页写入命令 */
SPI_FLASH_SendByte(0x02);
/* 发送要写入的地址,高位先行 */
SPI_FLASH_SendByte( (addr & 0xff0000) >> 16 );
SPI_FLASH_SendByte( ( addr & 0xff00) >> 8 );
SPI_FLASH_SendByte(addr & 0xff );
printf("Writing to address 0x%06X: ", addr);
for (uint8_t i = 0; i < size; i++)
{
SPI_FLASH_SendByte(pBuffer[i]); // 发送数据
printf("%02X ", pBuffer[i]);
}
printf("\n");
cs_high();
SPI1_FLASH_WaitEnd();
}
/* 读取数据,读取指定地址制定长度的数据 */
void SPI_FLASH_BufferRead(uint32_t addr, uint8_t *pBuffer, uint16_t size)
{
cs_low();
/* 发送读取命令 */
SPI_FLASH_SendByte(0x03);
/* 发送要读取的地址,高位先行*/
SPI_FLASH_SendByte( (addr & 0xff0000) >> 16 );
SPI_FLASH_SendByte( (addr & 0xff00 ) >> 8 );
SPI_FLASH_SendByte(addr & 0xff );
printf("Reading to address 0x%06X: ", addr);
/* 逐位读取数据到指针上 */
for (uint8_t i = 0; i < size; i++)
{
pBuffer[i] = SPI_FLASH_SendByte(Dummy_Byte); // 发送数据
}
cs_high();
}
3.2 详细分析
1. 地址分解
假设 addr
是一个24位的地址(0xFFFFFF范围内)。发送前需要将addr
分解成三个8位字节,因为SPI通常以字节为单位发送数据。
SPI_SendData((addr & 0xff0000) >> 16);
addr & 0xff0000
:使用位与操作(&
)保留addr
的高8位,其余位清零。>> 16
:将结果右移16位,使得高8位移到最低8位的位置。SPI_SendData()
:将移位后的结果发送出去,这发送的是addr
的高8位。- 例如:如果
addr
= 0x123456,那么(addr & 0xff0000)
= 0x120000,右移16位得到 0x12,即发送的第一个字节是 0x12。
其他同理
2. SPI1_FLASH_WaitEnd(void)函数
读取寄存器最低位BUSY的状态,如果为1表示忙,0表示空闲;
3.3 main.c函数
uint8_t Rx[100];
uint8_t Tx[] = "Hello!", n;
SPI_FLASH_SectorErase(0x00000);
n=sizeof(Tx) -1 ;
SPI_FLASH_PageWrite(0x00000 ,Tx ,n);
SPI_FLASH_BufferRead(0x00000 ,Rx ,n);
printf("the data is %s\n", Rx);