文章目录
- 一.概要
- 二.W25Q32 SPI FLASH主要参数
- 三.W25Q32 SPI FLASH芯片介绍
- 1.W25Q32 芯片内部框图
- 2.W25Q32 芯片指令表格
- 3.W25Q32 芯片通讯时序
- 四.GD32F407VET6单片机SPI简介
- 五.W25Q32 SPI FLASH读写实验
- 六.工程源代码下载
- 七.小结
一.概要
FLASH是一种存储芯片,通过程序可以修改数据,即平时所说的“闪存”。
FLASH闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何Flash器件的写入操作只能在空或已擦除的单元内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。
SPI Flash使用SPI接口进行数据传输,采用一种主从模式。主控制器通过发送命令和地址来访问SPI Flash,然后接收或写入数据。SPI Flash在接收到命令后,将相应的数据返回给主控制器。
SPI Flash的读取速度相对较慢,不支持直接执行代码。因此,它更适合用于存储配置数据、固件升级或数据存储等需要大容量存储但不需要频繁读取的应用。
本文对W25Q32 SPI FLASH的原理,指令,通讯时序等进行讲解,并通过GD32F103C8T6单片机对W25Q32 进行数据读写实验。
二.W25Q32 SPI FLASH主要参数
FLASH芯片型号:W25Q32JVSSIQ
制造商:Winbond
产品种类:SPI NOR FLASH
封装 :SOIC-8
系列:W25Q32JV
存储容量:32 Mbit
最大时钟频率:133 MHz
接口类型:SPI
数据总线宽度:8 bit
电源电压: 2.7~3.6 V
电源电流—最大值: 25 mA
最小工作温度: - 40°C~85°C
模块接口说明:
1.VCC 供电电源 3.3V
2.CS SPI通讯CS引脚
3.DO SPI通讯MISO引脚
4.GND 电源地
5.CLK SPI通讯CLK引脚
6.DI SPI通讯MOSI引脚
三.W25Q32 SPI FLASH芯片介绍
引脚定义
1.W25Q32 芯片内部框图
如上图所示:
W25Q32的存储单位
Page(页)
256字节,编程最小单位(向FLASH写入内容),一次最多编程256字节。
Sector(扇区)
擦除的最小单位,1个Sector一般包含16个Page,即4KB。
Block(块)
包含16个Sector,块擦除可以32KB(半块)、64KB(整块)两种擦除方式。
2.W25Q32 芯片指令表格
下图是指令表格,每个不同的指令所发的字节数量是不同的,第一个字节发送的都是命令号,比如擦除指令,擦除第0个Sector,SPI所发送的内容是0x20,0x00,0x00,0x00这四个字节。
3.W25Q32 芯片通讯时序
我们以读数据为例,解析下通讯时序,根据SPI通讯规则,CPOL为0,CPHA为0,MCU提供CLK,跟CS控制。
根据指令表格,读的命令4字节内容,是0x03+3字节地址,就能获取到对应地址的数据内容,数据内容是1字节。
四.GD32F407VET6单片机SPI简介
1.简介
SPI/I2S模块可以通过SPI协议或I2S音频协议与外部设备进行通信。
串行外设接口(Serial Peripheral Interface,缩写为SPI)提供了基于SPI协议的数据发送和接收功能,可以工作于主机或从机模式。SPI接口支持具有硬件CRC计算和校验的全双工和单工模式。
2.特点
具有全双工和单工模式的主从操作;
16位宽度,独立的发送和接收缓冲区;
8位或16位数据帧格式;
低位在前或高位在前的数据位顺序;
软件和硬件NSS管理;
硬件CRC计算、发送和校验;
发送和接收支持DMA模式;
支持SPI TI模式;
支持SPI四线功能的主机模式(只有SPI5)。
3.结构框图
4.SPI信号线描述
5.SPI 时序和数据帧格式
6.典型的全双工连接方式
五.W25Q32 SPI FLASH读写实验
硬件准备:
STLINK接GD32F407VET6开发板,STLINK接电脑USB口。
用6根杜邦线把模块与开发板相连
开发板3.3V <->模块VCC
开发板GND <->模块GND
开发板PD0 <->模块CS
开发板PC11 <->模块DO
开发板PC12 <->模块DI
开发板PC10 <->模块CLK
主要代码
uint8_t ReadBuff[10],WriteBuff[10]={0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55};//读写存储,写入的数据固定是10字节的0x55int main(void)
{systick_config();//配置系统主频168M,外部8M晶振,配置在#define __SYSTEM_CLOCK_168M_PLL_8M_HXTAL (uint32_t)(168000000)rcu_periph_clock_enable(RCU_GPIOC);//使能GPIOC时钟 rcu_periph_clock_enable(RCU_GPIOD);//使能GPIOD时钟 rcu_periph_clock_enable(RCU_SPI2);//使能SPI2时钟 gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_10);//复用功能6gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_11);//复用功能6gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_12);//复用功能6gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12); /* SPI2 GPIO config:SCK/PC10, MISO/PC11, MOSI/PC12 */gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12);gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0); /* PD0 CS */gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0); spi_config();//SPI初始化FlashJedecid = spi_flash_read_id();//读取JedecidFlashDeviceid=SFLASH_ReadID();//读取Device ID/* USER CODE END 2 */Flash_ReadSomeBytes(ReadBuff,0,8);//从FLASH 0地址读取8字节内容放入ReadBuff数组Flash_WriteSR(0x42);//解除保护delay_1ms(100);Flash_ReadSR();//读状态寄存器Flash_WriteSomeBytes(WriteBuff,0,8);//把WriteBuff数组中的内容写入FLASH 0地址delay_1ms(100);Flash_ReadSomeBytes(ReadBuff,0,8);//从FLASH 0地址读取8字节内容放入ReadBuff数组while(1);
}
//调用SPI HAL库驱动,使用SPI2
uint8_t spi_master_send_recv_byte(uint8_t spi_byte)
{ uint8_t ByteSend,ByteRecv;ByteSend=spi_byte;while(RESET == spi_i2s_flag_get(SPI2, SPI_FLAG_TBE));spi_i2s_data_transmit(SPI2,ByteSend);while(RESET == spi_i2s_flag_get(SPI2, SPI_FLAG_RBNE));ByteRecv=spi_i2s_data_receive(SPI2);return ByteRecv;}
//读数据驱动
void spi_master_recv_some_bytes( uint8_t *pbdata, uint16_t recv_length)
{uint8_t *temp_data = pbdata;while (recv_length--){*temp_data++ = spi_master_send_recv_byte(0xFF); //发送 0xff 为从设备提供时钟}}
//从FLASH的指定地址读取数据
void Flash_ReadSomeBytes(uint8_t *ucpBuffer, uint32_t _ulReadAddr, uint16_t _usNByte)
{uint8_t command = FLASH_READ_DATA;uint8_t temp_buff[3] = {0};temp_buff[0] = (uint8_t)(_ulReadAddr >> 16);temp_buff[1] = (uint8_t)(_ulReadAddr >> 8);temp_buff[2] = (uint8_t)(_ulReadAddr >> 0);FLASH_CS_0();spi_master_send_recv_byte(command);spi_master_send_recv_byte(temp_buff[0]);spi_master_send_recv_byte(temp_buff[1]);spi_master_send_recv_byte(temp_buff[2]);spi_master_recv_some_bytes(ucpBuffer, _usNByte);FLASH_CS_1();
}
//FLASH指定地址写入数据
void Flash_WriteSomeBytes(uint8_t *ucpBuffer, uint32_t _ulWriteAddr, uint16_t _usNByte)
{uint32_t ulSecPos = 0; //得到扇区位置uint16_t usSecOff = 0; //扇区偏移uint16_t usSecRemain = 0; //剩余扇区uint32_t i = 0;ulSecPos = _ulWriteAddr / 4096;//地址所在扇区(0--511)usSecOff = _ulWriteAddr % 4096;//扇区内地址偏移usSecRemain = 4096 - usSecOff;//扇区除去偏移,还剩多少字节if(_usNByte <= usSecRemain) //写入数据大小 < 剩余扇区空间大小{usSecRemain = _usNByte;}while(1){Flash_ReadSomeBytes(SectorBuf, ulSecPos*4096, 4096);//读出整个扇区的内容for (i = 0; i < usSecRemain; i++) //校验数据{if (SectorBuf[usSecOff + i] != 0xFF)//储存数据不为0xFF,需要擦除break;}if(i < usSecRemain) //需要擦除{Flash_EraseSector(ulSecPos); //擦除这个扇区for(i = 0; i < usSecRemain; i++) //保存写入的数据{SectorBuf[usSecOff + i] = ucpBuffer[i];}Flash_WriteNoCheck(SectorBuf, ulSecPos*4096, 4096); //写入整个扇区(扇区=老数据+新写入数据)}else{Flash_WriteNoCheck(ucpBuffer, _ulWriteAddr, usSecRemain);//不需要擦除,直接写入扇区}if(_usNByte == usSecRemain) //写入结束{Flash_WriteDisable();break;}else{ulSecPos++; //扇区地址增加1usSecOff = 0; //扇区偏移归零ucpBuffer += usSecRemain; //指针偏移_ulWriteAddr += usSecRemain; //写地址偏移_usNByte -= usSecRemain; //待写入的字节递减if(_usNByte > 4096){usSecRemain = 4096; //待写入一扇区(4096字节大小)}else{usSecRemain = _usNByte; //待写入少于一扇区的数据}}}}
实验调试:
下载程序,全速运行,可以看到ReadBuff数组8字节内容从FLASH中读出来的内容是0x55,说明写进去读出来没问题。
如果写入与读出来的数据不一致,可以在读取SPI FLASH的ID处设置断点,如下图所示,排查接线与SPI通讯参数问题。
六.工程源代码下载
源码下载链接如下:
CSDN
七.小结
SPI Flash广泛应用于各种产品中,如智能手机、平板电脑、路由器、网络设备、工业控制系统、汽车电子、穿戴设备等。由于其灵活性和可靠性,SPI Flash成为许多嵌入式系统首选的存储解决方案之一。