软件SPI读写W25Q64&硬件SPI读写W25Q64

目录

软件SPI读写W25Q64 

MySPI

W25Q64

主函数

硬件SPI读写W25Q64


软件SPI读写W25Q64 

  • 程序整体框架:

SPI模块包含通信引脚封装,初始化,SPI三个基本时序单元(起始,终止,交换一个字节)

W25Q64通过调用SPI基本时序完成整个时序(写使能,页编程,擦除,读数据)

主函数中调用W25Q64的硬件驱动层函数时序功能

MySPI

#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_W_CLK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_Init_Structure;
	//要求配置为推挽输出,浮空或上拉输入
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);

	MySPI_W_CS(1);
	MySPI_W_CLK(0);
}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}
//通过掩码,依次挑出每一位操作,优点是保留了原数据的值
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i,ByteReceive = 0x00;
	for(i = 0;i < 8;i ++)
	{
		MySPI_W_MOSI(ByteSend &(0x80>>i));
		MySPI_W_CLK(1);
		if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
		MySPI_W_CLK(0);
	}
	return ByteReceive;
}

//还可以按照移位示意图中的方式交换数据,优点是效率高,但不能保存原数据值
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
//	 uint8_t i;
//   for(i=0;i<8;i ++)
//    {
//    	MySPI_W_MOSI(ByteSend &0x80);
//		ByteSend <<=1;			//最高位移出,最后补0
//    	MySPI_W_CLK(1);
//    	if(MySPI_R_MISO() == 1) {ByteSend |= 0x01;}	 //输入的数据放在最低位
//    	MySPI_W_CLK(0);
//    }
//    return ByteSend;
//}

W25Q64

在注意事项中:写入操作后,芯片进入忙状态。这里通过定义的函数进行事前等待和事后等待。

事前等待:1、写入前先等待,等不忙了再写入   2、效率高。  3、在写入和读取操作之前都要等待。

事后等待:1、写入后立刻等待,不忙了退出。  2、这样最保险,函数结束后,函数之外的地方芯片肯定不忙。  3、只需在写入后等待。

#include "stm32f10x.h"                  // Device header
#include "MySPI.H"
#include "W25Q64_INS.H"

void W25Q64_Init(void)
{
	MySPI_Init();
}
//用指针的方式来获取多个函数的值!!!
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}
void W25Q64_WiteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}
/**
  * @brief 直到BUSY清零后结束
  * @param  
  * @retval 
  */
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout=100000;  //为了防止卡死
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	
	//直到busy不为1
	while((MySPI_SwapByte(0xFF) & 0X01) == 0X01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

//页编程写入,注意页编程写入一页的范围
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t count)	
{
	//没有24位,通过数组可以传多个字节。所以用32位,写入数据的数量范围0-256,所以用uint16不用uint8
	W25Q64_WiteEnable();
	
	uint16_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	//高两位会丢弃
	MySPI_SwapByte(Address);
	for(i=0;i < count ;i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)	
{
	//写使能仅对之后跟随的一条时序有效,结束之后会失能,所以每个函数加入这个就不用再写失能
	W25Q64_WiteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	
	MySPI_SwapByte(Address);
	MySPI_Stop();

	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t count)	//改为32位
{	
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	
	MySPI_SwapByte(Address);
	for(i=0;i < count ;i++)
	{
		//调用交换读取之后,内部指针自动自增
		DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.H"
#include "Key.h"
#include "OLED.H"
#include "W25Q64.H"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[]={0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];
int main(void)
{
	OLED_Init();
	W25Q64_Init();
	OLED_ShowString(1,1,"MID:   DID:");
	OLED_ShowString(2,1,"W:");
	OLED_ShowString(3,1,"R:");

	W25Q64_ReadID(&MID,&DID);
	OLED_ShowHexNum(1,5,MID,2);
	OLED_ShowHexNum(1,12,DID,4);
	
	//只擦除不写入,可以验证flash擦除之后变为ff
	//不擦除直接改写,可以测试不能由0到1,只能1到0

    //写之前先擦除。xxx000-xxffff
	W25Q64_SectorErase(0x000000); 	
	
	//页地址范围xxxx00-xxxxff
	W25Q64_PageProgram(0X000000,ArrayWrite,4); 
	
	W25Q64_ReadData(0X000000,ArrayRead,4);
	
	OLED_ShowHexNum(2,3,ArrayWrite[0],2);
	OLED_ShowHexNum(2,6,ArrayWrite[1],2);
	OLED_ShowHexNum(2,9,ArrayWrite[2],2);
	OLED_ShowHexNum(2,12,ArrayWrite[3],2);
	
	OLED_ShowHexNum(3,3,ArrayRead[0],2);
	OLED_ShowHexNum(3,6,ArrayRead[1],2);
	OLED_ShowHexNum(3,9,ArrayRead[2],2);
	OLED_ShowHexNum(3,12,ArrayRead[3],2);

	while(1)
	{

	}
}
  • 验证不能跨页写入时,

W25Q64_PageProgram(0X0000ff,ArrayWrite,4); 
W25Q64_ReadData(0X000000,ArrayRead,4);

因为读可以跨页,此时读到的结果为 55 FF FF FF

改为这句W25Q64_ReadData(0X000000,ArrayRead,4);结果如下。说明不能跨页存储,超过范围会从页开始存储。

硬件SPI读写W25Q64

只需要修改底层MySPI文件,将软件实现改为硬件实现,之后基于通信层的业务代码不用改变

转移到移位寄存器后会自动生成波形,硬件自动生成不需要像软件那样置高低电平

#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

	GPIO_InitTypeDef GPIO_Init_Structure;
	//硬件外设控制的输出MOSI,SCK;用复用推挽输出
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	//软件控制的输出信号ss,通用推挽输出
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	//MISO
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	
	SPI_InitTypeDef SPI_InitStrcture;
	SPI_InitStrcture.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
	//配置模式
	SPI_InitStrcture.SPI_CPHA=SPI_CPHA_1Edge;		//第一个边沿采样
	SPI_InitStrcture.SPI_CPOL=SPI_CPOL_Low; 
	SPI_InitStrcture.SPI_CRCPolynomial=7;
	
	SPI_InitStrcture.SPI_DataSize=SPI_DataSize_8b;
	SPI_InitStrcture.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
	
	SPI_InitStrcture.SPI_FirstBit=SPI_FirstBit_MSB;    //高位先行
	SPI_InitStrcture.SPI_Mode=SPI_Mode_Master;		//主机模式
	SPI_InitStrcture.SPI_NSS=SPI_NSS_Soft;
	
	SPI_Init(SPI1,&SPI_InitStrcture);
	
	//不要忘记开使能
	SPI_Cmd(SPI1,ENABLE);
	
	//默认不选择从机
	MySPI_W_CS(1);

}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}

//四步
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);
	
	SPI_I2S_SendData(SPI1,ByteSend);
	
	//发送接收同步,当接收完成标志着发送也完成
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);
	
	return SPI_I2S_ReceiveData(SPI1);
	
}

TXE RXNE标志位不用手动清除,中断标志位进中断后必须手动清除 

猜你喜欢

转载自blog.csdn.net/m0_70732442/article/details/131353574