STM32F103标准库开发---SPI实验---读写 W25Q128 外部 Flash

STM32F103标准库开发----目录
W25Q128读写----程序源码----点击下载
W25Qxx全系列数据手册—点击下载


一、实验前期准备

本次实验的 MCU 是 STM32F103C8T6 芯片,通过 SPI 通信实现 W25Q128 的读写操作。

1. 原理图

在这里插入图片描述

2. 引脚连接

SPI接口GPIO端口
CS(片选)PA4
SCLK(时钟)PA5
MISO(数据输入)PA6
MOSI(数据输出)PA7

二、SPI 底层驱动

SPI.c

#include "SPI.h"

/*
	SPI引脚初始化配置
	**PA4------CS
	**PA5------SCLK
	**PA6------MISO
	**PA7------MOSI
*/

static void SPI1_GPIO_Config(void)	
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA, ENABLE );//PORTA时钟使能 

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA5/6/7复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//初始化GPIOA
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //PA4推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//初始化GPIOA

	GPIO_SetBits(GPIOA,GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //初始上拉输出

}

//SPI1初始化函数
void SPI1_Init(void)
{
	SPI1_GPIO_Config();//SPI引脚初始化配置	
  
	SPI_InitTypeDef  SPI_InitStructure;
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_SPI1,  ENABLE );//SPI1时钟使能 	
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  	//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;													//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;											//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;														//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;													//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;															//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;	//定义波特率预分频的值:波特率预分频值为256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;										//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  			//根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
	SPI1_ReadWriteByte(0xFF);//启动传输
} 

/*
	SPI的读写操作
	**TxData:要写入的字节
	**返回值:读取到的字节
*/
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{			 	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
	{
		//等待发送完成
	}			  
	SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
	{
		//等待接收完成
	}	  						    
	return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据					    
}

SPI.h

#ifndef __SPI_H
#define	__SPI_H

#include "stm32f10x.h"

#define SPI_CS(a)		if (a)	\
						GPIO_SetBits(GPIOA,GPIO_Pin_4);\
						else		\
					    GPIO_ResetBits(GPIOA,GPIO_Pin_4)

void SPI1_Init(void);//SPI1初始化函数
uint8_t SPI1_ReadWriteByte(uint8_t TxData);//SPI1读写函数

#endif 

三、读取 W25Q128 设备 ID

本次实验是:读取 W25Q128 设备 ID,然后通过串口1打印出来。
通过简单的实验来调试,有利于发现问题。
通过W25Q128的数据手册可知其设备ID如下图所示:
在这里插入图片描述

W25Q128.c

#include "W25Q128.h"
#include "SPI.h"


uint8_t W25Q128_ReadWriteByte(uint8_t TxData)//函数包装一下
{     
 	  return SPI1_ReadWriteByte(TxData); 		
}

uint16_t W25Q128_ReadID(void)//读取芯片ID
{
	uint16_t Temp = 0;	  
	W25Q128_CS(0);				    
	W25Q128_ReadWriteByte(W25X_ManufactDeviceID);//发送读取ID命令	    
	W25Q128_ReadWriteByte(0x00); 	    
	W25Q128_ReadWriteByte(0x00); 	    
	W25Q128_ReadWriteByte(0x00); 	 			   
	Temp|=W25Q128_ReadWriteByte(0xFF)<<8;  
	Temp|=W25Q128_ReadWriteByte(0xFF);	 
	W25Q128_CS(1);				    
	return Temp;
}

W25Q128.h

#ifndef __W25Q128_H
#define	__W25Q128_H

#include "stm32f10x.h"

//操作指令表
#define W25X_ManufactDeviceID	 0x90       //制造商+设备ID

#define W25Q128_CS(a) SPI_CS(a)	

uint8_t W25Q128_ReadWriteByte(uint8_t TxData);//函数包装一下
uint16_t W25Q128_ReadID(void);//读取芯片ID

#endif 

main.c

#include <stdio.h>
#include "Uart1.h"
#include "delay.h"
#include "SPI.h"
#include "W25Q128.h"

uint16_t W25Q128_ID=0;

int main (void)
{
	Uart1_init();
	SPI1_Init();
	
	W25Q128_ID = W25Q128_ReadID();
	while(1)
	{
		printf("\nW25Q128_ID=0x%X\n",W25Q128_ID);
		delay_ms(500);
	}
}

实验结果

在这里插入图片描述
接收到 W25Q128 设备ID 为0xEF17


不了解串口通信的朋友,可以看下面这几篇文章
STM32F103标准库开发----Uart串口通信实验----初始化配置
STM32F103标准库开发----Uart串口通信实验----安装串口驱动和串口调试
STM32F103标准库开发----Uart串口通信实验----函数发送和中断接收
STM32F103标准库开发----Uart串口通信实验----printf()函数重定向
STM32F103标准库开发----Uart串口通信实验----程序源码 点击下载


四、读写 W25Q128 外部 Flash

W25Q128.c

#include "W25Q128.h"
#include "SPI.h"


uint8_t W25Q128_ReadWriteByte(uint8_t TxData)//函数包装一下
{     
 	return SPI1_ReadWriteByte(TxData); 		
}


uint16_t W25Q128_ReadID(void)//读取芯片ID
{
	uint16_t Temp = 0;	  
	W25Q128_CS(0);				    
	W25Q128_ReadWriteByte(W25X_ManufactDeviceID);//发送读取ID命令	    
	W25Q128_ReadWriteByte(0x00); 	    
	W25Q128_ReadWriteByte(0x00); 	    
	W25Q128_ReadWriteByte(0x00); 	 			   
	Temp|=W25Q128_ReadWriteByte(0xFF)<<8;  
	Temp|=W25Q128_ReadWriteByte(0xFF);	 
	W25Q128_CS(1);				    
	return Temp;
}

//读取W25Q128的状态寄存器
//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
uint8_t W25Q128_ReadSR(void)//读取状态寄存器
{
    uint8_t byte=0;
    W25Q128_CS(0);                               //使能器件
    W25Q128_ReadWriteByte(W25X_ReadStatusReg1);  //发送读取状态寄存器命令
    byte=W25Q128_ReadWriteByte(0Xff);            //读取一个字节
    W25Q128_CS(1);                               //取消片选
    return byte;
}

//写W25Q128状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25Q128_WriteSR(uint8_t sr)//写状态寄存器
{
    W25Q128_CS(0);                           		//使能器件
    W25Q128_ReadWriteByte(W25X_WriteStatusReg1);	//发送写取状态寄存器命令
    W25Q128_ReadWriteByte(sr);               		//写入一个字节
    W25Q128_CS(1);                            		//取消片选
}

void W25Q128_Write_Enable(void) //写使能 
{
   	W25Q128_CS(0);	
	W25Q128_ReadWriteByte(W25X_WriteEnable);
	W25Q128_CS(1);	
}

void W25Q128_Write_Disable(void) //禁止写入	
{
   	W25Q128_CS(0);	
	W25Q128_ReadWriteByte(W25X_WriteDisable);
	W25Q128_CS(1);	
}

void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 						    
	W25Q128_CS(0);	                          //使能器件   
    W25Q128_ReadWriteByte(W25X_ReadData);      //发送读取命令  
    W25Q128_ReadWriteByte((uint8_t)((ReadAddr)>>16));   //发送24bit地址    
    W25Q128_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
    W25Q128_ReadWriteByte((uint8_t)ReadAddr);   
    for(uint16_t i=0;i<NumByteToRead;i++)
	{ 
       pBuffer[i]=W25Q128_ReadWriteByte(0XFF);    //循环读数  
    }
	W25Q128_CS(1);				    	      
}  


void W25Q128_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
  
    W25Q128_Write_Enable();                  //SET WEL 
	W25Q128_CS(0);                            //使能器件   
    W25Q128_ReadWriteByte(W25X_PageProgram);   //发送写页命令   

    W25Q128_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址    
    W25Q128_ReadWriteByte((uint8_t)((WriteAddr)>>8));   
    W25Q128_ReadWriteByte((uint8_t)WriteAddr);   
    for(uint16_t i=0;i<NumByteToWrite;i++)
	{
		W25Q128_ReadWriteByte(pBuffer[i]);//循环写数  
	}
	W25Q128_CS(1);                            //取消片选 
	W25Q128_Wait_Busy();					   //等待写入结束
} 

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 
	uint16_t pageremain=256-WriteAddr%256; //单页剩余的字节数		 	    
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
	while(1)
	{	   
		W25Q128_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)	break;//写入结束了
	 	else 
		{
			pBuffer+=pageremain;
			WriteAddr+=pageremain;	
			NumByteToWrite-=pageremain;			  //减去已经写入了的字节数
			if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
			else pageremain=NumByteToWrite; 	  //不够256个字节了
		}
	}	    
} 

//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
uint8_t W25Q128_BUFFER[4096];		 
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 
 	uint16_t i;    
	uint8_t * W25Q128_BUF;	  
    W25Q128_BUF=W25Q128_BUFFER;	     
 	uint32_t secpos = WriteAddr/4096;//扇区地址  
	uint16_t secoff = WriteAddr%4096;//在扇区内的偏移
	uint16_t secremain = 4096-secoff;//扇区剩余空间大小   

 	if(NumByteToWrite<=secremain)		secremain=NumByteToWrite;//不大于4096个字节
	while(1) 
	{	
		W25Q128_Read(W25Q128_BUF,secpos*4096,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25Q128_BUF[secoff+i]!=0XFF)	break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25Q128_Erase_Sector(secpos*4096);//擦除这个扇区
			for(i=0;i<secremain;i++)	   //复制
			{
				W25Q128_BUF[i+secoff]=pBuffer[i];	  
			}
			W25Q128_Write_NoCheck(W25Q128_BUF,secpos*4096,4096);//写入整个扇区  

		}else W25Q128_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumByteToWrite==secremain)	break;//写入结束了
		else//写入未结束
		{
			secpos++;//扇区地址增1
			secoff=0;//偏移位置为0 	 

		    pBuffer+=secremain;  //指针偏移
			WriteAddr+=secremain;//写地址偏移	   
		    NumByteToWrite-=secremain;				//字节数递减
			if(NumByteToWrite>4096)	secremain=4096;	//下一个扇区还是写不完
			else 	secremain=NumByteToWrite;		//下一个扇区可以写完了
		}	 
	}	 
}

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms

void W25Q128_Erase_Sector(uint32_t Dst_Addr)   
{  
    W25Q128_Write_Enable();                  //SET WEL 	 
    W25Q128_Wait_Busy();   
  	W25Q128_CS(0);                            //使能器件   
    W25Q128_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令 
    W25Q128_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //发送24bit地址    
    W25Q128_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   
    W25Q128_ReadWriteByte((uint8_t)Dst_Addr);  
	W25Q128_CS(1);                            	//取消片选     	      
    W25Q128_Wait_Busy();   				    	//等待擦除完成
}  

//擦除整个芯片		  
//等待时间超长...
void W25Q128_Erase_Chip(void)   
{                                   
    W25Q128_Write_Enable();                  //SET WEL 
    W25Q128_Wait_Busy();   
  	W25Q128_CS(0);                           //使能器件   
    W25Q128_ReadWriteByte(W25X_ChipErase);   //发送片擦除命令  
	W25Q128_CS(1);                           //取消片选     	      
	W25Q128_Wait_Busy();   				   	//等待芯片擦除结束
}


//等待空闲
void W25Q128_Wait_Busy(void)   
{   
	while((W25Q128_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}  

//进入掉电模式
void W25Q128_PowerDown(void)   
{ 
  	W25Q128_CS(0);                            //使能器件   
    W25Q128_ReadWriteByte(W25X_PowerDown);    //发送掉电命令  
	W25Q128_CS(1);                            //取消片选     	      
}   
//掉电唤醒
void W25Q128_WAKEUP(void)   
{  
  	W25Q128_CS(0);                                //使能器件   
    W25Q128_ReadWriteByte(W25X_ReleasePowerDown);    
	W25Q128_CS(1);                                //取消片选     	      
}   

W25Q128.h

#ifndef __W25Q128_H
#define	__W25Q128_H

#include "stm32f10x.h"

//操作指令表
#define W25X_WriteEnable		   	0x06 	//写使能
#define W25X_WriteDisable		   	0x04 	//写禁止
#define W25X_ReadStatusReg1		 	0x05 	//读状态寄存器1
#define W25X_ReadStatusReg2		 	0x35 	//读状态寄存器2
#define W25X_ReadStatusReg3		 	0x15 	//读状态寄存器3
#define W25X_WriteStatusReg1   		0x01 	//写状态寄存器1
#define W25X_WriteStatusReg2   		0x31 	//写状态寄存器2
#define W25X_WriteStatusReg3   		0x11 	//写状态寄存器3
#define W25X_ReadData				0x03 	//读数据
#define W25X_FastReadData		   	0x0B 	//快读
#define W25X_FastReadDual		   	0x3B    //双输出快读
#define W25X_PageProgram		   	0x02 	//页编程
#define W25X_BlockErase			   	0xD8 	//块擦除(64K)
#define W25X_SectorErase		   	0x20    //扇区擦除(4K)
#define W25X_ChipErase			   	0xC7    //芯片擦除
#define W25X_PowerDown			   	0xB9    //掉电
#define W25X_ReleasePowerDown	 	0xAB    //释放掉电
#define W25X_DeviceID			    0xAB    //器件ID
#define W25X_ManufactDeviceID	 	0x90    //制造商+设备ID
#define W25X_JedecDeviceID		 	0x9F 	//电子元件ID


#define W25Q128_CS(a) SPI_CS(a)	


uint8_t W25Q128_ReadWriteByte(uint8_t TxData);//函数包装一下

uint16_t W25Q128_ReadID(void);//读取芯片ID

uint8_t W25Q128_ReadSR(void);//读取状态寄存器
void W25Q128_WriteSR(uint8_t sr);//写状态寄存器

void W25Q128_Write_Enable(void);//写使能
void W25Q128_Write_Disable(void);//禁止写入	

void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); //读取数据
void W25Q128_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//页写
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//无检验写数据,可自动翻页
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入数据,带擦写功能

void W25Q128_Erase_Sector(uint32_t Dst_Addr);//擦除扇区
void W25Q128_Erase_Chip(void);//擦除整个芯片	

void W25Q128_Wait_Busy(void);//等待空闲
void W25Q128_PowerDown(void); //进入掉电模式
void W25Q128_WAKEUP(void);//掉电唤醒

#endif 

main.c

#include <stdio.h>
#include "Uart1.h"
#include "delay.h"
#include "SPI.h"
#include "W25Q128.h"

uint16_t W25Q128_ID=0;
uint8_t Write_data[20]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
uint8_t Read_data[100];

int main (void)
{
	Uart1_init();
	SPI1_Init();
	
	W25Q128_ID=W25Q128_ReadID();
	W25Q128_Write_Page(Write_data,0x01,20);
	
	while(1)
	{
		W25Q128_Read(Read_data,0x00,100);
		for(int i=0;i<100;i++)
		{
			printf(" 0x%X ",Read_data[i]);
		}
		printf("\nW25Q128_ID=0x%X\n",W25Q128_ID);
		delay_ms(500);
	}
}

实验结果

在这里插入图片描述


STM32F103标准库开发----SPI实验----基本原理
STM32F103标准库开发----SPI实验----底层驱动程序
STM32F103标准库开发----SPI实验----W25Qxx系列外部Flash芯片


版权声明:本文为MQ0522原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。