《STM32从零开始学习历程》——SPI读写FLASH
发布日期:2021-05-10 01:33:10 浏览次数:23 分类:精选文章

本文共 13654 字,大约阅读时间需要 45 分钟。

《STM32从零开始学习历程》

SPI读写FLASH

相关链接:

参考资料:

[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》

1 实现功能

  • 实现对FLASH一扇区的擦除
  • 对该FLASH扇区写入数据,并读取该数据,通过串口调试助手进行调试观察
  • 写入256个数据,并读取写入的数据以及其余未写入的数据。
  • 对该扇区所有数据都写入。

2 硬件设计

本文使用的外设为SPI1(正点原子F4探索者开发板)、FLASH以及USART1。

USART用来调试程序,我们还是使用USART1,因此将PB9,PB10与TX,RX相连接即可。

查阅正点原子F4探索者开发板硬件手册,了解SPI引脚与GPIO的对应情况。

在这里插入图片描述
在这里插入图片描述
由上图可以看出:SPI的SCK,MISO,MOSI分别与芯片的PB3,PB4,PB5连接,片选信号F_CS与PB14相连接,因此在后面程序配置的时候需要注意不能配置错引脚。

3 软件设计流程

① 使能SPIx和IO口时钟

RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();
② 初始化IO口为复用功能
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
③ 设置引脚复用映射:
GPIO_PinAFConfig();
② 初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
③ 使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
④ 编写字节发送函数:uint8_t SPI_FLASH_ByteWrite(uint8_t data)

发送数据(指令):void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

接收返回的数据:uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;

⑤ 编写擦除扇区函数:void SPI_FLASH_Erase_Sector(uint32_t addr);

⑥ 编写写入数据函数:void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size);

⑦ 编写读取数据函数:void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size);
⑧ 查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);

⑨ 编写SPI发送函数

控制片选引脚:GPIO_ResetBits()、GPIO_SetBits();
⑩ 主函数调用,优化程序(超时函数、宏定义、等待空闲函数、写使能函数)

4 代码分析

  • 宏定义

这里大家可以按照自己的需求以及编程习惯进行定义,宏定义的好处在于便于程序的移植,方便改动程序。

/*命令定义-开头*/#define W25X_WriteEnable		      		0x06 #define W25X_WriteDisable		     		0x04 #define W25X_ReadStatusReg		      		0x05 #define W25X_WriteStatusReg		      		0x01 #define W25X_ReadData			      		0x03 #define W25X_FastReadData		      		0x0B #define W25X_FastReadDual		      		0x3B #define W25X_PageProgram		      		0x02 #define W25X_BlockErase			      		0xD8 #define W25X_SectorErase		      		0x20 #define W25X_ChipErase			      		0xC7 #define W25X_PowerDown			      		0xB9 #define W25X_ReleasePowerDown	      		0xAB #define W25X_DeviceID			      		0xAB #define W25X_ManufactDeviceID   	  		0x90 #define W25X_JedecDeviceID		      		0x9F /*SPI GPIO 接口*/#define FLASH_SPI                          	SPI1#define FLASH_SPI_CLK                       RCC_APB2Periph_SPI1#define FLASH_SPI_CLK_INIT					RCC_APB2PeriphClockCmd#define FLASH_SPI_SCK_PIN                  	GPIO_Pin_3                 #define FLASH_SPI_SCK_GPIO_PORT            	GPIOB                       #define FLASH_SPI_SCK_GPIO_CLK             	RCC_AHB1Periph_GPIOB#define FLASH_SPI_SCK_SOURCE               	GPIO_PinSource3#define FLASH_SPI_SCK_AF                   	GPIO_AF_SPI1#define FLASH_SPI_MOSI_PIN                  GPIO_Pin_5                #define FLASH_SPI_MOSI_GPIO_PORT            GPIOB                       #define FLASH_SPI_MOSI_GPIO_CLK             RCC_AHB1Periph_GPIOB#define FLASH_SPI_MOSI_SOURCE               GPIO_PinSource5#define FLASH_SPI_MOSI_AF                   GPIO_AF_SPI1#define FLASH_SPI_MISO_PIN                  GPIO_Pin_4                #define FLASH_SPI_MISO_GPIO_PORT            GPIOB                       #define FLASH_SPI_MISO_GPIO_CLK             RCC_AHB1Periph_GPIOB#define FLASH_SPI_MISO_SOURCE               GPIO_PinSource4#define FLASH_SPI_MISO_AF                   GPIO_AF_SPI1#define FLASH_SPI_CS_PIN                  	GPIO_Pin_14               #define FLASH_SPI_CS_GPIO_PORT           	GPIOB                      #define FLASH_SPI_CS_GPIO_CLK             	RCC_AHB1Periph_GPIOB#define FLASH_SPI_CS_LOW()					GPIO_ResetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)#define FLASH_SPI_CS_HIGH()					GPIO_SetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)/*等待超时时间*/#define SPIT_FLAG_TIMEOUT         			((uint32_t)0x1000)#define SPIT_LONG_TIMEOUT         			((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
  • 函数申明
/*函数申明*/static void SPI_GPIO_Config(void);										//引脚初始化函数void SPI_Mode_Config(void);												//SPI模式初始化函数void SPI_FLASH_Init(void);												//SPI FLASH外设初始化调用函数uint8_t SPI_FLASH_ByteWrite(uint8_t data);								//字节写入函数uint8_t SPI_FLASH_Read_ID(void);										//SPI读取FLASH ID函数void SPI_FLASH_Erase_Sector(uint32_t addr);								//FLASH擦除函数void SPI_FLASH_Write_Enable(void);										//SPI写使能函数void SPI_FLASH_Wait_For_Standby(void);									//SPI等待直到空闲函数void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size);	//SPI连续读函数void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size);	//SPI连续写函数void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size);	//SPI页写入函数

SPI子函数:

  • 静态函数申明:
//申明超时变量static uint32_t SPITimeOut = ((uint32_t)(10 * SPIT_FLAG_TIMEOUT));//申明错误代码返回函数static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
  • SPI GPIO初始化 复用函数
/*================================================SPI GPIO初始化 复用函数================================================*/static void SPI_GPIO_Config(void){   		  GPIO_InitTypeDef  GPIO_InitStructure; 		   		  FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE);				//使能SPI时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1)																				/* 使能 FLASH_SPI 及GPIO 时钟 */																  /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO, 																       SPI_FLASH_SPI_MISO_GPIO,SPI_FLASH_SPI_SCK_GPIO 时钟使能 */					  RCC_AHB1PeriphClockCmd(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK|								FLASH_SPI_MISO_GPIO_CLK|FLASH_SPI_CS_GPIO_CLK, ENABLE);					//初始化GPIO时钟		  //查看引脚,初始化SCK,MOSI,MISO,CS引脚GPIO		  //设置引脚复用		  GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT, FLASH_SPI_SCK_SOURCE, FLASH_SPI_SCK_AF);		  GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT, FLASH_SPI_MOSI_SOURCE, FLASH_SPI_MOSI_AF);  		  GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT, FLASH_SPI_MISO_SOURCE, FLASH_SPI_MISO_AF);		   			/*!< 配置 SPI_FLASH_SPI 引脚: SCK */		  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;		  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;		  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;		  GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);					/*!< 配置 SPI_FLASH_SPI 引脚: MISO */		  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;		  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);						/*!< 配置 SPI_FLASH_SPI 引脚: MOSI */			  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;		  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);						/*!< 配置 SPI_FLASH_SPI 引脚: CS */		  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;		  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;		  GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);}/*================================================SPI 初始化结构体初始化================================================*/void SPI_Mode_Config(void){   		/*!< 初始化SPI结构体函数 */		SPI_InitTypeDef		SPI_InitStructure;				SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;		SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;		SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;		SPI_InitStructure.SPI_CRCPolynomial=7;		SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;		SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;		SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;		SPI_InitStructure.SPI_Mode=SPI_Mode_Master;		SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;		SPI_Init(FLASH_SPI,&SPI_InitStructure);			SPI_Cmd(FLASH_SPI,ENABLE);}
  • SPI FLASH 外设初始化
================================================SPI FLASH 外设初始化================================================*/void SPI_FLASH_Init(void){     SPI_GPIO_Config();    SPI_Mode_Config();}
  • SPI字节写入
/*================================================通过SPI发送一个字节参数:要写入的数据返回值:错误代码================================================*/uint8_t SPI_FLASH_ByteWrite(uint8_t data){   	uint8_t re_data;		//等待TXE标志	SPITimeOut = SPIT_FLAG_TIMEOUT;  while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET )  {       if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(1);  }    		SPI_I2S_SendData(FLASH_SPI, data);		//等待RXNE标志 来确认发送完成,及准备读取数据	SPITimeOut = SPIT_FLAG_TIMEOUT;	while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET )  {       if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(2);  }   		re_data = SPI_I2S_ReceiveData(FLASH_SPI);		return re_data;}
  • 读取FLASH ID
/*================================================读ID0-ID7================================================*/uint8_t SPI_FLASH_Read_ID(void){   uint8_t id;	//控制片选引脚	FLASH_SPI_CS_LOW();		//指令代码	SPI_FLASH_ByteWrite(W25X_ReleasePowerDown);		SPI_FLASH_ByteWrite(0xFF);	SPI_FLASH_ByteWrite(0xFF);	SPI_FLASH_ByteWrite(0xFF);		//接收读取到的内容		id = SPI_FLASH_ByteWrite(0xFF);	FLASH_SPI_CS_HIGH();	return id;}
  • 擦除FLASH扇区函数
/*================================================擦除扇区addr:必须对齐到要擦除的扇区的首地址================================================*/void SPI_FLASH_Erase_Sector(uint32_t addr){   	//写使能	SPI_FLASH_Write_Enable();			//控制片选引脚	FLASH_SPI_CS_LOW();		//指令代码	SPI_FLASH_ByteWrite(W25X_SectorErase);		//发送要擦除的地址	SPI_FLASH_ByteWrite((addr>>16) & 0xFF);	SPI_FLASH_ByteWrite((addr>>8) & 0xFF);	SPI_FLASH_ByteWrite(addr & 0xFF);		FLASH_SPI_CS_HIGH();			//等待内部时序完成	SPI_FLASH_Wait_For_Standby();}
  • 页写入函数
/*================================================写入数据addr:要写入数据的首地址,buf:存储写入的数据的指针size:要写入多少个数据 不超过256================================================*/void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size){   	//写使能	SPI_FLASH_Write_Enable();			//控制片选引脚	FLASH_SPI_CS_LOW();		//指令代码	SPI_FLASH_ByteWrite(W25X_PageProgram);		//发送要写入的地址	SPI_FLASH_ByteWrite((addr>>16) & 0xFF);	SPI_FLASH_ByteWrite((addr>>8) & 0xFF);	SPI_FLASH_ByteWrite(addr & 0xFF);		while(size--)	{   	SPI_FLASH_ByteWrite(*buf);	buf++;	}		FLASH_SPI_CS_HIGH();			//等待内部时序完成	SPI_FLASH_Wait_For_Standby();}
  • 读取数据函数
/*================================================读取数据addr:要读取数据的首地址,buf:存储读取到的数据的指针size:要读取多少个数据================================================*/void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size){   	//控制片选引脚	FLASH_SPI_CS_LOW();		//指令代码	SPI_FLASH_ByteWrite(W25X_ReadData);		//发送要读取的地址	SPI_FLASH_ByteWrite((addr>>16) & 0xFF);	SPI_FLASH_ByteWrite((addr>>8) & 0xFF);	SPI_FLASH_ByteWrite(addr & 0xFF);		while(size--)	{   		*buf = SPI_FLASH_ByteWrite(0xFF);		buf++;	}		FLASH_SPI_CS_HIGH();	}
  • 写入数据,不受256数据限制
/*================================================写入数据addr:要写入数据的首地址,buf:要写入的数据的指针size:要写入多少个数据 不超过256================================================*/void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size){   	uint32_t count=0;//计算循环次数		while(size--)	{   		count++;				//第一次执行,第257次,256*2+1,256*3+1,addr对齐到4096时				if(count == 1 || (count%256) ==1 || (addr%4096)==0)		{   			//结束上一次的页写入指令			FLASH_SPI_CS_HIGH();				//等待上一次页写入的完成			SPI_FLASH_Wait_For_Standby();						//写使能,每次写入前都必须调用			SPI_FLASH_Write_Enable();							//控制片选引脚			FLASH_SPI_CS_LOW();						//指令代码			SPI_FLASH_ByteWrite(W25X_PageProgram);						//发送要写入的地址			SPI_FLASH_ByteWrite((addr>>16) & 0xFF);			SPI_FLASH_ByteWrite((addr>>8) & 0xFF);			SPI_FLASH_ByteWrite(addr & 0xFF);			}					SPI_FLASH_ByteWrite(*buf);		buf++;		addr++;		}		FLASH_SPI_CS_HIGH();		//等待内部时序完成	SPI_FLASH_Wait_For_Standby();}
  • 写使能函数
/*================================================写使能================================================*/void SPI_FLASH_Write_Enable(void){   	//控制片选引脚	FLASH_SPI_CS_LOW();		//指令代码	SPI_FLASH_ByteWrite(W25X_WriteEnable);		FLASH_SPI_CS_HIGH();}
  • 等待直到空闲函数
/*================================================等待直到空闲状态================================================*/void SPI_FLASH_Wait_For_Standby(void){   	uint8_t status ;	//控制片选引脚	FLASH_SPI_CS_LOW();			//指令代码 0x05 检测读取标志位	SPI_FLASH_ByteWrite(W25X_ReadStatusReg);		SPITimeOut = SPIT_LONG_TIMEOUT;		while(1)	{   		status = SPI_FLASH_ByteWrite(0xFF);				//如果条件成立,说明为空闲状态		if((status & 0x01) == 0) 			break;				//若SPITimeout为0,表示已检测SPITimeout次都仍为忙碌,跳出循环		if((SPITimeOut--)==0)		{   			SPI_TIMEOUT_UserCallback(3);			break;		}	}			FLASH_SPI_CS_HIGH();}
  • 错误代码 返回函数
/*================================================错误代码返回函数参数:写如的错误代码返回值:错误代码================================================*/static  uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode){     /* Block communication and all processes */  printf("\r\nSPI 等待超时!errorCode = %d\r\n",errorCode);    return errorCode;}

主函数

uint8_t read_buff[4096] = {   0};uint8_t write_buff[4096] = {   0};int main(void){   	int i = 0;		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);							//设置系统中断优先级分组2	delay_init(168);														//延时初始化 	uart_init(115200);														//串口初始化波特率为115200	LED_Init();		  														//初始化与LED连接的硬件接口  	SPI_FLASH_Init();		printf("\r\n=======================================\r\n");		printf("this is id : 0x%x",SPI_FLASH_Read_ID());		printf("\r\n擦除开始");	//擦除测试	SPI_FLASH_Erase_Sector(4096*0);		//擦除测试	SPI_FLASH_Erase_Sector(4096*1);			printf("\r\n擦除完成");		SPI_FLASH_Read_Buff(0,read_buff,4096);		for(i=0;i<4096;i++)	{   		//若不等于0xFF,说明擦除不成功		if(read_buff[i] != 0xFF)		{   			printf("\r\n擦除失败");		}		}		printf("\r\n擦除完成");		//初始化要写入的数据	for(i=0;i<256;i++)	{   		write_buff[i] = i;	}		printf("\r\n开始写入");		SPI_FLASH_Write_Buff(0,write_buff,256);							//写入数据 0为扇区首地址 write_buff为数据地址,256为写入数据数量		printf("\r\n写入完成");	SPI_FLASH_Read_Buff(0,write_buff,4096);		printf("\r\n读取到的数据:\r\n");	for(i=0;i<4096;i++)												//循环打印输出	{   		printf("0x%02x ",write_buff[i]);	}    while (1)  {           }  }

5 效果展示

  1. 写入256个数据。

在这里插入图片描述

可以看到前256个数据已经被写入为我们想要写入的数据了,其余数据均为擦除后的0xFF。

  1. 对整个扇区写入数据。
    修改主函数部分参数。
//初始化要写入的数据	for(i=0;i<4096;i++)	{   		write_buff[i] = i;	}		printf("\r\n开始写入");		SPI_FLASH_Write_Buff(0,write_buff,4096);		printf("\r\n写入完成");	SPI_FLASH_Read_Buff(0,write_buff,4096);		printf("\r\n读取到的数据:\r\n");	for(i=0;i<4096;i++)	{   		printf("0x%02x ",write_buff[i]);	}

在这里插入图片描述

可以看出整个扇区的数据都被写入。

上一篇:《STM32从零开始学习历程》——CAN通讯协议物理层
下一篇:《STM32从零开始学习历程》——SPI读取FLASH ID

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年04月27日 13时32分07秒