前言
大名鼎鼎的乐鑫ESP8266 WIFI模组你应该不陌生,不用我多说了。在这之后乐鑫还更迭了更多高性能的芯片型号,比如这次我要记录的ESP32-C3,搭载近期很火的RISC-V指令集处理器,支持2.4G wifi、BLE-5,拥有丰富应用场景。
是的,它很好。至少硬件很强大,不过再牛批的硬件也需要众多开发者的实践经验来不断完善它的生态。在动手这次开发之前,我对ESP32的认识仅限于乐鑫的另一款型号ESP8266,三年前在eclipse中编译运行过应用层,用已经下载调试好的基线和代码,我也完完全全不会想到,这将是又一段难忘的含泪填坑经历。
文章比较长,分上下两篇。 结合起来阅读,对你认识bootloader开发帮助更大。
文中有多处可以点击的跳转链接,都是我的辛酸开发史总结后,会对你有帮助的内容,根据需要跳转查看吧。
>> 注意 <<: 本文不是详细开发步骤的教程。只记录我在开发Bootloader过程,从头到尾遇到的问题点和解决办法以及一些启发。真的不知道怎么着手的新手盆友,建议移步官方的“ESP-IDF 编程指南”来入门,点这里去看。无论如何,感谢你花时间在这篇文章上。
好了,开始吧。
这次开发需要的材料:
硬件:ESP32-C3-WROOM-02开发板,USB连接线
软件:ESP-IDF V4.4.2,Ubuntu16.04,ESP DOWNLOAD TOOL等
背景
我这次的开发工作,如标题所说。在一个少有ESP32开发者踏足的区域 —— Bootloader。会这么说是因为后来了解到这次的开发任务做的事情,在乐鑫官方人员那里都未曾试过(至少没有发布过版本,或许因为需求太少?),对我这半路接手项目的人而言,更是全新的体验。
此次开发的功能主要是在Boot中,与外部MCU进行串口通讯,完成数据内容的协议解析并在flash中存储一些有用的数据给应用层使用。既然涉及通讯,当然也考虑一些超时重试,用到了定时器。
主要难点包括外设在boot怎么使用、如何添加自己的代码到boot固件运行起来、如何分配自定义分区并在boot使用、资源占用调整等等内容,计划在boot的工作两周工作日内完成,实际要多花一周时间…
准备工作
编译环境
这次用的是Ubuntu16.04,很经典的版本,没什么好说,就是能用、好用而已。
- 有个点值得一提,就是一定要预留给你的虚拟机镜像存放编译用的代码足够的硬盘空间。对于我这种笔记本只有原装256G固态,C、D盘全红的情况,只能寄希望于移动硬盘的救济。 虽然虚拟机有时反应慢点,好歹能跑,凑合。
- 但这里坑就坑在我使用的虚拟机镜像,也是多年前制作好专门用来编译代码的,安装了很多其他工具链的东西,当时分配了总共20G硬盘空间,仅剩下2、3G能存放新加入的东西。捉襟见肘,十分痛苦。要引以为戒。
讲完编译用的虚拟机,来到开发中编译的对象—— 基线代码。这可有的一说了。
基线代码
基线的选择
别用太老的,这点很重要(比如两三年前的那种)。基线代码更迭不频繁,但功能改动较多,修复的bug也很重要。我这次一上手用的还是多年前,从其他工程师那沿用下来的基线,是IDF V4.0 。 当然了,通常在公司里,从别人那接手的项目不会冒然更换基线再修改移植,而是从已经通过原来功能验证的基线着手。这也就是第一个坑所在,一上来我就掉进去了。在老基线遇到的问题,浪费我一周时间,教训惨痛。
- 在Github的基线下方,Readme内容如下图,可以查看到各型号支持的基线与维护周期等内容 => 点这里去看看。
使用老基线我这里遇到三个情况:
- 提问不方便。 什么意思?无论是团队内部追溯早期版本记录,还是遇到困难咨询论坛,都无法获得很准确或可靠的答复,一是该基线官方已经不再维护。二是我的团队在早期也没有建立完备的版本管理和文档记录工作,更是无从查起。这对于很多开发者来说都是非常头疼的事,所以一定一定要做好归档工作。
- 操作方法在新基线不全适用。 这里说的是之后我切换到了新基线,发现原来在老基线上做法OK的事情,到了新基线不能用了。比如我修改了分区配置,在新基线上用了老基线的修改方法导致固件运行有各种问题。
- 老基线有缺陷。这很显然了,是代码都会多少出现点问题,只要在特定情况下就会显现出来。
- 这第3点,以前我一直不以为然,觉得就算是老基线怎么说也是当时的发布版本,稳定性上面肯定还好啦。 应该说这话没错,一般场景下完全OK。但就拿我这次要在boot中使用外设为例,修改好的boot代码怎么也跑不起来,达不到预期效果。但一字不差放到新基线的boot上,运行没有问题… 哎,换了基线后为开发前选择的老基线着实郁闷好一阵。
基线的获取
三种途径:
- Github 代码库:但Github在国内访问,无论下载zip还是Git命令拉取,速度你懂得… 真感人 T.T
- 不建议使用Github自带的下载zip到本地的方式,这样下不了基线里包含的各种Submodule组件,编译不通过而且要补全很费时费力。我已经踩过这个坑,没等全部submodule下载完,先放弃了。
- 如果不介意Github网速问题,有两种方式能够获取完整的基线:
- 使用Git命令拉取到本地。最好是直接在linux中拷贝仓库地址,先拉取基线,然后用“git submodule update --init --recursive” 更新submodule,解决上一条提到的下载zip到本地出现的问题。 => 点这里去试试
- 根据代码库提交的tags,选择一个版本(右侧的Unverify是说作者没有对该tags签名认证,不放心则不选择就行),根据需要下载zip或tar.gz文件到本地。此时压缩包中的基线,就是带有完整Submodule代码的,可放心编译使用。 => 点这里去看看
- Gitee 代码库
- 这是国内的代码库站点,网速很给力 => 看看怎么下载
- 不想费劲阅读的,直接拷贝下面这四条命令到自己的linux用户目录,依次运行即可:
git clone https://gitee.com/EspressifSystems/esp-gitee-tools.git
git clone --single-branch --branch release/v4.4 https://gitee.com/EspressifSystems/esp-idf.git
cd esp-gitee-tools
./submodule-update.sh ../esp-idf
- 以上四步会把IDF V4.4基线拉取到linux目录下,并且通过submodule脚本更新好所有缺失的组件。可以根据需要修改命令中的基线版本号,没有相应目录去创建就行。
- 可以发现,以上两个方法基本是通过运行git命令拉取的,这也是在linux编译开发的推荐做法,不会遗漏IDF基线内的众多submodule组件,省心、可靠。
- 离线安装工具
- 这是一款在windows安装的软件,包含了所有在windows编译环境的依赖文件。相当于通过安装,把相应的ESP-IDF基线编译环境整个解压到本地 => 点这里去下载
- 我这次没在windows下编译运行,对该途径没有更深入的尝试。
- 这是一款在windows安装的软件,包含了所有在windows编译环境的依赖文件。相当于通过安装,把相应的ESP-IDF基线编译环境整个解压到本地 => 点这里去下载
- 说的有点多,要实在搞不懂怎么办?墙裂建议你去查阅官方的 “ESP-IDF 编程指南 ” 第二步 =》 点这里。左上角选好自己的型号和基线,跟着一步步完成配置吧。
bootloader 初看
搞定了以上编译工程所需的基本要素。终于来到此次开发任务的主战场 —— Bootloader
上战场首先要熟悉地形吧。boot文件结构如何,开发中需要注意什么,怎么让自己的代码跑起来,我又该怎么动手?别急,我们一起来了解下。
文件结构
- 在IDF V4.4.2的根目录,component中有两个boot相关的文件夹“bootloader”、“bootloader_support”,内部结构如下图。 其中bootloader_support中的mycode,是我添加的boot自定义代码主要所在位置。
- 想对ESP32-C3的bootloader有更全面详细的了解,找官方的“ESP-IDF 编程指南”吧 => 点这里
特点
bootloader固件独立于应用层app。在开发上有几个注意事项:
- 固件大小
- 不像在应用层,动辄几百K的固件都妥妥能运行。ESP32对boot的二进制文件,绝对大小限制为0x10000,也就是最大64KB。默认则是32KB,不够时的调整方法有三个:
- 调整编译boot固件的优化等级
- 降低boot日志输出级别
- 调整menuconfig的CONFIG_PARTITION_TABLE_OFFSET
- 关于以上三个调整大小的方法,在这里先不展开,想知道官方更多详细说明的,可以查阅“ESP-IDF 编程指南 - 引导加载程序大小” 章节 => 点这里。 后续会深入到boot去开发。涉及分区修改时,我会再提到调整boot大小相关的内容。
- 在我的开发后期,时不时boot固件会编译超过64KB,也挺蛋疼,提示如下:
- 不像在应用层,动辄几百K的固件都妥妥能运行。ESP32对boot的二进制文件,绝对大小限制为0x10000,也就是最大64KB。默认则是32KB,不够时的调整方法有三个:
- 大多数功能无法使用
- 在IDF中,应用层很容易调用各种driver接口让外设工作或操作系统调度起来。像定时器、串口、IIS这样的外设,其实他们的使用接口内部都含有freertos的组件,比如动态申请空间、各种信号量完成的上锁机制等等。而在boot中,其一无法使用操作系统(固件大小的限制也不允许),其二外设无法直接调用现成的应用层接口工作。包括各种初始化、让外设工作的接口,只能自己搞定。 这一点会在后面详细说到,是在bootloader开发要重点解决的问题之一。
- 当然,除非你用不到硬件上的任何外设啦。:)
- 在IDF中,应用层很容易调用各种driver接口让外设工作或操作系统调度起来。像定时器、串口、IIS这样的外设,其实他们的使用接口内部都含有freertos的组件,比如动态申请空间、各种信号量完成的上锁机制等等。而在boot中,其一无法使用操作系统(固件大小的限制也不允许),其二外设无法直接调用现成的应用层接口工作。包括各种初始化、让外设工作的接口,只能自己搞定。 这一点会在后面详细说到,是在bootloader开发要重点解决的问题之一。
怎么改
我们自己的代码,怎么加入到原来boot中让它生效,怎么输出库给到我们的客户让他也能使用?
修改后通过什么方式生效
- 首先要说明的是官方建议的bootloader开发方式,有以下两种:
- 通过钩子函数。扩展原来的bootloader流程,打个补丁。
- 通过覆盖bootloader。重写一个bootloader,覆盖原来bootloader的逻辑来运行。
- 可以发现,这两种方式一个针对小改动需求,一个针对大改动的需要。
- “ESP-IDF 编程指南”怎么说自定义bootloader呢? => 点这里看看。指南中提到的custom_bootloader?点这里
- 你尽可以去探索一下这两种方式的差别。我这里要说的,是第二种方式,但严格来说又不是这两者其一。
- 既没有使用钩子函数打补丁,也没有新建main文件夹在一个叫booloader_components的文件夹中。我认为这应该是老基线的bootloader产物,被保留到了新基线中。
- 我的方式,在上面说【文件结构】时已经提到,就是直接把代码放在自己创建的mycode文件夹中。
怎么添加自己的功能
如果在应用层开发,会做些什么?
- 嗯,你会在component目录下新建自己功能名的文件夹
- 之后呢?不知道了… 那就看看component其他组件都放了啥吧。哦,有CMakeLists.txt,component.mk,还有代码目录。那咱们依葫芦画瓢,也放上这些东西。
- 然后?既然有Cmake的东西,那就看看里面都说了啥。哦,原来里面就是设置好了要编译哪些源文件,头文件目录在哪里,还有配置好编译选项啊,库路径什么的。
- 是的,这些东西在bootloader里面都有,方法类似。就我的开发方式而言,mycode放在bootloader_support目录下之后,里面的源文件、头文件、库这些的,都跟应用层开发一样。该在CMakeLists.txt加什么就加。
- 这跟在boot还是在app其实就没啥关系,是CMake这种跨平台编译方式的基本操作。
- 这跟在boot还是在app其实就没啥关系,是CMake这种跨平台编译方式的基本操作。
bootloader初看完,你应该对它有些基本了解了。接下来,我们进入重头戏,看看bootloader里我是怎么搞定外设使用的。 => 【填坑】ESP32 bootloader初探(下)