**这是本文档旧的修订版!**
数字通信协议:I2C 和 SPI
探索这两种流行的通信协议并使用它们在 LCD 上显示数据
到目前为止,在本书中,我们已经研究了如何使用一些常见的硬件,但是随着您自己构建更多项目,您可能希望扩展到使用各种不同的传感器、执行器、和显示。你将如何与这些沟通?有时您可能会发现有一个 MicroPython 库可供您使用,其中有人已经将低级函数转换为易于使用的界面。然而,情况并非总是如此。 幸运的是,有几种在 MicroPython 中实现的将低级数字设备连接在一起的标准方法:内部集成电路 (I2C) 和串行外设接口 (SPI)。在许多方面,它们非常相似,因为它们都定义了一种在两个设备之间连接双向接口的方式。事实上,许多部件都有带有任一界面的版本,因此您可以选择适合您项目的版本。在这两种情况下,都有一个控制通信的设备(你的 Pico)和一个(或多个)等待来自主设备的指令。但是,存在一些差异。我们现在将研究这两种协议,然后我们将帮助您为每个项目选择合适的协议。
电压水平 Pico 的 GPIO 引脚工作电压为 3.3 伏。 对它们施加更高的电压可能会损坏它们。 幸运的是,这是一个常见的工作电压,您遇到的大部分设备都可以在 3.3 伏电压下工作。 但是,在将一些新硬件插入 Pico 之前,请务必仔细检查它是否为 3.3 V 设备,因为 I2C 和 SPI 设备有时都可以在 5 V 下运行,这会损坏您的 Pico。
I2C
I2C 通信发生在两条线上:时钟(通常标记为 SCL)和数据通道(通常标记为 SDA)。 这些必须连接到 Pico 上的特定引脚。有几个选择;查看选项的引脚图(图 10-1)。有两条 I2C 总线(I2C0 和 I2C1),您可以使用其中一个或两者。在我们的示例中,我们将使用 I2C0——GP0 用于 SDA,GP1 用于 SCL。 为了演示这些协议,我们将使用 SparkFun 的 SerLCD 模块。这样做的好处是它同时具有I2C和SPI接口,因此我们可以在相同的硬件下看到两种方法之间的差异。 此 LCD 可以显示两行,每行最多 16 个字符。它是输出有关我们系统的一些信息的有用设备。让我们来看看如何使用它。 接线 I2C 只是将 Pico 上的 SDA 引脚与 LCD 上的 SDA 引脚连接起来的情况,SCL 也是如此。由于 I2C 处理通信的方式,还需要一个电阻器将 SDA 连接到 3.3 V,将 SCL 连接到 3.3 V。通常这些电阻约为 4.7 kΩ。但是,对于我们的设备,这些电阻器已经包含在内,因此我们不需要添加任何额外的电阻器。 连接好后(参见图 10-2),在屏幕上显示信息非常简单: import machine sda=machine.Pin(0) scl=machine.Pin(1) i2c=machine.I2C(0,sda=sda, scl=scl, freq=400000) i2c.writeto(114, '\x7C') i2c.writeto(114, '\x2D') i2c.writeto(114, “hello world”) 这段代码的作用不大。 它连接到 I2C 设备并发送一些数据。 但是,有些位可能看起来有点不寻常。 i2c.writeto() 行中的 114 指的是 I2C 设备的地址。 您可以将许多设备连接到 I2C 总线(稍后会详细介绍),并且每次要发送或接收数据时,都需要指定要与之通信的设备的地址。 这个地址是硬连线到设备中的(尽管你可以通过在 PCB 上切割走线或焊接一个 blob 来改变它——有关详细信息,请参阅设备的文档)。 您应该在文档中找到您设备的地址,但您可以扫描 I2C 总线以查看当前正在使用的地址。 设置好I2C总线后,可以运行scan方法输出当前使用的地址: import machine sda=machine.Pin(0) scl=machine.Pin(1) i2c=machine.I2C(0,sda=sda, scl=scl, freq=400000) print(i2c.scan()) 接下来可能看起来有点奇怪的位是写入的 \x7C 和 \x2D 命令。 每个 I2C 设备都需要以特定格式发送数据。 对此没有标准,因此您必须参考要设置的任何 I2C 设备的文档。 其中每个开头的 \x 告诉 MicroPython 我们正在发送一个十六进制字符串(请参阅“十六进制”框),这是确保您发送的确切数据的常用方法。 对于我们的 LCD,7C 进入命令模式,2D 使 LCD 变黑并将光标设置在开头。 在此之后,我们可以发送显示在屏幕上的数据: file.close()
十六进制 十六进制是基数为 16 的编号系统。 这意味着有 16 位数字:0–F。 所以,十进制数 10 是 A 十六进制。 十进制的 17 是十六进制的 11。 这样做的好处是每个字节正好是两位数。 这使它成为一种紧凑但仍然可以理解的数字信息书写方式。 在处理将指令作为数字值的设备(例如我们的 LCD)时,您会经常遇到它。 如果您感到困惑,可以使用在线十六进制转十进制转换器在两者之间进行切换。 例如:hsmag.cc/hextodec。
当然,只显示 Hello World 的屏幕并没有多大用处,所以让我们来看看把它变成更有用的东西——温度计。 在第 8 章中,您学习了如何使用 ADC 使用 Pico 的内部温度传感器读取温度。 我们现在可以在此代码的基础上构建一个独立的温度计,它不需要计算机来读取输出。 在 LCD 仍像以前一样连接的情况下,运行以下代码: import machine import utime sda=machine.Pin(0) scl=machine.Pin(1) i2c=machine.I2C(0,sda=sda, scl=scl, freq=400000) adc = machine.ADC(4) conversionfactor = 3.3 / (65535) while True: reading = adc.readu16() * conversionfactor temperature = 25 - (reading - 0.706)/0.001721 i2c.writeto(114, '\x7C') i2c.writeto(114, '\x2D') outstring = “Temp: ” + str(temperature) i2c.writeto(114, out_string) utime.sleep(2) 这应该看起来很熟悉。 与之前的温度码唯一的细微变化是之前我们输出了我们的计算结果——一个数字——但是LCD需要字符来显示,所以我们使用了str函数将数字转换为字符串。 然后,我们可以通过将其与“Temp:”结合起来,将其构建为信息量稍多的输出。 如您所见,I2C 是一种将额外硬件连接到 Pico 的简单方法。 您需要确保为您想要连接的任何设备提供适当的文档,让您知道哪些命令可以做什么,但只要您知道这一点,您就可以轻松地将各种零碎添加到您的 Pico 并创建 令人印象深刻的建筑。
串行外设接口
我们已经了解了 I2C 的工作原理,现在让我们来看看 SPI。我们将使用完全相同的 LCD,因此命令和其他所有内容都相同,只是我们发送数据的协议不同。 SPI 有四个连接:SCLK、MOSI、MISO 和 CS(有时标记为 SS)。 SCLK 是时钟,MOSI 是将数据从 Pico 传输到外围设备的线路(请参阅“SPI 术语”框),而 MISO 则是将数据从外围设备传输到 Pico。 CS 代表芯片选择,用于将许多设备连接到单个 SPI 总线。您只需为 CS 线供电即可启用 SPI 外设并将其拉低以禁用它。稍微混淆一下,这个特定的设备没有 CS,但 /CS 代表 NOT CS - 换句话说,它与 CS 相反,所以你把它调低以启用 LCD 并调高以禁用它。您可以将 CS 连接到 GPIO 引脚并打开和关闭它以启用和禁用显示,但由于我们只有一个设备,我们可以简单地将其接地以保持启用状态(图 10-3)。 因此,将 SerLCD 的电源线连接到 VBUS 和 GND,我们只需要将其 SDO 连接到 Pico 的 MISO (GP4 / SPI0 RX)、SDI 到 MOSI (GP3 / SPI0 TX)、SCK 到 SCLK (GP2 / SPI0 SCK),和 /CS 到 GND。
SPI术语 SPI 需要四个连接:一个从主设备到从设备获取数据,另一个从相反方向获取数据,加上电源和地。 两条数据线意味着数据可以同时双向传输。 这些通常称为主出从入 (MOSI) 和主入从出 (MISO)。 但是,您会遇到它们具有不同的名称。 如果您查看 Raspberry Pi Pico 引脚分配(附录 B),它们被称为 SPI TX(发送)和 SPI RX(接收)。 这是因为 Pico 可以是 一个主设备或从设备,所以这些连接是 MOSI 还是 MISO 取决于 Pico 的当前功能。 在我们使用的 LCD 上,它们标有 SDI(串行数据输入)和 SDO(串行数据输出)。
SPI 中没有地址,因此我们可以深入编写代码: import machine spisck=machine.Pin(2) spitx=machine.Pin(3) spirx=machine.Pin(4) spi=machine.SPI(0,baudrate=100000,sck=spisck, mosi=spitx, miso=spirx) spi.write('\x7C') spi.write('\x2D') spi.write(“hello world”) 在这种情况下,我们使用 SPI0,一组可用的引脚是 GP2、GP3 和 GP4。 大多数类型的串行通信都有速度或波特率,这基本上是多少 它每秒可以通过通道推送的数据位。 很多事情都会对此产生影响,例如连接的两个设备的功能以及它们之间的接线(多长时间? 以及是否有其他设备的干扰)。 如果您发现损坏的数据存在问题,那么您可能需要减少它。 对于我们的小屏幕,我们只是为每个字符发送一个字节的数据,所以发送速度有多快并不重要,但对于其他一些 SPI 设备(例如基于像素的显示器),微调波特率 率可能很重要。 让我们来看看这如何留下我们的温度计代码: import machine import utime spisck=machine.Pin(2) spitx=machine.Pin(3) spirx=machine.Pin(4) spi=machine.SPI(0,baudrate=100000,sck=spisck, mosi=spitx, miso=spirx) adc = machine.ADC(4) conversionfactor = 3.3 / (65535) while True: reading = adc.readu16() * conversionfactor temperature = 25 - (reading - 0.706)/0.001721 spi.write('\x7C') spi.write('\x2D') outstring = “Temp: ” + str(temperature) spi.write(out_string) utime.sleep(2)
如您所见,I2C 和 SPI 之间的代码差异非常小。设置好所有内容后,唯一真正的变化是,使用 I2C 时,您必须在发送数据时指定地址,而使用 SPI 时则不需要(但请记住,如果您连接了多个设备,您需要指定地址)需要切换 CS GPIO 以选择适当的设备)。 那么,如果它们如此相似,您在构建项目时应该选择哪种协议?有几个因素需要考虑。首先是您要附加的东西的可用性。有时传感器只能用作 I2C 或 SPI,因此您必须使用它。然而,如果你有选择 在硬件方面,当您使用多个额外设备时,影响最大。使用 I2C,您最多可以将 128 个设备连接到单个 I2C 总线;但是,它们都需要有一个单独的地址。这些地址是硬接线的。有时可以通过可焊接(或可切割)连接来更改地址,但有时则不然。如果您想拥有多个相同类型的传感器(例如,如果您在项目的多个点监测温度),您可能会受到传感器 I2C 地址数量的限制。在这种情况下,SPI 可能是更好的选择。 或者,SPI 可以连接无限数量的设备;但是,每个人都必须有自己的 CS 线。在 Pico 上,有 26 个 GPIO 引脚。您需要其中三个用于 SPI 总线,这意味着有 23 个可用于 CS 线。这是假设您不需要任何其他东西。如果可用的 GPIO 非常珍贵,那么您可能需要查看 I2C。 实际上,对于许多项目,您可以很高兴地使用任一协议,并且您可能会发现选择使用哪个协议更多地与您在零件盒中找到的零件有关,而不是两者之间的技术差异。
比特敲打 您的 Pico 有两条硬件 I2C 总线和两条硬件 SPI 总线。 但是,如果您愿意,您可以使用更多。 I2C 和 SPI 都可以用软件而不是硬件来实现。 这意味着主处理核心处理通信协议,而不是微控制器的专用位。 这是众所周知的 作为'位砰砰'。 虽然它很有用,但与使用专用硬件相比,它会给您的处理器内核带来更多压力,并且您可能会发现无法达到高波特率。 Pico 有一个技巧——PIO。 我们将在本书后面(附录 C)更仔细地研究这一点,但它是微控制器中的额外硬件,可以专用于 I2C 和 SPI 等输入/输出协议。 使用 PIO,您可以创建额外的 I2C 或 SPI 总线,而不会增加主处理器内核的负担。