本文为对于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
和时钟相位CPHA
,CPOL
和CPHA
共同决定数据的实际采样时刻。
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协议
序列周邊介面- 维基百科,自由的百科全书