stm32使用外部flash w25Q128实现读写操作

这篇具有很好参考价值的文章主要介绍了stm32使用外部flash w25Q128实现读写操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

数据保存是所有项目的基本功能,但是对于STM32C8T6的原flash进行操作,一方面大小有可能不够,另一方面单片机的运行程序本来就放在这个里面,所以还是外接的好。这里选用w25Q128 FLASH存储器,参考实现简单读写。
作为一个初学者,技能都是东拼西凑的,基础可能不扎实,如有下列问题的解决方案,可以给我留言。
任然存在的问题:

  1. 读写时传入参数是uint8_t *的指针数组形式,可以传入u8类型的数组,但是存double类型的数据尚不知道怎么解决。
  2. 上面问题已解决,使用的是联合体,详情可见博文“stm32外设w25Q128存取浮点数问题解决”

w25Q128简介

挑重要的说:

  1. 16M字节,128Mbit,空间范围0x000000-0xFFFFFF。
  2. 分为256个块
    每块16个扇区(256*16个扇区)
    每个扇区16页 (2561616页)
    每块的大小 16384KB/256 =64KB
    每个扇区的大小 64KB/16 = 4KB
    每个页的大小 4KB/16 = 256B
  3. 每个块的地址:
    块0地址:0x000000-0x00FFFF
    块1地址:0x010000-0x01FFFF

    块0地址:0xFF0000-0xFFFFFF
    每个扇区的地址:
    0块0扇区:0x000000-0x000FFF
    0块1扇区:0x001000-0x001FFF

    0块16扇区:0x00F000-0x00FFFF
    由上可知:地址高8位(23-16)表示块的位置,第(15-12)表示扇区的位置。
  4. w25Q128属于SPI协议的FLASH。与EPROM相比,读写速度更快,但是读写次数只有10万次,EPROM读写次数100万次,w25Q128数据保存年限也更短。

硬件说明

硬件的主要引脚:

  1. CS片选,每次操作都得拉低片选,就好像STM32叫“谁在叫我”,W25Q128拉低CS,表示“是我在叫”。
  2. CLK,时钟信号,通过时钟可以计算发送的字节,如果需要持续发送,也需要时钟的持续信号。
  3. DO,我的板子上写的是DO,实际上应该和SO,或者说MISO是一样的,send out发送,从机上send out,那就是从机发送,主机接收了。
  4. DI,SI,MISI,send in ,主机发送,从机接收。

状态寄存器SR

BIT7  6   5   4   3   2   1   0
SPR   RV  TB BP2 BP1 BP0 WEL BUSY
BUSY位指示当前的状态,0表示空闲,1表示忙碌
WEL:写使能锁定,为1时,可以操作页/扇区/块。为0时,写禁止。
SPR:默认0,状态寄存器保护位,配合WP使用
TB,BP2,BP1,BP0:FLASH区域写保护设置

编程逻辑

  1. 写使能(拉低CS,发送06H,拉高CS)
  2. 读状态寄存器(拉低CS,发送05H,返回SR1值,拉高CS)
  3. 读时序(拉低CS,发送03H,发送24位地址,读取数据1,读取数据2…,拉高CS)
  4. 页写时序(拉低CS,发送02H,发送24位地址,发送数据1,发送数据n(n<=256),拉高CS)
  5. 扇区擦除:这类型的FLASH只能把原来1的数据改成0,而原来0的数据不能直接改成1,所以数据写入前,需要检查空间是否满足,不满足需要擦除。(拉低CS,发送20H,发送24位地址,拉高CS)

w25Q128 FLASH驱动的基本步骤

  1. SPI配置
    1. 设置SPI通信速度
    2. SPI发送接收数据
    3. SPI初始化
  2. w25Q128配置
    1. W25Q128初始化
    2. 读取W25Q128状态寄存器
    3. W25Q128写使能
    4. 擦除一个扇区
    5. 读取函数
    6. 写一页256个字节
    7. 写数据 (扇区写入)

代码

基于库函数版本,绝对完整代码,毕竟拿来主义是最香的。
stm32使用外部flash w25Q128实现读写操作文章来源地址https://www.toymoban.com/news/detail-401815.html

user_gpio.c
#include "user_gpio.h"

void Gpio_Init(void)
{	
	/*GPIO结构体*/
	GPIO_InitTypeDef GPIO_InitTypeDefstruct;
	
	/*使能GPIOA时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*使能UART1时钟*/
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	/*使能GPIOB时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	
	/*UART1发送引脚配置*/
	GPIO_InitTypeDefstruct.GPIO_Mode  = GPIO_Mode_AF_PP;//推挽复用输出
	GPIO_InitTypeDefstruct.GPIO_Pin   = GPIO_Pin_9;
	GPIO_InitTypeDefstruct.GPIO_Speed =	GPIO_Speed_10MHz;
	/*写入结构体到GPIOA*/
	GPIO_Init(GPIOA,&GPIO_InitTypeDefstruct);
	
	/*UART1接收引脚配置*/
	GPIO_InitTypeDefstruct.GPIO_Mode  = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_InitTypeDefstruct.GPIO_Pin   = GPIO_Pin_10;
	GPIO_InitTypeDefstruct.GPIO_Speed =	GPIO_Speed_10MHz;
	/*写入结构体到GPIOA*/	
	GPIO_Init(GPIOA,&GPIO_InitTypeDefstruct);
	
	/*配置SPI 13 时钟 14 MISO 15 MOSI*/
	GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_12| GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_AF_PP;  //PB12/13/14/15复用推挽输出 MISO推挽也正常
	GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_50MHz;
	/*写入结构体到GPIOB*/	
	GPIO_Init(GPIOB,&GPIO_InitTypeDefstruct);
	
	/*配置SPI 12 CS*/
	GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_Out_PP;  //PB12
	GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_50MHz;
	/*写入结构体到GPIOB*/	
	GPIO_Init(GPIOB,&GPIO_InitTypeDefstruct);
	
	/*高电平*/
	GPIO_SetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
}

user_gpio.h
#ifndef __USER_GPIO_H__
#define __USER_GPIO_H__

#include "stm32f10x.h"

void Gpio_Init(void);

#endif

user_spi.c
#include "user_spi.h"


/*
	设置SPI通信速度
*/
void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler)
{
	/*断言库函数里的*/
	assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
	/*清除第bit3、4、5位可以进库函数去看分频具体数值,分频就是操作 3 4 5bit位*/
	SPI2->CR1&=0XFFC7;
	/*设置SPI2速度*/
	SPI2->CR1|=SPI_BaudRatePrescaler;
	/*重新使能SPI*/
	SPI_Cmd(SPI2,ENABLE); 
 
} 
 
/*
	描述:读写函数,SPI因为2个位移寄存器相连写一个字节的时候会返回上一次接受到的字节
	输入参数:发送数据
	返回值:读取的字节
*/
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{		
	/*等待超时时间*/
	uint8_t retry=0;
	/*检查指定的SPI标志位设置与否:发送缓存空标志位*/
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) 
		{
			retry++;
			if(retry>200)
			{
				return 0;
			}
		}	
	/*通过外设SPI2发送一个数据*/
	SPI_I2S_SendData(SPI2, TxData); 
	/*等待超时时间*/
	retry=0;
	/*检查指定的SPI标志位设置与否:接受缓存非空标志位*/
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) 
		{
			retry++;
			if(retry>200)
			{
				return 0;			
			}
 
		}
    /*返回通过SPIx最近接收的数据*/
	return SPI_I2S_ReceiveData(SPI2); 				    
}
 
 
/*
	SPI初始化
*/
void Spi_Init(void)
{	
    /*SPI结构体*/
    SPI_InitTypeDef  SPI_InitStructure;
	
	/*SPI2时钟使能*/
	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,ENABLE );	
	
	/*SPI配置*/
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	 //设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//片选又软件控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//APB1(36M)/256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输从MSB(高)位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);         //写入SPI2里面
	/*使能SPI外设*/
	SPI_Cmd(SPI2, ENABLE); 
	
	/*主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输*/
	SPI2_ReadWriteByte(0xFF);
}

user_spi.h
#ifndef __USER_SPI_H__
#define __USER_SPI_H__

#include "stm32f10x.h"

void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler);
uint8_t SPI2_ReadWriteByte(uint8_t TxData);
void Spi_Init(void);

#endif

user_w25q128.c
#include "user_w25q128.h"

/*中转变量发现有其他值的全部取出来,然后擦除一个扇区在写回去*/
uint8_t W25Q128_BUFFER[4096];
 
/*
	W25Q128初始化
*/
void W25Q128_Init(void)
{	
	W25Q128_CS=1;	//W25Q128不片选
	Spi_Init();		//初始化SPI
	SPI2_SetSpeed(SPI_BaudRatePrescaler_2);	//设置为36/2=18M时钟,高速模式
}
 
/*
	状态寄存器
	BIT7  6   5   4   3   2   1   0
	SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 	SPR:默认0,状态寄存器保护位,配合WP使用
 	TB,BP2,BP1,BP0:FLASH区域写保护设置
 	WEL:写使能锁定
 	BUSY:忙标记位(1,忙;0,空闲)
 	默认:0x00
*/
/*	读取W25Q128状态寄存器	*/
uint8_t W25Q128_ReadSR(void)   
{  
	uint8_t byte=0;	//声明接收数据变量 
	W25Q128_CS=0;	//选中片选
	SPI2_ReadWriteByte(W25X_ReadStatusReg);	//发送读取状态命令,同时会返回一个无用字节
	byte=SPI2_ReadWriteByte(0XFF);	//写一个无用字节,返回状态寄存器值
	W25Q128_CS=1;  //取消片选
	return byte;  //返回状态 
}
 
/*
	SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写
*/
/*	写W25Q128状态寄存器	*/
void W25Q128_Write_SR(uint8_t sr)   
{  
	W25Q128_CS=0;	//选中片选 
	SPI2_ReadWriteByte(W25X_WriteStatusReg);	//发送写状态寄存器命令
	SPI2_ReadWriteByte(sr);	//写入一个字节
	W25Q128_CS=1;  //取消片选                              	      
} 
 
/*
	W25Q128写使能
*/
void W25Q128_Write_Enable(void)   
{
	W25Q128_CS=0;	//选中片选 
    SPI2_ReadWriteByte(W25X_WriteEnable);	//发送写使能命令
	W25Q128_CS=1;   //取消片选                         	     	      
} 
 
/*
	W25Q128写禁止
*/
void W25Q128_Write_Disable(void)   
{  
	W25Q128_CS=0;  //选中片选   
    SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令
	W25Q128_CS=1;  //取消片选                            	      
} 

/*
	描述:读取ID
	返回值:0XEF17,表示芯片型号为W25Q128
*/
uint16_t W25Q128_ReadID(void)
{
	uint16_t Temp = 0;	//声明ID返回变量
	W25Q128_CS=0;	//选中片选
	SPI2_ReadWriteByte(W25X_ManufactDeviceID);	//发送读取ID命令
	
	/*发送24bit位的地址*/
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00);
	
	/*发无用字节,接收ID*/
	Temp|=SPI2_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI2_ReadWriteByte(0xFF);	 
	
	W25Q128_CS=1;	//取消片选 
	return Temp;	//返回ID
} 
 
/*
	擦除一个扇区
*/
void W25Q128_Erase_Sector(u32 Dst_Addr)   
{  
 	Dst_Addr*=4096;		//一个扇区是4096个字节
    
	W25Q128_Write_Enable(); //写使能	
    while((W25Q128_ReadSR()&0x01)==0x01); //等待BUSY位清空

  	W25Q128_CS=0;	//选中片选
    SPI2_ReadWriteByte(W25X_SectorErase);	//发送扇区擦除指令
	
	/*发送24bit地址*/
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  	   
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   
    SPI2_ReadWriteByte((u8)Dst_Addr); 
		
	W25Q128_CS=1;	//取消片选                               	      
    while((W25Q128_ReadSR()&0x01)==0x01);  //等待擦除完成				   	
}  
 
/*
	擦除整个芯片
*/
void W25Q128_Erase_Chip(void)   
{                                   
    W25Q128_Write_Enable();	//写使能
    while((W25Q128_ReadSR()&0x01)==0x01);	//等待BUSY位清空
		
  	W25Q128_CS=0;	//选中片选
    SPI2_ReadWriteByte(W25X_ChipErase);		//发送片擦除命令 
			
	W25Q128_CS=1;                            	     	      
    while((W25Q128_ReadSR()&0x01)==0x01); 	//等待擦除完成
}
 
/*
	读取函数
	参数1:读取BUFF 参数2:读取地址 参数3:读取数量(最大65535)
*/
void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 
 	uint16_t i;
	
	W25Q128_CS=0;  //选中片选                          	
    SPI2_ReadWriteByte(W25X_ReadData);	//发送读取命令
	
    SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  //发送读取地址  
    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
    SPI2_ReadWriteByte((u8)ReadAddr); 
	
	/*开始发空指令,读取数据,返回寄存器的值*/
    for(i=0;i<NumByteToRead;i++)
	{    
		/*循环读数*/
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);   	 
    }
		
	W25Q128_CS=1; //取消片选			    	      
}  
 
/*
	描述:写一页256个字节
	参数1:要写的BUFF 参数2:要写的地址(24bit) 参数3:要写的数量(256字节最大)
*/
void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
 	u16 i;  

    W25Q128_Write_Enable();	//写使能                  	

	W25Q128_CS=0;  //选中片选      
    SPI2_ReadWriteByte(W25X_PageProgram);	//发送写页命令
	
	/*发送24bit地址*/
    SPI2_ReadWriteByte((u8)((WriteAddr)>>16));    
    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
    SPI2_ReadWriteByte((u8)WriteAddr);  
	
	/*开始循环写数*/
    for(i=0;i<NumByteToWrite;i++){
		SPI2_ReadWriteByte(pBuffer[i]);
	}

	W25Q128_CS=1; //取消片选	                            
    while((W25Q128_ReadSR()&0x01)==0x01); 	//等待擦除完成
} 
 
/*
	描述:检查是否一页当中有存其他值,可以换页 (页写入)
	参数1:要写的数组 参数2:要写的地址(24bit) 参数3:要写的数量(256字节最大)
*/
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 
	u16 pageremain;	
	
	pageremain=256-WriteAddr%256;	//单页剩余的字节数
	
	//如果要写入字节的大小<单页剩余字节数,说明不需要换页,则将要写入字节数赋值给pageremain
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;
	while(1)
	{	 
		W25Q128_Write_Page(pBuffer,WriteAddr,pageremain);	//写一页256个字节 
		if(NumByteToWrite==pageremain)	//如果要写入的字节和单页剩余字数正好相等,写入结束
		{	/*写入结束*/
			break;	
		}
	 	else //如果要写入的字节>单页剩余字节,需要换页。
		{
			pBuffer+=pageremain;		//写入数组=写入数组+剩余字节数
			WriteAddr+=pageremain;		//写入地址=写入地址+剩余字节数,相当于下一页的开始地址

			NumByteToWrite-=pageremain;	//要写入的数据=要写入的数据-上一页已经写入的字节数
			/*一次可以写入256个字节*/
			if(NumByteToWrite>256)	//如果要写入的数据还>256
			{
				pageremain=256;		//单页剩余字节数=256
			} 
			else
			{	/*不够256个字节了*/
				pageremain=NumByteToWrite;	//要写入数据字节给当页剩余字节数
			}  	  
		}
	}	    
}
 
/*
	描述:写数据	(扇区写入)
	参数1:写BUFF 参数2:要写入的地址 参数3:要写的数量(65535字节最大)
*/
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 
	u32 secpos;
	u16 secoff;
	u16 secremain;	   
 	u16 i;    
	u8 * W25QXX_BUF;	  
   	W25QXX_BUF=W25Q128_BUFFER;	
	
 	secpos=WriteAddr/4096;	//扇区地址
	secoff=WriteAddr%4096;	//在扇区内的偏移
	secremain=4096-secoff;	//扇区剩余空间大小
	
	/*不大于4096个字节*/
	//如果(传入数量字节<扇区剩余空间大小) 将传入数量字节赋值给扇区剩余空间
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;
	while(1) 
	{	/*读出整个扇区的内容*/
		W25Q128_Read(W25QXX_BUF,secpos*4096,4096);
		/*校验数据*/
		for(i=0;i<secremain;i++)
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)
			{
				/*需要擦除 */
				break;
			}
				 	  
		}
		/*//需要擦除 */
		if(i<secremain)
		{
			/*擦除这个扇区*/
			W25Q128_Erase_Sector(secpos);		
			/*复制*/
			for(i=0;i<secremain;i++)	   		
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			/*写入整个扇区*/
			W25Q128_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);
		}
		else
		{	/*写已经擦除了的,直接写入扇区剩余区间*/
			W25Q128_Write_NoCheck(pBuffer,WriteAddr,secremain);
		}					   
		if(NumByteToWrite==secremain)
		{	/*写入结束了*/
			break;
		}
		else/*写入未结束*/
		{	/*扇区地址增1*/
			secpos++;
			/*偏移位置为0*/
			secoff=0; 	 
			/*指针偏移*/
		   	pBuffer+=secremain;  				
			/*写地址偏移*/
			WriteAddr+=secremain;				
			/*字节数递减*/
		   	NumByteToWrite-=secremain;
			/*下一个扇区还是写不完*/
			if(NumByteToWrite>4096)
			{
				secremain=4096;
			}
			else 
			{	/*下一个扇区可以写完了*/
				secremain=NumByteToWrite;
			}		
		}	 
	} 
}

/*
	描述:获取地址块和扇区的位置,打印地址块、扇区的位置信息
	传入参数:存储地址 
*/
void Get_Address_Analysis(uint32_t address)
{
	u32 addr =address;		//存储地址
	u8 block = addr>>16;	//23-16位是块的位置
	u8 sector = (addr<<16)>>28;	//15-12位是扇区的位置
	Serial_Printf("addr:%x,block:%d,sector:%d\r\n",addr,block,sector);
}

user_w25q128.h
#ifndef _USER_W25Q128_H__
#define _USER_W25Q128_H__

#include "stm32f10x.h"
#include "user_spi.h"
#include "Serial.h"

//指令表数据手册上的
#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
 
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    
 
#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
//片选引脚
#define	W25Q128_CS  BIT_ADDR(GPIOB_ODR_Addr,12)  //W25QXX的片选信号 1取消片选 0 选中

uint16_t  W25Q128_ReadID(void);  	    		//读取FLASH ID
void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);   //读取flash
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25Q128_Init(void); //初始化
uint8_t W25Q128_ReadSR(void);
void W25Q128_Write_SR(uint8_t sr) ;
void W25Q128_Write_Enable(void);
void W25Q128_Write_Disable(void) ;
void W25Q128_Erase_Sector(u32 Dst_Addr);
void W25Q128_Erase_Chip(void);
void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void Get_Address_Analysis(uint32_t address);	//获取地址块和扇区的位置,打印地址块、扇区的位置信息

#endif
delay.c
#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

delay.h
#ifndef __DELAY_H
#define __DELAY_H

void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

Serial.c
#include "Serial.h"

void Serial_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;                  //定义USART结构体
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);   //开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出   USART1 TX使用
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_9;   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);     

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        //上拉输入模式   USART1 RX使用
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_10;   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);               

    USART_InitStructure.USART_BaudRate = 9600;              //设置波特率9600,init函数内部会自动算好9600对应的分频系数
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不启用
    USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;         //USART模式:发送、接收数据
    USART_InitStructure.USART_Parity = USART_Parity_No;     //校验位:无校验
    USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位1   
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;    //字长,这里不需要校验,字长选择8位
    USART_Init(USART1,&USART_InitStructure);                //初始化USART

    USART_Cmd(USART1,ENABLE);    
}

/*
    函数功能:发送数据
*/
void Serial_SendByte(u8 Byte)
{
    /*内部将Byte传递给Data变量,之后Data&01FF,把无关的高位清零,
    然后直接赋值给DR寄存器,通向TDR发送数据寄存器,再传递到移位寄存器最后一位一位传递给TX引脚*/
    USART_SendData(USART1,Byte);    //调用串口的SendDate()函数
    /*写完数据,需要等待,等TDR的数据到移位寄存器,不然数据还在TDR进行等待,再写入数据会产生覆盖
        所以发送之后,需要等待标志位
        USART_FLAG_TXE 发送数据寄存器为空 标志位,要等待TXE为1,这里要嵌套循环
        如果TXE为RESET就一直循环,直到TXE为SET
    */
   while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
   /*关于标志位是否需要手动清楚的问题:标志位置1之后,不需要手动清0。因为对USART_DR进行写操作,将该位清0*/
}

/*
    发送数组,通过串口发送到电脑
    参数1:指针指向传递数组的首地址 
    参数2:长度
*/
void Serial_SendArray(u8 *Array,u16 Length)
{
    u16 i;
    for ( i = 0; i < Length; i++)
    {
        Serial_SendByte(Array[i]);
    }
}

/*
    发送字符串函数
    字符串自带一个结束标志位,不需要再传递长度参数,对应空字符,是字符串的标志位
*/
void Serial_SendString(char *String)
{
    u8 i;
    for(i=0; String[i]!='\0'; i++)
    {
        Serial_SendByte(String[i]);     //发送字符串
    }
}

/*
    次方函数,提取千百十个
*/
u32 Serial_Pow(u32 X,u32 Y)        //2 3
{
    u32 Result = 1;
    while(Y--)                     //3--
    {
        Result *= X;               //1*2 *2 *2 = 2^3
    }
    return Result;
}

/*
    发送一个数字
*/
void Serial_SendNumber(u32 Number,u8 Length)
{
    u8 i;
    for ( i = 0; i < Length; i++)
    {
        /*
           (Number / Serial_Pow(10,i) % 10)     1234/1%10=4   1234/10%10=3 1234/100%10=2
           第一个数据不是个位,需要反过来 (Number / Serial_Pow(10, Length - i -1) % 10)
           目前循环,参数会以十进制从高位到低位依次发送,因为最终要以字符的形式显示,所以这里要加一个偏移
        */
        Serial_SendByte((Number / Serial_Pow(10,Length - i - 1) % 10) + '0');    
    }
    
}

/*
    printf函数默认是输出到屏幕,单片机没有屏幕,需要重定向printf函数
    fputc是printf函数的底层,printf打印时,不断调用fputc函数一个一个打印
*/
int fputc(int ch,FILE *f)
{
    Serial_SendByte(ch);
    return ch;
}

/*
    封装sprintf,参数1接收格式化字符串,...用来接收后面的可变参数列表
*/
void Serial_Printf(char *format,...)
{
    char String[100];
    va_list arg;    //参数列表变量
    va_start(arg,format);   //从format位置开始接收参数表,放在arg里面
    vsprintf(String,format,arg); //打印位置String,格式化字符串是format,参数表是arg
    va_end(arg);    //释放参数表
    Serial_SendString(String);
}

Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H

#include "stm32f10x.h"
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void);
void Serial_SendByte(u8 Byte);
void Serial_SendArray(u8 *Array,u16 Length);
void Serial_SendString(char *String);
void Serial_SendNumber(u32 Number,u8 Length);
void Serial_Printf(char *format,...);

#endif
main.c
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "delay.h"
#include "user_w25q128.h"
#include "Serial.h"
 
#define SIZE sizeof(TEXT_Buffer)
const uint8_t TEXT_Buffer[]={"SPI TEST AAAAAAAAA"};	 //19个字节
 

int main(void)
{	
	uint16_t id;	//声明id变量
	uint32_t FLASH_SIZE = 128*1024*1024;	//声明flash大小16M字节
	uint8_t datatemp[SIZE];		//声明读出数组
	uint16_t i;	 //声明打印变量
	 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//配置系统中断分组为2位抢占2位响应
	Gpio_Init();	//GPIO初始化
	Serial_Init();	//串口1初始化
	W25Q128_Init();	//W25Q128初始化
	
	while(1){
		id = W25Q128_ReadID();	//读取W25Q128的ID
		if(id == 0xef17)
		{
			Serial_Printf("W25Q128\r\n");
		}
 
		/*从倒数第200个地址处开始,写入SIZE长度的数据*/	 
		W25Q128_Write((uint8_t*)TEXT_Buffer,FLASH_SIZE-200,SIZE);
		/*从倒数第200个地址处开始,读出SIZE个字节*/
		W25Q128_Read(datatemp,FLASH_SIZE-200,SIZE);	
		
		for(i = 0; i < SIZE; i++)
		{
			Serial_Printf("%c",datatemp[i]);	
		}
			Serial_Printf("\r\n");
			Delay_ms(1000);
	 }
}
 

参考

  1. 39_SPI通讯W25Q128实验 原文链接:https://blog.csdn.net/Yuanghxb/article/details/128295231
  2. 一些视频教程,b站江科大自化协视频代码占比较多

到了这里,关于stm32使用外部flash w25Q128实现读写操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [10min速通]STM32CubemMX配置W25Q128

    下载github开源驱动代码。 GitHub - nimaltd/w25qxx: w25qxx SPI FLASH driver for stm32 HAL 此处声明此工程不是本人所作。 这个工程的作者在Readme中提供的视频教程配置非常详细了,会魔法的同学可以直接去看视频进行配置,不用看此文。 如果访问不了github,可以直接下载文末的资料,打包

    2024年02月12日
    浏览(29)
  • STM32CubeMX教程26 FatFs 文件系统 - W25Q128读写

    正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 野火DAP仿真器 XCOM V2.6串口助手 使用STM32CubeMX软件配置STM32F407开发板 使用FatFs中间件通过SPI通信协议对W25Q128芯片进行读写等操作 关于STM32F407使用SPI通信协议对W25Q128 FLASH芯片读写

    2024年02月19日
    浏览(42)
  • 使用stm32的模拟spi读取w25q128读取设备ID时一直出现0xFF

    由于公司的电路是前辈画的,只能使用模拟spi中如图所示   上图是stm32所对应的引脚  上图是w25q128的引脚 当读取的时候ID号一直是0xffff,在网上查了各种方法都试过了都不行,我这个情况稍微特殊,就是使用了PB3、PB4这两个引脚上电复位默认是作为调试端口使用的。所以得先

    2024年02月04日
    浏览(40)
  • W25Q128芯片手册精读

    之前写SPI通信的时候用到了W25Q128,其中对芯片手册的阅读我只写了我们所需要的的部分。 这篇博客就对这个芯片进行详细的阅读并记录,文章可能会比较长,但绝对是结合了自己的理解进行阐述。 芯片手册刚开始看的时候最大的拦路虎其实就是英文,看习惯了中文,直接看

    2024年02月07日
    浏览(34)
  • SPI FLASH(W25Q128BV) 包含SPI工作原理

    目录   一、SPI简介         1、全双工与半双工          2、同步与异步         3、SPI通信方式 二、SPI工作模式 三、W25Q128BV         1、读ID Read Manufacturer/Device ID(90h)                   2、读ID代码实现(硬件SPI)          3、IO口模拟SPI时序图实现 (软件SPI)  模式

    2024年02月14日
    浏览(29)
  • STM32使用QUADSPI读写外部Nor Flash(以W25Q64为例)

    QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线) SPI Flash 存储介质。该接 口可以在以下三种模式下工作: ①间接模式:使用 QUADSPI 寄存器执行全部操作。 ②状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会

    2024年02月11日
    浏览(39)
  • 【STM32】SPI初步使用 读写FLASH W25Q64

    (1) SS( Slave Select):从设备选择信号线,常称为片选信号线,每个从设备都有独立的这一条 NSS 信号线,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以 SPI通讯以 NSS 线置低电

    2024年02月10日
    浏览(42)
  • FPGA纯verilog代码读写N25Q128A QSPI Flash 提供工程源码和技术支持

    N25Q128A的参数有很多,作为FPGA开发者,需要关注如下参数: 1、4KBytes为1个Sector(扇区); 2、16个Sector(扇区)是1个Block(块)64KBytes; 3、容量为16M=128Mbite字节,共有256个Block,4096个Sector; 这三个参数直接决定了你怎么组织数据的读写操作,比如你的数据量很小,则考虑写入1个Sector(扇

    2024年02月02日
    浏览(48)
  • STM32 HAL库形式制作SPI Flash(W25Q16)的 Keil下载算法

    常见的SPI Flash:W25Qxx系列,本文以W25Q16为实例制作Keil下载算法。 如下图,红框内的东西就是下载算法。 只要导入下载算法后,就可以在烧录MCU的同时对W25Q16页进行烧录。此操作可方便LCD运用场景,字库、图片存放在外部Flash的烧录。 实际操作: 1、硬件:STM32G030C8T6、W25Q16(

    2024年02月21日
    浏览(36)
  • 基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)

    需要的软件: Keil STM32CubeMX J-Flash 参考文档: 方法1:在Keil中点击Help→uVision Help,然后再搜索框中输入FLM,点击列出主题,可以看到生成下载算法的大致步骤: 方法2:在ARM Keil官网,搜索KAN333,可以找到生成算法说明的PDF文档以及例程源码。链接 方法3:在Keil安装路径下Ke

    2024年02月13日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包