MicroPython manifest 檔案

概述

MicroPython 提供了一項功能,可將 Python 程式碼「凍結」進韌體中,作為從檔案系統載入程式碼的替代方案。

這樣做有下列好處:

  • 程式碼會預先編譯為位元組碼,免去在載入時編譯 Python 原始碼的需求。

  • 位元組碼可直接從 ROM(即快閃記憶體)執行,而不需複製到 RAM 中。同樣地,任何常數物件(字串、tuple 等)也都從 ROM 載入。這可讓您的應用程式擁有顯著更多的可用記憶體。

  • 在沒有檔案系統的裝置上,這是載入 Python 程式碼的唯一方式。

在開發階段,一般不建議使用凍結功能,因為它會大幅拖慢您的開發循環——每次更新都需要重新燒錄整個韌體。不過,選擇性地凍結某些很少變動的相依項目(例如第三方函式庫)仍然可能很有用。

列出要凍結進韌體的 Python 檔案的方法是透過「manifest」,這是一個會由建置流程解譯的 Python 檔案。通常您會將 manifest 檔案撰寫為開發板定義的一部分,但您也可以撰寫獨立的 manifest 檔案,並搭配既有的開發板定義使用。

manifest 檔案可以定義對 micropython-lib 函式庫的相依性,也可以定義對檔案系統上 Python 檔案的相依性,以及對其他 manifest 檔案的相依性。

撰寫 manifest 檔案

manifest 檔案是一個包含一系列函式呼叫的 Python 檔案。請參閱下方定義的可用函式。

manifest 檔案中使用的任何路徑都可以包含下列變數。這些變數都會解析為絕對路徑。

  • $(MPY_DIR) —— micropython 儲存庫的路徑。

  • $(MPY_LIB_DIR) —— micropython-lib 子模組的路徑。建議改用 require()

  • $(PORT_DIR) —— 目前 port 的路徑(例如 ports/stm32

  • $(BOARD_DIR) —— 目前開發板的路徑(例如 ports/stm32/boards/OPENMV4

自訂的 manifest 檔案不應放在主要的 MicroPython 儲存庫中。您應該將它們與專案的其餘部分一起納入版本控制。

通常用於編譯韌體的 manifest 會需要 include port manifest,其中可能包含開發板正常運作所必需的凍結模組。如果您只是想為既有的開發板新增額外模組,則應 include 開發板 manifest(它會進一步 include port manifest)。

使用自訂 manifest 建置

您的 manifest 可以在 make 命令列上指定如下:

$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py

這適用於所有 port,包括以 CMake 為基礎的 port(例如 rp2),因為 Makefile 包裝器會將此參數傳遞給 CMake 建置。

將 manifest 加入開發板定義

如果您有自訂的開發板定義,可以讓它自動 include 您的自訂 manifest。在以 make 為基礎的 port(大多數 port)上,請在您的 mpconfigboard.mk 中設定 FROZEN_MANIFEST 變數。

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

在以 CMake 為基礎的 port(例如 rp2)上,則改用 mpconfigboard.cmake

set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)

高階函式

這些是您通常會使用的函式。它們會將程式碼加入到預先編譯為位元組碼並凍結進韌體映像檔的集合中:

  • modulepackage 會凍結您自己的本機原始碼——分別是單一檔案或整個套件目錄。

  • require 會依名稱從 micropython-lib 凍結一個已發布的套件(及其相依項目)。

  • include 會拉入另一個 manifest,使其凍結模組也一併加入。

  • add_librarymetadata 是輔助函式(分別用於為 require 註冊額外的搜尋路徑,以及宣告套件中繼資料)。

典型的韌體 manifest 會先 include port 或開發板 manifest(使開發板所需的模組保持凍結),然後再加入自己的 module/package/require 行。

注意:opt 關鍵字引數可在各種函式上設定,用於控制交叉編譯器所使用的最佳化等級。請參閱 micropython.opt_level()

add_library(library, library_path, prepend=False)

註冊外部具名 library 的路徑。

當您希望 requiremicropython-lib 以外的目錄解析套件時,請使用此函式——例如您自己收集的驅動程式,或第三方函式庫的簽出版本。

在使用 require 時,會自動搜尋路徑 library_path。預設情況下,新增的函式庫會被加到待搜尋函式庫清單的末尾。傳入 True 可改為將其加到清單的開頭(prepend)。

此外,新增的函式庫也可以透過 require("name", library="library") 明確指定使用。

package(package_path, files=None, base_path='.', opt=None)

凍結整個套件——一個包含 .py 檔案(可選擇性地包含子套件)的目錄——使其可透過 import <package> 匯入。若是單一獨立檔案,請改用 module

這相當於將「package_path」目錄複製到裝置上(差別在於它是以凍結程式碼的形式存在)。

在最簡單的情況下,要凍結目前目錄中名為「foo」的套件:

package("foo")

將會遞迴地納入 foo 中的所有 .py 檔案,並凍結為 foo/**/*.py

如果套件不在與 manifest 檔案相同的目錄中,請使用 base_path

package("foo", base_path="path/to/libraries")

您可以在 base_path 中使用上述變數,例如 $(PORT_DIR)

若要限制只凍結套件中的特定檔案,請使用 files(注意:路徑應相對於套件):package("foo", files=["bar/baz.py"])

module(module_path, base_path='.', opt=None)

凍結單一獨立的 .py 檔案,使其可依名稱匯入(module("foo.py") 會讓 import foo 生效)。若是目錄/套件,請使用 package

如果檔案位於目前目錄中:

module("foo.py")

否則請使用 base_path 來定位檔案:

module("foo.py", base_path="src/drivers")

您可以在 base_path 中使用上述變數,例如 $(PORT_DIR)

require(name, library=None)

依名稱從 micropython-lib 要求一個套件(及其相依項目)。

標準函式庫擴充功能和社群驅動程式就是透過這種方式凍結進來的:具名套件會從 micropython-lib 子模組取得,並連同它所相依的一切一起凍結。若要凍結您自己的原始碼而非已發布的套件,請改用 modulepackage

可選擇性地指定 library(字串),以參照先前已透過 add_library 註冊的函式庫中的套件。否則將會使用函式庫路徑清單。

include(manifest_path)

include 另一個 manifest。manifest 就是這樣組合而成的:自訂韌體 manifest 應 include port(或開發板)manifest,使開發板所需的模組保持凍結,然後再加入自己的項目。

通常用於編譯韌體的 manifest 會需要 include port manifest,其中可能包含開發板正常運作所必需的凍結模組。

manifest 引數可以是字串(檔名)或字串的可疊代物件。

相對路徑會相對於目前的 manifest 檔案來解析。

如果路徑指向一個目錄,那麼它會隱含地 include 該目錄中的 manifest.py 檔案。

您可以在 manifest_path 中使用上述變數,例如 $(PORT_DIR)

metadata(description=None, version=None, license=None, author=None)

為此 manifest 檔案定義中繼資料。這對於 micropython-lib 套件的 manifest 很有用。

當套件透過 mip 發布到 / 從 micropython-lib 安裝時,會用到這些欄位;在開發板韌體 manifest 中並不需要它們。

低階函式

為求完整性而記錄這些函式,但除了 freeze_as_str 之外,所有功能都可透過高階函式存取。

各個 freeze* 函式的差別僅在於程式碼的儲存方式

  • freeze_as_mpy / freeze_mpy 會將預先編譯的位元組碼.mpy)儲存在快閃記憶體中。程式碼會直接從快閃記憶體執行,使用最少的 RAM,且匯入速度很快。這就是 modulepackagerequire 內部所使用的方式。

  • freeze_as_str 則改為凍結 Python 原始碼,並在匯入時編譯為位元組碼(會使用 RAM,且需要裝置上的編譯器)。這是高階函式唯一未公開的功能,也正是上述提到的例外。

freeze(path, script=None, opt=0)

高階函式所建構於其上的底層原語;建議優先使用高階函式。凍結由 path 指定的輸入,並自動判斷其類型。.py 指令碼會先編譯為 .mpy 再凍結,而 .mpy 檔案則會直接凍結。

path 必須是一個目錄,作為開始搜尋檔案的基底目錄。匯入產生的凍結模組時,模組名稱會從 path 之後開始,亦即 path 不會包含在模組名稱中。

如果 path 是相對路徑,它會相對於目前的 manifest.py 來解析。

如果 script 為 None,則會凍結 path 中的所有檔案。

如果 script 是可疊代物件,則會對該可疊代物件中的所有項目呼叫 freeze()(並傳入相同的 pathopt)。

如果 script 是字串,則它會指定要凍結的檔案或目錄,並可在檔案或最後一個目錄之前包含額外的目錄。系統會在 path 中搜尋該檔案或目錄。如果 script 是目錄,則會凍結該目錄中的所有檔案。

opt 是在將 .py 編譯為 .mpy 時要傳給 mpy-cross 的最佳化等級。這些等級在 micropython.opt_level() 中有所說明。

freeze_as_str(path)

將指定的 path 及其中所有 .py 指令碼凍結為字串,並會在匯入時編譯。僅在凍結的程式碼必須保留為 Python 原始碼時才使用此函式;相較於 .mpy 變體,它會耗用匯入時的 RAM。

freeze_as_mpy(path, script=None, opt=0)

凍結輸入:先將 .py 指令碼編譯為 .mpy 檔案,再凍結產生的 .mpy 檔案。這就是 modulepackage 在幕後所做的事。引數的更多細節請參閱 freeze()

freeze_mpy(path, script=None, opt=0)

凍結輸入,輸入必須是直接凍結的 .mpy 檔案(沒有編譯步驟)。引數的更多細節請參閱 freeze()

範例

若要從目前目錄凍結單一檔案,使其可作為 import mydriver 使用,請使用:

module("mydriver.py")

若要凍結目前目錄下子目錄「mydriver」中的一批檔案,使其可作為 import mydriver 使用,請使用:

package("mydriver")

若要從 micropython-lib 凍結「hmac」函式庫,請使用:

require("hmac")

一個較完整的自訂 manifest.py 檔案範例(適用於擁有自己預設 manifest 的開發板)如下:

# Include the board's default manifest.
include("$(BOARD_DIR)/manifest.py")
# Add a custom driver
module("mydriver.py")
# Add aiorepl from micropython-lib
require("aiorepl")

接著即可使用以下方式編譯該開發板

$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py

請注意,大多數開發板並沒有自己的 manifest.py,而是直接使用 port 的 manifest,在這種情況下,您的 manifest 只需改為 include("$(PORT_DIR)/boards/manifest.py") 即可。