3.1. 微控制器¶
OpenMV Cam 執行於一顆 微控制器(MCU)上:這是一顆單一晶片,整合了 CPU、工作記憶體(RAM)、程式儲存空間(flash),以及一組 周邊裝置 —— 用來與外界互動的硬體區塊。
周邊裝置才是有趣的部分。每一個都是一塊專責單一工作的矽晶片:把某個接腳拉高或拉低、量測類比電壓、透過序列匯流排把位元組依序時脈輸出。CPU 透過 暫存器 來設定與讀取每個周邊裝置 —— 暫存器是硬體會持續監看與更新的固定記憶體位址。
MicroPython 把這些暫存器包裝成 machine 模組裡的類別。machine.Pin(...) 會回傳一個控制 通用輸入/輸出(GPIO)接腳的物件 —— 這條線路可被晶片維持在 高 準位(約 3.3 V)或 低 準位(約 0 V),也可在外部驅動它時讀取為這兩種狀態之一。machine.ADC(...) 公開了類比數位轉換器,它量測某個接腳上的電壓並以數值回報。machine.UART(...) 則執行 通用非同步收發器(UART)—— 一個透過一對線路 TX(傳送)與 RX(接收)一次一位元地收發位元組的周邊裝置。其餘類別則涵蓋其他的周邊裝置。指令碼讀寫的是 Python 物件;MicroPython 會把每一次存取轉換成對應的暫存器讀寫,而那些動作便在實體線路上移動位元。
MCU 把 CPU、記憶體與周邊裝置封裝進單一晶片中。每個周邊裝置都透過 machine 模組裡的某個類別公開給 Python 使用。¶
3.1.1. 主迴圈¶
幾乎每一支微控制器程式都有相同的結構:在指令碼頂端進行一次性的設定(匯入模組、設定接腳、開啟匯流排),然後在底部是一個無限的 while True: 迴圈。在迴圈內,程式反覆地讀取輸入、做出決策並更新輸出。這個迴圈 就是 程式本身;當指令碼結束時,裝置便停止運作。
# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)
# main loop, runs forever
while True:
led.value(1)
# ... do work ...
led.value(0)
# ... do other work ...
這種結構 —— 設定一次,然後永遠循環 —— 就是 主迴圈 模式。接下來的一切都是關於迴圈內部該放些什麼。
3.1.2. 即時控制¶
桌面程式與許多其他程式並行執行。作業系統會把它的工作排程到一個或多個 執行緒 上 —— 這些是獨立的執行流,作業系統在它們之間以毫秒為單位來回切換。當某個執行緒在等待 I/O(磁碟、網路、使用者移動滑鼠)時,作業系統便把 CPU 交給另一個執行緒。程式大致上是事件驅動的:當輸入到達時,視窗管理員會呼叫進你的程式碼;當位元組到達 socket 時,HTTP 函式庫會恢復你的程式碼。是某個更大的東西在呼叫你。
微控制器程式則恰恰相反。在預設情況下沒有作業系統、沒有排程器、也沒有其他執行緒。剛才所示的主迴圈就是 唯一 的迴圈。周邊裝置會觸發中斷或公開狀態旗標;迴圈會輪詢它們或直接處理中斷。如果迴圈卡在 time.sleep_ms(1000) 裡,裝置在那一秒內什麼也不做;沒有其他執行緒可以填補這段空隙。
由此衍生出兩個處處適用的結論:
時間是真實的。 在緊湊的迴圈裡讀取一個接腳兩次只需數微秒;睡眠十毫秒就意味著有十毫秒什麼事都不會發生。非阻塞計時 模式便是對此的回應。
硬體是真實的。 把
machine.Pin.value設為1會在一條實體線路上加上大約 3.3 V;設為0則會在該處加上大約 0 V。電路的其他部分會立即看到這個電壓 —— 包括任何在接腳被錯誤驅動時可能受損的元件。