简介
串行外围设备接口 (SPI) 是一种接口总线,常用于微控制器与小型外围设备之间,使他们以串行方式通信。这些外围设备如位移寄存器、 传感器和SD卡等。 它使用单独的时钟、数据线以及从机通信选择线。
建议阅读
在继续阅读本教程前,建议你阅读以下文章:
普通串行端口有什么问题?
一般只含RX(接收)线和TX(传送)线的串行端口,我们会称它作异步串口。因为该类串口无法很精确地保证数据在通讯两端实现同步传收。这是由于计算机系统的一切操作都由标准时钟源信号驱动(计算机的晶振源),则如果两个系统的标准时间系统由稍微偏差,它们之间的通讯就会出问题。
为了解决这个问题,异步串口通讯系统在每一帧数据里额外增加了起始位和结束位,以保证接收端对每一帧的数据完整接收。另外,在通信两端,都必须设同样的通讯速率(如9600波特率)。起始位和结束位的设定下,保证了即使帧与帧之间的时间间隔有稍微偏差,也能实现正常通讯。
(另外,如上图所示,所发送的“11001010” 实际上不等于0x53,因为在串口通讯中一般会首先从数据的最低位开始发送,因此最左端的实际是数据的最低位。所以低半字节实际是0011 = 0x3,高半字节是0101 = 0x5。)
异步通讯纵然好,但它在每帧数据的起始位与结束位上浪费太多信息空间,并且对通讯端的硬件要求较高。另外,当你发现在你的项目中,通讯端之间的传输速率不一,那么传输的数据必然会严重失真。这是因为异步通信里,接收端只能在特定的时间点里对数据线进行采样(如上图虚线处)。否则,接收端会采集到错误的数据。
一个同步的解决方案
SPI 的工作原理有一点不同。它是一种“同步”的数据总线系统,这意味着它额外的增加了一个通讯线共享时钟源,以实现传输同步。这时钟源是一种震荡信号,用来告知接收端对数据线的正确采样时间点。这有可能是时钟信号的上升沿(电平由低到高)或下降沿(电平由高到低);这可以在硬件的数据手册查询。接收端一检测到时钟线的电平变化,就会对数据线进行采样一,以获得一位新的数据(如下图虚线处)。由于每一位数对应着据时钟越变信号,数据就不需要用很精准的速率传输,不过传输端还是会以最高速传输。(我们接着会讨论选择适合的时钟沿以及传输每一位数据的速度。)
SPI这么受欢迎的原因之一是它对接收端的硬件要求不高,一个位移寄存器就能实现接收。它比使用UART(全双工异步串口通信端)的硬件更简单和便宜。
如何接收数据
你可能会想,这听起来这么棒的通信系统是怎样把数据发送到另一端的呢?这原理就稍微有点复杂。
在SPI,只有一个端口发生时钟源信号(时钟源端一般简写为CLK、SCK),发送时间源信号的硬件叫主机,另一端则叫从机。在一个SPI通信系统里,只有一个主机(一般是MCU),从机则会有多个。
当数据从主机传到从机时,该数据传输线称MOSI(“Master Out / Slave In”),从机传发送数据到主机由另外一条数据线负责,叫MISO(“Master In / Slave Out”),以上两个过程中,主机产生时钟脉冲信号,对应每一位的数据传输,从机则按照时钟脉冲信号,把每一位数据按次序存储或输出。
由于主机一直操控时钟线,因此它会预先知道从机将会返回多少位数据。以上特点与异步串口通信很不一样,它的接收端接收来自发送端的数据时间上是相对随机的。在SPI通讯中,信息是以特殊的数据格式和协议来传输。例如,MCU对传感器发出“读数据”的指令,传感器一般会按通信协议返回两位字节数据。(如果,你想要一个完整原始的数据,你可以先让传感器先返回一或两个字节声明该原始数据的长度,然后再传输原始数据。)
注意SPI是“全双工”总线系统(拥有分别独立的发送线和接收线)因此,在实际情况下,你可以同时实现数据的发送和接收(例如,实现发送要求对一个传感器读数据的命令,同时接收另外一个传感器回传的数据)。硬件的数据手册会告诉你这是可行的。
从机通讯选择 (SS)
这是SPI总线系统里最后一个你需要关注的线,它叫SS(Slave Select)。这根线是用来激活主机所需通讯的从机。
SS在空闲时为高电平,即断开从机与主机之间的SPI通讯。(这种类型的逻辑被称为“低电平有效”,您经常会看到它用于使能和复位。)在数据传输之前,要拉低所需通讯从机的SS端。当你不与该从机通讯,则将它的SS端拉高。这对应于移位寄存器里的锁存端。
多从机工作
要是有两个或以上从机连在同一根SPI总线系统上:
- 一般来说每一个从机都要独立分配一个SS线。当要与指定的从机通讯时,你将对应的SS端拉低,其它从机的则保持高电平(你不会想同时让两个从机被激活,否则这两个从机就会同时在MISO端与主机通讯,造成数据的相互干扰失真)。每一个从机对应一条独立的SS线。如果你的主机输出口不够,可以用译码器扩展。
- 在另一方面,若是要环形传输数据,一个从机的MISO连到下一个从机的MOSI。则要把所有的从机激活通讯。只要数据传输完成,就要把对应的SS端拉高,以防多个从机同时激活。这类结构经常用在位移寄存器和可寻址LED硬件里。
注意在这设计里,当数据流从一个从机到另一个,至到任意一个,你将需要传输足够多的数据来推动这个数据流。同样,要注意你传出去的第一分数据是留给最后一个从机的。
这类型的设计通常用在单一输出的情况下,就如LED模块,工作时它并不会返回任何的数据。这样情况你便可以省去MISO端。另外,要是有数据返回,你可以选择环形总线结构来返回数据(上图蓝线所示)如果你选择这类结构。当你接收端来自从机1的数据时,则说明它的数据已经流过所有的从机。所以,为了接收到你所需的数据,你要传输足够多的接收命令。
SPI的程序
许多的MCU已经内置了SPI的硬设,来应对所需高速传输数据的所有要求。而且,对你们来说,按SPI的通讯协议配置相应的I/O口去传输数据是十分简单的。(一个很好的例子在维基百科的SPI。)
如果你想在一个Arduino实现SPI与其他硬件通讯,这有两种方法:
- 你可以用shiftIn()和shiftOut() 指令。这是基于软件的指令,你可以用任何的引脚作为输出,但这样传输速度会比较慢。
- 或者你可以用SPI Library,是集成到MCU的SPI模块。它比指令方法快多了,但它只能按特定的引脚输出。
在通讯之前,你要设置一些相关选项。这些设置必须要和你所通讯的外设相匹配;查阅它们的资料手册,看通讯时需要什么配置要求。
- 在通讯时可以先发送最高有效位(MSB)或最低有效位(LSB)。在Arduino的SPI库里,这由 setBitOrder() 函数控制。
- 从机会在时钟脉冲信号上升沿或下降沿读取数据。另外,要注意时钟线在空闲时时高电平还是低电平。在Arduino的SPI库里,这由 setDataMode() 函数控制。
- SPI可以高速传输数据(几兆每秒),这对于一些外设来说太快了。为了适应这些设备,你可以调节传输速度。在Arduino的SPI库里,这由setClockDivider() 函数来实现,它会将主机的输出时钟频率(对于大多Ardunio是16MHz)分频(8MHz (/2)到125kHz (/128)之间。
- 如果你用SPI库,你只能用它所指定提供的SCK, MOSI and MISO 引脚。另外,还有一条指定的SS端(对于SPI硬件模块来说,必须至少有一根SS端),不过你也要分配其他的端口作SS端,分别控制每一个从机。
- 在旧版的Arduino,你需要额外写程序控制相应SS端,让其在通讯前拉低,在通讯结束时拉高。在新版的Ardunio,如Due 可以自动控制每一个SS端,详情见 Due SPI documentation page。
更多相关资源
提示和技巧由于
- 由于SPI传输的是高速信号,它只能进行短距离通讯(仅几英尺)。如果你要远距离通讯,则要降低时钟脉冲频率,并加专用的驱动芯片。
- 如果SPI的传输情况不符合你的预期设想,用逻辑分析仪去排误是一个不错的选择。智能分析仪如Saleae USB Logic Analyzer,可以译码并显示和记录所测量的数据。
SPI的优点:
- 它比异步串口通讯要快
- 就收器的硬件要求较低,如移位寄存器
- 支持多从机通讯
SPI的缺点:
- 相比其他通讯方案,它需要的信号线较多
- 要制定通讯协议(你不可以随意发一堆数据)
- 只能有主机控制所有的通讯(从机之间不能直接通讯)
- 它需要额外分配SS线对应每一个从机,如果众多从机,这将是个大问题。
扩展阅读
查阅 SPI的维基百科, 那有很多关于SPI的信息以及一些同步串口通讯的资料。
这篇博客 展现了更多关于基于嵌入式硬件建立SPI通讯的正确方法,示例是用 Arduino
。
许多SparkFunA的产品支持SPI通讯。如 Bar Graph Breakout kit
。
其他通讯方案:
现在你是SPI的支持者之一了,以下有一些其他教程来练习你的新技能:
原始文章采用CC BY-SA 4.0,您可以自由地:
- 分享 — 在任何媒介以任何形式复制、发行本作品
- 演绎 — 修改、转换或以本作品为基础进行创作
- 在任何用途下,甚至商业目的。
- 只要你遵守许可协议条款,许可人就无法收回你的这些权利。
本文由翻译美国开源硬件厂商Sparkfun(火花快乐)的相关教程翻译,原始教程采用同样的CC BY-SA 4.0协议,为便于理解和方便读者学习使用,部分内容为适应国内使用场景稍有删改或整合,这些行为都是协议允许并鼓励的。
原始文章及相关素材链接:
https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi?_ga=1.36860256.1133118399.1488205103