0%

SPI协议简笔

本文为对于SPI协议的简要整理。

协议概述

串行外设接口(Serial Peripheral Interface Bus,SPI),是一种用于芯片通信的同步串行通信接口规范,主要应用于单片机系统中。类似IIC。 这种接口首先由摩托罗拉公司于20世纪80年代中期开发,后发展成了行业规范。

SPI设备之间使用全双工模式通信,是一个主机一个或多个从机的主从模式。主机负责初始化帧,这个数据传输帧可以用于读与写两种操作,片选线路可以从多个从机选择一个来响应主机的请求。

SPI是一种事实标准,也就是说这种规范没有对应的技术标准。因此各个厂家生产的SPI器件配置不一样,不一定有互操作性。

协议解析

物理层

SPI协议默认为标准四线制:

SCLK:串行时钟信号,由主机产生,决定了SPI的通信速率;

MOSI:主机数据输出,从机数据输入;

MISO:主机数据输入,从机数据输出;

SS:片选信号,由主机控制,用于选中SPI从机,低电平有效。

协议层

传输概况

  • 主机先将时钟信号拉低,表示开始数据传送;
  • 当接收端检测到时钟边沿信号时,立即读取数据线上的电平信号,得到1位数据;
  • 主机发送数据到从机:主机产生相应的时钟信号,将数据从MOSI逐位(SPI协议并无明确规定是高位在先还是低位在先,需要主从双方约定)发送到从机;
  • 主机接收从机数据:主机产生相应的时钟信号,从机从MISO将数据发送到主机;
  • 数据的采集时机可能是时钟信号的上升沿或下降沿,具体由工作模式来决定。

时钟极性&时钟相位

在进行通信前,SPI主设备需要配置时钟极性CPOL和时钟相位CPHACPOLCPHA共同决定数据的实际采样时刻。

  • CPOL=0:时钟的默认(空闲)状态为低电平;
  • CPOL=1:时钟的默认(空闲)状态为高电平;
  • CPHA=0:在时钟信号的第一个跳变沿采样;
  • CPHA=1:在时钟信号的第二个跳变沿采样。

综上所述,所有时钟配置组合如下(黑色:数据的采样时刻,蓝色:时钟信号):

工作模式

SPI优点&缺点

优点

  • 全双工串行通信;
  • 数据传输速率高;
  • 软件配置简单;
  • 数据传输灵活,数据帧可以是任意大小的字;
  • 硬件结构简单,从机不需要唯一地址,从机使用主机时钟,不需要收发器。

缺点

  • 没有应答信号;
  • 仅支持一个主机;
  • 需要更多引脚;
  • 没有定义硬件级别的错误检查协议;
  • 相较之下,传输距离短。

举个栗子

两片AT89C51均通过软件模拟SPI的方式建立通信,主机循环发送数值0~9,从机将接收到的数值显示在数码管上且将该数值返回给主机,主机也将从机返回的数值显示在数码管上。

原理图

主机源码

#include<reg51.h>
#include<intrins.h>

sbit SCLK=P1^0;
sbit MOSI=P1^1;
sbit MISO=P1^2;
sbit SS=P1^3;

void Delay_ms() //延时一段时间
{
unsigned char i,j,k;
_nop_();
i=4;
j=129;
k=119;
do
{
do
{
while(--k);
}
while(--j);
}
while(--i);
}

void Delay1ms()
{
unsigned char i,j;
_nop_();
i=2;
j=199;
do
{
while(--j);
}
while(--i);
}

void SPI_M_Write(unsigned char dat) //主机发送数据
{
unsigned char i;
SS=0;
for(i=0;i<8;i++)
{
if(dat&0x80)
{
MOSI=1;
}
else
{
MOSI=0;
}
dat<<=1;
SCLK=1;
_nop_();
_nop_();
SCLK=0;
_nop_();
_nop_();
}
SS=1;
Delay1ms();
}

unsigned char SPI_M_Read() //主机接收数据
{
unsigned char i;
unsigned char dat=0;
SS=0;
for(i=0;i<8;i++)
{
dat<<=1;
dat|=MISO;
SCLK=1;
_nop_();
_nop_();
SCLK=0;
_nop_();
_nop_();
}
SS=1;
return dat;
}

void main()
{
unsigned char LEDS[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned char i,temp;
SCLK=0;
P2=0x00;
while(1)
{
for(i=0;i<10;i++)
{
SPI_M_Write(i);
Delay_ms();
temp=SPI_M_Read();
P2=LEDS[temp];
Delay_ms();
}
}
}

从机源码

#include<reg51.h>
#include<intrins.h>

sbit SCLK=P1^0;
sbit MOSI=P1^1;
sbit MISO=P1^2;
sbit SS=P1^3;

void Delay_ms() //延时一段时间
{
unsigned char i,j,k;
_nop_();
i=4;
j=129;
k=119;
do
{
do
{
while(--k);
}
while(--j);
}
while(--i);
}

void SPI_S_Write(unsigned char dat) //从机发送数据
{
unsigned char i;
while(SS==1);
for(i=0;i<8;i++)
{
while(SCLK==0);
while(SCLK==1);
dat<<=1;
if(dat&0x80)
{
MISO=1;
}
else
{
MISO=0;
}
}
while(SS==0);
}

unsigned char SPI_S_Read() //从机接收数据
{
unsigned char i;
unsigned char dat=0;
while(SS==1);
for(i=0;i<8;i++)
{
while(SCLK==0);
while(SCLK==1);
dat<<=1;
dat|=MOSI;
}
while(SS==0);
return dat;
}

void main()
{
unsigned char LEDS[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned char i,temp;
P2=0x00;
while(1)
{
for(i=0;i<10;i++)
{
temp=SPI_S_Read();
P2=LEDS[temp];
Delay_ms();
SPI_S_Write(i);
Delay_ms();
}
}
}

参考资料

SPI协议详解(图文并茂+超详细)

SPI通讯协议介绍

单片机外设篇——SPI协议

序列周邊介面- 维基百科,自由的百科全书