在這篇教程中,我們看到的代碼看起來與其他的部分的代碼非常不同。那是因為我們大多數(shù)時候不得不在 MCU 的底層處理事情。大多數(shù)時候,MicroPython 可以隱藏很多在微控制器上工作的復(fù)雜性。
當(dāng)我們 print(“hello”) 的時候,我們不必擔(dān)心微控制器存儲字母的方式,或它們被發(fā)送到串行終端的格式,或串行終端所接受的時鐘周期的數(shù)量。這些都是在后臺處理的。然而,當(dāng)我們進入可編程輸入和輸出時(PIO),我們需要在更低的層次上處理這些邏輯。
我們將簡要介紹 PIO,并介紹一些高級主題,以便你了解正在發(fā)生的事情,并希望了解 Pico 上的 PIO 相對于其他微控制器是如何突顯優(yōu)勢的。但是,理解創(chuàng)建 PIO 程序所需的所有低級數(shù)據(jù)操作需要花時間來完全理解,所以如果它看起來有點不透明也不用擔(dān)心。如果你對處理這種低級編程感興趣,那么我們將幫助你入門。如果你更感興趣的是在更高的層次上工作,而寧愿把低層次的爭論留給其他人,我們將向你展示如何使用 PIO 程序。
數(shù)據(jù)輸入和數(shù)據(jù)輸出
樹莓派 Pico 不僅支持 SPI 和 I2C 控制器發(fā)送數(shù)據(jù)。它還有自己的特殊協(xié)議:可編程 IO。讓我們看一個例子:
from rp2 import PIO, StateMachine, asm_pio from machine import Pin import time @asm_pio(set_init=PIO.OUT_LOW) def led_quarter_brightness(): set(pins, 0)[2] set(pins, 1) @asm_pio(set_init=PIO.OUT_LOW) def led_half_brightness(): set(pins, 0) set(pins, 1) @asm_pio(set_init=PIO.OUT_HIGH) def led_full_brightness(): set(pins, 1) sm1 = StateMachine(1, led_quarter_brightness, freq=10000, set_base=Pin(25)) sm2 = StateMachine(2, led_half_brightness, freq=10000, set_base=Pin(25)) sm3 = StateMachine(3, led_full_brightness, freq=10000, set_base=Pin(25)) while(True): sm1.active(1) time.sleep(1) sm1.active(0) sm2.active(1) time.sleep(1) sm2.active(0) sm3.active(1) time.sleep(1) sm3.active(0)
這些方法實際上是運行在 PIO 狀態(tài)機上的小程序,且在不斷循環(huán)。例如,led_half_brightness() 會不斷地打開和關(guān)閉 LED,這樣 LED 就會有一半的時間是關(guān)閉的,一半的時間是打開的。led_full_brightness() 將類似地循環(huán),但由于惟一的指令是打開 LED,這實際上并沒有改變?nèi)魏螙|西。
這里稍微有點不尋常的是 led_quarter_brightness()。每個 PIO 指令只需要運行一個時鐘周期(可以通過設(shè)置頻率來更改時鐘周期的長度,稍后我們將看到)。但是,我們可以在一條指令之后用方括號添加一個 1 到 31 之間的數(shù)字,來告訴 PIO 狀態(tài)機在運行下一條指令之前暫停這個時鐘周期。
然后,在 led_quarter_brightness() 中,兩個 set 指令每個使用一個時鐘周期,延遲使用兩個時鐘周期,因此總循環(huán)使用四個時鐘周期。在第一行中,set 指令需要一個周期,延遲需要兩個周期,所以 GPIO 管腳在這四個周期中的三個是關(guān)閉的。這使得 LED 的亮度達到了持續(xù)亮燈的四分之一。
一旦設(shè)定了 PIO 程序,Pico 就需要將其加載到狀態(tài)機中。因為我們有三個程序,所以需要將它們加載到三個狀態(tài)機中(有 8 個狀態(tài)機可以使用,編號為 0-7)。這可以用下面一行來實現(xiàn):
sm1 = StateMachine(1, led_quarter_brightness, freq=10000, set_base=Pin(25))
參數(shù)如下:
– 狀態(tài)機器編號
– PIO 程序加載
– 頻率(必須在 2000 到 125000000 之間)
– 狀態(tài)機器操縱的 GPIO 引腳
還有一些額外的參數(shù),你會在其他程序中看到,我們這里不需要。一旦創(chuàng)建了狀態(tài)機,就可以使用 active 方法啟動和停止?fàn)顟B(tài)機,1 表示啟動,0 表示停止。
在我們的循環(huán)中,我們循環(huán)三個不同的狀態(tài)機。
一個真實的例子
讓我們通過一個實際示例來看看使用 PIO 的方法。WS2812B LED 燈條是一種包含三個 LED(一個紅色、一個綠色、一個藍色)和一個小型微控制器的燈組。它們由一根數(shù)據(jù)線控制,帶有計時相關(guān)協(xié)議。
LED 的接線很簡單,可能有一個插座,可以把頭部電線推進去,或者你可能需要自己焊接它們。
你需要注意的是從 Pico 上的 5V 引腳獲得的功率是有限的,如果無限擴展這個燈條,則需要額外給燈條供電。
現(xiàn)在我們已經(jīng)把燈條和 Pico 連接好了,讓我們看看如何用 PIO 來控制它:
import array, time from machine import Pin import rp2 from rp2 import PIO, StateMachine, asm_pio # Configure the number of WS2812 LEDs. NUM_LEDS = 10 @asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) def ws2812(): T1 = 2 T2 = 5 T3 = 3 label("bitloop") out(x, 1).side(0)[T3 - 1] jmp(not_x, "do_zero").side(1)[T1 - 1] jmp("bitloop").side(1)[T2 - 1] label("do_zero").side(0)[T2 - 1] nop() # Create the StateMachine with the ws2812 program, outputting on Pin(22). sm = StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(0)) # Start the StateMachine, it will wait for data on its FIFO. sm.active(1)
它的基本邏輯是每秒發(fā)送 800,000 位數(shù)據(jù)(注意頻率是 8000000,程序的每個周期是 10 個時鐘周期)。每一位數(shù)據(jù)都是一個脈沖——一個短脈沖表示 0,一個長脈沖表示 1。這個程序和我們之前的 程序之間的一個很大的區(qū)別是,MicroPython 需要能夠向這個程序發(fā)送數(shù)據(jù) PIO 的程序。
數(shù)據(jù)進入狀態(tài)機有兩個階段。第一個是稱為先進先出(FIFO)的內(nèi)存。這是我們的主 Python 程序發(fā)送數(shù)據(jù)到的地方。第二個是輸出移位寄存器(OSR)。這就是 out() 指令獲取數(shù)據(jù)的地方。兩者通過拉指令連接,拉指令從 FIFO 獲取數(shù)據(jù)并將其放在 OSR 中。然而,由于我們的程序設(shè)置了啟用 autopull 的閾值為 24,所以每次我們從 OSR 讀取 24 位時,它將從 FIFO 重新加載指令 out(x,1) 從 OSR 中獲取一位數(shù)據(jù),并將其放入名為 x 的變量中(PIO 中只有兩個可用變量:x 和 y)。
jmp 指令告訴代碼直接移動到特定的標(biāo)簽,但是它可以有一個條件。指令 jmp(not_x,”do_zero”) 告訴代碼,如果 x 的值為 0(或者,在邏輯術(shù)語中,如果 not_x 為真,并且 not_x 是 x 的 反面——在 pio 級別中,0 為假,任何其他數(shù)字為真),則移動到 do_zero。
這里我們一直忽略的一個方面是 .side() 位。它們與 set() 類似,但它們與另一條指令同時發(fā)生。這意味著 out(x,1) 發(fā)生時,.side(0) 設(shè)置側(cè)集引腳的值為 0。
哇,對于這么小的一個程序來說,這實在是太多了?,F(xiàn)在我們已經(jīng)激活了它,讓我們看看如何使用它。下面的代碼需要在程序中位于上述代碼之下,以便將數(shù)據(jù)發(fā)送到 PIO 程序。
# Display a pattern on the LEDs via an array of LED RGB values. ar = array.array("I", [0 for _ in range(NUM_LEDS)]) print("blue") for j in range(0, 255): for i in range(NUM_LEDS): ar[i] = j sm.put(ar,8) time.sleep_ms(100) print("red") for j in range(0, 255): for i in range(NUM_LEDS): ar[i] = j<<8 sm.put(ar,8) time.sleep_ms(100) print("green") for j in range(0, 255): for i in range(NUM_LEDS): ar[i] = j<<16 sm.put(ar,8) time.sleep_ms(100) print("white") for j in range(0, 255): for i in range(NUM_LEDS): ar[i]=j<<16+j<<8+j sm.put(ar,8) time.sleep_ms(100)
在這里,我們跟蹤一個名為 ar 的數(shù)組,它保存了我們希望 LED 擁有的數(shù)據(jù)。數(shù)組中的每個數(shù)字都包含了一盞燈上所有三種顏色的數(shù)據(jù)。格式有點奇怪,因為它是二進制的。使用 PIO 的一個問題是,你經(jīng)常需要處理單個數(shù)據(jù)位。每一位數(shù)據(jù) 都是 1 或 0,數(shù)字可以通過這種方式建立,所以以 10 為基數(shù)的 2 就是二進制的 10。以 10 為基數(shù)的 3 在二進制中等于 11。二進制數(shù)的 8 位中最大的數(shù)是 11111111,或者以 10 為基數(shù)的 255。我們不會在這里深入討論二進制。讓人更困惑的是,我們實際上把三個數(shù)字存儲在一個數(shù)字中。這是因為在 MicroPython 中,整數(shù)存儲在 32 位,但每個數(shù)字只需要 8 位。最后還有一點空閑空間,因為我們只需要 24 位,不過沒關(guān)系。
前八位是藍色,后八位是紅色,最后八位是綠色。8 位最多可以存儲 255 個數(shù)字,所以每個 LED 都有 255 個亮度級別。我們可以使用移位運算符 << 來實現(xiàn)這一點。這將在一個數(shù)字的末尾加上一 定數(shù)量的 0,所以如果我們想讓 LED 的紅色、綠色和藍色亮度達到 1 級,我們將每個值都設(shè)為 1,然后將它們移動到合適的位數(shù)。對于綠色,我們有:
1 << 16 = 10000000000000000
對于紅色,我們有:
1 << 8 = 100000000
對于藍色,我們根本不需要移位位,所以我們只有 1。如果我們把所有這些加在一起, 我們得到以下(如果我們把前面的位加起來,得到一個 24 位的數(shù)):
000000010000000100000001
最右邊的八位是藍色的,接下來的八位是紅色的,最左邊的八位是綠色的。最后一點可能看起來有點令人困惑的是這行:
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
這創(chuàng)建了一個數(shù)組,第一個值是 I,然后每個 LED 都是 0。在開頭有一個I的原因是它告訴 MicroPython 我們使用的是一系列 32 位的值。但是,對于每個值,我們只需要將 24 位發(fā)送給 PIO,所以我們告訴 put 命令刪除 8 位:
sm.put(ar,8)
相關(guān)說明
PIO 狀態(tài)機使用的語言非常簡潔,所以只有少量的指令。除了我們已經(jīng)看過的,你還可以使用:
in():移動 1 到 32 位到狀態(tài)機)與out()類似,但相反)。
push():將數(shù)據(jù)發(fā)送到連接狀態(tài)機和主存的內(nèi)存中 MicroPython 程序。
pull():從連接狀態(tài)機和主存的內(nèi)存塊中獲取數(shù)據(jù) MicroPython 程序。這里我們沒有使用它,因為通過在程序中包含 autopull=True,當(dāng)我們使用 out() 時,會自動發(fā)生這種情況。
mov():在兩個位置之間移動數(shù)據(jù)(例如 x 和 y 變量)。
irq():控制中斷。如果你需要觸發(fā)一個特定的東西以在程序的 MicroPython 端運行,就可以使用這些。
wait():暫停直到發(fā)生一些事情(例如 IO pin 更改了一個設(shè)定值或中斷發(fā)生)。
雖然只有少量的指令,但可以實現(xiàn)大量的通信協(xié)議。大多數(shù)指令都是用于以某種形式移動數(shù)據(jù)。如果你需要以任何特定的方式準(zhǔn)備數(shù)據(jù),例如操縱你希望 LED 的顏色,這應(yīng)該在你的主 MicroPython 程序中完成,而不是在 PIO 程序中。
審核編輯:劉清
-
微控制器
+關(guān)注
關(guān)注
48文章
7464瀏覽量
150876 -
狀態(tài)機
+關(guān)注
關(guān)注
2文章
491瀏覽量
27461 -
I2C協(xié)議
+關(guān)注
關(guān)注
0文章
26瀏覽量
8451 -
樹莓派
+關(guān)注
關(guān)注
116文章
1696瀏覽量
105457
原文標(biāo)題:樹莓派 Pico 之可編程 IO(PIO)
文章出處:【微信號:趣無盡,微信公眾號:趣無盡】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論