一句話總結:MicroPython 把 ESP32 的開發循環從「編譯三十秒加燒錄一分鐘」壓到「改一行 0.5 秒生效」,一個晚上從裸板寫到四種動畫引擎。這篇文章是那一夜的實錄,含完整程式碼與踩坑清單。
半年前我做嵌入式開發的時候,一天裡有大量的時間在等。改一行 C,等編譯;編譯完,等燒錄;燒錄完,看著板子重開機,然後發現改錯地方,重來一次。真正的開發時間被切成碎片,每一次等待都在把腦中剛成形的假設放涼。

前天晚上十一點多,我拿出塵封的 ESP32-S3 和一顆 WS2812 RGB 燈,決定換一種做法。這次的目標很簡單:不寫 C,改用 MicroPython,看看一個晚上能走多遠。結果是,凌晨一點半的時候,我有了自己的韌體協議、四種動畫引擎、一個瀏覽器調色盤前端,還有另一個 repo。
為什麼是不用編譯、不用燒錄
傳統嵌入式的循環是「改、編譯、燒、重開」。MicroPython 把前三步砍到只剩「改」,而且改的地方不是本機檔案,是板子上正在跑的直譯器。
打開序列埠的 REPL,直接敲三行,燈就亮了:
>>> from machine import Pin
>>> from neopixel import NeoPixel
>>> np = NeoPixel(Pin(48, Pin.OUT), 1)
>>> np[0] = (255, 128, 0)
>>> np.write()
沒有 toolchain,沒有 linker 錯誤,沒有等。想看不同顏色?改那組數字再 np.write() 一次。想調呼吸燈的頻率?改完參數 0.5 秒內生效。這種回饋速度改變的不是效率,是嘗試的勇氣:敢試的次數變多,找到對的參數就變成必然。
從點燈到設計自己的韌體協議
點燈只是入場券。那天晚上真正好玩的部分,是替 LED 設計一個序列協議,讓瀏覽器可以直接對板子下指令。
協議設計得很克制,一行一個指令,換行結尾:
# R,G,B 靜態色 0-255(同時停止動畫)
# red/green/blue/white/off 具名色 / 關燈
# stop 停止動畫(保留目前色)
# anim,NAME,R,G,B,PERIOD_MS 動畫:breath / rainbow / strobe / heart
四種動畫引擎裡,我最喜歡的是心跳。它不是簡單的明暗交替,而是模擬真實心跳的雙峰:一次比較重的「咚」,停頓零點零八秒,再來一下比較輕的「咚」。寫成程式只有幾行:
def f_heart(t):
r, g, b = color
p = (t % period) / period
if p < 0.12: # 咚
k = math.sin(math.pi * p / 0.12)
elif 0.2 <= p < 0.35: # 再咚一下
k = math.sin(math.pi * (p - 0.2) / 0.15)
show(r * k, g * k, b * k)主迴圈是整個韌體的心臟:8 毫秒輪詢一次序列埠,有新指令就處理;動畫引擎則鎖在 16 毫秒一幀,大約 60fps。命令的低延遲與動畫的穩定幀率,兩者互不干擾:
while True:
if poll.poll(8): # 8ms 輪詢,命令低延遲
line = sys.stdin.readline()
if line:
handle(line)
now = time.ticks_ms()
if anim and time.ticks_diff(now, last_frame) >= 16: # ~60fps
last_frame = now
ANIMS[anim](now)踩坑實錄:便利的背面
一夜順利的背後,該踩的坑一個沒少。這些都記在 repo 的 AGENTS.md 裡,這裡挑四個最有價值的:
序列埠佔用有四種來源。mpremote 連線中算一種,本頁的 Web Serial 連線算一種,其他瀏覽器分頁開著的 Web Serial 也算一種,最後是 FTDI 橋接。用 lsof /dev/cu.usbmodem101 查,會看到 Google 程序握著埠,那其實是另一個分頁。
esptool 的重置序列會咬死 USB-JTAG。監聽器運行中直接燒錄,裝置會進入僵死狀態,解法是先進 machine.bootloader() 再用 no-reset 參數。真的救不回來,拔插 USB 線斷電,物理治療最有效。
mpremote exec 之後裝置會停在 REPL,main.py 的監聽器已經死了。判斷方法:開埠讀兩秒,看到 PALETTE-LISTENER READY 是監聽中,看到 >>> 就是卡住了。
MicroPython 是裁剪版 Python。print() 沒有 flush、str 沒有 ljust、array 沒有 itemsize。從 CPython 的習慣直接搬過來會撞牆,這份裁剪清單值得先讀一遍。

瀏覽器直連板子,免伺服器
韌體有了協議,下一步是前端。Web Serial API 讓瀏覽器直接開序列埠,配上前端的調色盤,選一個顏色,WS2812 就變色。整條鏈路是「瀏覽器、USB、板子」,沒有中間伺服器,沒有雲端轉發,延遲低到像是板子長在螢幕旁邊。
前端用 Vue 3 加 Vite 堆了調色面板、Pinia 狀態和一個 2D 調色板,四種動畫引擎都能從網頁觸發。硬體工程和前端工程在同一晚被同一條 USB 線串起來,這種體驗在編譯式的世界裡很難發生。
理性與情緒的拉扯
理智上我知道這個選擇的代價。後來的瓶頸分解實驗顯示,MicroPython 的逐元素運算有 69% 是直譯器開銷,這是語言的骨子,不是程式碼能修的。同樣的計算工作交給編譯式語言,數字會漂亮很多。
但那晚我看著呼吸燈在 0.5 秒內改變頻率,看著心跳動畫的雙峰曲線隨著我調整參數而變化,那個當下我明白了一件事:原型階段最貴的不是運算,是假設死掉的速率。直譯器的稅金買回來的,是假設可以活著被驗證。
後來我也真的用 Rust 在同一塊板子上寫了一個 no_std 的核心,成功跑起來了。但那次體驗充滿了插線、換埠、等燒錄的循環,兩種語言的定位在一夜之間變得非常清楚:Rust 負責需要性能與確定性的地方,MicroPython 負責探索與業務邏輯。它們不是競爭者,是同事。
給想入門的人三個建議
第一,韌體選對等於容量翻倍。我這塊板子換上支援 octal PSRAM 的韌體後,可用記憶體從 220KB 變成 8.32MB,裝置能處理的資料量直接差兩個數量級。
第二,序列埠的佔用清單貼在工作桌上,每次插線前對一遍。省下來的不是時間,是脾氣。
第三,把踩坑寫進文件。那晚的四個坑如果只存在記憶裡,下週就會用兩倍時間再踩一次。寫下來,它們就變成文件,變成這篇文章,變成別人的捷徑。

創意的半衰期很短,工具的第一責任是讓它活過今晚。MicroPython 沒有讓我的程式跑得更快,它讓我的想法跑得更快,而開發這件事,追的從來都是後者。
如果你的專案也在 ESP32 或 MicroPython 的世界裡,歡迎寫信到 partner@calpa.me 交流踩坑經驗,或到 GitHub 看更多實驗。














