手把手教你如何实现SPI读写W25Q64

目录

前言

一、从0开始讲清SPI通信协议

(1)简介

(2)通信本质

(3)通信时序 

1.起始条件

2.终止条件 

3.交换字节

4.完整时序

二、W25Q64

(1)简介

(2)引脚功能图

(3)内部空间框图 

三、 软件模拟SPI

(1)通信层 

1.通信引脚配置

2.SPI起始条件 和 终止条件

3.SPI写入一个字节

(2)硬件驱动层

(3)主函数 

总结

前言

本文为作者对SPI通信协议的知识总结以及简单应用,通过软件翻转GPIO电平模拟SPI实现读写W25Q64。


提示:如有错误请尽情指出,希望对广大读者有所帮助。

一、从0开始讲清SPI通信协议

(1)简介

SPI是一种通信速率极高的全双工同步通信协议,它是一主多从的模式。

共有四根及以上根信号线,分别是SCK时钟线、MOSI主机输出从机线(Master Output Slave Input)、MISO主机输入线(Master Input Slave Output)和SS片选信号线。

有多少个从机,则配置多少根SS信号线,空闲状态时,SS信号线默认处于高电平。当需要与某一从机进行通信时,拉低相对应的SS信号线即可。由此可知,SPI通信的起始条件和终止条件分别是拉低SS线和拉高SS线。

SPI的输出引脚(MOSI、SS、SCK)应配置为推挽输出,从而使得高低电平驱动变化十分迅速,达到更高的传输速度。

当有多个从机时,从机输出也有多个,但主机输入只有一个,所以当从机的SS引脚为高电平时,也就是从机未被选中时,它的MISO引脚必须为高阻态,相当于引脚断开,不输出任何电平,这样就可以防止一条线有多个输出,而导致电平冲突的问题了。

(2)通信本质

 SPI的通信本质就是主机和从机之间通过移位寄存器交换字节数据。

主机的移位寄存器会将一个字节的8位通过MOSI逐次放在从机的MOSI上。

同理,从机的移位寄存器也会将一个字节的8位通过MISO逐次放在主机的MISO上,由此即可完成数据的交换。

当我们只需要一个设备的数据时,那么我们可以只对一方的数据进行操作,舍弃另一方的数据即可。

(3)通信时序 

SPI的通信时序主要由三大部分组成,分别是起始条件、交换字节、终止条件。 

1.起始条件

SS从高电平切换到低电平

2.终止条件 

 SS从低电平切换到高电平

3.交换字节

SPI的交换字节时序共有四种模式,它们之间的区别在于不同电平时期,移入/移出数据。

由于模式0最常用,所以本文只对模式0进行讲解。

 在模式0中,空闲状态时,SCK默认为低电平。

在SCK由高电平切换为低电平时,主机和从机会移出数据,在由低电平切换为高电平时,主机和从机会移入数据,也就是读取数据,如下图所示。

4.完整时序

在从机的芯片手册中,一般会有相对应的指令集,其中的每个指令所对应的数据称为指令码。

SPI的通信时序一般为起始条件+发送指令码+需要进行交换的数据+终止条件。下文会通过读写W25Q64存储芯片来帮助读者进一步理解。

二、W25Q64

(1)简介

W25Q64是一款24位的非易失性存储器,也就是掉电不丢失,它有8M的存储空间。单片机可以外挂该芯片来拓展其存储空间。

(2)引脚功能图

(3)内部空间框图 

如下图所示,整个芯片的存储空间为8M字节,从0x00 00 00 ~ 0x7F FF FF,将其分割成128块储存区域,每块区域为64Kb,再对块进行分割成若干扇区,扇区再分割为很多页。对W25Q64芯片的读写操作是基于页的。每页的大小为256字节。

8M  >>  块  >>  扇区  >>  页

  •  每页的存储空间为256字节,连续写入数据时,最多只能写入一页的数据,若超过256字节,超过的部分则会回到页首部分,对原数据进行覆盖。
  • 写入操作前,必须先发送写使能指令
  • 写入数据前必须先擦除,擦除后,所有数据位变为1。数据位只能从1变为0,而不能从0变为1。
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作。
  • 读取操作没有页数的限制,可以连续读取。

 

三、 软件模拟SPI

程序整体框架分为通信层、硬件驱动层、主函数

(1)通信层 

  1.通信引脚配置

SS片选信号线默认高电平, SCK时钟线默认低电平。

/* SS片选信号线 */
void MySPI_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

/* SCK时钟线 */
void MySPI_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}

/* MOSI主机输出线 */
void MySPI_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}

/* MISO主机输入线 */
uint8_t MySPI_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

/* 通信引脚初始化 */
void MySPI_Init(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	MySPI_SS(1);
	MySPI_SCK(0);
}

2.SPI起始条件 和 终止条件

/* 通信开始信号 */
void MySPI_Start(void)
{
	MySPI_SS(0);
}

/* 通信停止信号 */
void MySPI_Stop(void)
{
	MySPI_SS(1);
}

3.SPI写入一个字节

/* 写入一个字节 */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;
	for(i=0;i<8;i++)
	{
		MySPI_MOSI(ByteSend & 0x80 >> i);//从高到低依次移出数据的每一位
		MySPI_SCK(1);//上升沿读取数据
		if(MySPI_MISO() == 1)
		{
			ByteReceive |= (0x80 >> i);
		}
		MySPI_SCK(0);//下降沿移出数据
	}
	return ByteReceive;
}

(2)硬件驱动层

W25Q64.c

/* W25Q64初始化 */
void W25Q64_Init(void)
{
	MySPI_Init();
}

/* W25Q64写使能函数 */
void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

/* W25Q64等待不忙函数 */
void W25Q64_WaitBusy(void)
{
	uint32_t TimeOut = 100000;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		TimeOut--;
		if(TimeOut == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

/* W25Q64页编程函数 */
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArry,uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	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(DataArry[i]);
	}
	
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/* W25Q64扇擦除函数 */
void W25Q64_Sector_erasure(uint32_t Address)
{
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >>= 16);
	MySPI_SwapByte(Address >>= 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/* W25Q64读取数据函数 */
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArry,uint32_t Count)
{
	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++)
	{
		DataArry[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	
	MySPI_Stop();
}

写入字节函数的参数为芯片手册中的指令码 如下所示

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

 芯片手册原图

(3)主函数 

 以下主函数为在0x000000内存处写入数据0x10、0x20、0x30

读写操作流程为:

  1. 初始化引脚状态
  2. 擦除对应扇区
  3. 在对应页写入数据
  4. 在对应页读取数据
uint8_t WriteArry[] = {0x10,0x20,0x30};//写入数组
uint8_t ReadArry[3];//读取数组
int main(void)
{
	OLED_Init();
	
	W25Q64_Init();
	
	W25Q64_Sector_erasure(0x000000);

	W25Q64_PageProgram(0x000000,WriteArry,3);
	
	W25Q64_ReadData(0x000000,ReadArry,3);
	
	OLED_ShowHexNum(0,0,ReadArry[0],2,OLED_8X16);
	OLED_ShowHexNum(32,0,ReadArry[1],2,OLED_8X16);
	OLED_ShowHexNum(64,0,ReadArry[2],2,OLED_8X16);
	
	OLED_Update();
	while (1)
	{
		
	}
}

实验现象:

 


总结

相比于硬件SPI,软件SPI的引脚会更加灵活,但响应速度会比硬件SPI慢。可根据实际应用需求,自由选择软件或硬件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值