ESP8266入门学习(一)
阅读原文时间:2021年04月20日阅读:1

在得到一份代码后我们最先应当了解一下它的目录结构,这里用ESP8266_RTOS_SDK_V1.5.0为例:

app:用户代码主目录,用户代码都将放在这里
bin :编译生成和SDK提供的bin文件,用于下载到Flash中
driver_lib : RTOS驱动示例代码
examples:示例代码
extra_include:Xtensa编译器头文件(使用XCC编译时使用,一般用GCC)
include:SDK头文件(包含可用的 API函数和相关的宏定义)
ld:链接时所需的脚本文件,如无特殊需求无需修改
lib:SDK的库文件
third_party:第三方开源库(源码),编译后会放到lib目录
tools:编译bin文件所需的工具,无需修改
Makefile:编译入口脚本(执行make时所执行的文件)
bin文件
文件列表 是否必选 说明 Non-FOTA FOTA
master_device_key.bin 可选 乐鑫云服务,在Espressif Cloud申请 ☑ ☑
esp_init_data_default.bin 必选 初始化射频参数,SDK提供 ☑ ☑
blank.bin 必选 初始化系统参数,SDK提供 ☑ ☑
eagle.flash.bin 必选 主程序,编译生成(app=0) ☑ ❌
eagle.irom0text.bin 必选 主程序,编译生成(app=0) ☑ ❌
user1.bin 初次必选 主程序,编译生成(app=1) ❌ ☑
user2.bin FOTA升级 主程序,编译生成(app=2) ❌ ☑
(文件名不一定相同)
注:user1.bin和user2.bin实际上除了烧录位置不同其它是几乎是一样的,因为在线升级时没有数据缓存位置,所以下载的数据是直接写入flash的,但又不能覆盖当前程序(否则升级一半掉电就无法开机了),所以user1.bin程序FOTA升级使用user2.bin,user2.bin程序FOTA升级使用user1.bin。第一次烧录使用user1.bin。

Flash布局
以下为Flash使用布局,可以根据需要进行修改,这里大致了解一下就可以。

Flash布局
系统程序:程序固件
用户数据:未使用的Flash部分可以给用户自行存储用户数据
用户参数:地址可自定义,IOT_Demo设置为0x3C000开始的4个扇区(master_device_key.bin放在第三个扇区)
系统参数:固定为Flash最后的4个扇区(blank.bin放在倒数第2、1扇区,esp_init_data_default.bin放在倒数第4、3扇区)
Boot信息:FOTA升级相关信息
预留:与Boot信息区对应的预留部分
注:一个扇区为4kb(Byte)
编译过程
如果要了解一个工程的结构,那么从工程的编译来看是最为深刻的,那么了解这个编译过程有什么用?老实说,并没有什么卵用。那为什么还要写这部分?因为我就想把文章写的长一点,啊哈哈哈哈。。。

入口脚本
看过官方的文档都知道,我们编译项目是要进入app这个目录然后执行gen_misc.bat这个文件(Linux下是gen_misc.sh)来编译的,那么我们就从这个文件下刀吧:

@echo off

Rem NOTICE
Rem MUST set SDK_PATH & BIN_PATH firstly!!!
Rem example:
Rem set SDK_PATH=/c/esp_iot_sdk_freertos
Rem set BIN_PATH=/c/esp8266_bin

set SDK_PATH=/c/ESP8266_RTOS_SDK
set BIN_PATH=/c/ESP8266_BIN

echo gen_misc.bat version 20150911
echo .

if not %SDK_PATH% == “” (
echo SDK_PATH: %SDK_PATH%
) else (
echo ERROR: Please set SDK_PATH in gen_misc.bat firstly, exit!!!
goto end
)

if not %BIN_PATH% == “” (
echo BIN_PATH: %BIN_PATH%
) else (
echo ERROR: Please set BIN_PATH in gen_misc.bat firstly, exit!!!
goto end
)

echo .
echo Please check SDK_PATH/BIN_PATH, enter (Y/y) to continue:
set input=default
set /p input=

if not %input% == Y (
if not %input% == y (
goto end
)
)
文件开头这部分,很简单,这里设置SDK_PATH和BIN_PATH两个变量(官方文档也会叫你先改这两个值后在编译),如果没设置就报错,结束编译。什么?你问我Rem是什么意思?那只是注释啦(好学的孩子可以出门左拐看看windows批处理,这里就简单带过了)。

echo .
echo Please follow below steps(1-5) to generate specific bin(s):
echo STEP 1: use boot_v1.2+ by default
set boot=new

echo boot mode: %boot%
echo.

echo STEP 2: choose bin generate(0=eagle.flash.bin+eagle.irom0text.bin, 1=user1.bin, 2=user2.bin)
set input=default
set /p input=enter (0/1/2, default 0):

-----------------------------------这里省略部分代码---------------------------------------

echo.
echo start…
echo.
这部分有点长,中间略写了,就是分5步用选择的方式定义了boot、app、spi_speed、spi_mode和spi_size_map这五个变量。

make clean

make COMPILE=xcc BOOT=%boot% APP=%app% SPI_SPEED=%spi_speed% SPI_MODE=%spi_mode% SPI_SIZE_MAP=%spi_size_map%

:end
看结尾这部分,首先先执行了make clean清除构建,然后进行make编译,把上面五个变量传递进去,make执行的即当前目录下的Makefile文件。
啥?你说:end又是啥?这还是一个注释啦,啊哈哈哈哈哈哈哈哈
最后这一小部分则是这个文件最关键的,给后面make操作提供了参数(COMPILE、BOOT、APP、SPI_SPEED、SPI_MODE和SPI_SIZE_MAP)。gen_misc.sh类似区别在于使用的脚本语音不同,最后参数就COMPILE不一样(用于选择编译器的)。

入口Makefile
看Makefile可以对照《跟我一起写Makefile》或者我的一起来看神奇的Makefile

TARGET = eagle
#FLAVOR = release
FLAVOR = debug

#EXTRA_CCFLAGS += -u
parent_dir:=$(abspath ( s h e l l p w d ) / (shell pwd)/ (shellpwd)/(lastword ( M A K E F I L E L I S T ) ) ) p a r e n t d i r : = (MAKEFILE_LIST))) parent_dir:= (MAKEFILEL​IST)))parentd​ir:=(shell dirname ( p a r e n t d i r ) ) p a r e n t d i r : = (parent_dir)) parent_dir:= (parentd​ir))parentd​ir:=(shell dirname $(parent_dir))

SDK_PATH= ( p a r e n t d i r ) B I N P A T H = (parent_dir) BIN_PATH= (parentd​ir)BINP​ATH=(SDK_PATH)/bin
开头定义了两个变量TARGET和FLAVOR表示编译的目标和版本,接下来的parent_dir比较有意思,从字面上看是父路径的意思,猜测就是当前的上一级也就是工程根目录,但这里采用了一个很复杂的方式取得:先从MAKEFILE_LIST取最后一个词(也就是当前Makefile的文件名),加上pwd取得当前路径,然后再取绝对路径。而后又连续取两次目录名(去掉两级路径)也就是当前目录的上一级,可绕脑了,这是想让看Makefile的小朋友望而怯步吗。。。
接下来主要的还是定义SDK_PATH和BIN_PATH两个目录(工程根目录和bin目录)

ifndef PDIR # {
GEN_IMAGES= eagle.app.v6.out
GEN_BINS= eagle.app.v6.bin
SPECIAL_MKTARGETS=$(APP_MKTARGETS)
SUBDIRS=
user
driver

endif # } PDIR
这里PDIR没有定义,为什么?因为我们一路看下来并没有发现哪里有定义啊!
这里定义了SUBDIRS变量,记住它。

LDDIR = $(SDK_PATH)/ld

CCFLAGS += -Os

TARGET_LDFLAGS =
-nostdlib
-Wl,-EL
–longcalls
–text-section-literals

ifeq ($(FLAVOR),debug)
TARGET_LDFLAGS += -g -O2
endif

ifeq ($(FLAVOR),release)
TARGET_LDFLAGS += -g -O0
endif
定义了几个变量LDDIR(ld文件目录)、CCFLAGS(编译参数)和TARGET_LDFLAGS(链接参数),这里上面定义的FLAVOR变量已经使用上了。

COMPONENTS_eagle.app.v6 =
user/libuser.a
driver/libdriver.a

LINKFLAGS_eagle.app.v6 =
-LKaTeX parse error: Expected 'EOF', got '#' at position 24: …H)/lib \ #̲ 定义链接库的搜索路径是 SD…(LD_FILE) \ # 读取链接描述脚本,以确定符号等的定位地址
-Wl,–no-check-sections \ # Do not check section addresses for overlaps 不检查重叠地址
-u call_user_start \ # 取消定义的宏(call_user_start)
-Wl,-static \ # 使用静态链接
-Wl,–start-group \ #库列表开始
-lcirom
-lcrypto
-lespconn
-lespnow
-lfreertos
-lgcc
-lhal
-ljson
-llwip
-lmain
-lmesh
-lmirom
-lnet80211
-lnopoll
-lphy
-lpp
-lpwm
-lsmartconfig
-lspiffs
-lssl
-lwpa
-lwps
$(DEP_LIBS_eagle.app.v6)
-Wl,–end-group # 库列表结束

DEPENDS_eagle.app.v6 =
$(LD_FILE)
$(LDDIR)/eagle.rom.addr.v6.ld
定义三个变量COMPONENTS_eagle.app.v6(需要生成的目标)、LINKFLAGS_eagle.app.v6(链接库)和DEPENDS_eagle.app.v6(ld文件)。LINKFLAGS_eagle.app.v6中-Wl,–start-group前面的为链接参数和-Wl,–end-group间为链接库,可以根据需要进行删减。

CONFIGURATION_DEFINES = -DICACHE_FLASH

DEFINES +=
$(UNIVERSAL_TARGET_DEFINES)
$(CONFIGURATION_DEFINES)

DDEFINES +=
$(UNIVERSAL_TARGET_DEFINES)
$(CONFIGURATION_DEFINES)
定义DEFINES和DDEFINES,给编译用。两个值都是"-DICACHE_FLASH"具体做啥用我也不清楚,字面上看应该是Flash的cache缓存相关的。

INCLUDES := $(INCLUDES) -I $(PDIR)include
sinclude $(SDK_PATH)/Makefile

.PHONY: FORCE
FORCE:
最后给INCLUDES添加了"include"目录然后调用根目录的Makefile文件(这里只是展开文件并没有切换目录,还是在app目录下执行),最后两行是定义了一个FORCE的伪目标,啥都没做。
这里只要记住这个Makefile文件定义了SUBDIRS、COMPONENTS_eagle.app.v6、LINKFLAGS_eagle.app.v6和DEPENDS_eagle.app.v6这几个变量即可。

主Makefile
这个文件是主要的编译文件,主要是具体的编译,比较长,这里只取较为关键的部分。

ifeq ($(COMPILE), xcc)
AR = xt-ar
CC = xt-xcc
NM = xt-nm
CPP = xt-xt++
OBJCOPY = xt-objcopy
OBJDUMP = xt-objdump
else
AR = xtensa-lx106-elf-ar
CC = xtensa-lx106-elf-gcc
NM = xtensa-lx106-elf-nm
CPP = xtensa-lx106-elf-g++
OBJCOPY = xtensa-lx106-elf-objcopy
OBJDUMP = xtensa-lx106-elf-objdump
endif
根据COMPILE选择编译器,从开头的脚本克制Windows使用xcc,Linux使用gcc。实际测试在windows下使用gcc也似乎并没有问题。

BOOT?=new
APP?=1
SPI_SPEED?=40
SPI_MODE?=QIO
SPI_SIZE_MAP?=2
设置参数的默认值,这里说一下上一节我们编译机智云的工程为啥使用不一样的方式,原因就是机智云的工程里这里的默认值是不一样的,我们直接使用了make而没有传入参数,所以会导致编辑结果不一样,事实上也无需关心这部分,我们只要给它传参就可以了,开篇只是作为验证编译器是否正常而已。
后续一百多行的脚本根据这几个变量定义了boot、app、freqdiv、mode、addr、size_map、flash、LD_FILE以及BIN_NAME,比较简单这里不赘述。

CSRCS ?= $(wildcard *.c) # $(wildcard xxx)这个意思是在当前目录下使用通配符列出所有文件
CPPSRCS ?= $(wildcard *.cpp)
ASRCs ?= $(wildcard *.s)
ASRCS ?= $(wildcard *.S)
SUBDIRS ?= ( p a t s u b s t (patsubst %/,%, (patsubst(dir $(wildcard */Makefile)))

ODIR := .output
OBJODIR := ( O D I R ) / (ODIR)/ (ODIR)/(TARGET)/$(FLAVOR)/obj

OBJS := ( C S R C S : (CSRCS:%.c= (CSRCS:(OBJODIR)/%.o)
( C P P S R C S : (CPPSRCS:%.cpp= (CPPSRCS:(OBJODIR)/%.o)
( A S R C s : (ASRCs:%.s= (ASRCs:(OBJODIR)/%.o)
( A S R C S : (ASRCS:%.S= (ASRCS:(OBJODIR)/%.o)

DEPS := ( C S R C S : (CSRCS:%.c= (CSRCS:(OBJODIR)/%.d)
( C P P S R C S : (CPPSRCS:%.cpp= (CPPSRCS:(OBJODIR)/%.d)
( A S R C s : (ASRCs:%.s= (ASRCs:(OBJODIR)/%.d)
( A S R C S : (ASRCS:%.S= (ASRCS:(OBJODIR)/%.d)

LIBODIR := ( O D I R ) / (ODIR)/ (ODIR)/(TARGET)/$(FLAVOR)/lib
OLIBS := ( G E N L I B S : (GEN_LIBS:%= (GENL​IBS:(LIBODIR)/%)

IMAGEODIR := ( O D I R ) / (ODIR)/ (ODIR)/(TARGET)/$(FLAVOR)/image
OIMAGES := ( G E N I M A G E S : (GEN_IMAGES:%= (GENI​MAGES:(IMAGEODIR)/%)

BINODIR := ( O D I R ) / (ODIR)/ (ODIR)/(TARGET)/$(FLAVOR)/bin
OBINS := ( G E N B I N S : (GEN_BINS:%= (GENB​INS:(BINODIR)/%)
定义了一些变量,后续会反复使用的一些文件,这里将文件赋值给变量后续操作就方便了比如说编译跟clean就会用到一大堆相同的.o文件。
这里有一个有趣的地方,就是SUBDIRS ?= ( p a t s u b s t (patsubst %/,%, (patsubst(dir $(wildcard */Makefile)))如果你是在app目录进行make,那么app目录下的make文件会定义SUBDIRS,如果在根目录下直接make这个不会定义,然后就会执行这一句,接着就会把app这个目录包含进来,最后编译的时候就会编译到app目录的Makefile定义SUBDIRS最后又会回到这里。
( C S R C S : (CSRCS:%.c= (CSRCS:(OBJODIR)/%.d)意思是把CSRCS中的.c全部替换成$(OBJODIR)/.d具体为什么是这样写的,我只能说:你猜。Makefile真是一个神奇的东西。

CCFLAGS +=
-g
-Wpointer-arith
-Wundef
-Werror
-Wl,-EL
-fno-inline-functions
-nostdlib
-mlongcalls
-mtext-section-literals
-ffunction-sections
-fdata-sections
-fno-builtin-printf

-Wall

CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(INCLUDES)
DFLAGS = $(CCFLAGS) $(DDEFINES) $(EXTRA_CCFLAGS) $(INCLUDES)
一堆编译参数放到CFLAGS和DFLAGS这两个变量里面。
接下来就是关键的编译部分的代码,这里先跳过编译的代码回头再来看,先看脚本

ifneq ( ( M A K E C M D G O A L S ) , c l e a n ) i f n e q ( (MAKECMDGOALS),clean) ifneq ( (MAKECMDGOALS),clean)ifneq((MAKECMDGOALS),clobber)
ifdef DEPS
sinclude $(DEPS)
endif
endif
endif
这段代码比较典型,MAKECMDGOALS并没有定义,所以会执行sinclude $(DEPS),DEPS根据前面的定义可以知道是当前目录下的源文件(.c .cpp .s)生成的.d,根据.d文件的生成规则可以知道是使用gcc -M编译得到,即对应.o的依赖关系,包括包含的.h(新建a.c文件仅写一个a.h,新建a.h放空,编译后的a.d为.output/eagle/debug/obj/a.o .output/eagle/debug/obj/a.d : a.c a.h),sinclude $(DEPS)就是将这个.d文件展开,意义在于我们写依赖关系的时候我们并不能把源文件里面引用的.h文件都加到依赖关系里面,如果不加进来,那么仅修改.h的不会重新生成.o文件的。
简单一句话就是.c文件中包含的.h发生改变的时候重新生成对应的.o

define ShortcutRule
$(1): .subdirs ( 2 ) / (2)/ (2)/(1)
endef

define MakeLibrary
DEP_LIBS_$(1) = ( f o r e a c h l i b , (foreach lib, (foreachlib,(filter %.a,KaTeX parse error: Got function '$' with no arguments as subscript at position 13: (COMPONENTS_$̲(1))),(dir ( l i b ) ) (lib)) (lib))(LIBODIR)/ ( n o t d i r (notdir (notdir(lib)))
DEP_OBJS_$(1) = ( f o r e a c h o b j , (foreach obj, (foreachobj,(filter %.o,KaTeX parse error: Got function '$' with no arguments as subscript at position 13: (COMPONENTS_$̲(1))),(dir ( o b j ) ) (obj)) (obj))(OBJODIR)/ ( n o t d i r (notdir (notdir(obj)))
KaTeX parse error: Can't use function '$' in math mode at position 11: (LIBODIR)/$̲(1).a: (OBJS) KaTeX parse error: Got function '$' with no arguments as subscript at position 11: (DEP_OBJS_$̲(1)) (DEP_LIBS_$(1)) KaTeX parse error: Got function '$' with no arguments as subscript at position 10: (DEPENDS_$̲(1)) @mkdir…(LIBODIR)
( i f (if (if(filter %.a, ? ) , m k d i r − p ?),mkdir -p ?),mkdir−p(EXTRACT_DIR)$(1)) ( i f (if (if(filter %.a, ? ) , c d ?),cd ?),cd(EXTRACT_DIR)$(1); ( f o r e a c h l i b , (foreach lib, (foreachlib,(filter %.a, ? ) , ?), ?),(AR) xo ( U P E X T R A C T D I R ) / (UP_EXTRACT_DIR)/ (UPE​XTRACTD​IR)/(lib)😉)
( A R ) r u (AR) ru (AR)ru@ ( f i l t e r (filter %.o, (filter?) ( i f (if (if(filter %.a, ? ) , ?), ?),(EXTRACT_DIR)_$(1)/*.o)
( i f (if (if(filter %.a, ? ) , ?), ?),(RM) -r $KaTeX parse error: Expected group after '_' at position 14: (EXTRACT_DIR)_̲(1))
endef

define MakeImage
DEP_LIBS_$(1) = ( f o r e a c h l i b , (foreach lib, (foreachlib,(filter %.a,KaTeX parse error: Got function '$' with no arguments as subscript at position 13: (COMPONENTS_$̲(1))),(dir ( l i b ) ) (lib)) (lib))(LIBODIR)/ ( n o t d i r (notdir (notdir(lib)))
DEP_OBJS_$(1) = ( f o r e a c h o b j , (foreach obj, (foreachobj,(filter %.o,KaTeX parse error: Got function '$' with no arguments as subscript at position 13: (COMPONENTS_$̲(1))),(dir ( o b j ) ) (obj)) (obj))(OBJODIR)/ ( n o t d i r (notdir (notdir(obj)))
KaTeX parse error: Can't use function '$' in math mode at position 13: (IMAGEODIR)/$̲(1).out: (OBJS) KaTeX parse error: Got function '$' with no arguments as subscript at position 11: (DEP_OBJS_$̲(1)) (DEP_LIBS_$(1)) KaTeX parse error: Got function '$' with no arguments as subscript at position 10: (DEPENDS_$̲(1)) @mkdir…(IMAGEODIR)
( C C ) (CC) (CC)(LDFLAGS) ( i f (if (if(LINKFLAGS_ ( 1 ) ) , (1)), (1)),KaTeX parse error: Expected group after '_' at position 11: (LINKFLAGS_̲(1)), ( L I N K F L A G S D E F A U L T ) (LINKFLAGS_DEFAULT) (LINKFLAGSD​EFAULT)(OBJS) KaTeX parse error: Got function '$' with no arguments as subscript at position 11: (DEP_OBJS_$̲(1)) (DEP_LIBS_$(1))) -o $$@
endef

--------------------------------------------------------跳过若干行代码----------------------------------------------

( f o r e a c h l i b , (foreach lib, (foreachlib,(GEN_LIBS),$(eval ( c a l l S h o r t c u t R u l e , (call ShortcutRule, (callShortcutRule,(lib),$(LIBODIR))))

( f o r e a c h i m a g e , (foreach image, (foreachimage,(GEN_IMAGES),$(eval ( c a l l S h o r t c u t R u l e , (call ShortcutRule, (callShortcutRule,(image),$(IMAGEODIR))))

( f o r e a c h b i n , (foreach bin, (foreachbin,(GEN_BINS),$(eval ( c a l l S h o r t c u t R u l e , (call ShortcutRule, (callShortcutRule,(bin),$(BINODIR))))

( f o r e a c h l i b , (foreach lib, (foreachlib,(GEN_LIBS),$(eval ( c a l l M a k e L i b r a r y , (call MakeLibrary, (callMakeLibrary,(basename $(lib)))))

( f o r e a c h i m a g e , (foreach image, (foreachimage,(GEN_IMAGES),$(eval ( c a l l M a k e I m a g e , (call MakeImage, (callMakeImage,(basename ( i m a g e ) ) ) ) ) 前 面 部 分 定 义 了 S h o r t c u t R u l e 、 M a k e L i b r a r y 和 M a k e I m a g e 三 个 函 数 , 后 面 部 分 则 调 用 这 三 个 函 数 来 生 成 。 首 先 看 后 面 这 五 个 f o r e a c h 循 环 , 以 第 一 个 为 例 : 取 G E N L I B S 的 值 代 入 (image))))) 前面部分定义了ShortcutRule、MakeLibrary和MakeImage三个函数,后面部分则调用这三个函数来生成。 首先看后面这五个foreach循环,以第一个为例:取GEN_LIBS的值代入 (image)))))前面部分定义了ShortcutRule、MakeLibrary和MakeImage三个函数,后面部分则调用这三个函数来生成。首先看后面这五个foreach循环,以第一个为例:取GENL​IBS的值代入(eval ( c a l l S h o r t c u t R u l e , (call ShortcutRule, (callShortcutRule,(lib), ( L I B O D I R ) ) ) 执 行 , S h o r t c u t R u l e 函 数 为 生 成 一 个 规 则 , 第 一 个 参 数 依 赖 于 . s u b d i r s 和 第 二 个 参 数 和 第 一 个 参 数 组 成 的 文 件 路 径 , 即 : (LIBODIR)))执行,ShortcutRule函数为生成一个规则,第一个参数依赖于.subdirs和第二个参数和第一个参数组成的文件路径,即: (LIBODIR)))执行,ShortcutRule函数为生成一个规则,第一个参数依赖于.subdirs和第二个参数和第一个参数组成的文件路径,即:(lib): .subdirs ( L I B O D I R ) / (LIBODIR)/ (LIBODIR)/(lib), ( e v a l t e x t ) 函 数 是 将 t e x t 放 到 M a k e f i l e 中 重 新 解 析 , 也 就 是 说 l i b 这 个 目 标 依 赖 于 输 出 目 录 下 的 同 名 文 件 , 再 简 化 一 下 就 是 l i b 这 个 目 标 就 是 要 生 成 输 出 目 录 下 的 同 名 l i b 文 件 ( 这 个 保 留 怀 疑 , 因 为 测 试 输 出 在 / 和 (eval text)函数是将text放到Makefile中重新解析,也就是说lib这个目标依赖于输出目录下的同名文件,再简化一下就是lib这个目标就是要生成输出目录下的同名lib文件(这个保留怀疑,因为测试输出在/和 (evaltext)函数是将text放到Makefile中重新解析,也就是说lib这个目标依赖于输出目录下的同名文件,再简化一下就是lib这个目标就是要生成输出目录下的同名lib文件(这个保留怀疑,因为测试输出在/和(lib)之间会出现一个空格)。这里还依赖一个.subdirs目标:

.subdirs:
@set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C ( d ) ; ) s e t − e 表 示 后 面 操 作 如 果 出 错 就 停 止 编 译 , 取 出 S U B D I R S 后 执 行 m a k e − C , S U B D I R S 是 在 我 们 入 口 M a k e f i l e ( a p p 目 录 下 的 M a k e f i l e ) 中 定 义 的 。 m a k e − C d i r 表 示 在 d i r 目 录 下 执 行 m a k e 。 简 单 的 说 就 是 在 S U B D I R S 目 录 各 执 行 一 次 m a k e 。 M a k e L i b r a r y 和 M a k e I m a g e 类 似 的 , 有 一 点 就 是 (d);) set -e表示后面操作如果出错就停止编译,取出SUBDIRS后执行make -C,SUBDIRS是在我们入口Makefile(app目录下的Makefile)中定义的。make -C dir表示在dir目录下执行make。简单的说就是在SUBDIRS目录各执行一次make。 MakeLibrary和MakeImage类似的,有一点就是 (d);)set−e表示后面操作如果出错就停止编译,取出SUBDIRS后执行make−C,SUBDIRS是在我们入口Makefile(app目录下的Makefile)中定义的。make−Cdir表示在dir目录下执行make。简单的说就是在SUBDIRS目录各执行一次make。MakeLibrary和MakeImage类似的,有一点就是 表 示 转 义 一 个 表示转义一个 表示转义一个。结果就是生成GEN_LIBS、GEN_IMAGES和GEN_BINS中存放的文件。具体生成原理可以推敲一下那三个函数。
最后看一下目标规则部分

$(BINODIR)/%.bin: $(IMAGEODIR)/%.out
@mkdir -p $(BIN_PATH)
@mkdir -p $(BINODIR)

ifeq ( ( A P P ) , 0 ) @ (APP), 0) @ (APP),0)@(RM) -r $(BIN_PATH)/eagle.S ( B I N P A T H ) / e a g l e . d u m p @ (BIN_PATH)/eagle.dump @ (BINP​ATH)/eagle.dump@(OBJDUMP) -x -s $< > ( B I N P A T H ) / e a g l e . d u m p @ (BIN_PATH)/eagle.dump @ (BINP​ATH)/eagle.dump@(OBJDUMP) -S $< > $(BIN_PATH)/eagle.S
else
@mkdir -p ( B I N P A T H ) / u p g r a d e @ (BIN_PATH)/upgrade @ (BINP​ATH)/upgrade@(RM) -r ( B I N P A T H ) / u p g r a d e / (BIN_PATH)/upgrade/ (BINP​ATH)/upgrade/(BIN_NAME).S ( B I N P A T H ) / u p g r a d e / (BIN_PATH)/upgrade/ (BINP​ATH)/upgrade/(BIN_NAME).dump
@$(OBJDUMP) -x -s $< > ( B I N P A T H ) / u p g r a d e / (BIN_PATH)/upgrade/ (BINP​ATH)/upgrade/(BIN_NAME).dump
@$(OBJDUMP) -S $< > ( B I N P A T H ) / u p g r a d e / (BIN_PATH)/upgrade/ (BINP​ATH)/upgrade/(BIN_NAME).S
endif

@$(OBJCOPY) --only-section .text -O binary $< eagle.app.v6.text.bin
@$(OBJCOPY) --only-section .data -O binary $< eagle.app.v6.data.bin
@$(OBJCOPY) --only-section .rodata -O binary $< eagle.app.v6.rodata.bin
@$(OBJCOPY) --only-section .irom0.text -O binary $< eagle.app.v6.irom0text.bin

@echo ""
@echo "!!!"
@echo "SDK_PATH: $(SDK_PATH)"

ifeq ($(app), 0)
@python $(SDK_PATH)/tools/gen_appbin.py $< 0 $(mode) $(freqdiv) $(size_map) @mv eagle.app.flash.bin $(BIN_PATH)/eagle.flash.bin @mv eagle.app.v6.irom0text.bin $(BIN_PATH)/eagle.irom0text.bin @rm eagle.app.v6.* @echo “BIN_PATH: $(BIN_PATH)” @echo “” @echo “No boot needed.” @echo “Generate eagle.flash.bin and eagle.irom0text.bin successully in BIN_PATH” @echo “eagle.flash.bin-------->0x00000”
@echo “eagle.irom0text.bin---->0x20000”
else
@echo “BIN_PATH: $(BIN_PATH)/upgrade”
@echo “”

ifneq ($(boot), new)
    @python $(SDK_PATH)/tools/gen_appbin.py $< 1 $(mode) $(freqdiv) $(size_map)
    @echo "Support boot_v1.1 and +"
else
    @python $(SDK_PATH)/tools/gen_appbin.py $< 2 $(mode) $(freqdiv) $(size_map)

    ifeq ($(size_map), 6)
    @echo "Support boot_v1.4 and +"
    else
        ifeq ($(size_map), 5)
    @echo "Support boot_v1.4 and +"
        else
    @echo "Support boot_v1.2 and +"
        endif
    endif
endif

@mv eagle.app.flash.bin $(BIN_PATH)/upgrade/$(BIN_NAME).bin
@rm eagle.app.v6.*
@echo "Generate $(BIN_NAME).bin successully in BIN_PATH"
@echo "boot.bin------------>0x00000"
@echo "$(BIN_NAME).bin--->$(addr)"

endif

@echo "!!!"

#############################################################

Rules base

Should be done in top-level makefile only

all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)

clean:
$(foreach d, $(SUBDIRS), $(MAKE) -C $(d) clean;)
$(RM) -r ( O D I R ) / (ODIR)/ (ODIR)/(TARGET)/$(FLAVOR)

clobber: $(SPECIAL_CLOBBER)
$(foreach d, $(SUBDIRS), $(MAKE) -C $(d) clobber;)
$(RM) -r ( O D I R ) 这 是 我 们 编 译 要 生 成 的 目 标 , 直 接 m a k e 默 认 是 生 成 a l l 这 个 目 标 , 原 因 是 默 认 生 成 第 一 个 目 标 , 也 许 你 会 问 第 一 个 目 标 不 是 最 前 面 的 (ODIR) 这是我们编译要生成的目标,直接make默认是生成all这个目标,原因是默认生成第一个目标,也许你会问第一个目标不是最前面的 (ODIR)这是我们编译要生成的目标,直接make默认是生成all这个目标,原因是默认生成第一个目标,也许你会问第一个目标不是最前面的(BINODIR)/%.bin么?这个我还查了一下,百度无果,在GNU官网上找到这样一段话:
默认目标

划红线的意思是,默认取第一个目标,但有两个除外,一个是模式规则(pattern rule)的目标。$(BINODIR)/%.bin正是模式规则,所以这里默认目标为all。
我们看一下这个这个目标all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) ( S P E C I A L M K T A R G E T S ) . s u b d i r s 上 面 已 经 说 过 了 , 是 到 子 目 录 ( S U B D I R S ) 下 进 行 m a k e 操 作 ; (SPECIAL_MKTARGETS) .subdirs 上面已经说过了,是到子目录(SUBDIRS)下进行make操作; (SPECIALM​KTARGETS).subdirs上面已经说过了,是到子目录(SUBDIRS)下进行make操作;(OBJS) $(OLIBS) $(OIMAGES)使用对应的规则生成对应的文件;
( O B I N S ) 使 用 (OBINS) 使用 (OBINS)使用(BINODIR)/%.bin: ( I M A G E O D I R ) / ( (IMAGEODIR)/%.out这个规则生成最终的bin文件: ( (IMAGEODIR)/(@–目标文件, − − 所 有 的 依 赖 文 件 , ^--所有的依赖文件, −−所有的依赖文件,<–第一个依赖文件)
首先创建bin目录,将生成文件进行反编译(反编译这个做啥。。。),拷贝bin文件。
记住拷贝的这四个bin文件eagle.app.v6.text.bin、eagle.app.v6.data.bin、eagle.app.v6.rodata.bin和eagle.app.v6.irom0text.bin
然后调用gen_appbin.py(天啊,又要看一门语音–Python)这个脚本(传入依赖文件、app、mode、freqdiv和size_map)
最后重命名bin文件。至此整个编译工作已经结束了,接下来我们来看一下子目录里的Makefile。
INCLUDES := $(INCLUDES) -I $(SDK_PATH)/include -I $(SDK_PATH)/extra_include
INCLUDES += -I $(SDK_PATH)/driver_lib/include
INCLUDES += -I $(SDK_PATH)/include/espressif
INCLUDES += -I $(SDK_PATH)/include/lwip
INCLUDES += -I $(SDK_PATH)/include/lwip/ipv4
INCLUDES += -I $(SDK_PATH)/include/lwip/ipv6
INCLUDES += -I $(SDK_PATH)/include/nopoll
INCLUDES += -I $(SDK_PATH)/include/spiffs
INCLUDES += -I $(SDK_PATH)/include/ssl
INCLUDES += -I $(SDK_PATH)/include/json
这个不需要讲吧,头文件目录

子Makefile
随便拷贝了app/user目录下的Makefile出来,每个子目录的Makefile都差不多的:

#############################################################

Required variables for each makefile

Discard this section from all parent makefiles

Expected variables (with automatic defaults):

CSRCS (all “C” files in the dir)

SUBDIRS (all subdirs with a Makefile)

GEN_LIBS - list of libs to be generated ()

GEN_IMAGES - list of images to be generated ()

COMPONENTS_xxx - a list of libs/objs in the form

subdir/lib to be extracted and rolled up into

a generated lib/image xxx.a ()

ifndef PDIR
GEN_LIBS = libuser.a
endif

#############################################################

Configuration i.e. compile options etc.

Target specific stuff (defines etc.) goes in here!

Generally values applying to a tree are captured in the

makefile at its root level - these are then overridden

for a subtree within the makefile rooted therein

#DEFINES +=

#############################################################

Recursion Magic - Don’t touch this!!

Each subtree potentially has an include directory

corresponding to the common APIs applicable to modules

rooted at that subtree. Accordingly, the INCLUDE PATH

of a module can only contain the include directories up

its parent path, and not its siblings

Required for each makefile to inherit from the parent

INCLUDES := $(INCLUDES) -I ( P D I R ) i n c l u d e I N C L U D E S + = − I . / P D I R : = . . / (PDIR)include INCLUDES += -I ./ PDIR := ../ (PDIR)includeINCLUDES+=−I./PDIR:=../(PDIR)
sinclude $(PDIR)Makefile
看注释实际上就差不多知道怎么弄了,刚开始PDIR并没有定义,所以定义了GEN_LIBS,然后添加头文件路径定义PDIR,最后再展开主Makefile,主Makefile就会对GEN_LIBS进行编译。
简单是说就是GEN_LIBS赋值为我们要的lib名字,将写好的源文件放到子目录里面,然后编译就可以了。

大蟒蛇
天啊,分析个代码结构居然要看这么多语言,来吧,让暴风雨来的更猛烈些吧,打开上面提到的tools/gen_appbin.py:
注意:这个文件我们从主Makefile中传了5个参数进来(依赖文件、app、mode、freqdiv和size_map)

TEXT_ADDRESS = 0x40100000

app_entry = 0

data_address = 0x3ffb0000

data_end = 0x40000000

text_end = 0x40120000

CHECKSUM_INIT = 0xEF

chk_sum = CHECKSUM_INIT
blocks = 0
开头,定义了几个变量

def write_file(file_name,data):
if file_name is None:
print ‘file_name cannot be none\n’
sys.exit(0)

fp = open(file_name,'ab')

if fp:
    fp.seek(0,os.SEEK_END)
    fp.write(data)
    fp.close()
else:
    print '%s write fail\n'%(file_name)

定义write_file函数,将data数据追加文件末尾,这么简单的代码如果看不懂不要跟我说你是干程序的哟。

def combine_bin(file_name,dest_file_name,start_offset_addr,need_chk):
global chk_sum
global blocks
if dest_file_name is None:
print ‘dest_file_name cannot be none\n’
sys.exit(0)

if file_name:
    fp = open(file_name,'rb')
    if fp:
        ########## write text ##########
        fp.seek(0,os.SEEK_END)
        data_len = fp.tell()
        if data_len:
    if need_chk:
                tmp_len = (data_len + 3) & (~3)
    else:
            tmp_len = (data_len + 15) & (~15)
            data_bin = struct.pack('<II',start_offset_addr,tmp_len)
            write_file(dest_file_name,data_bin)
            fp.seek(0,os.SEEK_SET)
            data_bin = fp.read(data_len)
            write_file(dest_file_name,data_bin)
    if need_chk:
        for loop in range(len(data_bin)):
            chk_sum ^= ord(data_bin[loop])
            # print '%s size is %d(0x%x),align 4 bytes,\nultimate size is %d(0x%x)'%(file_name,data_len,data_len,tmp_len,tmp_len)
            tmp_len = tmp_len - data_len
            if tmp_len:
                data_str = ['00']*(tmp_len)
                data_bin = binascii.a2b_hex(''.join(data_str))
                write_file(dest_file_name,data_bin)
        if need_chk:
        for loop in range(len(data_bin)):
            chk_sum ^= ord(data_bin[loop])
            blocks = blocks + 1
        fp.close()
    else:
        print '!!!Open %s fail!!!'%(file_name)

combine_bin函数,从名字上看就知道是合并两个bin文件的,这里是从file_name的start_offset_addr地址开始拷贝到dest_file_name末尾,need_chk表示是否进行四字节对齐检查。

def getFileCRC(_path):
try:
blocksize = 1024 * 64
f = open(_path,“rb”)
str = f.read(blocksize)
crc = 0
while(len(str) != 0):
crc = binascii.crc32(str, crc)
str = f.read(blocksize)
f.close()
except:
print ‘get file crc error!’
return 0
return crc
getFileCRC函数,生成文件的CRC校验值

def gen_appbin():
global chk_sum
global crc_sum
global blocks
if len(sys.argv) != 6: # 判断参数,默认一个加传递的五个
print ‘Usage: gen_appbin.py eagle.app.out boot_mode flash_mode flash_clk_div flash_size_map’
sys.exit(0)
# 保存参数
elf_file = sys.argv[1]
boot_mode = sys.argv[2]
flash_mode = sys.argv[3]
flash_clk_div = sys.argv[4]
flash_size_map = sys.argv[5]

flash_data_line  = 16
data_line_bits = 0xf
# bin文件
irom0text_bin_name = 'eagle.app.v6.irom0text.bin'
text_bin_name = 'eagle.app.v6.text.bin'
data_bin_name = 'eagle.app.v6.data.bin'
rodata_bin_name = 'eagle.app.v6.rodata.bin'
flash_bin_name ='eagle.app.flash.bin' # 要生成的目标文件

BIN_MAGIC_FLASH  = 0xE9 # 魔数(没写错别字呦)
BIN_MAGIC_IROM   = 0xEA
data_str = ''
sum_size = 0
# 列出依赖文件的符号清单->eagle.app.sym
if os.getenv('COMPILE')=='xcc' :
    cmd = 'xt-nm -g ' + elf_file + ' > eagle.app.sym'
else :
    cmd = 'xtensa-lx106-elf-nm -g ' + elf_file + ' > eagle.app.sym'

os.system(cmd)

fp = file('./eagle.app.sym')
if fp is None:
    print "open sym file error\n"
    sys.exit(0)
# 读取符号清单
lines = fp.readlines()
fp.close()
# 取得程序入口地址
entry_addr = None
p = re.compile('(\w*)(\sT\s)(call_user_start)$') # 编译正则表达式
for line in lines:
    m = p.search(line)
    if m != None:
        entry_addr = m.group(1)
        # print entry_addr

if entry_addr is None:
    print 'no entry point!!'
    sys.exit(0)
# 数据区起始地址
data_start_addr = '0'
p = re.compile('(\w*)(\sA\s)(_data_start)$')
for line in lines:
    m = p.search(line)
    if m != None:
        data_start_addr = m.group(1)
        # print data_start_addr
# 常量数据起始地址
rodata_start_addr = '0'
p = re.compile('(\w*)(\sA\s)(_rodata_start)$')
for line in lines:
    m = p.search(line)
    if m != None:
        rodata_start_addr = m.group(1)
        # print rodata_start_addr

# write flash bin header
#============================
#  SPI FLASH PARAMS
#-------------------
#flash_mode=
#     0: QIO
#     1: QOUT
#     2: DIO
#     3: DOUT
#-------------------
#flash_clk_div=
#     0 :  80m / 2
#     1 :  80m / 3
#     2 :  80m / 4
#    0xf:  80m / 1
#-------------------
#flash_size_map=
#     0 : 512 KB (256 KB + 256 KB)
#     1 : 256 KB
#     2 : 1024 KB (512 KB + 512 KB)
#     3 : 2048 KB (512 KB + 512 KB)
#     4 : 4096 KB (512 KB + 512 KB)
#     5 : 2048 KB (1024 KB + 1024 KB)
#     6 : 4096 KB (1024 KB + 1024 KB)
#-------------------
#   END OF SPI FLASH PARAMS
#============================
byte2=int(flash_mode)&0xff
byte3=(((int(flash_size_map)<<4)| int(flash_clk_div))&0xff)

if boot_mode == '2': # 这个就是我们Makefile中是app的值
    # write irom bin head
    data_bin = struct.pack('<BBBBI',BIN_MAGIC_IROM,4,byte2,byte3,long(entry_addr,16))
    sum_size = len(data_bin)
    write_file(flash_bin_name,data_bin) # 文件头

    # irom0.text.bin
    combine_bin(irom0text_bin_name,flash_bin_name,0x0,0) # 追加到eagle.app.flash.bin

data_bin = struct.pack('<BBBBI',BIN_MAGIC_FLASH,3,byte2,byte3,long(entry_addr,16))
sum_size = len(data_bin)
write_file(flash_bin_name,data_bin)

# text.bin
combine_bin(text_bin_name,flash_bin_name,TEXT_ADDRESS,1)

# data.bin
if data_start_addr:
    combine_bin(data_bin_name,flash_bin_name,long(data_start_addr,16),1)

# rodata.bin
combine_bin(rodata_bin_name,flash_bin_name,long(rodata_start_addr,16),1)

# write checksum header
sum_size = os.path.getsize(flash_bin_name) + 1
sum_size = flash_data_line - (data_line_bits&sum_size)
if sum_size:
    data_str = ['00']*(sum_size)
    data_bin = binascii.a2b_hex(''.join(data_str))
    write_file(flash_bin_name,data_bin)
write_file(flash_bin_name,chr(chk_sum & 0xFF)) # 校验和

if boot_mode == '1':
    sum_size = os.path.getsize(flash_bin_name)
    data_str = ['FF']*(0x10000-sum_size)
    data_bin = binascii.a2b_hex(''.join(data_str))
    write_file(flash_bin_name,data_bin)

    fp = open(irom0text_bin_name,'rb')
    if fp:
        data_bin = fp.read()
        write_file(flash_bin_name,data_bin)
        fp.close()
    else :
        print '!!!Open %s fail!!!'%(flash_bin_name)
        sys.exit(0)
if boot_mode == '1' or boot_mode == '2':
    all_bin_crc = getFileCRC(flash_bin_name)
    if all_bin_crc < 0:
        all_bin_crc = abs(all_bin_crc) - 1
    else :
        all_bin_crc = abs(all_bin_crc) + 1
    print "bin crc: %x"%all_bin_crc
    write_file(flash_bin_name,chr((all_bin_crc & 0x000000FF))+chr((all_bin_crc & 0x0000FF00) >> 8)+chr((all_bin_crc & 0x00FF0000) >> 16)+chr((all_bin_crc & 0xFF000000) >> 24))
cmd = 'rm eagle.app.sym'
os.system(cmd)

gen_appbin这就是我们的入口函数了,为什么?我们看最后的两行

if name==‘main’:
gen_appbin()
当我们执行一个python脚本的时候name就会是’main’,这里直接调用了gen_appbin()所以gen_appbin()就算是我们的入口函数了。代码中加了少量的注释,这个就是合并生产的几个bin文件为eagle.app.flash.bin。主Makefile最后会把这个文件重命名为对应的文件名。可见这个脚本是通用的脚本,没有太多深究的价值。

视乎漏了什么
入口Makefile的LD_FILE漏了有没有,这个在主Makefile的MakeImage函数使用了,这个是连接脚本用于生成bin文件时各个代码段分布的
一般代码片段分布

注:我们的工程有多部分代码(boot user1 user2),也就是有多个这样的分布。
那我们随便来看一个吧:
/* user1.bin @ 0x1000 */

/* Flash Map (512KB + 512KB), support 1MB/2MB/4MB SPI Flash / / |…|…|…|…|…|…|…|…| / / ^ ^ ^ ^ ^ ^ ^ ^ / / |_boot start(0x0000) | | |_pad start(0x80000) | | / / |_user1 start(0x1000) |_user1 end |_user2 start(0x81000) |_user2 end / / |_system param symmetric area(0x7b000) |_system param area(0xfb000) */

/* NOTICE: / / 1. You can change irom0 len, but MUST make sure user1 end not overlap system param symmetric area. / / 2. Space between user1 end and pad start can be used as user param area. / / 3. Don’t change any other seg. */

MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x18000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0x6B000
}

INCLUDE “…/ld/eagle.app.v6.common.ld”
这里先普及一下我们的内存结构,ARM架构的芯片地址是4个字节,也就是最大寻址为4GB。ram和rom共用这4GB的地址范围,所以这里要对这些地址进行分配,分配依据不是随便分的,要根据硬件实际挂载位置进行分配,不然访问就出错了。
ram这里分为两部分,一个是iram为内部的内存,只有32KB(0x8000),另一个是dram为挂载的内存,比较大(速度会慢点),有96KB,所以ESP8266整个ram就只有128KB(感觉好小啊)分别挂载在0x40100000和0x3FFE8000地址,dport是什么鬼我也不知道。
从注释中可以知道我们可以修改irom0,它的挂载地址为0x40201010,没猜错的话实际Flash的首地址应该是0x40200000,因为前面有boot区,这个链接文件是放user1.bin的,0x1010+0x6B000=432KB,这个脚本是1MB的Flash分两个区(512+512),剩余的就是数据区(参考我们最上面的Flash布局),所以这个调整最多调整到512KB满,否则就溢出了。烧录后软件运行就会出错。
接着看user2的分配:

/* user2.bin @ 0x81000 */

/* Flash Map (512KB + 512KB), support 1MB/2MB/4MB SPI Flash / / |…|…|…|…|…|…|…|…| / / ^ ^ ^ ^ ^ ^ ^ ^ / / |_boot start(0x0000) | | |_pad start(0x80000) | | / / |_user1 start(0x1000) |_user1 end |_user2 start(0x81000) |_user2 end / / |_system param symmetric area(0x7b000) |_system param area(0xfb000) */

/* NOTICE: / / 1. You can change irom0 len, but MUST make sure user2 end not overlap system param area. / / 2. Space between user2 end and system param area can be used as user param area. / / 3. Don’t change any other seg. */

MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x18000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40281010, len = 0x6B000
}

INCLUDE “…/ld/eagle.app.v6.common.ld”
这里dram和iram都是一样的,只有irom地址变化了,实际就是往后偏移了0x80000(512KB),这和我们最前面说的Flash布局一致。
最后展开了eagle.app.v6.common.ld这个文件,我们来看一下这个文件具体如何实行分配的:
额。。。
这个。。。
各位看官这么厉害自己都能看懂了吧,我就不多说了(我实在是看不懂了T.T)。

总结
原本就想写稍微长点,写着写着也忒长了。主要是讲了Flash分配以及编译过程。涉及较多语音,自己也是一知半解,我觉得Makefile写的也不好,使用类似递归的方式一直调用主Makefile来编译感觉很混乱(也可能是我比较菜b),用树形方式调用就好多了,不过不用担心,这些都不是必须的,只要知道这几点就够了:

入口Makefile(app/Makefile)中的FLAVOR用于控制软件是debug还是release
入口Makefile中的SUBDIRS用于选择编译的模块
入口Makefile中的COMPONENTS_eagle.app.v6用于选择加入最终的bin文件的模块(和上一条对应)
入口Makefile中的LINKFLAGS_eagle.app.v6用于选择SDK提供是库加入最终的bin文件
添加模块代码只要在app中创建一个文件夹和拷贝一份对应的Makefile修改GEN_LIBS的模块名
模块文件夹内的所有源文件都会自动加入编译
公共的头文件放在app/include下
需要修改Flash分配时可以修改eagle.app.v6.xxx.xxx.xxx.ld内的对应数据(irom),一般不要修改
清除编译使用make clean
编译时使用make COMPILE=gcc BOOT=new APP=1 SPI_SPEED=40 SPI_MODE=DIO SPI_SIZE_MAP=6(使用gen_misc.sh根本无法彰显我们的逼格)

3人点赞
智慧物联
“你就确定不打赏一下么.!”

赞赏支持

共2人赞赏

云华兄
沉迷于写代码,无法自拔
总资产76 (约7.65元)共写了2.2W字获得217个赞共372个粉丝

关注

全部评论
0
只看作者
按时间倒序
按时间正序
被以下专题收入,发现更多相似内容

智能家居

程序员
推荐阅读
更多精彩内容
ESP8266学习笔记(二)
上一周讲了ESP8266的初步开发,也就是AT模式工作,这种模式是最适合初学者使用的,因为wifi模块内部的函数都…

JaydenOnly
阅读 2,499
评论 3
赞 6

android系统定制从听说到入门二
Android程序员面试宝典 android系统定制系列: android系统定制从听说到入门一 android系…

马伟奇
阅读 8,980
评论 6
赞 30
Ubuntu完全教程,让你成为Ubuntu高手!
Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意…

萤火虫de梦
阅读 44,487
评论 12
赞 408
Android OS知识点整理
1:InputChannel提供函数创建底层的Pipe对象 2: 1)客户端需要新建窗口 2)new ViewRo…

自由人是工程师
阅读 1,540
评论 0
赞 14
歌声
鸟儿的歌声总和黎明一起上升,一起膨大浑圆鲜活水灵灵时间要长出会歌唱的喙啄破永远不回来的十四五岁 那歌声像二十一年的…

万象更新_f742
阅读 91
评论 0
赞 3

云华兄

关注
总资产76 (约7.65元)
开源播放器ijkplayer的编译
阅读 1,434
Node.js从白痴到入门(五):MongoDB
阅读 467
推荐阅读
郭德纲:于谦家里真有钱
阅读 3,809
大S姐妹老公结扎:男人爱不爱你,避个孕就知道了
阅读 5,993
“我离婚前一夜……”
阅读 1,806
今年必看第一国剧:平庸,才是每个人最终的归宿
阅读 5,942
《武林外传》后,闫妮姚晨都走红13年了,这次终于轮到她了吗?
阅读 8,810

作者:云华兄
链接:https://www.jianshu.com/p/772d01ed5d09
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章