STM32学习笔记—软件SPI读取W25Q64

一、W25Q64介绍

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
在这里插入图片描述

二、SPI介绍

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线,四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select),同步,全双工,支持总线挂载多设备(一主多从),区别一下IIC通信,SPI直接在硬件上下功夫,有钱就是任性,不计较成本,只在乎效率。SPI硬件部分有两个移位寄存器,通信的大概流程就是主机发送1bit数据的同时,主机也会接受从机发来的1bit的数据
在这里插入图片描述

三、软件设计

3.1、SPI程序设计

这里的话类比IIC软件设计,从SPI的时序图着手写程序,大概包括开始信号、停止信号和交换一个字节;

1、开始信号

在这里插入图片描述
由上图可知:SS拉低开始点名;代码如下:

/*---------------起始信号------------------*/
void SPI_Start(void)
{
	CSWrite(0);//主机开始点名
}
2、停止信号

在这里插入图片描述
由上图可知:SS拉高结束点名;代码如下:

/*---------------停止信号------------------*/
void SPI_Stop(void)
{
	CSWrite(1);//点名结束
}
3、交换一个字节

在这里插入图片描述
由上图可知:SS拉低时,主机传输数据;SCK(时钟线)拉高时主机接受数据;代码如下:

/*-------------交换一个字节(模式0)---------------*/
uint8_t SPI_SwapByte(uint8_t SendByte)
{
	for(uint8_t i = 0;i < 8;i++)
	{
		MOSIWrite(SendByte & 0x80); //点名线拉低 开始传输1bit数据(高位先行)
		SendByte <<= 1;		//传输完1bit数据 左移清零 留出位置来接收数据
		SCKWrite(1);				//时钟线拉高开始接收数据
		SendByte |= MISORead();
		SCKWrite(0);				//时钟线拉低准备传输下1bit数据
	}
	return SendByte;
}

SPI协议在交换一个字节的时候有四种模式,主要是相位和时钟线高低电平的区别,还有就是W25Q64这个模块只支持模式0和模式3;

4.GPIO引脚初始化和宏定义
#define 		CSWrite(x)			GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)x)
#define 		SCKWrite(x)			GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)x)
#define 		MOSIWrite(x)		GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)x)
#define         MISORead()			GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;				//这个地方不要写错!!!!!!!!
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;// SS SCK MOSI
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;					//MISO配置为上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	CSWrite(1);				// 初始状态 点名线拉高 谁也不点名
	SCKWrite(0);			//时钟线拉低
}
3.2 W25Q64模块代码
初始化函数
void W25Qxx_Init(void)
{
	MySPI_Init();
}
写使能
void W25Qxx_Write_Enable(void)
{
	SPI_Start();
	SPI_SwapByte(W25Q64_WRITE_ENABLE);//发送获取ID指令
	SPI_Stop();
}
忙等待
void W25Qxx_WaitBusy(void) 
{
	uint32_t TimeOut = 100000;
	
	W25Qxx_Write_Enable();//写使能
	
	SPI_Start();
	SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//读取寄存器状态
	while((SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//最低位1:忙;0:空闲!!!!!!!这个地方写错了!!!!!!!!!!!!
	{
		if(--TimeOut == 0)										//防止程序卡死
			break;
	}
	SPI_Stop();
}
页写
void W25Qxx_PageProgramer(uint32_t Address,uint8_t *Array,uint32_t Count)//页写函数 有页限制
{
	W25Qxx_Write_Enable();//写使能
	
	SPI_Start();
	SPI_SwapByte(W25Q64_PAGE_PROGRAM);//发送指令
	/*--------发送3字节地址作为写入的起始地址------------*/
	SPI_SwapByte(Address >> 16);
	SPI_SwapByte(Address >> 8);
	SPI_SwapByte(Address);
	/*------------写入数据----------------*/
	for(uint32_t i = 0;i < Count;i++)
		SPI_SwapByte(Array[i]);
	SPI_Stop();
	
	W25Qxx_WaitBusy();//等待
}
擦除
void W25Qxx_SectorEreza(uint32_t Address)//擦除函数  每次写入的时候提前擦除下内存 否则写入数据出错(只能1写0 不能0写1 相当于&)
{
	W25Qxx_Write_Enable();//写使能
	SPI_Start();//开始
	SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//发送指令
	SPI_SwapByte(Address >> 16);
	SPI_SwapByte(Address >> 8);
	SPI_SwapByte(Address);
	SPI_Stop();
	
	W25Qxx_WaitBusy();//等待
}
读数据(无页限制)
void W25Qxx_ReadData(uint32_t Address,uint8_t *Array,uint32_t Count)//读函数 无页限制
{
	W25Qxx_Write_Enable();
	SPI_Start();
	SPI_SwapByte(W25Q64_READ_DATA);//发送读指令
	/*--------发送3字节地址作为写入的起始地址------------*/
	SPI_SwapByte(Address >> 16);
	SPI_SwapByte(Address >> 8);
	SPI_SwapByte(Address);
	/*------------写入数据----------------*/
	for(uint32_t i = 0;i < Count;i++)
		Array[i] = SPI_SwapByte(W25Q64_DUMMY_BYTE);
	SPI_Stop();
}
主函数部分
uint16_t DID;
uint8_t MID;
uint8_t Array_Write[] = {0x12,0x34,0x45};
uint8_t Array_Read[4];

int main(void)
{
	OLED_Init();//OLED初始化
	W25Qxx_Init();//W25Qxx初始化
	OLED_ShowString(1,1,"MID:");
	OLED_ShowString(1,8,"DID:");
	OLED_ShowString(2,1,"W:");
	OLED_ShowString(3,1,"R:");
	
	W25Qxx_SectorEreza(0x000000);//先擦除
	W25Qxx_PageProgramer(0x000000,Array_Write,3);
	
	W25Qxx_ReadData(0x000000,Array_Read,3);
	
	while(1)	
	{
		W25Qxx_GetID(&MID,&DID);//循环执行达到发挥多个值得效果
		OLED_ShowHexNum(1,5,MID,2);
		OLED_ShowHexNum(1,12,DID,4);
		
		OLED_ShowHexNum(2,3,Array_Write[0],2);
		OLED_ShowHexNum(2,6,Array_Write[1],2);
		OLED_ShowHexNum(2,9,Array_Write[2],2);
		
		OLED_ShowHexNum(3,3,Array_Read[0],2);
		OLED_ShowHexNum(3,6,Array_Read[1],2);
		OLED_ShowHexNum(3,9,Array_Read[2],2);
	}
}

四、实验现象

实验现象很完美,完成了我预期的效果,展示!!!!!
在这里插入图片描述

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值