初玩 CircuitPython:給微控制器的「另一種」MicroPython — — 使用 Adafruit Metro M4、Arduino Nano-33 IoT 及 Seeeduino XIAO

2013 年,澳洲程式設計師兼物理學家 Damien George 靠集資推出了 MicroPython,以及第一個用來跑這種嵌入式系統語言的 PyBoard。簡單地說,MicroPython 是 Python 3.4 的瘦身版,它擁有自己的 Python 直譯器,在保留絕大部分Python 語言的核心特色同時縮減周邊模組(函式庫),好讓 Python 這種出了名的…吃記憶體的語言也能在微控制板上運行。

和 C++(Arduino)相比,MicroPython 有其優點與缺點。C++ 擁護者最喜歡攻擊 MicroPython 的乃是其執行效率,而 MicroPython 在記憶體的使用上也遜色得多(因此它的設計對象多半是記憶體跟儲存空間較大的硬體,但即使如此還是很容易不小心用光記憶體)。然而 Python 身為直譯器語言,你可以直接在板子的直譯器執行草稿碼而不需等待編譯/上傳,這加上 Python 語言寫起來相對簡單的特性,反而使 MicroPython 很適合快速打造或實驗一些嵌入式應用。等到開發結束後,再移植到 C++ 就行了。

MicroPython 有幾個衍生版本,包括 2015 年專為 BBC micro:bit 設計的極簡版(因為它記憶體相對實在過小),2019 年還有給樂高 Mindstorm EV3 的版本。本文要介紹的,是 MicroPython 在 2017 年衍生出來的一個近親,叫做 CircuitPython

CircuitPython:給 SAMD21/51 及 nRF52 處理器的 Python

CircuitPython 乃美國 Adafruit 公司為了自家及其他公司相關產品所寫的 MicroPython 版(如果你去看它的 Github repo,其實是從 MicroPython 分支出來再修改的),主要目的為讓這種語言能用在該公司以 SAMD21 處理器為主力的產品上,這後來也擴展到其 SAMD51 和 nRF52 處理器板子上。

相較於「標準」MicroPython 對不同板子提供不同程式模組的作法,CircuitPython 的目的是統一語言功能,可以用一樣的語法套用在不同板子上。(當然,不同硬體需要不同的韌體,且不知為何 Adafruit 產品的腳位也彼此有些差異,所以這種軟體上的一致究竟有多大的實用性,筆者目前是持保留態度。)

既然「標準」MicroPython 最主要支援的板子之一為低價、性能不錯的 ESP8266/ESP32,東方世界的我們比較容易接觸到標準 MicroPython。相對的,Adafruit 和 Sparkfun 等國外公司主流的 SAMD21/51 系列開發板因得進口的因素,幾乎看不到有人在玩或討論 CircuitPython。

不過,這現象也許將會扭轉 — — Arduino 在 2019 年春推出的新 Nano 系列,其中的 Nano 33 IoT 和過去的 Arduino Zero、MKR WiFi 1010 一樣採用 SAMD21,台灣有些人有代理。此外,中國廠商 Seeeduino 推出了 XIAO,一塊奇小而且非常便宜的 SAMD21 開發板。(事實上 Seeeduino 還推出使用 SAMD51、內含螢幕的 Wio Terminal,在筆者寫本篇的同時台灣廠商那邊已經一概缺貨。)

Seeeduino 的產品打一開始就特意加入對 CircuitPython 的支援,甚至已經裝好 UF2 bootloader,這表示 CircuitPython 或許會慢慢在我們這一端的世界引起關注呢!

以下,我們就來看看要怎麼初次踏入 CircuitPython 的世界。

UF2 bootloader:拖拉檔案就能上傳程式到開發板的神奇科技

如果說 CircuitPython 是另一個版本的 MicroPython,那聽起來可能沒什麼好稀奇的。問題就在於,CircuitPython 有個秘密武器 — — UF2 (USB Flashing Format) bootloader。(bootloader 簡單地說,就是安裝在開發板上、用來將使用者程式燒錄到板子的程式。)

當年 BBC micro:bit 在開發時,為了簡化小朋友燒錄程式的麻煩,它採用了獨樹一格的雙處理器架構:nRF51 是主晶片,KL26 則扮演燒錄介面,後者本身擁有個叫做 DAPLink 的 bootloader。(事實上 KL26 有兩套 bootloader,第二套是用來更新第一套 bootloader 的。)

micro:bit 的雙處理器架構

DAPLink 會在電腦上創造出一個假的 USB 磁碟區,使用者只要把編輯器編譯好的 hex 檔(若是 MicroPython,hex 會包括一份 Python 韌體及使用者的草稿碼)丟進這個磁碟區,KL26 就會將之傳給 nRF51,根本不需要任何外部驅動程式。

當時負責開發 micro:bit MakeCode 編輯器的微軟,或許想將這種拖曳式的程式燒錄方式推廣到其他嵌入式硬體上(這需求也有可能來自 Adafruit,因為兩者的關係似乎相當好;微軟在開發的試驗版 MakeCode MakerMakeCode Arcade 也是以這些廠商的產品為主)。當然,要說服其他廠商在板子上裝兩顆處理器是不可能的,因為成本太貴了。因此兩間廠商後來選擇了 Atmel 公司的 SAMD21 晶片 — — 一個本身就擁有 USB 介面的處理器。

簡單地說,UF2 就是在 SAMD21 上實現類似 DAPLink 的效果。SAMD21 的做法是它裝有 UF2 bootloader 時,按兩下 reset 就會讓晶片重開並進入 bootloader 模式,在電腦上產生一個模擬的 USB 磁碟區。這時把你的程式檔(.uf2)丟進去,晶片會完成燒錄然後自己重啟到正常模式。同樣不須使用任何外部燒錄工具,方便吧!

現在 Adafruit 家的產品出貨時都已經裝好 UF2 bootloader,比較麻煩的是一些別家產品(如 Arduino)需要自行安裝。幸好,他們也提供了一些工具,所以對某些板子來說沒有那麼困難(下面會看到範例)。

對一些 SAMD21 Arduino 開發板來說,你甚至能用它們的 UF2 bootloader 取代板子上原有的 Arduino bootloader,但依舊支援在 Arduino IDE 燒錄程式。此外,像是 TinyGo 這種語言也能借用 UF2 的優點,直接將編譯好的 Golang 程式寫入板子,而無須任何外部輔助。甚至,若你想安裝 CircuitPython 或者更新 UF2 bootloader 的版本,用同樣的拖拉方式丟 CircuitPython 韌體進去就行了。

同一塊板子裝有 UF2 bootloader,想寫 C++、Python、Go 都沒問題,這樣是不是很神?

Photo by Darius Soodmand on Unsplash

安裝或更新 CircuitPython 韌體

Adafruit Metro M4

本篇的第一個例子,是筆者今年稍早花費不少銀子向海外訂購的 Adafruit Metro M4(使用 SAMD51 處理器),我在這篇有稍微介紹過。它的造型和 Metro M0(SAMD21)一樣走 Arduino Uno 造型,對不懂焊接的使用者來說比較友善。

但雖然腳位位置很像 Uno,卻不是 100% 相容 — — 比如在 D13 後面多了 I2C 腳位(不像 Uno/Nano 等舊 Arduino 接在 A5/A4),SPI 腳位則和 Arduino Leonardo 一樣是在前方的 ICSP 腳位,所以坊間的 Uno 擴充板還不見得能用呢。

而由於這塊板子一開始就是要讓使用者寫 CircuitPython,所以出廠時也已經裝好 CircuitPython 韌體。筆者已經不記得拿到時的版本是多少(後來更新過),但運作原理是一樣的,所以這裡先用這塊來當示範。等一下講到其他板子時,就會看到做法是一樣的。

當你將此板子接上電腦時,電腦上會跳出一個磁碟機出來。這可不是 UF2 bootloader 創造的假磁碟區,而是 CircuitPython 的真‧磁碟區。

實際看到的會因你的版本及你放的檔案而有所不同

若點開裡面的 boot_out.txt,可以看到下面這行字(筆者寫這篇時更新到 CircuitPython 5.3.1 版):

Adafruit CircuitPython 5.3.1 on 2020–07–13; Adafruit Metro M4 Express with samd51j19

你可以隨意剪貼任何檔案到該磁碟區中,要是點右鍵查看磁碟區內容,更會發現它是個 2MB 大小的磁碟區,這是因為板子上額外裝了 2MB QSPI Flash。就筆者所知,Adafruit 目前的 SAMD21/51 產品後面只要有 Express 這行字,就應該有裝這個。這使得你能很快地把大量驅動程式庫、音效檔(SAMD21/51 都有腳位支援 DAC,可直接輸出類比信號)等丟到板子上,十分便利。

好,如果你想更新 CircuitPython,到這裡下載最新的 CircuitPython 韌體(.uf2):

然後按兩下板子角落的 reset。這會使板子進入 bootloader 模式,並出現那個假‧磁碟區:

這個磁碟區內的 INFO_UF2.TXT 記錄了 bootloader 的版本:

UF2 Bootloader v3.10.0 SFHWRO
Model: Metro M4 Express
Board-ID: SAMD51J19A-Metro-v0

把新下載的韌體檔拖曳到該磁碟區放開,板子就會開始更新 CircuitPython。過段時間後它重開就會是新版本了。

Arduino Nano 33 IoT

之前也在別的文章介紹過,Arduino 於 2019 年春推出了四塊 Nano 尺寸的新板子,其腳位都和舊 Nano 相同,但硬體規格上更好、價格(和之前的 Arduino 高檔產品相比)也較低。看得出來 Arduino 想要擺脫 Uno/Nano 的舊時代,並努力地推自己的編輯器/雲端工具,但成效如何就有待觀察了。

Nano 33 IoT 不是 Adafruit 產品,所以一開始當然沒有 UF2 bootloader。但 CircuitPython 網站上替這塊板子提供了 .bin 和 .uf2 兩種版本,因此你可以使用外部工具將 .bin 燒錄到板子上。

不過,還有個更簡單的解法:

  1. 打開 https://github.com/adafruit/uf2-samdx1/releases
  2. 尋找並下載 update-bootloader-nano33iot-v3.10.0.ino

沒錯 — — 這是個 Arduino IDE 草稿碼,你只要在 Arduino IDE 打開和燒錄到 Nano 33 IoT,就能達到一樣的效果!

當然,如果你還沒安裝的話,你得安裝 Arduino IDE。接著到編輯器的工具→開發板→開發板管理員尋找「Arduino SAMD Boards」並安裝之:

把板子接上電腦,然後到選單內選擇它(而且別忘了選好序列埠):

注意!燒錄後你的電腦(以及之前板子曾連線過的其他電腦)會無法正確辨識你的裝置。這時請打開裝置管理員,在裝置上按右鍵然後解除安裝驅動程式。

接著將板子重新接上電腦,按兩下 reset 讓它進入 bootloader 模式,應該就會抓到了:

UF2 Bootloader v3.10.0 SFHWRO
Model: Arduino NANO 33 IoT
Board-ID: SAMD21G18A-NANO-33

在 bootloader 模式下,下載 Nano 33 IoT 的 CircuitPython 韌體(.uf2)和把它丟進磁碟區,等它重開就完成了:

Adafruit CircuitPython 5.3.1 on 2020-07-13; Arduino Nano 33 IoT with samd21g18

檢查一下磁碟區大小,只有 47 KB。所以 CircuitPython 是可以在 SAMD21 處理器上跑,只是沒有外部記憶體的話,能放進板子的東西就很有限了。以 Nano 33 IoT 來說,它可能就沒有足夠記憶體支援板子上的 LSM6DS3 加速計/陀螺儀感測器,當然更無法使用 NINA-W102 網路模組了。

很多 ESP8266 開發板是裝有 4MB 快閃記憶體的,拿 D1 mini 來測一下,空機也有 3.35 MB 的空間。其實較早期的 CircuitPython 確實是有支援 ESP8266,Adafruit 和 Sparkfun 也有推自己的 ESP 產品,但因為支援度不佳(ESP 沒有原生 USB 介面)所以後來從某一版起就放棄了。

Seeeduino XIAO

和前兩者相比,Seeeduino XIAO(小)真的是小得不可思議,跟台幣十元硬幣一樣大。事實上,它比 ATTiny85 和 Arduino Micro Pro 更小,因此很適合用來開發穿戴式裝置。此外,其價格只有台幣兩百多元,這以東方仍屬少見的 SAMD21 產品來說真是匪夷所思的低價。

當然,它改用更新的 USB-C 接頭,所以想用的人記得買條線。

當然 Seeeduino 的點子也許不是新的,因為 2017 年就有出現這塊 Nerdonic Atom X1,同樣是超小型的 SAMD21 開發板,有 reset 鈕但腳位較少。

我猜 Seeeduino 的主要目的其實是打進國外市場,但 XIAO 出廠時已經安裝好 UF2 bootloader,這使得東方市場的人想接觸 CircuitPython 突然變得容易許多。

不過,這種迷你尺寸跟價格也有其代價:XIAO 沒有空間裝 reset 鈕,唯一「重設」它和使之進入 bootloader 模式的辦法就是…拿條線或金屬鑷子去連接 USB-C 接頭旁的那兩個小點兩下。顯然這是需要一些練習的。

(假如你只用某種語言寫程式的話,你大概只需這麼做一次。但若要燒錄不同語言,你就得如此啟動 bootloader 模式啦。)

成功的話,你會看到板子上的 LED 燈以很慢的速度變亮和變暗。你也能看到有個磁碟區出現在檔案總管,顯示它確實有 UF2 bootloader:

UF2 Bootloader v3.7.0-33-g90ff611-dirty SFHWRO
Model: Seeeduino XIAO
Board-ID: SAMD21G18A-XIAO-v0

同樣的,只要下載 CircuitPython 韌體和放進磁碟區,新磁碟區就會出現,裡面會顯示韌體資訊:

Adafruit CircuitPython 5.3.1 on 2020-07-13; Seeeduino XIAO with samd21g18

磁碟大小同樣是 47 KB。

Photo by Petri Heiskanen on Unsplash

撰寫 CircuitPython 程式

下載並設定 Thonny IDE 編輯器

下載 Thonny IDE,這是個簡單易用的 Python 編輯器,自身帶有 Python 3.7 環境(3.2.7 版),而且也支援 ESP 家族 MicroPython、micro:bit 版 micro:bit 和 CircuitPython。

下面我們以 Seeeduino XIAO 為例,先將它(已照前面的安裝好 CircuitPython)接上電腦,然後打開 Thonny,到工具→選項然後選擇 CircuitPython 為直譯器:

記得在下面選擇板子的序列埠,再按確認。運作正常的話,就會看到編輯器的 REPL 畫面出現以下字樣:

如果出現錯誤訊息,就多按幾次停止鈕。訊息出現就表示編輯器跟板子上的 CircuitPython 直譯器順利連線了。

我們來看一下 CircuitPython 的內建模組有哪些:

>>> help('modules')__main__          digitalio         pulseio           sys
analogio gc random time
array math rotaryio touchio
board microcontroller rtc usb_hid
builtins micropython storage usb_midi
busio neopixel_write struct
collections os supervisor
Plus any modules on the filesystem

最基本的 LED 閃爍程式

在上面的編輯器畫面新增一個 .py 檔案,然後輸入

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

按編輯器上的綠色執行箭頭或 F5 執行程式,你就能看到板子上的 LED 閃爍了。

注意 XIAO 的內建 LED 燈雖也在腳位 13,但就和 ESP8266 的內建 LED 燈一樣,低電位時才會亮。

按鈕和 LED 燈

現在加入一個按鈕(D10 並啟用上拉電阻)和一個實體 LED 燈(D9)。注意一下 Gnd 腳位在哪:

import board, digitalio, timeled = digitalio.DigitalInOut(board.D9)
led.direction = digitalio.Direction.OUTPUT
switch = digitalio.DigitalInOut(board.D10)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP
while True:
led.value = not switch.value
time.sleep(0.05)

類比輸出入

下面的程式是讀取類比信號(接在 A7 的光敏電阻)來控制一顆外部 LED 燈(A0)的亮度。

要注意的是,這裡的 AnalogOut 是給有 DAC(Digital-to-analog converter)的腳位用的,而 SAMD21 只有 A0 有這功能(SAMD51 則有兩個)。

類比信號的範圍都是 0~65535,其範圍取決於你接的固定電阻值,所以你看到的結果不見得會一樣。

import board, analogio, timeanalog_in = analogio.AnalogIn(board.A7)
analog_out = analogio.AnalogOut(board.A0)
while True:
print(analog_in.value)
analog_out.value = analog_in.value
time.sleep(0.05)

正如前面所提,若是 Adafruit Metro M4 這類有較大磁碟空間的板子,你可以存音效檔進去,然後用 DAC 腳位放出來(得自己擴音)。

PWM

若要用 A0 以外的腳位輸出類比信號,就得用 PWM(Pulse Width Modulation)了,PWM 的 duty cycle 同樣可設為 0~65535 之間的值:

import board, analogio, pulseio, timeanalog_in = analogio.AnalogIn(board.A7)
pwm = pulseio.PWMOut(board.D2, frequency=5000, duty_cycle=0)
while True:
print(analog_in.value)
pwm.duty_cycle = analog_in.value
time.sleep(0.05)

有趣的是 LED 燈對 PWM 的變化敏感度高於純類比信號的樣子。

儲存程式到開發板

想把 Python 草稿碼上傳到板子上也很簡單:把你剛才編輯的檔案另存成 code.py,貼上到板子的磁碟區根目錄下。以後開發板通電時,它就會自動執行 code.py 的內容。(這就像 MicroPython 會自動執行板子上的 main.py。)

事實上,你稍後可以直接用編輯器打開並修改 code.py 的內容。若編輯器沒有跟 CircuitPython 直譯器直接連線,那麼你一存檔,板子就會自動執行新程式。這樣是不是超方便?

目前對於 CircuitPython 最好的入門參考文件,大概是 Adafruit 提供的 CircuitPython Essentials。當然這文件感覺還不是非常完整,但至少足夠提供一些基礎示範了。

Photo by Mae Mu on Unsplash

結語

本文我們講到什麼是 CircuitPython(當然,我們沒討論 Python 基礎知識),以及如何在一些開發板上使用 CircuitPython。

就基本語法來說,CircuitPython 是比標準 MicroPython 囉嗦了一點,但其實也相去不遠。此外,有些較新的嵌入式系統語言如 TinyGo 也深深受到 CircuitPython 的影響。CircuitPython 在西方世界的推廣程度之深入,是身在東方的人們仍無法想像、需要了解一下的。

當然,CircuitPython 目前也有一些缺點,最主要是它(以及相關驅動程式庫)更新太快(笑),文件的更新經常顯得過時或不完整。此外,有些驅動程式是為了 Adafruit 自家販賣的感測器而作,這卻不見得相容於市面上的其他類似硬體。下次有機會時,筆者再來講講 CircuitPython 如何運用 Adafruit 提供的電子元件驅動程式,以及其他運用到 UF2 技術的 MakeCode 系列編輯器。

Photo by Yuiizaa September on Unsplash

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.