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. 写数据 (扇区写入)

代码

基于库函数版本,绝对完整代码,毕竟拿来主义是最香的。
在这里插入图片描述

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站江科大自化协视频代码占比较多
  • 8
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值