如何为 esp32 编译和配置及烧写 MicroPython 固件。

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)。

结语

最后,如果遇到在本文的流程下出现编译问题,可以留言,也可以直接问我。

posted @ 2019-09-13 20:51 Juwan 阅读(...) 评论(...) 编辑 收藏