SPI FLASH(W25Q128BV)工作原理解析及SPI协议详解

目录

 

一、SPI简介

        1、全双工与半双工

         2、同步与异步

        3、SPI通信方式

二、SPI工作模式

三、W25Q128BV

        1、读ID Read Manufacturer/Device ID(90h)         

         2、读ID代码实现(硬件SPI)

         3、IO口模拟SPI时序图实现 (软件SPI)  模式3


一、SPI简介

        SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高速的(10Mbps),全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,如NRF24L01、VS1053、SD卡等。

        1、全双工与半双工

                a:半双工常见于485总线,即下图所示,使用的是差分信号,这样子就决定了是半双工,即任何一个传输时刻,只有一个传输方向,要么是发送,要么是接收。优点在于传输距离远,理论上达到1.2km。

                 d:全双工常见SPI通信,即下图所示,两边可以同时收发数据,优点是速度快,延迟小。

         2、同步与异步

                 a:同步的典型特征:有时钟线,可以控制通信的速度。

                 b:异步的典型特征:约束好通信的波特率,数据帧格式.

        3、SPI通信方式

根据上图我们可以看到,由于SPI是全双工,同步的通信总线,SCLK,MOSI,MISO这三个引脚可以共用于从机,根据片选引脚(/SS)来区别来区别从机(输出低电平的从机有效工作),注意:硬件连接看起来是一对多,一个主机对应多个从机,但是实际上通信只能一对一通信,当一个硬件处于通信状态(输出低电平),其他硬件处于等待状态(输出高电平),不然容易出现数据的冲突

SCLK(Serial Clock):串行时钟线

MOSI(Master Output Slave Input):主机输出数据,从机接收数据  

MISO(Master Input Slave Output):从机输出数据,主机接收数据

/SS(slave select):从机选择,该引脚输出低电平,该引脚有效工作

二、SPI工作模式

SPI总线有四种工作模式,其中使用最为广泛的是模式0和模式3方式。

CPOL(Clock Polarity):时钟极性选择,为0时SPI总线空闲时,时钟线为低电平 ; 为1时SPI总线空闲时,时钟线为高电平。

CPHA(Clock Phase):时钟相位选择,为0时在SCLK第一个跳变沿,主机对MISO引脚电平采样;为1时在SCLK第二个跳变沿,主机对MISO引脚电平采样

通过上面两张图我们可以发现,模式0是从机先发送第一个数据,模式3是主机先发送第一个数据,它们之间有什么区别呢,其实没有什么区别,作用就是做好通信前的准备,约定好数据的采集时机

三、W25Q128BV

        W25Q128BV:内存128MBit/16MType,每个可编程页256字节,支持SPI模式0和模式3。具体可以看相关手册。由于手册繁多且复杂,这边就不多做描述。

关于SPI SLASH具体描述可参考博主:(15条消息) W25Q128数据手册阅读总结_百里之外的博客-CSDN博客https://blog.csdn.net/qq_40993639/article/details/122053404

        1、读ID Read Manufacturer/Device ID(90h)         

 本文我们讲一讲常用的指令中的一种,读ID Read Manufacturer/Device ID(90h),下图是完整的时序图,数据手册是分两个图来看,我这里合成一个图看着更方便一点。关于这个时序图的工作方式在手册有总结,是一段英文解释,我这边用自己的话解释这个时序图的工作原理。

在这个指令传输之前,我们要使/cs(/ss)引脚拉低移位传输指令码90h(1001 0000)对应时钟线(CLK)0-7,每一个时钟周期对应一个Bit(比特),紧接着我们要传输24-BIt(比特)地址(A23-A0),其对应时钟线(CLK)8-31,也就是3个字节地址,完成上诉工作之后,厂商ID和设备ID就会发给我们主机,通信结束后,使/cs(/ss)引脚拉高。

通过读ID往往能够检测到SPI通信是否正常,也能够知道从机设备是否在线。

         2、读ID代码实现(硬件SPI)

编程环境:keil5 STM32f407

 首先初始化w25q128。

GPIO_InitTypeDef  GPIO_InitStructure;
SPI_InitTypeDef  SPI_InitStructure;

void w25q128_init(void)
{

	//使能端口B的硬件时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	//使能SPI1的硬件时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	
	//配置PB3~PB5为复用功能模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;	//指定3、4、5号引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//配置为复用功能模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
	
	
	//将PB3~PB5连接到SPI1的硬件
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);		
	
	
	//配置PB14为输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;	//指定14号引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//配置为推挽功能模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
	
	//PB14初始电平状态为高电平,因为总线空闲的时候,SS引脚(片选引脚)为高电平
	PBout(14)=1;
	
	
	//SPI1参数的配置        //硬件SPI设置  模拟SPI不需要这个则删除掉
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//每次传输最小单元为字节
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//根据从机的手册来配置,模式3
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//根据从机的手册来配置,模式3,第二跳变沿

    //由软件代码控制片选引脚     SPI_NSS_Hard表示由硬件控制引脚
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    
    //波特率  根据从机的手册来配置,SPI的硬件时钟=84MHz/16=5.25MHz
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;

    //根据从机的手册来配置 MSB表示最高有效位传输
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_Init(SPI1, &SPI_InitStructure);
	
	
	//使能SPI1硬件工作
	SPI_Cmd(SPI1,ENABLE);

}

读ID

//官方代码,
uint8_t SPI1_SendByte(uint8_t byte)
{
  /*!< Loop while DR register in not emplty */
  //检测是否发送完毕
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);    

  /*!< Send byte through the SPI1 peripheral */
  //如果发送完,就把数据发送出去
  SPI_I2S_SendData(SPI1, byte);

  /*!< Wait to receive a byte */
  
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);

  /*!< Return the byte read from the SPI bus */
   //对方返回一个字节回来
  return SPI_I2S_ReceiveData(SPI1);
}

void w25q128_read_id(uint8_t *m_id,uint8_t *d_id)
{
	W25Q128_SS=0;

	SPI1_SendByte(0x90);

	SPI1_SendByte(0x00);
	SPI1_SendByte(0x00);
	SPI1_SendByte(0x00);

	//这里参数随便填,从机会忽略引脚电平状态
	*m_id=SPI1_SendByte(0xFF);
	*d_id=SPI1_SendByte(0xFF);

	W25Q128_SS=1;	//拉高结束
}

获取厂商ID和设备ID并打印 

int main()
{
    uint8_t m_id,d_id;
    
    //串口初始化
    usart1_init(115200);
       
    w25q128_init();
    
    w25q128_read_id(m_id,d_id);

    printf("m_id=%X,d_id=%x\r\n",m_id,d_id);

    while(1)
    {
        
    }
}

打印结果 

  从0地址连续读64字节的数据实现        Read Data(03H)

 基本上不变,指令变为03H,后面就是传输连续字节,接收完一个字节,其指向会指向新的地址,每个地址都指向有效数据(会自动偏移)。

//通用写法
void w25q128_read(uint32_t addr,uint8_t *buf,uint32_t len)
{
	uint8_t *p = buf;
	
    //拉低开始
	W25Q128_SS=0;

	SPI1_SendByte(0x03);

	//假如有个地址为0x123456.
	SPI1_SendByte(addr>>16);    //0x12
	SPI1_SendByte(addr>>8);	    //0x34
	SPI1_SendByte(addr);	    //0x56
	
	while(len--)
	{
		*p=SPI1_SendByte(0xFF);
		p++;
	}


    //拉高结束
	W25Q128_SS=1;	
}	

 打印

         3、IO口模拟SPI时序图实现 (软件SPI)  模式3

 初始化模拟SPI w25q128

GPIO_InitTypeDef  GPIO_InitStructure;
SPI_InitTypeDef  SPI_InitStructure;
#define W25Q128_SS		PBout(14)
#define W25Q128_SCLK   	PBout(3)
#define W25Q128_MOSI   	PBout(5)
#define W25Q128_MISO	PBin(4)

void w25q128_init(void)
{

	//使能端口B的硬件时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);	
	
	
	//配置PB3 PB5 PB14为输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_5|GPIO_Pin_14;	//指定3、5、14号引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//配置为输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	

	
	//配置PB4为输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;	//指定4号引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//配置为输入模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
	
	//PB14初始电平状态为高电平,因为总线空闲的时候,SS引脚(片选引脚)为高电平
	W25Q128_SS=1;
	
	//CPOL=1,SCLK引脚在SPI总线空闲的时候为高电平
	W25Q128_SCLK=1;
	
	//MOSI引脚随意电平    可高可低
	W25Q128_MOSI=1;


}

读ID,只不过发送需要自己写

uint8_t SPI1_SendByte(uint8_t byte)
{
	int32_t i=0;
	
	uint8_t d=0;
	
    //一个周期的变化
	for(i=7; i>=0; i--)
	{
	
		if(byte & (1<<i))
			W25Q128_MOSI=1;
		else
			W25Q128_MOSI=0;
	
		
        
		W25Q128_SCLK=0;    //模式3单个周期  SCLK拉低
		delay_us(1);       //延时1微秒
		
		W25Q128_SCLK=1;    //模式3单个周期  SCLK拉高
		delay_us(1);       //延时1微秒
        
        //判断MISO是否是高电平
		if(W25Q128_MISO)
			d|=1<<i;
	
	}
	
	return d;
}


void w25q128_read_id(uint8_t *m_id,uint8_t *d_id)
{
	W25Q128_SS=0;

	SPI1_SendByte(0x90);

	SPI1_SendByte(0x00);
	SPI1_SendByte(0x00);
	SPI1_SendByte(0x00);

	
	*m_id=SPI1_SendByte(0xFF);
	*d_id=SPI1_SendByte(0xFF);

	W25Q128_SS=1;	
}
	

成功打印数据,与硬件SPI打印是一样的 

 本次就分享到这里,如看到我理解错的地方也请指正我,感谢各位的观看❤

物联沃分享整理
物联沃-IOTWORD物联网 » SPI FLASH(W25Q128BV)工作原理解析及SPI协议详解

发表评论