GD32F407单片机开发入门(二十一)华邦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字节的0x55

int 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);//复用功能6
		gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_11);//复用功能6
		gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_12);//复用功能6
	  gpio_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();//读取Jedecid
	FlashDeviceid=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++;		//扇区地址增加1
			usSecOff = 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成为许多嵌入式系统首选的存储解决方案之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值