差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
mp_serial_bus [2021/10/05 00:06]
gongyusu [数字通信协议:I2C 和 SPI]
mp_serial_bus [2021/10/05 17:35] (当前版本)
gongyusu [2. 串行外设接口SPI]
行 12: 行 12:
 </​WRAP>​ </​WRAP>​
  
-### I2C +### 1. I2C 
-I2C 通信发生在两条线上:时钟(通常标记为 SCL)和数据通道(通常标记为 SDA)。 +I2C通信发生在两条线上:时钟(通常标记为 SCL)和数据通道(通常标记为 SDA)。 
-这些必须连接到 Pico 上的特定引脚。有几个选择;查看选项的引脚图(图 10-1)。有两条 I2C 总线(I2C0 和 I2C1),您可以使用其中一个或两者。在我们的示例中,我们将使用 I2C0——GP0 用于 SDA,GP1 用于 SCL。 +这些必须连接到Pico上的特定引脚。有几个选择;查看选项的引脚图(图 10-1)。有两条I2C总线(I2C0 和 I2C1),您可以使用其中一个或两者。在我们的示例中,我们将使用I2C0——GP0用于 SDA,GP1用于SCL。 
-为了演示这些协议,我们将使用 ​SparkFun 的 SerLCD ​模块。这样做的好处是它同时具有I2C和SPI接口,因此我们可以在相同的硬件下看到两种方法之间的差异。 + 
-此 LCD 可以显示两行,每行最多 16 个字符。它是输出有关我们系统的一些信息的有用设备。让我们来看看如何使用它。 +为了演示这些协议,我们将使用SSD1306 串行OLED模块。这样做的好处是它同时具有I2C和SPI接口,因此我们可以在相同的硬件下看到两种方法之间的差异。 
-接线 I2C 只是将 Pico 上的 SDA 引脚与 LCD 上的 SDA 引脚连接起来的情况,SCL 也是如此。由于 I2C 处理通信的方式,还需要一个电阻器将 SDA 连接到 3.3 V,将 SCL 连接到 3.3 V。通常这些电阻约为 4.7 kΩ。但是,对于我们的设备,这些电阻器已经包含在内,因此我们不需要添加任何额外的电阻器。+ 
 +此LCD可以显示两行,每行最多16个字符。它是输出有关我们系统的一些信息的有用设备。让我们来看看如何使用它 
 + 
 +接线I2C只是将Pico上的SDA引脚与LCD上的SDA引脚连接起来的情况,SCL也是如此。由于I2C处理通信的方式,还需要一个电阻器将SDA连接到 3.3 V,将SCL连接到3.3V。通常这些电阻约为4.7kΩ。但是,对于我们的设备,这些电阻器已经包含在内,因此我们不需要添加任何额外的电阻器。
 连接好后(参见图 10-2),在屏幕上显示信息非常简单: 连接好后(参见图 10-2),在屏幕上显示信息非常简单:
 <code python> <code python>
行 23: 行 26:
 sda=machine.Pin(0) sda=machine.Pin(0)
 scl=machine.Pin(1) scl=machine.Pin(1)
-i2c=machine.I2C(0,​sda=sda,​ scl=scl, freq=400000) i2c.writeto(114,​ '​\x7C'​) +i2c=machine.I2C(0,​sda=sda,​ scl=scl, freq=400000) ​ 
-i2c.writeto(114,​ '​\x2D'​) i2c.writeto(114,​ "hello world"​)+i2c.writeto(114,​ '​\x7C'​) 
 +i2c.writeto(114,​ '​\x2D'​) ​ 
 +i2c.writeto(114,​ "hello world"​)
 </​code>​ </​code>​
-这段代码的作用不大。 它连接到 I2C 设备并发送一些数据。 但是,有些位可能看起来有点不寻常。 + 
-i2c.writeto() 行中的 114 指的是 I2C 设备的地址。 您可以将许多设备连接到 I2C 总线(稍后会详细介绍),并且每次要发送或接收数据时,都需要指定要与之通信的设备的地址。 这个地址是硬连线到设备中的(尽管你可以通过在 PCB 上切割走线或焊接一个 blob 来改变它——有关详细信息,请参阅设备的文档)。 +这段代码的作用不大。 它连接到I2C设备并发送一些数据。 但是,有些位可能看起来有点不寻常。 
-您应该在文档中找到您设备的地址,但您可以扫描 I2C 总线以查看当前正在使用的地址。 设置好I2C总线后,可以运行scan方法输出当前使用的地址:+i2c.writeto()行中的114指的是I2C设备的地址。 您可以将许多设备连接到I2C总线(稍后会详细介绍),并且每次要发送或接收数据时,都需要指定要与之通信的设备的地址。这个地址是硬连线到设备中的(尽管你可以通过在PCB上切割走线或焊接一个 blob 来改变它——有关详细信息,请参阅设备的文档)。 
 + 
 +您应该在文档中找到您设备的地址,但您可以扫描I2C总线以查看当前正在使用的地址。设置好I2C总线后,可以运行scan方法输出当前使用的地址:
 <code python> <code python>
 import machine import machine
 sda=machine.Pin(0) sda=machine.Pin(0)
 scl=machine.Pin(1) scl=machine.Pin(1)
-i2c=machine.I2C(0,​sda=sda,​ scl=scl, freq=400000) print(i2c.scan())+i2c=machine.I2C(0,​sda=sda,​ scl=scl, freq=400000) ​ 
 +print(i2c.scan())
 </​code>​ </​code>​
-接下来可能看起来有点奇怪的位是写入的 \x7C 和 \x2D 命令。 每个 I2C 设备都需要以特定格式发送数据。 对此没有标准,因此您必须参考要设置的任何 I2C 设备的文档。 其中每个开头的 \x 告诉 MicroPython 我们正在发送一个十六进制字符串(请参阅“十六进制”框),这是确保您发送的确切数据的常用方法。 对于我们的 LCD,7C 进入命令模式,2D 使 LCD 变黑并将光标设置在开头。 在此之后我们可以发送显示在屏幕上的数据:+接下来可能看起来有点奇怪的位是写入的 \x7C 和 \x2D 命令。 每个I2C设备都需要以特定格式发送数据。 对此没有标准,因此您必须参考要设置的任何I2C设备的文档。 其中每个开头的\x 告诉 MicroPython我们正在发送一个十六进制字符串(请参阅“十六进制”框),这是确保您发送的确切数据的常用方法。 对于我们的LCD,7C 进入命令模式,2D使LCD变黑并将光标设置在开头。 在此之后我们可以发送显示在屏幕上的数据: 
 <code python> <code python>
 file.close() file.close()
 </​code>​ </​code>​
 +
 <WRAP center round tip 60%> <WRAP center round tip 60%>
 **十六进制** **十六进制**
  
-十六进制是基数为 16 的编号系统。 这意味着有 16 位数字:0–F。 所以,十进制数 10 是 A +十六进制是基数为16的编号系统。 这意味着有16位数字:0–F。 所以,十进制数10是A 
-十六进制。 十进制的 17 是十六进制的 11。 这样做的好处是每个字节正好是两位数。 这使它成为一种紧凑但仍然可以理解的数字信息书写方式。 在处理将指令作为数字值的设备(例如我们的 LCD)时,您会经常遇到它。+十六进制。 十进制的17是十六进制的11。这样做的好处是每个字节正好是两位数。这使它成为一种紧凑但仍然可以理解的数字信息书写方式。在处理将指令作为数字值的设备(例如我们的LCD)时,您会经常遇到它。 
 如果您感到困惑,可以使用在线十六进制转十进制转换器在两者之间进行切换。 例如:hsmag.cc/​hextodec。 如果您感到困惑,可以使用在线十六进制转十进制转换器在两者之间进行切换。 例如:hsmag.cc/​hextodec。
 </​WRAP>​ </​WRAP>​
  
-当然,只显示 Hello World 的屏幕并没有多大用处,所以让我们来看看把它变成更有用的东西——温度计。 在第 8 章中,您学习了如何使用 ADC 使用 Pico 的内部温度传感器读取温度。 我们现在可以在此代码的基础上构建一个独立的温度计,它不需要计算机来读取输出。 在 LCD 仍像以前一样连接的情况下,运行以下代码:+当然,只显示Hello World的屏幕并没有多大用处,所以让我们来看看把它变成更有用的东西——温度计。 在第8章中,您学习了如何使用ADC使用Pico的内部温度传感器读取温度。 我们现在可以在此代码的基础上构建一个独立的温度计,它不需要计算机来读取输出。 在LCD仍像以前一样连接的情况下,运行以下代码:
 <code python> <code python>
 import machine ​ import machine ​
行 66: 行 77:
  
 这应该看起来很熟悉。 与之前的温度码唯一的细微变化是之前我们输出了我们的计算结果——一个数字——但是LCD需要字符来显示,所以我们使用了str函数将数字转换为字符串。 然后,我们可以通过将其与“Temp:​”结合起来,将其构建为信息量稍多的输出。 这应该看起来很熟悉。 与之前的温度码唯一的细微变化是之前我们输出了我们的计算结果——一个数字——但是LCD需要字符来显示,所以我们使用了str函数将数字转换为字符串。 然后,我们可以通过将其与“Temp:​”结合起来,将其构建为信息量稍多的输出。
-如您所见,I2C 是一种将额外硬件连接到 Pico 的简单方法。 您需要确保为您想要连接的任何设备提供适当的文档,让您知道哪些命令可以做什么,但只要您知道这一点,您就可以轻松地将各种零碎添加到您的 Pico 并创建 令人印象深刻的建筑。+如您所见,I2C是一种将额外硬件连接到Pico的简单方法。您需要确保为您想要连接的任何设备提供适当的文档,让您知道哪些命令可以做什么,但只要您知道这一点,您就可以轻松地将各种零碎添加到您的 Pico并创建令人印象深刻的建筑。
  
-### 串行外设接口 +### 2. 串行外设接口SPI 
-我们已经了解了 I2C 的工作原理,现在让我们来看看 SPI。我们将使用完全相同的 LCD,因此命令和其他所有内容都相同,只是我们发送数据的协议不同。 +我们已经了解了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有四个连接: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。
  
 <WRAP center round tip 60%> <WRAP center round tip 60%>
 **SPI术语** **SPI术语**
  
-SPI 需要四个连接:一个从主设备到从设备获取数据,另一个从相反方向获取数据,加上电源和地。 两条数据线意味着数据可以同时双向传输。 这些通常称为主出从入 (MOSI) 和主入从出 (MISO)。 但是,您会遇到它们具有不同的名称。 如果您查看 Raspberry Pi Pico 引脚分配(附录 B),它们被称为 SPI TX发送和 SPI RX接收。 这是因为 Pico 可以是 +SPI需要四个连接:一个从主设备到从设备获取数据,另一个从相反方向获取数据,加上电源和地。 两条数据线意味着数据可以同时双向传输。 这些通常称为主出从入(MOSI)和主入从出(MISO)。 但是,您会遇到它们具有不同的名称。 如果您查看Raspberry Pi Pico引脚分配(附录 B),它们被称为SPI TX(发送)和SPI RX(接收)。 这是因为Pico可以是一个主设备或从设备,所以这些连接是MOSI还是 MISO取决于Pico的当前功能。 在我们使用的LCD上,它们标有SDI(串行数据输入)和SDO(串行数据输出)
-一个主设备或从设备,所以这些连接是 MOSI 还是 MISO 取决于 Pico 的当前功能。 在我们使用的 LCD 上,它们标有 SDI串行数据输入和 SDO串行数据输出+
 </​WRAP>​ </​WRAP>​
  
-SPI 中没有地址,因此我们可以深入编写代码:+SPI中没有地址,因此我们可以深入编写代码: 
 <code python> <code python>
 import machine import machine
行 91: 行 103:
 spi.write("​hello world"​) spi.write("​hello world"​)
 </​code>​ </​code>​
-在这种情况下,我们使用 SPI0,一组可用的引脚是 GP2、GP3 和 GP4。 +在这种情况下,我们使用SPI0,一组可用的引脚是GP2、GP3和GP4。 
-大多数类型的串行通信都有速度或波特率,这基本上是多少 +大多数类型的串行通信都有速度或波特率,这基本上是多少它每秒可以通过通道推送的数据位。 很多事情都会对此产生影响,例如连接的两个设备的功能以及它们之间的接线(多长时间?以及是否有其他设备的干扰)。 如果您发现损坏的数据存在问题,那么您可能需要减少它。 对于我们的小屏幕,我们只是为每个字符发送一个字节的数据,所以发送速度有多快并不重要,但对于其他一些SPI设备(例如基于像素的显示器),微调波特率率可能很重要。 
-它每秒可以通过通道推送的数据位。 很多事情都会对此产生影响,例如连接的两个设备的功能以及它们之间的接线(多长时间? +
-以及是否有其他设备的干扰)。 如果您发现损坏的数据存在问题,那么您可能需要减少它。 对于我们的小屏幕,我们只是为每个字符发送一个字节的数据,所以发送速度有多快并不重要,但对于其他一些 SPI 设备例如基于像素的显示器,微调波特率 率可能很重要。+
 让我们来看看这如何留下我们的温度计代码: 让我们来看看这如何留下我们的温度计代码:
 <code python> <code python>
-import machine import utime +import machine ​ 
-spi_sck=machine.Pin(2) spi_tx=machine.Pin(3) spi_rx=machine.Pin(4) +import utime 
-spi=machine.SPI(0,​baudrate=100000,​sck=spi_sck,​ mosi=spi_tx,​ miso=spi_rx) adc = machine.ADC(4)+spi_sck=machine.Pin(2) ​ 
 +spi_tx=machine.Pin(3) ​ 
 +spi_rx=machine.Pin(4) 
 +spi=machine.SPI(0,​baudrate=100000,​sck=spi_sck,​ mosi=spi_tx,​ miso=spi_rx) ​ 
 +adc = machine.ADC(4)
 conversion_factor = 3.3 / (65535) conversion_factor = 3.3 / (65535)
 while True: while True:
-reading = adc.read_u16() * conversion_factor temperature = 25 - (reading - 0.706)/​0.001721 spi.write('​\x7C'​)+    ​reading = adc.read_u16() * conversion_factor ​ 
 +    ​temperature = 25 - (reading - 0.706)/​0.001721 spi.write('​\x7C'​)
 spi.write('​\x2D'​) spi.write('​\x2D'​)
 out_string = "Temp: " + str(temperature) out_string = "Temp: " + str(temperature)
-spi.write(out_string) utime.sleep(2)+spi.write(out_string) ​ 
 +utime.sleep(2)
 </​code>​ </​code>​
-如您所见,I2C 和 SPI 之间的代码差异非常小。设置好所有内容后,唯一真正的变化是,使用 I2C 时,您必须在发送数据时指定地址,而使用 SPI 时则不需要(但请记住,如果您连接了多个设备,您需要指定地址)需要切换 CS GPIO 以选择适当的设备)。 
-那么,如果它们如此相似,您在构建项目时应该选择哪种协议?有几个因素需要考虑。首先是您要附加的东西的可用性。有时传感器只能用作 I2C 或 SPI,因此您必须使用它。然而,如果你有选择 
-在硬件方面,当您使用多个额外设备时,影响最大。使用 I2C,您最多可以将 128 个设备连接到单个 I2C 总线;但是,它们都需要有一个单独的地址。这些地址是硬接线的。有时可以通过可焊接(或可切割)连接来更改地址,但有时则不然。如果您想拥有多个相同类型的传感器(例如,如果您在项目的多个点监测温度),您可能会受到传感器 I2C 地址数量的限制。在这种情况下,SPI 可能是更好的选择。 
  
-或者,SPI可以连接无限数量的设备;但是,每个人都必须有自己的 CS 线。在 Pico 上,有 26 个 GPIO 引脚。您需要其中三个用于 SPI 总线,这意味着有 23 个可用于 CS 线。这是假设您不需要任何其他东西。如果可用的 GPIO 非常珍贵,那么您可能需要查看 I2C。+如您所见,I2C和SPI之间的代码差异非常小。设置好所有内容后,唯一真正的变化是,使用I2C时,您必须在发送数据时指定地址,而使用SPI时则不需要(但请记住,如果您连接了多个设备,您需要指定地址)需要切换CS GPIO以选择适当的设备)。 
 + 
 +那么,如果它们如此相似,您在构建项目时应该选择哪种协议?有几个因素需要考虑。首先是您要附加的东西的可用性。有时传感器只能用作I2C或SPI,因此您必须使用它。然而,如果你有选择在硬件方面,当您使用多个额外设备时,影响最大。使用I2C,您最多可以将128个设备连接到单个I2C总线;但是,它们都需要有一个单独的地址。这些地址是硬接线的。有时可以通过可焊接(或可切割)连接来更改地址,但有时则不然。如果您想拥有多个相同类型的传感器(例如,如果您在项目的多个点监测温度),您可能会受到传感器I2C地址数量的限制。在这种情况下,SPI可能是更好的选择。 
 + 
 +或者,SPI可以连接无限数量的设备;但是,每个人都必须有自己的CS线。在Pico上,有26个GPIO引脚。您需要其中三个用于SPI总线,这意味着有23个可用于CS线。这是假设您不需要任何其他东西。如果可用的GPIO非常珍贵,那么您可能需要查看I2C。 
 实际上,对于许多项目,您可以很高兴地使用任一协议,并且您可能会发现选择使用哪个协议更多地与您在零件盒中找到的零件有关,而不是两者之间的技术差异。 实际上,对于许多项目,您可以很高兴地使用任一协议,并且您可能会发现选择使用哪个协议更多地与您在零件盒中找到的零件有关,而不是两者之间的技术差异。
  
行 117: 行 136:
 **比特敲打** **比特敲打**
  
-您的 Pico 有两条硬件 I2C 总线和两条硬件 SPI 总线。 但是,如果您愿意,您可以使用更多。 I2C 和 SPI 都可以用软件而不是硬件来实现。 这意味着主处理核心处理通信协议,而不是微控制器的专用位。 这是众所周知的+您的Pico有两条硬件I2C总线和两条硬件SPI总线。 但是,如果您愿意,您可以使用更多。 I2C和SPI 都可以用软件而不是硬件来实现。 这意味着主处理核心处理通信协议,而不是微控制器的专用位。 这是众所周知的 
 作为'​位砰砰'​。 虽然它很有用,但与使用专用硬件相比,它会给您的处理器内核带来更多压力,并且您可能会发现无法达到高波特率。 作为'​位砰砰'​。 虽然它很有用,但与使用专用硬件相比,它会给您的处理器内核带来更多压力,并且您可能会发现无法达到高波特率。
-Pico 有一个技巧——PIO。 我们将在本书后面(附录 C)更仔细地研究这一点,但它是微控制器中的额外硬件,可以专用于 I2C 和 SPI 等输入/​输出协议。 使用 PIO,您可以创建额外的 I2C 或 SPI 总线,而不会增加主处理器内核的负担。+Pico有一个技巧——PIO。 我们将在本书后面(附录 C)更仔细地研究这一点,但它是微控制器中的额外硬件,可以专用于I2C和SPI等输入/​输出协议。 使用PIO,您可以创建额外的I2C或SPI总线,而不会增加主处理器内核的负担。
 </​WRAP>​ </​WRAP>​
 +
 +### 3. MMA7660姿态传感器信息获取
 +#### 扫描I2C总线获取设备的信息
 +
 +<code python>
 +# Scanner i2c en MicroPython | MicroPython i2c scanner
 +# Renvoi l'​adresse en decimal et hexa de chaque device connecte sur le bus i2c
 +# Return decimal and hexa adress of each i2c device
 +# https://​projetsdiy.fr - https://​diyprojects.io (dec. 2017)
 +
 +import machine
 +i2c = machine.I2C(scl=machine.Pin(5),​ sda=machine.Pin(4))
 +
 +print('​Scan i2c bus...'​)
 +devices = i2c.scan()
 +
 +if len(devices) == 0:
 +  print("​No i2c device !")
 +else:
 +  print('​i2c devices found:',​len(devices))
 +
 +  for device in devices:  ​
 +    print("​Decimal address: ",​device,"​ | Hexa address: ",​hex(device))
 +</​code>​
 +
 +
 +#### 读取测量的数据
 +<code python>
 +import machine, time, bme280
 +i2c = machine.I2C(scl=machine.Pin(22),​ sda=machine.Pin(18))
 +bme = bme280.BME280(i2c=i2c,​address=0x76)
 +while True:
 +  print("​BME280 values:"​)
 +  temp,pa,hum = bme.values ​
 +  print(temp)
 +  print(pa) ​
 +  print(hum)
 +  time.sleep_ms(2000)
 +</​code>​
 +
 +#### 显示读取的数据
 +<​code>​
 +import machine, ssd1306
 +i2c = machine.I2C(scl=machine.Pin(22),​ sda=machine.Pin(18))
 +oled = ssd1306.SSD1306_I2C(128,​ 64, i2c, 0x3c)
 +oled.fill(0)
 +oled.text("​Hello World",​ 0, 0)
 +oled.show()
 +</​code>​