在这篇教程中,我们看到的代码看起来与其他的部分的代码非常不同。那是因为我们大多数时候不得不在 MCU 的底层处理事情。大多数时候,MicroPython 可以隐藏很多在微控制器上工作的复杂性。
当我们 print 的时候,我们不必担心微控制器存储字母的方式,或它们被发送到串行终端的格式,或串行终端所接受的时钟周期的数量。这些都是在后台处理的。然而,当我们进入可编程输入和输出时,我们需要在更低的层次上处理这些逻辑。
我们将简要介绍 PIO,并介绍一些高级主题,以便你了解正在发生的事情,并希望了解 Pico 上的 PIO 相对于其他微控制器是如何突显优势的。但是,理解创建 PIO 程序所需的所有低级数据操作需要花时间来完全理解,所以如果它看起来有点不透明也不用担心。如果你对处理这种低级编程感兴趣,那么我们将帮助你入门。如果你更感兴趣的是在更高的层次上工作,而宁愿把低层次的争论留给其他人,我们将向你展示如何使用 PIO 程序。
数据输入和数据输出,树莓派 Pico 不仅支持 SPI 和 I2C 控制器发送数据。它还有自己的特殊协议:可编程 IO。让我们看一个例子>
这些方法实际上是运行在 PIO 状态机上的小程序,且在不断循环。例如,led_half_brightness 会不断地打开和关闭 LED,这样 LED 就会有一半的时间是关闭的,一半的时间是打开的。led_full_brightness 将类似地循环,但由于惟一的指令是打开 LED,这实际上并没有改变任何东西。
这里稍微有点不寻常的是 led_quarter_brightness。每个 PIO 指令只需要运行一个时钟周期。这可以用下面一行来实现>
参数如下:
– 状态机器编号
– PIO 程序加载
– 频率
– 状态机器操纵的 GPIO 引脚,还有一些额外的参数,你会在其他程序中看到,我们这里不需要。一旦创建了状态机,就可以使用 active 方法启动和停止状态机,1 表示启动,0 表示停止。
在我们的循环中,我们循环三个不同的状态机。
一个真实的例子,让我们通过一个实际示例来看看使用 PIO 的方法。WS2812B LED 灯条是一种包含三个 LED和一个小型微控制器的灯组。它们由一根数据线控制,带有计时相关协议。
LED 的接线很简单,可能有一个插座,可以把头部电线推进去,或者你可能需要自己焊接它们。
你需要注意的是从 Pico 上的 5V 引脚获得的功率是有限的,如果无限扩展这个灯条,则需要额外给灯条供电。
现在我们已经把灯条和 Pico 连接好了,让我们看看如何用 PIO 来控制它>
它的基本逻辑是每秒发送 800,000 位数据。每一位数据都是一个脉冲——一个短脉冲表示 0,一个长脉冲表示 1。这个程序和我们之前的 程序之间的一个很大的区别是,MicroPython 需要能够向这个程序发送数据 PIO 的程序。
数据进入状态机有两个阶段。第一个是称为先进先出的内存。这是我们的主 Python 程序发送数据到的地方。第二个是输出移位寄存器。这就是 out 指令获取数据的地方。两者通过拉指令连接,拉指令从 FIFO 获取数据并将其放在 OSR 中。然而,由于我们的程序设置了启用 autopull 的阈值为 24,所以每次我们从 OSR 读取 24 位时,它将从 FIFO 重新加载指令 out 从 OSR 中获取一位数据,并将其放入名为 x 的变量中。
jmp 指令告诉代码直接移动到特定的标签,但是它可以有一个条件。指令 jmp 告诉代码,如果 x 的值为 0,则移动到 do_zero。
这里我们一直忽略的一个方面是 .side 位。它们与 set 类似,但它们与另一条指令同时发生。这意味着 out 发生时,.side 设置侧集引脚的值为 0。
哇,对于这么小的一个程序来说,这实在是太多了。现在我们已经激活了它,让我们看看如何使用它。下面的代码需要在程序中位于上述代码之下,以便将数据发送到 PIO 程序。
在这里,我们跟踪一个名为 ar 的数组,它保存了我们希望 LED 拥有的数据。数组中的每个数字都包含了一盏灯上所有三种颜色的数据。格式有点奇怪,因为它是二进制的。使用 PIO 的一个问题是,你经常需要处理单个数据位。每一位数据 都是 1 或 0,数字可以通过这种方式建立,所以以 10 为基数的 2 就是二进制的 10。以 10 为基数的 3 在二进制中等于 11。二进制数的 8 位中最大的数是 11111111,或者以 10 为基数的 255。我们不会在这里深入讨论二进制。让人更困惑的是,我们实际上把三个数字存储在一个数字中。这是因为在 MicroPython 中,整数存储在 32 位,但每个数字只需要 8 位。最后还有一点空闲空间,因为我们只需要 24 位,不过没关系。
前八位是蓝色,后八位是红色,最后八位是绿色。8 位最多可以存储 255 个数字,所以每个 LED 都有 255 个亮度级别。我们可以使用移位运算符 << 来实现这一点。这将在一个数字的末尾加上一 定数量的 0,所以如果我们想让 LED 的红色、绿色和蓝色亮度达到 1 级,我们将每个值都设为 1,然后将它们移动到合适的位数。对于绿色,我们有>
对于红色,我们有>
对于蓝色,我们根本不需要移位位,所以我们只有 1。如果我们把所有这些加在一起, 我们得到以下>
最右边的八位是蓝色的,接下来的八位是红色的,最左边的八位是绿色的。最后一点可能看起来有点令人困惑的是这行>
这创建了一个数组,第一个值是 I,然后每个 LED 都是 0。在开头有一个I的原因是它告诉 MicroPython 我们使用的是一系列 32 位的值。但是,对于每个值,我们只需要将 24 位发送给 PIO,所以我们告诉 put 命令删除 8 位>
相关说明,PIO 状态机使用的语言非常简洁,所以只有少量的指令。除了我们已经看过的,你还可以使用:
in:移动 1 到 32 位到状态机)与out类似,但相反)。
push:将数据发送到连接状态机和主存的内存中 MicroPython 程序。
pull:从连接状态机和主存的内存块中获取数据 MicroPython 程序。这里我们没有使用它,因为通过在程序中包含 autopull=True,当我们使用 out 时,会自动发生这种情况。
mov:在两个位置之间移动数据。
irq:控制中断。如果你需要触发一个特定的东西以在程序的 MicroPython 端运行,就可以使用这些。
wait:暂停直到发生一些事情。
虽然只有少量的指令,但可以实现大量的通信协议。大多数指令都是用于以某种形式移动数据。如果你需要以任何特定的方式准备数据,例如操纵你希望 LED 的颜色,这应该在你的主 MicroPython 程序中完成,而不是在 PIO 程序中。