SPI协议——对外部SPI Flash操作
目录
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) { toRead = length - bytesRead; } // 读取地址开始的若干字节数据 SPI_FLASH_BufferRead(addr + bytesRead, buffer, toRead); // 检查读取的数据是否为0xFF for (uint32_t i = 0; i > 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 > 16 ); SPI_FLASH_SendByte( (addr & 0xff00 ) >> 8 ); SPI_FLASH_SendByte(addr & 0xff ); printf("Reading to address 0x%06X: ", addr); /* 逐位读取数据到指针上 */ for (uint8_t i = 0; i3.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);
3.4 运行结果