SPI通信协议详解
一、SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种高速、全双工、同步通信总线,所以可以在同一时间发送和接收数据。
SPI是一种事实标准,并没有一个官方标准,已知已有的器件SPI 速率可达到50Mbps,具体到产品中SPI的速率主要看主从器件SPI控制器的性能限制。此外, SPI没有相应的流控和应答机制,这样跟IC协议相比在数据可靠性上有一定的缺陷。
SPI还有升级的类型 DSPI(Dual SPI)和 QSPI(Quad SPI),这两种接口拥有更快的通讯速度,代价分别是失去全双工特性和增加额外通讯线。本篇文章暂不对其进行介绍。
二、SPI物理协议层
1、SPI信号线详解
SPI协议是主从通信,在物理接线上很简单,在双向传输时,至少需要4根线,单向传输时3根线,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)和CS/SS(片选):
-
MISO( Master Input Slave Output):主设备数据输入,从设备数据输出;
-
MOSI(Master Output Slave Input):主设备数据输出,从设备数据输入;
-
SCLK(Serial Clock):时钟信号,由主设备产生;
-
CS/SS(Chip Select/Slave Select):从设备使能信号,低电平使能。
在通信时,主机要拉低要通信的从设备的CS/SS引脚,只有这样,主芯片对此从芯片的操作才有效。
2、SPI通信主从器件连接方式
2.1 单从机连接
2.2 多从机连接
通过主机的GPIO口来控制主机与哪一个从机通信。
三、SPI通信时序
SPI的通讯时序中NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。 MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO各传输一位数据(SPI协议中数据读写是同步进行的)。
1、SPI的四种工作模式
在了解SPI的四种工作模式之前,我们需要先了解两个概念,这个在SPI通信协议中是非常重要的,因为时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据。
时钟极性:
根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOL。CKP或CPOL指的是SCLK总线在不进行通讯时的电平。CKP或CPOL可以配置为1或0。这意味着你可以根据需要将时钟的默认状态(IDLE)设置为高或低。
- CKP = 0:时钟空闲IDLE为低电平 0;
- CKP = 1:时钟空闲IDLE为高电平1;
时钟相位
根据硬件制造商的不同,时钟相位通常写为CKE或CPHA。CPHA指在一个时钟周期中采样是发生在第一个边缘还是第二个边缘;
- CKE = 0:在时钟信号SCK的第一个跳变沿采样;
- CKE = 1:在时钟信号SCK的第二个跳变沿采样;
起始和终止信号—NSS
NSS 信号线由高变低,是 SPI 通讯的起始信号。 NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。 NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
1.1 模式0(CPOL=0, CPHA=0)
MODE0:CPOL=0, CPHA=0:当空闲态时,SCK处于低电平,数据采样是在第1个边沿,也就是SCK由低电平到高电平的跳变, MOSI 及 MISO 的数据在 SCK 的下降沿期间变化输出,在 SCK 的上升沿时被采样。即在 SCK 的上升沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。
1.2 模式1 (CPOL=0, CPHA=1)
MODE1:CPOL=0, CPHA=1:当空闲态时,SCK处于低电平,数据发送是在第2个边沿,也就是SCK由低电平到高电平的跳变, MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。
1.3 模式2(CPOP=1, CPHA=0)
MODE2:CPOP=1, CPHA=0:当空闲态时,SCK处于高电平,数据采集是在第1个边沿,也就是SCK由高电平到低电平的跳变,MOSI 及MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。
1.4 模式3(CPOP=1, CPHA=1)
MODE3:CPOP=1, CPHA=1:当空闲态时,SCK处于高电平,数据发送是在第2个边沿,也就是SCK由高电平到低电平的跳变,MOSI 及MISO 的数据在 SCK 的下降沿期间变化输出,在 SCK 的上升沿时被采样。即在 SCK 的上升沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。
四、SPI软件模拟驱动(C语言版)
- 源文件代码
#include "spi.h"
/*
*********************************************************************************************************
* SPI四种工作模式介绍:
* Mode0:CPOL = 0, CPHA = 0 , SCLK 空闲是为低电平,数据在上升沿有效
* Mode1: CPOL = 0,CPHA = 1 , SCLK 空闲是为低电平,数据在下降沿有效
* Mode2: CPOL = 1,CPHA = 0 , SCLK 空闲是为高电平,数据在下降沿有效
* Mode3: CPOL = 1, CPHA = 1 , SCLK 空闲是为高电平,数据在上升沿有效
*********************************************************************************************************
*/
static void spi_delay_us(char tim)
{
smr_delay_us(tim);
}
/* CPOL = 0, CPHA = 0, MSB first, SCLK 空闲是为低电平,数据在上升沿有效 */
uint8_t smr_spr_rw_mode0( uint8_t write_dat )
{
uint8_t i, read_dat=0;
SCK_L;
for( i = 0; i < 8; i++ )
{
if( write_dat & 0x80 )
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay_us(1);
SCK_H;
read_dat <<= 1;
if( MISO )
read_dat++;
spi_delay_us(1);
SCK_L;
}
return read_dat;
}
/* CPOL=0,CPHA=1, MSB first, SCLK 空闲是为低电平,数据在下降沿有效 */
uint8_t smr_spr_rw_mode1(uint8_t byte)
{
uint8_t i,Temp=0;
SCK_L;
for(i=0;i<8;i++) // 循环8次
{
SCK_H; //拉高时钟
if(byte&0x80)
{
MOSI_H; //若最到位为高,则输出高
}
else
{
MOSI_L; //若最到位为低,则输出低
}
byte <<= 1; // 低一位移位到最高位
spi_delay_us(1);
SCK_L; //拉低时钟
Temp <<= 1; //数据左移
if(MISO)
Temp++; //若从从机接收到高电平,数据自加一
spi_delay_us(1);
}
return (Temp); //返回数据
}
/* CPOL=1,CPHA=0, MSB first, SCLK 空闲是为高电平,数据在下降沿有效 */
uint8_t smr_spr_rw_mode2(uint8_t byte)
{
uint8_t i,Temp=0;
SCK_H;
for(i=0;i<8;i++) // 循环8次
{
if(byte&0x80)
{
MOSI_H; //若最到位为高,则输出高
}
else
{
MOSI_L; //若最到位为低,则输出低
}
byte <<= 1; // 低一位移位到最高位
spi_delay_us(1);
SCK_L; //拉低时钟
Temp <<= 1; //数据左移
if(MISO)
Temp++; //若从从机接收到高电平,数据自加一
spi_delay_us(1);
SCK_H; //拉高时钟
}
return (Temp); //返回数据
}
/* CPOL = 1, CPHA = 1, MSB first, SCLK 空闲是为高电平,数据在上升沿有效 */
uint8_t smr_spr_rw_mode3( uint8_t write_dat )
{
uint8_t i, read_dat=0;
SCK_H;
for( i = 0; i < 8; i++ )
{
SCK_L;
if( write_dat & 0x80 )
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay_us(1);
SCK_H;
read_dat <<= 1;
if( MISO )
read_dat++;
spi_delay_us(1);
}
return read_dat;
}
- 头文件代码
#ifndef _SPI_H_
#define _SPI_H_
//引脚配置 根据使用的平台自行配置
#define MOSI_H 1
#define MOSI_L 0
#define MISO 0
#define SCK_H 1
#define SCK_L 0
uint8_t smr_spr_rw_mode0( uint8_t write_dat );
uint8_t smr_spr_rw_mode1( uint8_t byte );
uint8_t smr_spr_rw_mode2( uint8_t byte );
uint8_t smr_spr_rw_mode3( uint8_t write_dat );
#endif
文章编写参考以下几篇文章: