MicroPython 在 esp-idf (esp32) 上編譯固件
esp32 編譯 micropython 的固件相關的資料應該很多吧,我也會出一篇,但會額外講一些 linux 的東西的。
資料將按照以下順序進行說明。
- 什么是 esp-idf ?
- 配置 esp32 工具鏈
- 准備 micropython 倉庫
- 建立 micropython for esp32 固件
注意,以下操作截圖全部在 linux 下完成(but 我在虛擬機,方便截圖),順便一提,我寫的資料,並不會考慮開發新手,如果有問題可以評論解答,但我是不會在寫的內容中照顧他人的,隨心所欲,但是有問題歡迎來提。
什么是 esp-idf ?
esp-idf 就是 esp32 的官方標准 SDK 支持,進入倉庫看下 readme 即可,但在這里並非必要了解的知識。
往下看前請先准備 esp-idf 的官方文檔,進入 快速開始 一章,按步驟開始部署開發環境。
配置 esp32 交叉編譯工具鏈
因為在電腦上寫的程序將要編譯運行在 esp32 上,所以這必然就需要交叉編譯工具鏈,所以寫代碼先需要先配置好編譯環境。
值得一提的是官方的配置文檔(/get-started/linux-setup)寫的流程很好,所以照着做就行,但要注意的是,它們都是在 i686(i386) 或 ARM64 上的機器上跑的二進制(bin)文件,如果需要在類似樹莓派的 arm linux 上編譯,則需要重新編譯工具鏈了。
請准備一台 linux 按上述官方配置文檔配置完成后,在命令行下輸入 xtensa-esp32-elf
然后按 tab 鍵補全系統命令,確認配置完成,如下圖。
注意工具鏈相關的文件需要綁定到系統全局下,作為命令(符號)給 makefile 接入。
所以記得在 linux 環境變量里添加路徑,例如配置文檔里講的,至此編譯環境已經建立,注意,不同版本的 idf 可能會變更 工具鏈 ,所以編譯出錯的時候不妨檢查檢查 esp-idf 和 工具鏈 是否匹配。
export PATH="$HOME/esp/xtensa-esp32-elf/bin:$PATH"
;
至於如果你想編譯工具鏈,可以繼續看官方文檔 從零開始設置 Linux 環境下的工具鏈 ,可喜可賀的是如今都有中文了鴨,上述我們設置的是編譯后的二進制版本,如果想學習工具鏈的本質,就要親自試試編譯工具鏈了。
交叉編譯工具鏈源碼倉庫 在這里 ,是很值得學習的開源代碼呢。
准備 micropython 倉庫
看到這里,我希望你已經按上述的文檔和步驟,成功搭建 esp32 的編譯環境和獲取 esp-idf 源碼。
如果發現 git clone 很慢,記得在尾巴添加 --depth=1
的命令,讓它不要獲取歷史提交(commit),這樣下載就會快很多了。
- 准備好工具鏈
xtensa-esp32-elf
。 - 准備好開發 SDK 的倉庫 esp-idf 。
- 看一眼 esp32 目錄下的 readme.md 。
通過下述命令獲取 micropython 的倉庫。
$ git clone https://github.com/micropython/micropython --depth=1
接着編譯一下 mpy-coress ,用來給 Python 文件預編譯為 bytecode 到固件里的工具鏈。
$ make -C mpy-cross
然后初始化一下相關的子倉庫。
$ git submodule init lib/berkeley-db-1.xx
$ git submodule update
最后在編譯(make)一下。
$ cd ports/esp32
$ make
此時編譯就開始了,會有如下滾動信息。
大致是這樣的流程,但要注意的是我這里只是簡化了操作,接着說一下下述幾個注意點。
注意看 micropython/port/esp32 的 readme.md 。
有如下內容:
Setting up the toolchain and ESP-IDF
------------------------------------
There are two main components that are needed to build the firmware:
- the Xtensa cross-compiler that targets the CPU in the ESP32 (this is different to the compiler used by the ESP8266)
- the Espressif IDF (IoT development framework, aka SDK)
The ESP-IDF changes quickly and MicroPython only supports a certain version. The
git hash of this version can be found by running `make` without a configured
`ESPIDF`. Then you can fetch only the given esp-idf using the following command:
$ git clone https://github.com/espressif/esp-idf.git
$ git checkout <Current supported ESP-IDF commit hash>
$ git submodule update --init --recursive
所以沒事多看 readme ,比看一般人的博客強多了,所以我這里主要是交待一些編譯的方法和常見錯誤的坑。
上述的意思很簡單,就是 micropython 依賴於 esp-idf ,但是需要切換 esp-idf 的版本,也就是說隨着 micropython 不一定會支持最新的 esp-idf 代碼,如果出現錯誤,你需要通過 git checkout
切換版本號。
命令格式示范:git checkout <Current supported ESP-IDF commit hash>
而尾巴的<hash>
存放在 Makefile 文件中的,如下內容。
# the git hash of the currently supported ESP IDF version
ESPIDF_SUPHASH := 6b3da6b1882f3b72e904cc90be67e9c4e3f369a9
所以在 esp-idf 的目錄下有如下操作(注意和 readme 有點點不同鴨)
$ cd esp-idf
$ git checkout 6b3da6b1882f3b72e904cc90be67e9c4e3f369a9
$ git submodule update --init --recursive
那么現在就可以繼續編譯 esp32 的 micropython 了。
此時注意,雖然前面說過直接 make 是可以,但實際上官方的做法是額外在 ports/esp32 里准備一個 makefile ,並且區別於 Makefile 文件(Linux 文件區分大小寫),按 readme 所述填下列內容即可。
ESPIDF = <path to root of esp-idf repository> # such as ESPIDF = /root/esp-idf
BOARD = GENERIC
# PORT = /dev/ttyUSB0
# FLASH_MODE = qio
# FLASH_SIZE = 4MB
# CROSS_COMPILE = xtensa-esp32-elf-
include Makefile
注意這里可以指定填寫你的 ESP-IDF 路徑,這樣做的好處就是不需要添加到系統環境變量中了,所以你可以同時擁有許多份不同工程用的 esp-idf 倉庫,並且此時 make 命令調用的是 makefile ,接着 makefile 末尾將會調用 Makefile(即 include Makefile),其他內容可以選填(# 為注釋),相當於預先為 Makefile 的執行載入環境變量。
此時就開始編譯固件吧,編譯成功如下圖:
此時常用命令有如下(查閱 readme 可知):
- 打開串口收發,組合 Ctrl + A 和 Ctrl + Q 退出串口(需要安裝 picocom )
$ picocom -b 115200 /dev/ttyUSB0
- 擦除 esp32 中 flash 。
$ make erase
- 編譯后燒錄 micropython 固件。
$ make deploy
- 清理編譯結果。
$ make clean
- 組合命令,燒錄完固件后打開串口。
$ make deploy && picocom -b 115200 /dev/ttyUSB0
如下圖運行結果:
順手輸入了 print('hello esp32')
,值得注意的是,這里也支持 tab 補全操作多多體驗吧。
額外的信息
為什么固件只有一個 firmware.bin 文件?
固件編譯后的 firmware.bin 文件產生在 port/esp32/build 文件夾,它是通過 makeimg.py 合成的,看一下就知道發生了什么。
import sys
OFFSET_BOOTLOADER = 0x1000
OFFSET_PARTITIONS = 0x8000
OFFSET_APPLICATION = 0x10000
files_in = [
('bootloader', OFFSET_BOOTLOADER, sys.argv[1]),
('partitions', OFFSET_PARTITIONS, sys.argv[2]),
('application', OFFSET_APPLICATION, sys.argv[3]),
]
file_out = sys.argv[4]
cur_offset = OFFSET_BOOTLOADER
with open(file_out, 'wb') as fout:
for name, offset, file_in in files_in:
assert offset >= cur_offset
fout.write(b'\xff' * (offset - cur_offset))
cur_offset = offset
with open(file_in, 'rb') as fin:
data = fin.read()
fout.write(data)
cur_offset += len(data)
print('%-12s% 8d' % (name, len(data)))
print('%-12s% 8d' % ('total', cur_offset))
可以得知,firmware.bin 是由 bootloader + partitions + application 而來的一個單獨的 bin ,即為固件,所以只需要在 0x1000 起始位置燒入 esp32 的 flash 里就可以運行 micropython 了。
如何編譯 8M spiflash 的固件?
此外我們還需要知道如何編譯帶 SPIRAM 固件,通常來說,只需要修改 makefile 的 BOARD = GENERIC_SPIRAM
。
ESPIDF = <path to root of esp-idf repository> # such as ESPIDF = /root/esp-idf
BOARD = GENERIC_SPIRAM
include Makefile
不過你也可能會遇到一些問題,比如我修改了編譯工具鏈里的一些頭文件才編譯通過的,所以得做好准備隨時修改源碼的准備趴。
至於我遇到了什么問題,唔,改一下編譯中的文件之間的符號關系就好了。
編譯后運行可以看到 Found 64MBit SPI RAM device
,然后查看 gc.mem_free()
可以看到 3997 * 4096 Byte,也就是 4M RAM
,如下圖(帶有 SPIRAM
的固件)。
下圖就是對照的無 Flash 固件,可以看到 111 * 1024
的,用的是內部的 RAM (小於 384 KB)。
結語
最后,如果遇到在本文的流程下出現編譯問題,可以留言,也可以直接問我。