SPI(Serial Peripheral Interface)总线是主要应用于嵌入式系统内部通信的串行同步传输总线协议。通常为四线制的SPI总线支持全双工通信。SPI最初由Motorola在2000年提出,Motorola所定义的SPI标准为业界广泛引用,但不同半导体公司的实施细节可能有所不同,这些区别体现在寄存器设置、信号定义、数据格式等。业界没有统一的SPI标准,具体应用需要参考特定器件手册。
SPI协议特点包括主从模式、全双工通信、片选功能、模式错误标识及CPU中断、缓冲数据寄存器和可配置时钟相位极性等。SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。
SPI以其简单高效应用于绝大多数SoC系统上,这些SoC通常同时支持作为主模式或从模式(二选一)。
FPGAs和其它专用芯片也广泛使用SPI传输数据。比如:
在高性能系统中,FPGAs通常使用SPI连接主从设备,比如连接外部传感器,和应用SPI加载配置。相比于JTAG,SPI定位用于高速配置(初始化)板上设备;而JTAG的初衷是为控制设备以相对低的准确度扫描和检测板上IO,在严格要求的场合,JTAG协议支持改变时钟占空比以满足建立和保持时间的要求。因此,JTAG并不定位于高速数据传输的场合。
优点:
缺点:
SPI总线定义两个及以上设备间的数据传输,提供时钟的设备为主设备(Master),接收时钟的设备为从设备(Slave)。下图为单个Master与单个Slave的SPI连接:
SPI协议定义四根信号线,分别为:
其中MISO方向为从设备到主设备,其余三个信号均为主设备到从设备。
注意:
通过多个片选信号(SS)或菊花链方式(Daisy Chain Configuration),单个主设备可以同时控制多个不同从设备。
每个从设备都需要单独的片选信号,主设备每次只能选择其中一个从设备进行通信。因为所有从设备的SCK、MOSI、MISO都是连在一起的,未被选中从设备的MISO要表现为高阻状态(Hi-Z)以避免数据传输错误。由于每个设备都需要单独的片选信号,如果需要的片选信号过多,可以使用译码器产生所有的片选信号。
数据信号经过主从设备所有的移位寄存器构成闭环。数据通过主设备发送(绿色线)经过从设备返回(蓝色线)到主设备。在这种方式下,片选和时钟同时接到所有从设备,通常用于移位寄存器和LED驱动器。注意,菊花链方式的主设备需要发送足够长的数据以确保数据送达到所有从设备。切记主设备所发送的第一个数据需(移位)到达菊花链中最后一个从设备。
菊花链式连接常用于仅需主设备发送数据而不需要接收返回数据的场合,如LED驱动器。在这种应用下,主设备MISO可以不连。如果需要接收从设备的返回数据,则需要连接主设备的MISO形成闭环。同样地,切记要发送足够多的接收指令以确保数据(移位)送达主设备。
SPI的工作方式略有不同。它是一个“同步”数据总线,这意味着它使用单独的数据线和“时钟”,使双方保持完美同步。时钟是一个振荡信号,告诉接收器确切地何时采样数据线上的位。这可能是时钟信号的上升沿(低到高)或下降沿(从高到低); 数据表将指定使用哪一个。当接收器检测到该边沿时,它将立即查看数据线以读取下一位(参见下图中的箭头)。由于时钟与数据一起发送,因此指定速度并不重要,尽管设备将具有可以运行的最高速度(我们将讨论选择合适的时钟边沿和速度)。
你可能会觉得,这对于单向通信来说很简单,但是如何以相反的方向发送数据呢?事情变得稍微复杂一些。
在SPI中,只有一侧产生时钟信号(通常称为串行ClocK的CLK或SCK)。生成时钟的一侧称为“Master”,另一侧称为“Slave。总是只有一个主设备(基本上都是微控制器),但是可以有多个从设备。
当数据从Master发送到Slave时,它将在MOSI线上发送,用于“Master输出/Slave输入”。如果Slave需要将响应发送回Master,则Master将继续生成预先安排好了的时钟周期,Slave将数据放入MISO线进行传输,用于“Master输入/Slave输出”。
注意:
我们在上面的描述中说“预先安排好了”。由于Master始终生成时钟信号,因此必须事先知道Slave何时需要返回数据以及将返回多少数据。这与异步串行不同,异步串行是随时可以向任一方向发送随机数据量。在实际应用中,这不是问题,因为SPI通常用于与具有非常特定命令结构的传感器通信。例如,如果我们将“读取数据”命令从Master发送到Slave,那么我们肯定知道Slave会回应我们(根据芯片文档寄存器的操作可知)。
SPI是“全双工”(具有单独的发送和接收线),因此,在某些情况下,可以发送和接收数据在同一时间(例如,请求一个新的传感器读数的同时接收来自上一个传感器的数据)。
SPI通信有4种不同的模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的Master的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
Mode0:CPOL=0,CPHA=0
Mode1:CPOL=0,CPHA=1
Mode2:CPOL=1,CPHA=0
Mode3:CPOL=1,CPHA=1
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA
是用来配置数据采样是在第几个边沿:
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿
例如:
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是
SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是
SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是
SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是
SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
注意:
我们的主设备能够控制时钟,因为我们的SPI通信并不像UART或者IIC通信 那样有专门的通信周期,有专门的通信起始信号,有专门的通信结束信号;所以我们的 SPI协议能够通过控制时钟信号线,当没有数据交流的时候我们的时钟线要么是保持高电平要么是保持低电平。
下面是将SPI协议作为SPI主控位进行位操作的例子,CPOL = 0,CPHA = 0,每次传输8位。该示例使用C编程语言编写。因为这是CPOL = 0,所以必须在激活芯片选择之前将时钟拉低。必须激活芯片选择线,这通常意味着在传输开始之前为外设切换为低电平,然后在之后停用。选择线为低时,大多数外设允许或需要多次传输; 在取消选择芯片之前,可能会多次调用此例程。
/ *
*同时在SPI上发送和接收一个字节。
*
*假设极性和相位均为0,即:
* - 在SCLK的上升沿捕获输入数据。
* - 输出数据在SCLK的下降沿传播。
*
*返回接收的字节。
* /
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;
for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);
/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);
/* Pull the clock line high */
write_SCLK(HIGH);
/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;
/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);
/* Pull the clock line low */
write_SCLK(LOW);
}
return byte_in;
}
读例子:
/*——————————————————————————
* 函 数 名:SPI_Read_Reg
* 输入参数:addr: 寄存器地址
* 输出参数:None
* 返 回 值:寄存器数据
* 功能说明:读计量参数 下降沿发送和读取数据, 先发送8位地址,再读取24位数据
*——————————————————————————*/
uint32_t SPI_Read_Reg(uint8_t addr)
{
uint8_t i;
uint32_t temp = 0;
addr &= 0x7F;
HIGH_CS();
SPI_Delay();
LOW_CS();
SPI_Delay();
SPI_Delay();
SPI_Delay();
LOW_CLK();
//SPI_Delay();
//LOW_CS();
for (i=0; i<8; i++)
{
SPI_Delay();
HIGH_CLK();
if (addr&0x80)
{
HIGH_DIN();
}
else
{
LOW_DIN();
}
addr <<= 1;
LOW_CLK();
}
SPI_Delay();
SPI_Delay();
SPI_Delay();
SPI_Delay();
SPI_Delay();
SPI_Delay();
for (i=0; i<24; i++)
{
SPI_Delay();
temp <<= 1;
HIGH_CLK();
SPI_Delay();
if (PIN_DOUT)
{
temp |= 0x01;
}
LOW_CLK();
}
SPI_Delay();
HIGH_CS();
SPI_Delay();
HIGH_CLK();
return (temp);
}
写例子:
/*——————————————————————————
* 函 数 名:SPI_Write_Reg
* 输入参数:addr: 寄存器地址
* temp: 寄存器数据
* 输出参数:None
* 返 回 值:None
* 功能说明:写寄存器参数 下降沿发送 先发送8位地址数据,再发送24位数据位数据
*——————————————————————————*/
void SPI_Write_Reg(uint8_t addr, uint32_t temp)
{
uint8_t i;
addr |= 0x80;
HIGH_CS();
SPI_Delay();
LOW_CS();
SPI_Delay();
SPI_Delay();
SPI_Delay();
LOW_CS();
for (i=0; i<8; i++)
{
SPI_Delay();
HIGH_CLK();
if (addr&0x80)
{
HIGH_DIN();
}
else
{
LOW_DIN();
}
addr <<= 1;
LOW_CLK();
}
for (i=0; i<24; i++)
{
SPI_Delay();
HIGH_CLK();
SPI_Delay();
if (temp&0x800000)
{
HIGH_DIN();
}
else
{
LOW_DIN();
}
temp <<= 1;
LOW_CLK();
}
SPI_Delay();
HIGH_CS();
SPI_Delay();
HIGH_CLK();
}
【硬件通信协议】1. IIC通信协议
参考链接:
http://www.wangdali.net/spi/
https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi
https://en.wikipedia.org/wiki/Serial_Peripheral_Interface