Raspberry Pi Pico:這片 4 美元、可跑 MicroPython 的小開發板有什麼特別?

昨天拿到了我訂的 RPi Pico,多花了一點銀子,但新玩意總得親自動手玩才能有點概念。

2021 年 1 月下旬,樹莓派基金會出人意表地發表了 Raspberry Pi Pico — — 一款小型、便宜的微控制器,搭載自家研發的 RP2040(雙核 Cortex M0+,最高時脈 133 HMz)。而且不僅如此:MicroPython 的開發者 Damien George 已經替它設計了專屬的韌體,可直接從樹莓派單板電腦 OS 內附的 Thonny IDE 連線,連官方書籍都準備好了。此外,包括 Arduino、Adafruit、Sparkfun 和 Pimoroni 都會用 RP2040 生產自己的開發板。

(Pico 只裝有一個類比溫度感應器。寫這篇文時,Arduino 的版本 Arduino Nano RP2040 Connect 尚未公開,但它將會擁有 ESP32 WiFi/BLE 模組、麥克風、九軸加速計。)

樹莓派的愛好者發瘋了:「哇,史上最小最便宜的樹莓派!這是什麼東東?太神奇了!」玩過微控制器的人則心想,這是怎樣?雙核 133 MHz 難道會強過雙核 240 MHz、價格又不會貴到哪去的 ESP32 麼?

不只是開發板,更能當成一個元件

近年來使用 3.3V 電壓的低耗能 32 位元處理器越來越流行,幾乎所有電子元件都能在 3.3V 下運作,Arduino AVR 家族和樹莓派單片電腦那過度強大的 5V 腳位輸出反而就不再那麼方便。

但看了一些影片後,我才意識到其實還有另一個趨勢:有越來越多開發板的腳位採用 castellated hole,底部也是完全平的,沒有任何元件。

簡單地說,castellated hole 就是腳位接點延伸到板子邊緣,這麼一來就能焊接在 PCB 板上。換言之:你能先用麵包板和杜邦線開發雛形,然後將完全一樣的板子放進整合好的電路板。

像是 Arduino 的 Nano 33 家族、Seeeduino 的 XIAO、DFRobot 的 Firebeetle Board-M0 等都已經使用這種設計,而 RPi Pico 僅僅 4 美元的超低成本(雖然加上運費就會多上不少)意味著你能大量採購和使用在各種地方。說真的,我就一直很難理解樹莓派愛好者為何總會特地拿一片 25 美元的小電腦來做些小型專案,甚至直接在其腳位上焊接元件。

其實從 RPi Pico 的包裝也能看出來有意壓低成本,不會再像以往的樹莓派產品配有漂亮的盒子跟說明書了。

RPi 單板電腦的重新定位?

過去樹莓派常視為創客教育產品,因為你能透過它的 GPIO 腳位控制外部裝置。然而,除了前面提到的成本以外,用樹莓派控制裝置總有點不便 — — 要開機得花時間,要讓程式自動執行也需要額外的設定步驟,更別提供電的要求更高。而每當有新產品出現時,YouTube 上的影片多半也只是在討論拿它來看影片、聽音樂夠不夠順暢。

(就筆者所見,樹莓派更大的價值是當成放在家或辦公室某處的影音處理裝置,這樣就不需要占用你自己的電腦。它也比較適合用於做跟視覺或聲音有關的 AI 辨識應用。)

RPi 4 似乎印證了這種路線:它們的雙 HDMI 接頭和更大的記憶體,正是為了讓你當成桌機使用,而 RPi 400 更直接包成鍵盤的形狀。就算背後有保留 GPIO,這個用途也被忽略不提了。

因此,樹莓派基金會也許是想將 RPi 單板電腦當成真正的電腦,讓使用者從它對一個微控制器寫程式。過去有段時間,從樹莓派電腦給 Arduino Uno 寫程式相當流行,連台灣的書都會列入教學,所以幹嘛不把 Uno 換成自家產品、一條龍全包了呢?

從官方書籍的內容來看,顯然他們也想吃下孩童程式/創客教育這一塊:

頭號潛力:UF2 bootloader

我在「初玩 CircuitPython」這篇文章曾提過,UF2 乃微軟與 Adafruit 給特定微控制器產品所設計的燒錄器韌體,能將板子變成一個 USB 磁碟區,直接將新韌體拖拉進去就能改變板子的用途,且仍支援 Arduino C++等程式的寫入。就筆者所見,這正是 RPi Pico 有別於 ESP32 等的最大優勢。

ESP8266/ESP32 不支援原生 USB,因此板子上必須有 USB 晶片(通常是 CP2102 或 CH340G),並得使用 esptool.py 來協助燒錄韌體或程式,這在一些編輯器或工具都會內建。不過,現在 RPi Pico 的 MicroPython 以及 CircuitPython 韌體都可以直接透過拖拉檔案的方式燒錄,等於是跳過了需要外部燒錄程式的步驟,燒錄速度也很快。這對於從樹莓派電腦來操作 Pico,無疑就會容易許多。

當然,目前 UF2 的最大使用者仍是 CircuitPython,很難說將來會有其他語言加入這個行列。但,這仍使得 RPi Pico 成為一個真正同時支援 MicroPython 及 CircuitPython 的產品,而這兩個姊妹語言的特性跟生態系是有些差異的。

燒錄韌體

如果要燒錄新韌體,你必須先進入燒錄模式,辦法是「按住 BOOTSEL 鍵並接上 USB 線」(這點很像 BBC micro:bit),而不是像 SAMD21/51 微處理器那樣按兩下 reset:

燒錄模式的磁碟區會顯示 bootloader 版本

只要將新韌體的 .uf2 檔在這個「磁碟區」貼上,就能更換或更新韌體了。

韌體下載處:

  • MicroPython(如 rp2-pico-20210202-v1.14.uf2)
  • CircuitPython(如 adafruit-circuitpython-raspberry_pi_pico-en_US-6.2.0.uf2)

MicroPython v1.14 穩定版有缺少像是 array 之類的模組,請使用 v1.15 或更新版。

將 .uf2 檔案複製貼上或拖曳到燒錄模式的磁碟區,等板子重開即可。

Raspberry Pi OS(舊稱 Raspbian)會內建 Thonny IDE,沒有的話自己到Thonny.org 下載,版本至少得為 v3.3.3。打開編輯器後到工具(Tools)→選項(Options)→直譯器(Interpreter)選擇目標:

  • MicroPython 選 MicroPython (Raspberry Pi Pico)
  • CircuitPython 選 CircuitPython

按確定後,應該會看見 REPL(即「互動視窗」)跟板子的直譯器連線。沒有的話按按看編輯器上方的停止鈕,或在 REPL 視窗按 Ctrl+C:

MicroPython v1.14 on 2021-02-02; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>>

Adafruit CircuitPython 6.2.0-beta.1 on 2021-01-27; Raspberry Pi Pico with rp2040
>>>

本文撰寫時,CircuitPython 仍在 6.2.0 beta,這個版本加入了對 RPi Pico 支援,目前已有正式 6.2.0 版。

若想在板子上保存程式,MicroPython 必須在編輯器中用儲存複本的方式寫入到 main.py。CircuitPython 則是只要修改磁碟區中的 code.py 或直接覆蓋即可,然後在 REPL 按 Ctrl+D 重啟板子。其他細節網路上很多教學,這邊就不多討論了。

Hello World

來個最基本的程式,讓板子內建的 LED 閃動吧。

MicroPython:

from machine import Pin
import time
led = Pin(25, Pin.OUT)while True:
led.value(not led.value()) # 也可直接寫 led.toggle()
time.sleep(0.5)

CircuitPython:

import board, digitalio, timeled = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
while True:
led.value = not led.value
time.sleep(0.5)

使用雙核運算

官方範例簡單示範了雙核運算,或者應該說怎麼在另一個核心跑一個執行緒(主程式會在第一個核心執行,所以你也只能啟用一個 thread)。thread 模組是 Python 3 早期的多執行緒模組,這在 ESP32 上也有。

import time, _thread, machinedef task(n, delay):
led = machine.Pin(25, machine.Pin.OUT)
for i in range(n):
led.high()
time.sleep(delay)
led.low()
time.sleep(delay)
print('done')
_thread.start_new_thread(task, (10, 0.5))

隱藏選手:Programmable I/O state machine

另一個可能同等重要的是 PIO — — Programmable I/O,即可程式化 GPIO。就筆者的超外行知識所能理解,PIO 就是 GPIO 加裝超簡化版的處理器,你能給它們實作 I2C、SPI、UART 以外的一些特殊硬體協定,例如控制 SD 卡或 VGA 輸出。RP2040 有兩個 PIO 介面(輸出和輸入),每個各有 4 個 state machine,每個 state machine 可存 32 KB 指令。

RPi Pico 搭配 Pimoroni Pico VGA Demo Base,從 SD 卡輸出 VGA 影像

我們不妨來看看官方教材的 NeoPixel 範例,就用了 PIO 來實作其驅動程式(所以,RPi Pico 的 MicroPython 韌體沒有附 NeoPixel module 也許是故意的):

# Example using PIO to drive a set of WS2812 LEDs.
# 這支程式我砍了一部分以便簡化
import array, time
from machine import Pin
import rp2
# Configure the number of WS2812 LEDs.
NUM_LEDS = 8
PIN_NUM = 28 # GPIO 28
brightness = 0.5
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
T1 = 2
T2 = 5
T3 = 3
wrap_target()
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")
nop() .side(0) [T2 - 1]
wrap()
# 用 ws2812() 創造 state machine 並於指定的 Pin 輸出
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
# 啟動 state machine 並等待 FIFO (first-in, first-out) 資料
sm.active(1)
# NeoPixel 的顏色與亮度資料 (GGRRBB)
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
##################################################
def pixels_show():
dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
for i,c in enumerate(ar):
r = int(((c >> 8) & 0xFF) * brightness)
g = int(((c >> 16) & 0xFF) * brightness)
b = int((c & 0xFF) * brightness)
dimmer_ar[i] = (g << 16) + (r << 8) + b
sm.put(dimmer_ar, 8)
def pixels_set(i, color):
ar[i] = (color[1] << 16) + (color[0] << 8) + color[2]

def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
def rainbow_cycle(wait):
for j in range(255):
for i in range(NUM_LEDS):
rc_index = (i * 256 // NUM_LEDS) + j
pixels_set(i, wheel(rc_index & 255))
pixels_show()
time.sleep(wait)
while True:
rainbow_cycle(0.001)

可見 PIO 其實是用組合語言來寫的(ws2812() 的部分),並在 pixels_show() 將資料寫入 state machine。資料格式是 uint16 array(來自 array 模組),用 GGRRBB 的格式。剩餘的程式碼(wheel() 與 rainbow_cycle())則是取自 Adafruit 的 NeoPixel 彩燈旋轉範例。

對,這是我的 RPi Pico

我想一般使用者應該沒辦法寫 PIO 程式(官方的 Python SDK 顯然寫得沒有非常詳細;事實上你也許得讀 C++ SDK),但官方或許會鼓勵開發商用這種方式撰寫驅動程式。

此外奇怪的是官方範例裡有 SSD1306 的範例,可是 RPi Pico 的 MicroPython 目前卻沒有這個模組,而我安裝 ESP8266 的官方版本也跑不起來。所以硬體架構的差異有可能將是導致你非用 PIO 不可的原因?

補:後來發現是現在 MicroPython 把 I2C(硬體 I2C)和 SoftI2C(軟體 I2C)分開,而硬體 I2C() 呼叫時第一個參數要給 ID。換成 SoftI2C 就能正常作用了。你能在此找到驅動程式:https://github.com/stlehmann/micropython-ssd1306

電壓和腳位設計

軟體部分還有待觀察,這裡先來說說硬體。RP2040 只有 4 個 ADC 類比輸入腳位(這些腳位不建議輸入超過 300 mA 電流),算不上很多,而在 RPi Pico 上其中一個 ADC 還分給溫度感測器,故沒有接出到外界。這點無疑會在應用範圍上帶來一些限制。

且注意 RP2040 的腳位無法容忍 5V 電壓,這表示你不能直接使用會輸出 5V 的感測器。至於板子本身能輸出多大電流量呢?文件的說法是這取決於輸入電量,但最好不要從 3.3V 腳位汲取超過 300 mA。

VBUS 和 USB 頭連接,我還沒試過但應該是能直接輸出 5V。VSYS 用來對板子輸入供電,跟 VBUS 隔著一個二極體。RPi Pico 的輸入電壓範圍是 1.8~5.5V,彈性很大,想用 3.7V 鋰電池或三個 AA 電池都行。

其他嘛,我不是很喜歡腳位號碼寫在板子底下,這樣還要常常查腳位圖。此外一邊有 20 個腳位,若你想配迷你麵包板(17 x 10),一邊就要犧牲三個腳位了。我有一片 ESP32-Pico-Kit 買來時就是這樣的。

另外板子沒有 reset 鈕,但你可以在 Run 腳位(GPIO 22/26 中間)和 GND 之間接一個按鈕。

效能比一比:MicroPython vs. CircuitPython

CircuitPython 早期曾經支援過 ESP8266/ESP32,但後來因為支援度不佳(正是因為它們缺乏原生 USB)而放棄。現在,RPi Pico 成了第一個正式橫跨兩種嵌入式 Python 的硬體,那麼兩者的執行效能有沒有差別呢?

筆者拿了手邊現成的程式跑了點測試(除了少數像是 time 模組取時間的語法不同外,其餘程式完全修同):

解 8 皇后問題(單 list 跑遞迴,整數資料)

  • ESP32 (MicroPython): 2100.727 ms
  • RPi Pico (MicroPython): 2284.749 ms
  • Metro M4/SAMD51 (CircuitPython): 1575.32 ms
  • RPi Pico (CircuitPython): 1956.05 ms

康威生命遊戲(64 x 32,bytearrays,boolean 資料)跑一回合的大概時間

  • ESP32 (MicroPython): 561~564 ms
  • RPi Pico (MicroPython): 739~742 ms
  • Metro M4 (CircuitPython): 496~501 ms
  • RPi Pico (CircuitPython): 583~584 ms

SEFR 線性分類演算法對鳶尾花資料集(150 筆浮點數資料 x 4 特徵,3 個分類)做訓練

  • ESP32 (MicroPython): 178.024 ms
  • RPi Pico (MicroPython): 182.918 ms
  • Metro M4 (CircuitPython): 124.39 ms
  • RPi Pico (CircuitPython): 167.969 ms

非常有趣的是我的 Adafruit Metro M4(SAMD51 處理器,120 HMz)表現居然最好,而 RPi Pico 和 ESP32 這兩個雙核處理器的表現非常接近。RPi Pico 當然沒有 WiFi 和藍芽,但在運算能耐上似乎不俗。

當然我沒有使用任何特殊設定,所以無法得知它們運行時是否有用到雙核心。但也許有吧。另外 MicroPython 與 CircuitPython 都指出 RPi Pico 的時脈為 125 MHz。這應該才是它正常的運作速度。

初期而言,CircuitPython 也許是更實用的 Python 選擇

很快的 Arduino IDE 應該就也有官方或第三方的 RPi Pico 或其他 RP2040 開發板支援了(C++),TinyGo 或許也許會跟進,不過這裡我們就只先討論兩個嵌入式 Python 吧。

當然,為什麼要用 Python 替微控制寫程式?Python 比 C++慢很多不是嗎?其實,如果你不需要做非常密集的運算,Python 高度彈性的語法、能做錯誤攔截等等就會使開發過程簡單許多,特別是你能直接從電腦對開發板直譯程式,等到確定都 ok 了再上傳。相對於正規 Python 學習已經一面倒向資料科學/機器學習的現況,嵌入式 Python 反而是更好玩和用途更廣泛的。

除了運算速度稍稍略勝外,CircuitPython 有個比 MicroPython 更實務的地方:儘管其周邊裝置的驅動程式也不能說是包山包海,但一直以來也是由 Adafruit 與 CP 社群積極維護,所以就生態系、現成函式庫甚至說明文件來說,比起硬體相容性可能仍有疑問的 MicroPython,CircuitPython 反而能更快投入應用。

再者,如我在其他文章所提的,CircuitPython 韌體會讓開發板變成真正的 USB 碟,這表示你能同樣用複製貼上來新增驅動程式、甚至上傳影音跟文件等。只要實際使用過,你就會發現那真是方便到不可思議。MicroPython 則和 ESP 家族一樣,仍得透過編輯器的介面來上傳和瀏覽檔案。

最後,CircuitPython 韌體還包含一項秘密武器:ulab 模組,也就是簡化版的 NumPy,能用來提高某些數學處理的速度。以前面的 SEFR 分類演算法來說,若用 ulab 來改寫,所需時間就會降到 1/3 不到!ulab 在諸如 SADM51、ESP32-S2 和 RPi Pico 的韌體都有提供,且當然也有初步的 NeoPixel 支援。

不服氣的 MicroPython 則指出它有 machine.Timer 模組,可輕鬆地實現平行運算,甚至比正規版 Python 還簡單。有個有趣的點是,RPi Pico 的 MicroPython 有某些語法已經跟 ESP 家族的不太一樣,在某些方面反而還更接近 CircuitPython。這或許顯示樹莓派基金會在軟體開發上向 Adafruit 借了不少力吧。

你該買 Raspberry Pi Pico 嗎?

如果你不是樹莓粉,那麼當然不見得 — — RPi Pico 身為進口貨,要賣到僅僅 4 美元是不可能的,而 ESP 家族在亞洲依舊享有效能與價格的優勢(更別提其 IoT)。

當然,如果之後台灣貨源多,價格能壓在 200 再低幾十塊的話,它或許就能拿來取代 Arduino Nano/Micro Pro 這類開發板,對於做小型專案就會蠻方便的。別忘了,很多 ESP32 開發板體型偏大,且最便宜也要 300、400 以上,有時用起來難免就是有點顧忌。

若想玩 CircuitPython,它也會是個不錯的選擇,至少比 Adafruit 產品便宜,快閃記憶體也比 Seeeduino XIAO 大。UF2 bootloader 能讓你很快的切換到不同語言,使你有更多的開發選擇。

至於現在要觀察的,是 RPi Pico 的 PIO 和 MicroPython 生態系能否達到超越 ESP 家族現有的程度,以及 Arduino Nano RP2040 Connect 的實際售價。說不定等到 RP2040 處理器開放其他廠商使用後,中國那邊會順勢做出更便宜和更強大的新品也說不定吧。

目前仍只聞樓梯響的 Arduino Nano RP2040 Connect

(2/10 補:Arduino 在推特上貼出了 RP2040 Connect 的照片:)

考慮到 RP2040 的執行速度比 Nano 33 BLE 的 nRF52840(64 MHz)快,Tensorflow Lite 也已經支援 Pico,RP2040 Connect 應該會成為接下來 TinyML 領域的新寵。

I just like to write weird stuff that have very little to do with my actual work. My normal blog is https://krantasblog.blogspot.com.

I just like to write weird stuff that have very little to do with my actual work. My normal blog is https://krantasblog.blogspot.com.