考虑 SPI 或 I2C 时的权衡?

电器工程 spi i2c
2022-01-24 22:25:45

在决定使用 SPI 或 I2C 接口时,我应该考虑哪些权衡?

该加速度计/陀螺仪分线板有两种型号,每个接口一种。哪一个更容易集成到 Arduino 项目中?

http://www.sparkfun.com/products/11028

在此处输入图像描述

4个回答

概括

  • SPI 更快。
  • 如果您的微控制器没有 I2C 控制器,I2C 会更复杂且不易使用。
  • I2C 只需要 2 条线。

I2C 是在 SDA 线上具有双向数据的总线系统。SPI 是点对点连接,数据输入和数据输出在不同的线路(MOSI 和 MISO)上。

SPI本质上由一对移位寄存器组成,您可以将数据输入一个移位寄存器,同时将数据输出另一个移位寄存器。通常数据以字节为单位写入,每次连续有 8 个时钟脉冲,但这不是 SPI 要求。如果您愿意,您还可以拥有 16 位甚至 13 位的字长。而在 I2C 中,同步是由 SPI 中的启动序列完成的,它是由 SS 变为高电平(SS 为低电平有效)完成的。您在这是多少个时钟脉冲后自己决定。如果您使用 13 位字,则 SS 将在 13 个时钟脉冲后锁存最后输入的位。
由于双向数据位于两条单独的线路上,因此易于连接。

标准模式下的SPI至少需要四根线:SCLK(串行时钟)、MOSI(Master Out Slave In)、MISO(Master In Slave Out)和SS(Slave Select)。在 bieroctional 模式下至少需要三条线:SCLK(串行时钟)、MIMO(主输入主输出),它是 MOSI 或 MISO 线之一和 SS(从选择)。在具有多个从站的系统中,每个从站都需要一条 SS 线,因此对于个从站,您在标准模式下线,在双向模式如果您不想这样,在标准模式下,您可以通过将一个从属设备的 MOSI 信号连接到下一个从属设备的 MISO 以菊花链式连接从属设备。这将减慢通信速度,因为您必须循环浏览所有从属数据。NN+3N+2

就像 tcrosley 所说,SPI 可以在比 I2C 高得多的频率下运行。

I2C有点复杂。由于它是总线,因此您需要一种寻址设备的方法。您的通信从一个独特的启动序列开始:当时钟 (SCL) 为高时,数据线 (SDA) 被拉低,因为其余的通信数据仅在时钟为低时才允许更改。此启动序列同步每个通信。
由于通信包括寻址,因此对于任意数量的设备(最多 127 个)只需要两条线。

编辑
很明显,数据线是双向的,但值得注意的是,时钟线也是如此。从机可以延长时钟以控制总线速度。这使得 I2C 不太方便进行电平转换或缓冲。(标准模式下的 SPI 线都是单向的。)

在发送每个字节(地址或数据)后,接收器必须通过在 SDA 上放置一个确认脉冲来确认接收。如果您的微控制器具有 I2C 接口,这将自动得到处理。如果您的微控制器不支持它,您仍然可以对其进行 bit-bang,但是对于每个确认或读取数据,您必须将 I/O 引脚从输出切换到输入,除非您使用 I/O 引脚进行读取和一个用于写作。

在 400kHz 时,标准 I2C 比 SPI 慢得多。有高速 I2C 设备以 1MHz 运行,仍然比 20MHz SPI 慢得多。

(编辑:需要明确的是,正如 Olin 正确指出的那样,以下许多问题与 I2C/SPI 设备的板对板使用引起的信号完整性有关。)

除非您有强烈要求减少电线的限制(我们有一个带有密封连接器的项目,每个额外的触点都相当昂贵),否则尽可能避免 I2C,并坚持使用 SPI。

SPI 在硬件和软件基础上相当容易处理。在硬件上,有两条共享数据线,Master In Slave Out(MISO 或 SOMI)和 Master Out Slave In(MOSI 或 SIMO),一个由 master 生成的共享时钟,每个设备一个片选。CS 线变为低电平,时钟周期基本上移入输入位并移出输出位,直到事务完成,此时 CS 线变为高电平。当它们的 CS 线为高电平时,从设备不进行通信:它们忽略 CLK 和 MOSI 线,并将其 MISO 引脚置于高阻抗状态以让其他人使用它。

如果您有一个使用多个 SPI 设备的微控制器,并且它有一个内置的 SPI 外设,则将微控制器的 CS 输出发送到多路分配器(例如 74HC138)并控制地址线以在 SPI 事务之间选择设备;您将字写入寄存器以将它们排队等待输出,并在 CS 引脚拉高后将它们读回。

由于 SPI 信号都是单向的,它们可以被缓冲,通过数字隔离器的隔离栅使用,并且可以使用 LVDS 等线路驱动器从板到板发送。您唯一需要担心的是往返传播延迟,这将限制您的最大频率。


I2C 是一个完全不同的故事。虽然从布线的角度来看它要简单得多,只有两条线 SCL 和 SDA,这两条线都是共享的双向线,它们使用带有外部上拉的开漏器件。I2C 有一个协议,它首先传输设备地址,因此如果每个设备都有自己的地址,则可以使用多个设备。

从硬件的角度来看,在有任何显着噪声的系统中使用 I2C 是非常困难的。为了缓冲或隔离 I2C 线路,您必须使用外来 IC — 是的,它们存在,但数量不多:我们使用了一对一项目,并意识到您可以使用一个隔离器,但您不能使用两个串联——它使用小的压降来确定哪一侧是事物的驱动端,两个串联的压降是两个多。

I2C 的逻辑电平阈值取决于 Vcc,因此如果在同一系统中使用 3V/3.3V 和 5V 设备,则必须非常小心。

任何使用超过一英尺或两英尺电缆的信号都必须担心电缆电容。100pf/meter 的电容对于多芯电缆来说并不少见。这导致您必须降低总线速度,或使用较低的上拉电阻,才能正确处理额外电容并满足上升时间要求。

因此,假设您有一个您认为自己设计得很好的系统,并且您可以处理大多数信号完整性问题,并且噪声很少(但仍然存在)。你有什么需要担心的?

您必须准备好处理许多错误情况:

  • 从设备不确认特定字节。您必须检测到这一点并停止并重新启动通信序列。(使用 SPI,如果您想确保接收到的数据没有错误,通常可以读回您发送的数据。)

  • 您正在从从设备读取一个字节的数据,并且由于时钟线上的噪声,该设备被“催眠”:您已经发送了必要的 8 个时钟来读取该字节,但是由于噪声,从设备认为它已经接收了 7 个时钟,并且还在数据线上传输一个 0。如果设备接收到第 8 个时钟,它将释放数据线高电平,以便主机可以升高或降低数据线以发送 ACK 或 NACK 位,或者主机可以发送停止 (P) 条件。但从机仍将数据线拉低,徒劳地等待另一个时钟。如果主机不准备尝试额外的时钟,I2C 总线将陷入死锁。虽然我使用了几个处理正常 ACK/NACK 条件的微控制器,

  • 真正可怕的情况是,当主设备正在向一个从设备写入数据时,另一个从设备错误地解释了设备地址并认为传输的数据是为它准备的。我们的 I2C 设备(I/O 扩展器)偶尔会因此而错误地设置寄存器。几乎不可能检测到这种情况,并且为了对噪声具有鲁棒性,您必须定期设置所有寄存器,以便如果您确实遇到此错误,至少它会在短时间内修复。(SPI 从来没有这个问题——如果你碰巧在 CS 线上有一个小故障,它永远不会持续很长时间,你也不会得到错误的从设备意外读取的数据。)

如果有错误检测(CRC 码),协议中可以正确处理许多这些条件,但很少有设备具有此功能。


我发现我必须在我的 I2C 主设备中构建复杂的软件来处理这些情况。在我看来,除非接线限制迫使我们使用 I2C 而不是 SPI,否则这是不值得的。

SparkFun 设备的分线板实际上仅适用于 I2C 版本(MPU-6500)。MPU-6000 版本在同一芯片上同时具有 SPI 和 I2C 接口,我没有看到 SparkFun 有带有该芯片的板。所以我相信如果你想使用那个特定的板,你只能使用 I2C。但是出于以下原因,我还是建议在您的情况下使用 I2C。

一般来说,您会发现从硬件的角度来看,I2C 总线比 SPI 总线更易于使用。I2C 是 2 线总线 (SCL/SDA):

SCL – Serial clock.
SDA – Serial data (bidirectional).

SPI 是 4 线总线 (SCLK/MOSI/MISO/CS):

SCLK– Serial clock.
MOSI – Master-out, Slave-in. Data from the CPU to the peripheral.
MISO – Master-in, Slave out. Data from the peripheral back to the CPU.
CS – Chip select.

您可以将多个设备连接到一条 I2C 总线。每个设备都有自己的一组内置于芯片的地址。该地址实际上是作为每个命令的第一个字节(以及一个读/写位)在总线上广播的。这与其他一些开销一起,需要通过 I2C 总线而不是 SPI 发送更多位以实现相同的功能。

不同类别的设备(内存、I/O、LCD 等)具有不同的地址范围。一些在系统中经常使用多次的设备(例如 PCF8574 I/O 扩展器)使用一条或多条地址线(PCF8574 为 AD0-2),这些地址线可以连接高或低以指定低位地址的。MPU-6500 有一条这样的地址线(AD0),因此可以在同一系统中使用其中两条。

您还可以在 SPI 总线上拥有多个设备,但每个设备必须有自己的片选 (CS) 线。因此,4 线描述有点用词不当——它实际上是一个 3 线接口 + 每个设备一根额外的线。我对 Arduino 系列板没有经验,但我相信这会使在 Arduino 上使用 SPI 变得更加困难,因为如果您需要大量芯片选择线,这将开始变得繁琐,因为各种屏蔽使用的常见引脚分配.

我相信大多数 Arduino 板的运行电压为 5 伏,一些较新的板运行在 3.3 伏。MPU-6500 在 3.3v 下运行。如果 5v CPU 上的 I2C 总线的最小输入“高”电压为 3v 或更低,您可以通过在 SCL 和 SDA 线上提供 10K 上拉电阻到 3.3v 来避免电平转换问题,因为总线是开路的集电极。确保禁用 CPU 上的任何 5v 内部上拉。

但是我检查了 ATmega2560 的数据表(以 ADK 5v Arduino 为例),它的最小输入“高”电压为 0.7*Vcc,或 3.5v,大于 3.3v。所以你需要某种活动电平TI PCA9306需要在芯片的 5v 和 3.3v 两侧都使用上拉电阻,单件成本仅为 78 美分。

那为什么还要选择 SPI 而不是 I2C?主要是因为 SPI 可以运行得更快——在某些情况下可以达到几十兆赫兹。I2C 通常限制在 400 KHz。但这对于 MPU-6050/6000 加速度计来说并不是真正的问题,因为它的 I2C 运行频率为 400 KHz,SPI 运行频率仅为 1 MHz——差别不大。

一般来说,SPI 是一种更快的总线——时钟频率可以在 MHz 范围内。但是,SPI 需要至少 3 条线进行双向通信,并且为总线上的每个设备提供一个额外的从设备选择。

I2C 只需要 2 条线,无论您有多少设备(当然在限制范围内)。然而,速度在 kHz 范围内(典型值为 100-400kHz)。

如今,大多数微控制器都为两种总线提供硬件支持,因此两者都同样易于使用。