SPI总线协议原理详解及配置方法

单片机应用中,最常用的通信协议主要有三个,即USART、IIC和SPI。关于前两个的介绍在之前文章学习过,这次介绍一下第三个通信协议——SPI。

 

SPI(Serial Peripheral Interface Bus)由摩托罗拉公司开发,它是高速全双工同步串 行通信协议。SPI 支持一主多从,这点类似于 IIC,但是又与 IIC 选通从设备的方式不同, IIC 是通过发送从机地址来选通从机,而 SPI 则是通过拉低连接到从机的 NSS 引脚对从机进行选通的。SPI 一般应用由四个引脚组成(一主一从):

⚫ SCLK(Serial Clock):串行时钟,由主机发出

⚫ MOSI(Master Output,Slave Input):主机输出从机输入信号,由主机发出

⚫ MISO(Master Input,Slave Output):主机输入从机输出信号,由从机发出

⚫ NSS(Slave Selected):选择信号,由主机发出,一般是低电位有效

SPI与IIC主要的区别:

SPI是高速(高达30Mbps)远距离全双工同步串行同行的,IIC是低速(最大4Mbps)近距离半双工同步串行通行的,
■相同点
1、均采用串行,同步的方式
2、均采用TTL电平,传输距离和应用场景类似
3、均采用主从方式工作
■不同点
1、IIC为半双工,SPI为全双工(MOSI,MISO同时收发)
2、IIC有应答机制,SP1无应答机制
3、IC通过向总线广播从机地址来寻址,SP1通过向对应从机发送使能信号来寻址

4、IIC总线读写时序比较固定统一,设备驱动编写方便;而SPI则需要根据不同的设备的datasheet来实现读写,相对要复杂一些。

SPI特点:

1、SPI的寻址方式是主设备向从设备的片选线上发送使能信号,表示选中该从设备,注意如果 在单片机的接口当中只有一个NSS接口,因此是只能够连接一个外部设备的,如果我们要连接多个外设就需要通过利用单片机中的其它IO口来模拟NSS接口。因此在接多个设备的情况下就要占用多个IO口了,这也是SPI通信的一个缺点,这一点就不如IIC的寻址方式。
2、SPI总线在进行数据传输时,先发高位,后发低位(这一点跟USART有区别),而且发完一个字节不用应答。

3、时钟线在上升沿或者下降沿是发送数据,然后在下降沿或者上升沿接收数据

SPI主从连接示意图

         图中可以看出虽然 SPI 也是串行通信协议,但是主机所占用的引脚依然比 IIC 和 UART 的多,而且主机引脚数量会随着从机数量的增加而增加(增加对从机的选通部分)。

        主机在通过 MOSI 数据线发送数据的同时,从机也会通过 MISO 将数据传输给主机(收发 同时进行),它们以虚拟环形拓扑连接。数据通常先移出最高位,在时钟边沿,主机和从机 均移出一位,然后在传输线上输出给对方(改变数据)。在下一个时钟沿,主从设备的接收 器都从传输线接受该位,并设置为移位寄存器的新的最低有效位(采样数据)。在完成这样 一个移出-移入的周期后,主机和从机就交换了寄存器中的一位,传输可能会持续任意数量的时钟周期。传输完成后,主设备会停止时钟信号,并拉高 NSS 选通线。下图是 SPI 通信时序:

 SPI通信时序图

 SPI 是一种非常灵活的通信协议,我们可以配置它的时钟极性、时钟相位、一次传输的 数据位数等。我们依次来看一下:

⚫ 时钟极性(CPOL) 时钟极性用于设置时钟在空闲时的电平状态,CPOL 为 1 则时钟空闲时为高电平,CPOL 为 0,时钟空闲时为低电平,如下图。这就直接导致了第一个信号沿是下降沿还是上升沿, 通常 CPOL 和时钟相位(CPHA)配合使用。

⚫ 时钟相位(CPHA) 时钟相位用于设置在第几个时钟沿发生时对数据线进行采样,当 CPHA=1 时表示在第二 个时钟沿对数据线进行采样,当 CPHA 为 0 时,在第一个时钟沿对数据进行采样,如下图:

 另外在图中可以看到,CPOL 和 CPHA 的组合能够产生 4 中不同的工作模式,以适应不同 的使用场景。具体的模式由具体的芯片手册参考设置,一般建议 CPOL=CPHA=0,CPOL=CPHA=1。

SPI 内部结构框图:

 STM32 的 SPI 外设包含两种通信协议,第一个是 SPI 协议,第二个是 I2S 协议,他们通过 SPI_I2S 配置寄存器(SPI_I2S_CFGR)的 11 位 I2SMOD 进行选择。SPI 接口默认工作在 SPI 方式,可以通过软件把功能从 SPI 模式切换到 I2S 模式。在小容量和中容量产品上,不支持 I2S 音频协议。因为 STM32 将 SPI 和 I2S 合并到了一起,因此我们在使用 SPI 时要先设置该寄存器的 位 11 来指定使用 SPI 功能。 但是这些配置已经在固件库中定义好了,我们直接利用固件库中对应SPI函数库或者I2S函数库即可。

SPI 固件库:

ST 固件库主要使用 SPI_TypeDef 这个结构体类型来管理 SPI 外设。通过配置这个结构体来实现对 SPI 外 设的配置,并调用固件库提供接口函数中的初始化函数,实现将配置参数写入 STM32 寄存器中。SPI 外设的 接口函数如下所示:

配置 SPI 步骤如下:

1. 初始化 SCK、MOSI 引脚为复用推挽输出,初始化 MISO 引脚为浮空输入;

2. 初始化 NSS 引脚为推挽输出并默认拉高(使用软件 NSS 情况下);

3. 配置 SPI 初始化结构体,内容包括:主/从模式,高/低位在前,数据帧位数(8 位/16 位),配置时钟极性(CPOL)、时钟相位(CPHA)、软件 NSS、时钟分频值等。

SPI初始化配置:

void SPI2_Init(void){ //SPI2初始化
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//使能SPI_2时钟

	GPIO_InitStructure.GPIO_Pin = SPI2_MISO;  //SPI2的MISO(PB14)为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_MOSI | SPI2_SCK;	//SPI2的MOSI(PB15)和SCLK(PB13)为复用推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_NSS;	 //SPI2的NSS(PB12)为推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线输入输出全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置为SPI的主机模式(SCK主动产生时钟)
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI数据大小:发送8位帧数据结构;8位还是16位需要根据需要根据具体的芯片手册参考来确定
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//空闲状态时SCK的状态,High为高电平,Low为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位,1表示在SCK的奇数沿边采样,2表示偶数沿边采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS由软件控件片选,只是针对点对点通信的情况下,如果是连接了多个设备,就要使用硬件模式,在通信之前手动选择哪一个设备通信。
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//时钟的预分频值(0~256),保证总线的稳定性,如果总线稳定性不好就可以相对应加大预分频值
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB高位在前
	SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC较验和的多项式,例如CH376芯片没有校验功能
	SPI_Init(SPI2,&SPI_InitStructure); //初始化SPI2的配置项
	SPI_Cmd(SPI2,ENABLE); //使能SPI2  
}

 接收发送函数:

//SPI2数据发+收程序(主要用于发送)
u8 SPI2_SendByte(u8 Byte){ //通过SPI2口发送1个数据,同时接收1个数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == RESET); //如果发送寄存器数据没有发送完,循环等待
	SPI_I2S_SendData(SPI2,Byte);  //往发送寄存器写入要发送的数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //如果接受寄存器没有收到数据,循环
	return SPI_I2S_ReceiveData(SPI2);
}

物联沃分享整理
物联沃-IOTWORD物联网 » SPI总线协议原理详解及配置方法

发表评论