最近刚完成本身8266的小项目,已经发布在github上,有兴趣的朋友能够看一下html
github地址:esp-ujnpython
1. 经过MQTT协议与服务器交互git
2. 内置HTTP服务器,支持经过浏览器进行参数配置github
咱们在编译8266代码时可使用项目中的gen_misc.sh(Windows下为gen_misc.bat)脚本,选择合适的参数后就会在sdk/bin/文件夹中生成可烧录的文件,如eagle.flash.bin,eagle.irom0text.bin。 但这样存在的问题是每次编译时都须要选择一遍编译参数,因此通常会使用make命令进行编译,如:json
make COMPILE=gcc BOOT=none APP=0 SPI_SPEED=40 SPI_MODE=QIO SPI_SIZE_MAP=4
这是由于gen_misc.sh的做用仅仅是供用户选择编译参数,最终的编译过程是经过make命令依据Makefile文件中定义的若干规则来进行的。接下来经过以下几个方面来探讨整个编译流程浏览器
SDK中Makefile文件以树形结构组织。整体上分为3类:主文件,项目配置文件,库配置文件。服务器
|--sdk/ |----Makefile |----project/ |------Makefile |------user/ |--------Makefile |------json/ |--------Makefile
如上图所示架构
日常开发过程当中,通常咱们只须要关注项目配置文件与库配置文件便可。若有时为了程序的模块化,须要将不一样的功能模块编译成独立的库。这时须要修改项目配置文件,并建立对应的库配置文件。例如咱们须要添加一个json库。这时就须要:app
须要在两个Makefile中作出的改动以下:模块化
#sdk/project/json/Makefile GEN_LIBS = libjson.a #库名 #sdk/project/Makefile SUBDIRS = user \ json #库目录 COMPONENTS_eagle.app.v6 = user/libuser.a \ json/libjson.a #库路径
对于Non-FOTA模式,编译完成后会在sdk/bin/目录下生成eagle.flash.bin与eagle.irom0text.bin。显然这两个文件并非编译器的直接产物,通常来讲编译器会经过咱们的代码生成一个可执行程序。那么这两个文件是从何而来的呢?实际上这两个文件是编译后生成的可执行文件的一部分。可执行文件被拆解成了多个部分,而后拼凑出了这两个文件供咱们烧录。咱们的代码通过编译后会生成一个elf文件,它的路径在sdk/project/.output/eagle/debug/image/eagle.app.v6.out。这个一个标准的elf文件,可使用readelf命令查看它的一些信息。
#readelf -h ELF 头: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 类别: ELF32 数据: 2 补码,小端序 (little endian) 版本: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 类型: EXEC (可执行文件) 系统架构: Tensilica Xtensa Processor 版本: 0x1 入口点地址: 0x40100004 程序头起点: 52 (bytes into file) Start of section headers: 549292 (bytes into file) 标志: 0x300 本头的大小: 52 (字节) 程序头大小: 32 (字节) Number of program headers: 5 节头大小: 40 (字节) 节头数量: 19 字符串表索引节头: 16 #readelf -S 共有 19 个节头,从偏移量 0x861ac 开始: 节头: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .data PROGBITS 3ffe8000 0000e0 000804 00 WA 0 0 16 [ 2] .rodata PROGBITS 3ffe8810 0008f0 0015d0 00 A 0 0 16 [ 3] .bss NOBITS 3ffe9de0 001ec0 006f18 00 WA 0 0 16 [ 4] .irom0.text PROGBITS 40210000 0092c0 038b44 00 AX 0 0 16 [ 5] .text PROGBITS 40100000 001ec0 0073fc 00 AX 0 0 4 [ 6] .xtensa.info NOTE 00000000 041e04 000038 00 0 0 1 [ 7] .comment PROGBITS 00000000 041e3c 001bbd 00 0 0 1 [ 8] .debug_frame PROGBITS 00000000 0439fc 00211c 00 0 0 4 [ 9] .debug_info PROGBITS 00000000 045b18 014bec 00 0 0 1 [10] .debug_abbrev PROGBITS 00000000 05a704 003f8a 00 0 0 1
将可执行文件eagle.app.v6.out转变为可烧录文件的过程定义在sdk/Makefile,也就是在主文件中。大致流程以下:
#将.text、.data、.rodata和.irom0.text节的数据转存到文件 objcopy --only-section .text -O binary eagle.app.v6.out eagle.app.v6.text.bin objcopy --only-section .data -O binary eagle.app.v6.out eagle.app.v6.data.bin objcopy --only-section .rodata -O binary eagle.app.v6.out eagle.app.v6.rodata.bin objcopy --only-section .irom0.text -O binary eagle.app.v6.out eagle.app.v6.irom0text.bin #经过eagle.app.v6.text.bin、eagle.app.v6.data.bin和eagle.app.v6.rodata.bin生成eagle.app.flash.bin python sdk/tools/gen_appbin.py eagle.app.v6.text.bin eagle.app.v6.data.bin eagle.app.v6.rodata.bin #将最后生成的可烧录文件放到sdk/bin/目录下 mv eagle.app.flash.bin sdk/bin/eagle.flash.bin mv eagle.app.v6.irom0text.bin sdk/bin/eagle.irom0text.bin
经过上边readelf -S获取到的节区表信息能够看到,实际在内存中出现的节只有.text、.data、.bss、.rodata和.irom0.text。
.text + .data + .rodata => eagle.flash.bin
.irom0.text => eagle.irom0text.bin
经过比较这几个节的大小与烧录文件大小的关系能够获得相同的结果(eagle.flash.bin文件中除了包含程序节数据,还有少许的配置数据)。.bss节虽然在内存中出现可是在程序初始化时会被整个清零,因此没必要出如今烧录文件中。这几个节包含的数据内容以下:
节名 | 做用 |
.text | 存放代码 |
.data | 存放已初始化的全局变量 |
.bss | 存放未初始化的全局变量 |
.rodata | 存放只读数据 |
.irom0.text | 存放标注有ICACHE_FLASH_ATTR的代码或ICACHE_RODATA_ATTR的变量 |
在前边已经提到,咱们写的代码最终会编译为一个elf格式的可执行文件(eagle.app.v6.out),接下咱们经过具体Makefile文件中的代码对整个编译的执行过程进行分析。以前讲到sdk/Makefile为主文件,也就是全部编译时用到的逻辑都在其中定义。咱们整个的编译流程中须要按顺序产生以下几类目标:二进制目标文件、库文件、elf文件、烧录文件。那么如何经过一个主Makefile来完成这些工做呢,这里须要先看一下其他两类起配置做用的Makefile。这两类Makefile的最后都会有以下代码:
PDIR := ../$(PDIR) sinclude $(PDIR)Makefile
它的做用是包含本身父文件夹中的Makefile文件,那么最后主Makefile文件中的内容会被包含到库配置文件与项目配置文件中。在项目配置文件中,它的做用是产生elf可执行文件与烧录文件,在库配置文件中,它的做用是产生静态连接库。主Makefile中,最主要的显式规则以下,经过这两条规则产生了全部咱们须要的文件。
...
314 all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)
... 324 .subdirs: 325 @set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);)
这两条规则的目标都是伪目标,并不会产生任何文件,咱们所须要的全部文件都是目标all的依赖文件。
$(OBJS) |
二进制目标文件 |
$(OLIBS) |
静态连接库 |
$(OIMAGES) |
elf可执行文件 |
$(OBINS) |
烧录文件 |
$(SPECIAL_MKTARGETS) |
一直为空 |
第一个依赖文件.subdirs是一个伪目标,也就是每次编译时都会先执行.subdirs中定义的操做,也就是遍历全部含Makefile文件的子文件夹,执行make命令。经过这种方式产生的结果就是make工具的当前路径发生了改变。拿上边列出的项目结构为例,咱们一次编译过程可分为
这时若是执行了sdk/project/json/Makefile,那么此时make工具的当前路径变为了sdk/project/json/。此时sdk/project/json/Makefile对上层Makefile进行包含后再次构建目标all。通常来讲sdk/project/json/中不会再有包含Makefile的子文件夹,那么此时目标all的第一个依赖项.subdirs会马上返回,而后再对其他的依赖项进行构建。
还有一点须要说明的是目标all的依赖项并非全都有值的,好比$(SPECIAL_MKTARGETS)的值就一直为空,表示不存在此依赖项。继续拿上边的项目结构举例:
make当前路径 | $(OBJS) |
$(OLIBS) |
$(OIMAGES) |
$(OBINS) |
sdk/project/ | 空 | 空 | eagle.app.v6.out | eagle.app.v6.bin |
sdk/project/json/ | json.o | libjson.a | 空 | 空 |
根据make当前路径的不一样,目标all有不一样的依赖项,而后再根据主Makefile中定义隐式规则对依赖项进行构建,即完成了整个项目的构建过程。
有的朋友可能对Makefile的语法不熟悉,这里推荐一个网站
https://www.gnu.org/s/make/manual/make.html
官方的教程很详细