花10幾元買ESP32-C3,體驗一下MicroPython (和CircuitPython)

2023-03-01 12:00:35

ESP32是近年很火的國產低成本MCU系列。

買了晶片ESP32-C3的模組安信可 ESP-C3-32S的開發板安信可 NodeMCU ESP-C3-32S-Kit 。開發板很小,沒有任何多餘的東西,還不如叫它「最小系統+最小連線板」。

燒錄只需要以上加一條microUSB線就可以,不用買任何的232 TTL、燒錄器之類的,開發板上有USB轉串列埠的晶片。

另外,看檔案說,改變接線後,可以啟用USB JTAG(無需任何額外晶片),然後可以單步偵錯、看暫存器之類的(有對應的開源跨平臺軟體openocd)。這一開發板也引出了USB資料的兩個pin

便宜是便宜,但買得不夠好。不好的原因及造成的限制:

  1. ESP32-C3這個晶片型號是RISC-V架構。若使用MicroPython,那麼MicroPython的native code或viper(這兩個東西能讓python寫的東西執行更快)都尚未支援。ESP32-Cx屬於ESP32系列中的便宜精簡系列。要求高的建議買ESP32系列的其他架構的型號。

  2. 這個模組配的Flash只有2M。MicroPython官方提供的bin檔案(1.4M左右)雖然足夠燒進去,但功能有問題。建議至少選4M Flash的。

    不過還好

    1. 這裡有個老外編譯了2M Flash版本的ESP32C3的MicroPython的bin。版本號 1.16.0 210824 v1.16-236-gb51e7e9d0,python 3.4.0。其中也包括他修改自己的安信可開發板,焊上缺失的兩個開關管的說明。
    2. (建議)我自己編譯了MicroPython 1.19 for ESP32-C3 2M Flash

    若選CircuitPython: 這是MicroPython的衍生版。它提供針對這一開發板的2M Flash韌體adafruit-circuitpython-ai_thinker_esp32-c3s-2m-en_US-7.3.3.bin。Python 3.4.0

  3. 這個開發板買回來缺少兩開關管,和幾個0402的電阻電容。可能就是這個原因,導致ampy(一個PC上的與MicroPython通訊的工具,非必須)無法使用。不過我也試出了補救方法。你可以像上面那個老外那樣自己焊上去,也可以用我這裡將要介紹的經驗,在不用ampy的情況下使用

以下描述都是在Linux下進行。Windows使用者請將串列埠/dev/ttyUSB0自行替換為Windows的COM

燒錄MicroPython/CircuitPython到ESP32C3 (2M Flash)

  1. 使用esptool.py清除整個Flash(必須)。其中的esptool.py來自ESP官方IDF

  2. 燒錄:

    esptool.py --chip esp32c3 --port /dev/ttyUSB0 write_flash -z 0x0 firmware_gereric_c3_2mb_210824.bin
    

    (下載上面連結的老外提供的MicroPython .bin檔案。(你若願意自己編譯更好).bin

    或選擇CircuitPython

    esptool.py --chip esp32c3 --port /dev/ttyUSB0 write_flash -z 0x0 adafruit-circuitpython-ai_thinker_esp32-c3s-2m-en_US-7.3.3.bin
    

通過USB串列埠線連線獲得python shell

使用Linux上picocom這個串列埠工具

picocom -b 115200 --lower-dtr --lower-rts   /dev/ttyUSB0

啟動後自動連wifi

連路由器的wifi也可以。若是想在Linux電腦上設個專門的wifi也行:

sudo lnxrouter --ap wlan0 ssid -p 密碼 -g 192.168.5.1

我們想要在Flash上建立wifi.pymain.py(在boot.py之後韌體會自動呼叫main.py),實現啟動後自動連線wifi。由於目前ampy不可用,所以,在python shell中利用檔案讀寫函數來建立檔案

fileContent = """
import network
nic = network.WLAN(network.STA_IF)
nic.active(True)
nic.ifconfig( [ "192.168.5.20", "255.255.255.0", "192.168.5.1", "192.168.5.1" ])
nic.connect("ssid", "密碼")
"""
wfile = open('wifi.py','w')
wfile.write(fileContent)
wfile.flush()
wfile.close()


fileContent = """
import wifi
"""
wfile = open('main.py','w')
wfile.write(fileContent)
wfile.flush()
wfile.close()

以上是MicroPython的。CircuitPython的wifi函數都不一樣,略

webREPL

webREPL是MicroPython帶的東西,可以在電腦和ESP的Flash之間傳檔案上傳、下載檔案,也可以提供無線python shell。有了這個就能夠快速更新.py程式碼,相當於可以OTA。(它是通過websocket協定通訊的。)

用python shell時還是串列埠線好用,無線webREPL的輸入和回顯有延時

CircuitPython那邊,似乎還沒有這樣完整的一套無線shell和無線傳檔案的東西。有相關討論、有一些檔案,略看了一下,還不完整不易用。

讓webREPL自動啟動

在MicroPython的python shell中:

>>> import webrepl_setup
WebREPL daemon auto-start status: enabled

Would you like to (E)nable or (D)isable it running on boot?

輸入E。然後會讓你設定密碼。完成之後它會改寫boot.py,並建立webrepl_cfg.py用於記錄密碼

用webREPL在電腦和ESP之間傳檔案

用來當OTA升級程式真的快。

webREPL倉庫裡的檔案下載下來。裡面的HTML可以用瀏覽器開啟(Firefox不支援),即可以獲得一個GUI介面。

雖然有web GUI,但頻繁的上傳.py檔案當然是用CLI更快:

./webrepl_cli.py  -p 密碼   /電腦上的路徑/test.py   192.168.5.20:test.py

上傳了test.py後,平時就可以在python shell裡使用

exec(open('test.py').read())

(用import test也會執行,但與exec不一樣)

來直接執行該檔案

GPIO點個燈

import machine
pin5 = machine.Pin(5, machine.Pin.OUT)
pin5.value(0)
pin5.value(1)

ADC採個樣

from machine import Pin
from machine import ADC
pin0 = Pin(0, Pin.IN)
ad0 = ADC(pin0)
ad0.read_u16()

偶爾用UDP代替串列埠接收輸出

串列埠的預設115200的baud很慢。在有大量文字輸出時,不如電腦用wifi UDP接收。經測試,電腦顯示最快達到過1MB/s接收速度。

在電腦上:

nc -k -u -l 0.0.0.0 9995

在MicroPython那邊:

import socket
u = socket.socket(socket.AF_INET,  socket.SOCK_DGRAM) 
def log(s) :
    u.sendto(str(s)+'\n' , ('192.168.5.1', 9995) )
log("xxxxxx")

但要注意:

  1. 測試while連續地使用UDP的sendto()函數,有時會出現ENOMEM錯誤(有時換個wifi ap可以避免)。要try處理一下gc.collect()time.sleep_ms()、重發。
  2. 而且,UDP是不保證收到的

串列埠高波特率

ESP32C3手冊說支援5M baud。開發板上的CH340C串列埠轉USB晶片說支援2M baud。實測調成2M(即200kB/s)在連續傳送時有位元組丟失。調成1M baud(即100kB/s)則無問題。

簡單的效能測試

記憶體:經測試,gc.mem_free()顯示的可用記憶體,MicroPython最大時有約110kB(在gc.collect()過之後)。據說自己編譯MicroPython,改些引數,就可以讓使用者可分配的記憶體更多。而CircuitPython有87kB。這一晶片的實際記憶體是300kB+。

速度:Python解析執行起來肯定比C慢(據說慢100倍)。

MicroPython的time模組裡有一些可以用於記錄時間的函數:

import time
time.ticks_cpu()  # 據說最精確
time.ticks_us()  # 微秒 
time.ticks_ms()  # 毫秒 

經試驗,每通過python語句執行一個與硬體有關的操作,至少需要30us。

而CircuitPython則是用

import time
time.monotonic_ns()

可以看時間。

這速度,在有實時性高的需求時,不啟用DMA或native code或viper肯定是不行的。但risc-v架構還不能使用MicroPython的native或viper。(所以目前ESP32的「C3」這個型號在這種情況下建議選用)

另外,測試了MicroPython的長時間執行一個有實時性要求的任務。剛開始10分鐘內沒什麼問題的,但幾分鐘後,開始有多次時不時的定時器中斷不能及時響應的現象。所以,它還算不上一個可靠的實時系統。

略介紹直接記憶體讀寫(類似指標)、操作暫存器、DMA

MicroPython只對部分的型號新增了DMA模組,我們這個還沒有支援DMA。

但MicroPython支援用mem32,mem16,mem8來直接讀寫任何地址,也就可以設定MCU暫存器,來讓DMA工作。也可以操作任何的硬體模組。

CircuitPython目前未實現直接讀寫任何地址。

創造自己的python模組

由於樂鑫官方ESP-IDF提供了大量example,多於MicroPython已支援的。

MicroPython的韌體本身就是C寫的,用ESP-IDF編譯出來的。為了不浪費ESP官方的example,應該學習一下如何搞自己的python韌體編譯進MicroPython裡。

官方檔案:

  1. Implementing a Module — MicroPython latest documentation
  2. MicroPython external C modules — MicroPython latest documentation

或者參考上面我自己編譯的例子

樂鑫ESP的初步體驗總結

官方晶片手冊檔案寫得不夠完整,不如ESP-IDF完整。手冊中會出現某部分篇章未完成(已是極少量了)之類的提示。

試過自己設定暫存器,結果出現過這樣的狀況:1. 發現過手冊中的錯誤。 2. 按照手冊裡的軟體流程做,失敗。利用ESP-IDF裡的examples才成功。