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慢。可根据实际应用需求,自由选择软件或硬件。

    作者:专打逆风橘

    物联沃分享整理
    物联沃-IOTWORD物联网 » SPI读写W25Q64实现教程:一步步操作指南

    发表回复