新手必看!!STM32-SPI串行全双工通信协议-SPI驱动W25Q64!


  
  SPI(Serial Peripheral Interface)是一种串行通信协议,通常用于连接微控制器和外部设备,如传感器、存储器、显示屏等。它是一种全双工的通信协议,意味着数据可以在同时的时候在两个方向上传输。

●IIC、UART、SPI的比较:

通信协议UARTIICSPI
通信特征异步串行全双工同步串行半双工同步串行全双工
接口TX、RXSCL、SDAMOSI、MISO、SCL、CS/NSS
速度多种波特率100Khz、400Khz、3.4Mhz由时钟频率与实际功能决定
数据帧格式起始位+数据位+校验位+停止位起始条件+位传输+应答+停止条件四种模式:MODE0~MODE3
主从设备通信没有主从有主从有主从
总线结构一对一一对多一对多

一、SPI通讯设备连接方式

  在通信过程中,主机只能选择一个从机进行通信。将进行通信的从机A的CS/NSS(片选)引脚拉低(低电平),其他从机的CS/NSS(片选)引脚全部拉高(高电平),即可进行与从机A进行通信。

信号线

  • SCK (Serial Clock): 这是时钟信号,它由主设备产生,用于同步数据传输速度。通常SPI设备在上升沿或下降沿读取数据。

  • MISO (Master In Slave Out): 这是从设备向主设备传输数据的线路。

  • MOSI (Master Out Slave In): 这是主设备向从设备传输数据的线路。

  • CS/SS (Chip Select/Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS。每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。

  • 引脚模式配置:
    CS -推挽输出
    SCK- 推挽输出
    MISO -浮空输入
    MOSI -推挽输出

在这里插入图片描述

在这里插入图片描述

二、SPI通讯原理

所有通信(读/写)均有主设备通过控制时钟线产生跳变沿发起。

  1. 由主机拉低对应的CS,找到从机(选中从机通信)。
  2. 主机控制时钟线产生跳变沿(上升沿、下降沿),进行数据读/写。
  3. 主机拉高片选(完成通信,断开从机)。

三、SPI工作原理

(1)硬件上为4根信号线。
(2)主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输
(3)串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
(4)外设的写操作和读操作是同步完成的。
主机只写从机:主机只需忽略接收到的字节;
主机只读从机:若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

四、SPI工作模式

   四种模式:MODE0~MODE3。
  SPI可以工作在不同的模式,主要取决于时钟信号的相位(CPHA)和极性(CPOL)。这两个参数决定了数据的采样时机。实际中采用较多的是“模式 0”与“模式 3”。

  CPHA(Clock Phase,时钟相位):表示SCK在第几个时钟边缘采样数据。当CPHA=0,在SCK周期的第一个边沿采样数据,当CPHA=1,在SCK周期的第二个边沿采样数据。

  CPOL(Clock Polarity,时钟极性):表示SCK在空闲时为高电平还是低电平。当CPOL=0,SCK空闲时为低电平,当CPOL=1,SCK空闲时为高电平。

模式CPOL(SCK)CPHASCK空闲采样点
000低电平数据在周期的第一个电平转换沿处采样(上升沿采样)
101低电平数据在周期的第二个电平转换沿处采样(下降沿采样)
210高电平数据在周期的第一个电平转换沿处采样(下降沿采样)
311高电平数据在周期的第二个电平转换沿处采样(上升沿采样)

●CPHA=0时SPI通讯模式:在这里插入图片描述

●CPHA=1时SPI通讯模式:
在这里插入图片描述

五、SPI通讯时序图

在这里插入图片描述
(1)起始信号: 在上图①处,NSS 信号线由高变低,是 SPI 通讯的起始信号。
NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
(2)数据的有效性: 图中的②③④⑤标号处,MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO为下一次表示数据做准备。
(3)停止信号: 在上图⑥处,NSS 信号由低变高,是 SPI 通讯的停止信号。表示本次通讯结束,从机的选中状态被取消。

六、实验一:IO口模拟SPI驱动W25Q64

使用IO口来模拟出SPI时序信号,即通过拉高拉低IO口电平来实现。
在这里插入图片描述

1. 传输一个字节的数据(发送和接收数据同时进行。sByte:发送数据,rByte:接收数据)

u8 SPI_TransferByte (u8 sByte)
{
	u8 i, rByte;
	for(i =0; i<8;i++)
	{
		SPI_SCK_L;               //下降沿-准备发送数据
		if(sByte &(0x80 >> i))  //高位发起
		    SPI_MOSI_H;
		else 
		    SPI_MOSI_L;
		    
        rByte <<= 1;      //空出最低位,准备接收数据
        SPI_SCK_H;        //上升沿-采集数据
        if(SPI_MISO_R)   //读取 MISO的电平
         rByte |= 1;
   }
         return rByte;
}

2. W25Q64指令时序分析

●统一解答:下文中为什么读数据时,使用SPI_TransferByte(0xFF); 发送0xFF。
答:因为SPI读取数据和接收数据是同步进行的,所以我们想要接收(读取)数据时,必须先发送一个任意数据,这里我们使用0xFF(自定)。SPI_TransferByte()函数的返回值就是我们要读取的数据。

(1) 读状态寄存器1:指令0x05

在这里插入图片描述

u8 w25Q64_Readstatus1(void)
{
	u8 status;
	SPI_CS_L;//拉低片选
	SPI_TransferByte(0x05);//发送指令0x05
	status = SPI_TransferByte(0xFF); //接收状态寄存器的值,发送任意数据都行。
	SPI_CS_H;//拉高片选
	
	return status;
}

(2) 写使能:指令0x06

在这里插入图片描述

void w25Q64writeEnable(void)
{
	SPI_CS_L;//拉低片选
	SPI_TransferByte (0x06);//发送写使能指令0x06
	SPI_CS_H;//拉高片选
}

(2)扇区擦除:指令0x20

在这里插入图片描述

void w25Q64_sectorErase (u32 addr) //扇区擦除
{
	u8 *pAddr = (u8*) &addr; //扇区首地址
	u8 status;
	w25Q64writeEnable(); //写使能
	
	SPI_CS_L;//拉低片选
	SPI_TransferByte (0x20);//发送扇区擦除指令0x20
	
	//发送24bit内部地址
	SPI_TransferByte (pAddr[2]); // bit 23-16
	SPI_TransferByte (pAddr[1]); //bit 15-8
	SPI_TransferByte (pAddr[0]);  //bit 7-0
	
	SPI_CS_H; //拉高片选
	
	//等待擦除完成 BUSY ==0
	do{
        status=w25Q64_Readstatus1();  //状态寄存器值为0时,擦除完成
        delay_ms(1);
     } while(status &(0x1));

}

(3)页写:指令0x20

例如:addr: 0x123456
bit 23-16: 0x12(addr >> 16)& OxFF
bit 15-8:  Ox34(addr >> 8)& OxFF

u8 *pAddr = &addr;
*pAddr==pAddr[0]== 0x56
*(pAddr+1)==pAddr[1]= 0x34
*(pAddr+2) ==DAddr[2]= 0x12

在这里插入图片描述

void w25Q64_Page_Write_Data(u32 addr, u32 num, u8 *sBuf) //首地址、写几个、写什么
{
	u8 *pAddr = (u8 *) &addr;
	u8 status;
	w25Q64_writeEnable(); //写使能
	
	SPI_CS_L;//拉低片选
	SPI_TransferByte (0x02);//发送指令0x03
	
	//发送24bit内部地址
	SPI_TransferByte(pAddr[2]); // bit 23-16
	SPI_TransferByte(pAddr[1]); //bit 15-8
	SPI_TransferByte(pAddr[0]);//bit 7-0
	
	while (num--)
	{
		SPI_TransferByte(*sBuf++);
	}
	SPI_CS_H;//拉高片选
	
	//等待写入完成 BUSY ==0
	do{
        status=w25Q64_Readstatus1();  //状态寄存器值为0时,写入完成
        delay_ms(1);
     } while(status &(0x1));
}

●补充:顺序写

页写不可以跨页写,但是顺序写可以。
在这里插入图片描述

void w25Q64_Orderwrite(u32 addr, u32 num, u8 *sBuf)
{
	u32 remain = 0; 
	remain = 256 - addr % 256; //从本页首地址算出能写下的数量
	if (num <= remain)//判断本页能否写下
	{
	    w25Q64_Page_Write_Data(addr, num, sBuf);//直接写完所有数据
	}
	else   //本页写不完
	{
	    w25Q64_Page_Write_Data(addr, remain, sBuf);//将本页写完
	 }   
	 
    addr +=remain;	//地址偏移
   	sBuf +=remain;	//数据地址偏移
    remain = num - remain;//剩下的需要写入的数据项
    
	//判断剩下的数据一页能否写完  
	while (remain >=256)		//不能写完
	{
		w25Q64_Page_Write_Data(addr, 256, sBuf); //写满一页
		addr +=256;		//地址偏移
		sBuf += 256;   //数据地址偏移
		remain = remain - 256;//剩下的需要写入的数据项
	)
	
	if (remain > 0)    // 0 <num <256剩下的不够写满一页的
	{
	    w25Q64_Page_Write_Data(addr,remain,sBuf);//剩下的写完
	}
}

(4)读ID:指令0x90

在这里插入图片描述
在这里插入图片描述

ul6 w25Q64_ReadID(void)
{
	ul6 ID;
	w25Q64writeEnable();  //写使能
	
	SPI_CS_L; //拉低片选
	SPI_TransferByte (0x90);//发送读ID指令0x90
	SPI_TransferByte (0x00);//发送0x000000
	SPI_TransferByte (0x00);
	SPI_TransferByte (0x00);
	
	ID = SPITransferByte (0xFF);// Manufacturer ID
    ID <<= 8;
    ID |=SPI_TransferByte (0xFF); // Device ID
	
	SPI_CS_H;//拉高片选
	
	return ID;
}

(5)读数据:指令0x30

在这里插入图片描述

void w25Q64_ReadData(u32 addr, u32 num, u8 *rBuf) //首地址、读几个、读到哪
{
	u8 *pAddr = (u8 *) &addr;
	w25Q64_writeEnable(); //写使能
	SPI_CS_L;//拉低片选
	SPI_TransferByte (0x03);//发送指令0x03
	
	//发送24bit内部地址
	SPI_TransferByte(pAddr[2]); // bit 23-16
	SPI_TransferByte(pAddr[1]); //bit 15-8
	SPI_TransferByte(pAddr[0]);//bit 7-0
	
	while (num--)
	{
		*rBuf++ = SPI_TransferByte(OxFF);//接收数据
	}
	SPI_CS_H;//拉高片选
}

七、实验二:SPI控制器驱动W25Q64

  使用SPI控制器来驱动W25Q64就不需要IO口来模拟拉高拉低数据线,而是将IO口复用为SPI控制器功能,直接把要传输的内容写入SPI控制器的DR寄存器中,SPI控制器就会自动模拟出SPI时序信号去发送或接收数据。

1. 框图

在这里插入图片描述
在这里插入图片描述

2. 软件设计步骤

代码部分只需要在实验一的基础上修改下面的内容即可。页写、顺序写等代码不需要改动。

●修改SPI初始化函数
1.修改MOSI、MISO、SCK三个引脚配置为复用功能
2.映射到SPI1
3.配置CR1、CR2
4.使能SPI1

●修改SPI传输字节函数
1.等待发送缓冲区为空2.写 DR-
3.等待接收缓冲区非空4.读DR-

3. 代码设计

●修改SPI初始化函数

void SPI_Init(void)
{
	//打开GPIOA/B时钟
	RCC->AHB1ENRl=(Ox3 <<0);
	//CS -PB14 推挽输出
	GPIOB->MODER&=~(0x3 <<28);
	GPIOB->MODER|=(0x1 <<28);//输出
	GPIOB->OTYPER&= ~(0x1<<14);//推挽
	GPIOB->OSPEEDR|=(0x3 <<28);//100Mhz
	
	
	//SCK -PA5复用功能
	GPIOA->MODER&= ~(0x3<<10);
	GPIOA->MODER|= (0x2<<10);//复用
	GPIOA->OTYPER&= ~(0x1<<5);//推挽
	GPIOA->OSPEEDR =(0x3 <<10);//100Mhz
	GPIOA->AFR[O]&= ~(0xE<<20);//映射
	GPIOA->AFR[0]|= (0x5<<20) ;//映射- AF5
	
	//MOSI -PA7复用功能
	GPIOA->MODER&= ~(0x3 <<14);
	GPIOA->MODER |=(0x2 <<14);//复用
	GPIOA->OTYPER&= ~(0x1<<7);//推挽
	GPIOA->OSPEEDR |=(0x3<<14); //100Mhz
	GPIOA->AFR[0]&= ~(0xEu<<28);//映射
	GPIOA->AFR[0]|=(0x5u <<28) ;//映射- AF5
	
	//MISO -PA6 复用功能
	GPIOA->MODER&= ~(0x3<< 12);
	GPIOA->MODER |=(0x2<< 12); //复用
	GPIOA->PUPDR&= ~(0x3 <<12);//浮空
	GPIOA->AFR[0]&= ~(0xEu<<24);//映射
	GPIOA->AFR[0]|=(0x5u<<24);//映射-AF5
	
	//初始化SPI控制器
	RCC->APB2ENR |= (0x1<<12);//打开SPI1的时钟
	
	//配置CR1
	SPI1->CR1 =0;
	/*
	*选择双线单向通信
	*禁止CRC
	*8bit数据帧★全双工
	*MSB在前
	*scK波特率=fPCLR/2=84Mhz/ 2=42Mhz
	*/
	SPI1->CR1 |=(0x1<< 9);//使能软件从器件管理
	SPI1->CR1 |=(0x1 <<8);//SSI =1
	SPI1->CR1 |=(0x1 << 2);//SPI1配置为主模式
	SPI1->CR1 |=(0x3 << 0) ;//SPI模式为MODE3
	
	//配置CR2
	SPI1->CR2 &=~(0x1<<4);//SPI Motorola模式
	
	//使能SPI1
	SPI1->CR1 |=(0x1<<6);

}



●修改SPI传输字节函数

u8 SPI_TransferByte (u8 sByte)
{
  u8 rByte;
  
  while( ( SPI1->SR&(0x1<<1) )==0 );//等待发送缓冲区为空
  SPI1->DR = sByte;  //写DR
  while( ( SP11->SR &(Ox1 << 0) )==0 );//等待接收缓冲区非空
  rByte = SPI1->DR;  //读DR
  
  return rByte;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值