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
$ git submodule update --init --recursive
所以没事多看 readme ,比看一般人的博客强多了,所以我这里主要是交待一些编译的方法和常见错误的坑。
上述的意思很简单,就是 micropython 依赖于 esp-idf ,但是需要切换 esp-idf 的版本,也就是说随着 micropython 不一定会支持最新的 esp-idf 代码,如果出现错误,你需要通过 git checkout
切换版本号。
命令格式示范:git checkout
而尾巴的
存放在 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 = # 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 = # 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)。
结语
最后,如果遇到在本文的流程下出现编译问题,可以留言,也可以直接问我。