芯與心的破冰(二):帶你認(rèn)識ESP8266工程

目錄結(jié)構(gòu)

在得到一份代碼后我們最先應(yīng)當(dāng)了解一下它的目錄結(jié)構(gòu),這里用ESP8266_RTOS_SDK_V1.5.0為例:

  • app:用戶代碼主目錄界牡,用戶代碼都將放在這里
  • bin :編譯生成和SDK提供的bin文件雁竞,用于下載到Flash中
  • driver_lib : RTOS驅(qū)動示例代碼
  • examples:示例代碼
  • extra_include:Xtensa編譯器頭文件(使用XCC編譯時使用钦椭,一般用GCC)
  • include:SDK頭文件(包含可用的 API函數(shù)和相關(guān)的宏定義)
  • ld:鏈接時所需的腳本文件,如無特殊需求無需修改
  • lib:SDK的庫文件
  • third_party:第三方開源庫(源碼)碑诉,編譯后會放到lib目錄
  • tools:編譯bin文件所需的工具彪腔,無需修改
  • Makefile:編譯入口腳本(執(zhí)行make時所執(zhí)行的文件)

bin文件

文件列表 是否必選 說明 Non-FOTA FOTA
master_device_key.bin 可選 樂鑫云服務(wù),在Espressif Cloud申請 ? ?
esp_init_data_default.bin 必選 初始化射頻參數(shù)进栽,SDK提供 ? ?
blank.bin 必選 初始化系統(tǒng)參數(shù)德挣,SDK提供 ? ?
eagle.flash.bin 必選 主程序,編譯生成(app=0) ? ?
eagle.irom0text.bin 必選 主程序快毛,編譯生成(app=0) ? ?
user1.bin 初次必選 主程序格嗅,編譯生成(app=1) ? ?
user2.bin FOTA升級 主程序,編譯生成(app=2) ? ?

(文件名不一定相同)
注:user1.bin和user2.bin實際上除了燒錄位置不同其它是幾乎是一樣的祸泪,因為在線升級時沒有數(shù)據(jù)緩存位置吗浩,所以下載的數(shù)據(jù)是直接寫入flash的,但又不能覆蓋當(dāng)前程序(否則升級一半掉電就無法開機了)没隘,所以user1.bin程序FOTA升級使用user2.bin懂扼,user2.bin程序FOTA升級使用user1.bin。第一次燒錄使用user1.bin。

Flash布局

以下為Flash使用布局阀湿,可以根據(jù)需要進(jìn)行修改赶熟,這里大致了解一下就可以。


Flash布局
  • 系統(tǒng)程序:程序固件
  • 用戶數(shù)據(jù):未使用的Flash部分可以給用戶自行存儲用戶數(shù)據(jù)
  • 用戶參數(shù):地址可自定義陷嘴,IOT_Demo設(shè)置為0x3C000開始的4個扇區(qū)(master_device_key.bin放在第三個扇區(qū))
  • 系統(tǒng)參數(shù):固定為Flash最后的4個扇區(qū)(blank.bin放在倒數(shù)第2映砖、1扇區(qū),esp_init_data_default.bin放在倒數(shù)第4灾挨、3扇區(qū))
  • Boot信息:FOTA升級相關(guān)信息
  • 預(yù)留:與Boot信息區(qū)對應(yīng)的預(yù)留部分
    注:一個扇區(qū)為4kb(Byte)

編譯過程

如果要了解一個工程的結(jié)構(gòu)邑退,那么從工程的編譯來看是最為深刻的,那么了解這個編譯過程有什么用劳澄?老實說地技,并沒有什么卵用。那為什么還要寫這部分秒拔?因為我就想把文章寫的長一點莫矗,啊哈哈哈哈。砂缩。作谚。

入口腳本
看過官方的文檔都知道,我們編譯項目是要進(jìn)入app這個目錄然后執(zhí)行g(shù)en_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
    )
)

文件開頭這部分妹懒,很簡單,這里設(shè)置SDK_PATH和BIN_PATH兩個變量(官方文檔也會叫你先改這兩個值后在編譯)喳挑,如果沒設(shè)置就報錯彬伦,結(jié)束編譯滔悉。什么伊诵?你問我Rem是什么意思?那只是注釋啦(好學(xué)的孩子可以出門左拐看看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

看結(jié)尾這部分,首先先執(zhí)行了make clean清除構(gòu)建侄泽,然后進(jìn)行make編譯礁芦,把上面五個變量傳遞進(jìn)去,make執(zhí)行的即當(dāng)前目錄下的Makefile文件。
啥柿扣?你說:end又是啥肖方?這還是一個注釋啦,啊哈哈哈哈哈哈哈哈
最后這一小部分則是這個文件最關(guān)鍵的未状,給后面make操作提供了參數(shù)(COMPILE俯画、BOOT、APP司草、SPI_SPEED艰垂、SPI_MODE和SPI_SIZE_MAP)。gen_misc.sh類似區(qū)別在于使用的腳本語音不同埋虹,最后參數(shù)就COMPILE不一樣(用于選擇編譯器的)材泄。

入口Makefile
看Makefile可以對照《跟我一起寫Makefile》或者我的一起來看神奇的Makefile

TARGET = eagle
#FLAVOR = release
FLAVOR = debug

#EXTRA_CCFLAGS += -u
parent_dir:=$(abspath $(shell pwd)/$(lastword $(MAKEFILE_LIST)))
parent_dir:=$(shell dirname $(parent_dir))
parent_dir:=$(shell dirname $(parent_dir))

SDK_PATH= $(parent_dir)
BIN_PATH=$(SDK_PATH)/bin

開頭定義了兩個變量TARGET和FLAVOR表示編譯的目標(biāo)和版本,接下來的parent_dir比較有意思吨岭,從字面上看是父路徑的意思拉宗,猜測就是當(dāng)前的上一級也就是工程根目錄,但這里采用了一個很復(fù)雜的方式取得:先從MAKEFILE_LIST取最后一個詞(也就是當(dāng)前Makefile的文件名)辣辫,加上pwd取得當(dāng)前路徑旦事,然后再取絕對路徑。而后又連續(xù)取兩次目錄名(去掉兩級路徑)也就是當(dāng)前目錄的上一級急灭,可繞腦了姐浮,這是想讓看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沒有定義,為什么畴嘶?因為我們一路看下來并沒有發(fā)現(xiàn)哪里有定義暗坝狻!
這里定義了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(編譯參數(shù))和TARGET_LDFLAGS(鏈接參數(shù))蒋院,這里上面定義的FLAVOR變量已經(jīng)使用上了亏钩。

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

LINKFLAGS_eagle.app.v6 = \
    -L$(SDK_PATH)/lib       \ # 定義鏈接庫的搜索路徑是 SDK/lib
    -Wl,--gc-sections   \     # 減少靜態(tài)庫不必要的調(diào)用
    -nostdlib   \            # 不使用標(biāo)準(zhǔn)庫
    -T$(LD_FILE)   \          # 讀取鏈接描述腳本,以確定符號等的定位地址
    -Wl,--no-check-sections \ # Do not check section addresses for overlaps  不檢查重疊地址
    -u call_user_start      \ # 取消定義的宏(call_user_start)
    -Wl,-static          \ # 使用靜態(tài)鏈接
    -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            # 庫列表結(jié)束

DEPENDS_eagle.app.v6 = \
                $(LD_FILE) \
                $(LDDIR)/eagle.rom.addr.v6.ld

定義三個變量COMPONENTS_eagle.app.v6(需要生成的目標(biāo))欺旧、LINKFLAGS_eagle.app.v6(鏈接庫)和DEPENDS_eagle.app.v6(ld文件)姑丑。LINKFLAGS_eagle.app.v6中-Wl,--start-group前面的為鏈接參數(shù)和-Wl,--end-group間為鏈接庫,可以根據(jù)需要進(jìn)行刪減辞友。

CONFIGURATION_DEFINES = -DICACHE_FLASH

DEFINES +=              \
    $(UNIVERSAL_TARGET_DEFINES) \
    $(CONFIGURATION_DEFINES)

DDEFINES +=             \
    $(UNIVERSAL_TARGET_DEFINES) \
    $(CONFIGURATION_DEFINES)

定義DEFINES和DDEFINES栅哀,給編譯用。兩個值都是"-DICACHE_FLASH"具體做啥用我也不清楚,字面上看應(yīng)該是Flash的cache緩存相關(guān)的昌屉。

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

.PHONY: FORCE
FORCE:

最后給INCLUDES添加了"include"目錄然后調(diào)用根目錄的Makefile文件(這里只是展開文件并沒有切換目錄钙蒙,還是在app目錄下執(zhí)行),最后兩行是定義了一個FORCE的偽目標(biāo)间驮,啥都沒做躬厌。
這里只要記住這個Makefile文件定義了SUBDIRS、COMPONENTS_eagle.app.v6竞帽、LINKFLAGS_eagle.app.v6和DEPENDS_eagle.app.v6這幾個變量即可扛施。

主Makefile
這個文件是主要的編譯文件,主要是具體的編譯屹篓,比較長疙渣,這里只取較為關(guān)鍵的部分。

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

根據(jù)COMPILE選擇編譯器堆巧,從開頭的腳本克制Windows使用xcc妄荔,Linux使用gcc。實際測試在windows下使用gcc也似乎并沒有問題谍肤。

BOOT?=new
APP?=1
SPI_SPEED?=40
SPI_MODE?=QIO
SPI_SIZE_MAP?=2

設(shè)置參數(shù)的默認(rèn)值啦租,這里說一下上一節(jié)我們編譯機智云的工程為啥使用不一樣的方式,原因就是機智云的工程里這里的默認(rèn)值是不一樣的荒揣,我們直接使用了make而沒有傳入?yún)?shù)篷角,所以會導(dǎo)致編輯結(jié)果不一樣,事實上也無需關(guān)心這部分系任,我們只要給它傳參就可以了恳蹲,開篇只是作為驗證編譯器是否正常而已。
后續(xù)一百多行的腳本根據(jù)這幾個變量定義了boot俩滥、app嘉蕾、freqdiv、mode举农、addr荆针、size_map敞嗡、flash颁糟、LD_FILE以及BIN_NAME,比較簡單這里不贅述喉悴。

CSRCS ?= $(wildcard *.c)        # $(wildcard xxx)這個意思是在當(dāng)前目錄下使用通配符列出所有文件
CPPSRCS ?= $(wildcard *.cpp)
ASRCs ?= $(wildcard *.s)
ASRCS ?= $(wildcard *.S)
SUBDIRS ?= $(patsubst %/,%,$(dir $(wildcard */Makefile)))

ODIR := .output
OBJODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/obj

OBJS := $(CSRCS:%.c=$(OBJODIR)/%.o) \
        $(CPPSRCS:%.cpp=$(OBJODIR)/%.o) \
        $(ASRCs:%.s=$(OBJODIR)/%.o) \
        $(ASRCS:%.S=$(OBJODIR)/%.o)

DEPS := $(CSRCS:%.c=$(OBJODIR)/%.d) \
        $(CPPSRCS:%.cpp=$(OBJODIR)/%.d) \
        $(ASRCs:%.s=$(OBJODIR)/%.d) \
        $(ASRCS:%.S=$(OBJODIR)/%.d)

LIBODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/lib
OLIBS := $(GEN_LIBS:%=$(LIBODIR)/%)

IMAGEODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/image
OIMAGES := $(GEN_IMAGES:%=$(IMAGEODIR)/%)

BINODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/bin
OBINS := $(GEN_BINS:%=$(BINODIR)/%)

定義了一些變量棱貌,后續(xù)會反復(fù)使用的一些文件,這里將文件賦值給變量后續(xù)操作就方便了比如說編譯跟clean就會用到一大堆相同的.o文件箕肃。
這里有一個有趣的地方婚脱,就是SUBDIRS ?= $(patsubst %/,%,$(dir $(wildcard */Makefile)))如果你是在app目錄進(jìn)行make,那么app目錄下的make文件會定義SUBDIRS,如果在根目錄下直接make這個不會定義障贸,然后就會執(zhí)行這一句错森,接著就會把app這個目錄包含進(jìn)來,最后編譯的時候就會編譯到app目錄的Makefile定義SUBDIRS最后又會回到這里篮洁。
$(CSRCS:%.c=$(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)

一堆編譯參數(shù)放到CFLAGS和DFLAGS這兩個變量里面瓦阐。
接下來就是關(guān)鍵的編譯部分的代碼,這里先跳過編譯的代碼回頭再來看篷牌,先看腳本

ifneq ($(MAKECMDGOALS),clean)
ifneq ($(MAKECMDGOALS),clobber)
ifdef DEPS
sinclude $(DEPS)
endif
endif
endif

這段代碼比較典型睡蟋,MAKECMDGOALS并沒有定義,所以會執(zhí)行sinclude $(DEPS)枷颊,DEPS根據(jù)前面的定義可以知道是當(dāng)前目錄下的源文件(.c .cpp .s)生成的.d戳杀,根據(jù).d文件的生成規(guī)則可以知道是使用gcc -M編譯得到,即對應(yīng).o的依賴關(guān)系夭苗,包括包含的.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文件展開坐求,意義在于我們寫依賴關(guān)系的時候我們并不能把源文件里面引用的.h文件都加到依賴關(guān)系里面,如果不加進(jìn)來晌梨,那么僅修改.h的不會重新生成.o文件的桥嗤。
簡單一句話就是.c文件中包含的.h發(fā)生改變的時候重新生成對應(yīng)的.o


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

define MakeLibrary
DEP_LIBS_$(1) = $$(foreach lib,$$(filter %.a,$$(COMPONENTS_$(1))),$$(dir $$(lib))$$(LIBODIR)/$$(notdir $$(lib)))
DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(dir $$(obj))$$(OBJODIR)/$$(notdir $$(obj)))
$$(LIBODIR)/$(1).a: $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1))
    @mkdir -p $$(LIBODIR)
    $$(if $$(filter %.a,$$?),mkdir -p $$(EXTRACT_DIR)_$(1))
    $$(if $$(filter %.a,$$?),cd $$(EXTRACT_DIR)_$(1); $$(foreach lib,$$(filter %.a,$$?),$$(AR) xo $$(UP_EXTRACT_DIR)/$$(lib);))
    $$(AR) ru $$@ $$(filter %.o,$$?) $$(if $$(filter %.a,$$?),$$(EXTRACT_DIR)_$(1)/*.o)
    $$(if $$(filter %.a,$$?),$$(RM) -r $$(EXTRACT_DIR)_$(1))
endef

define MakeImage
DEP_LIBS_$(1) = $$(foreach lib,$$(filter %.a,$$(COMPONENTS_$(1))),$$(dir $$(lib))$$(LIBODIR)/$$(notdir $$(lib)))
DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(dir $$(obj))$$(OBJODIR)/$$(notdir $$(obj)))
$$(IMAGEODIR)/$(1).out: $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1))
    @mkdir -p $$(IMAGEODIR)
    $$(CC) $$(LDFLAGS) $$(if $$(LINKFLAGS_$(1)),$$(LINKFLAGS_$(1)),$$(LINKFLAGS_DEFAULT) $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1))) -o $$@ 
endef

--------------------------------------------------------跳過若干行代碼----------------------------------------------

$(foreach lib,$(GEN_LIBS),$(eval $(call ShortcutRule,$(lib),$(LIBODIR))))

$(foreach image,$(GEN_IMAGES),$(eval $(call ShortcutRule,$(image),$(IMAGEODIR))))

$(foreach bin,$(GEN_BINS),$(eval $(call ShortcutRule,$(bin),$(BINODIR))))

$(foreach lib,$(GEN_LIBS),$(eval $(call MakeLibrary,$(basename $(lib)))))

$(foreach image,$(GEN_IMAGES),$(eval $(call MakeImage,$(basename $(image)))))

前面部分定義了ShortcutRule、MakeLibrary和MakeImage三個函數(shù)仔蝌,后面部分則調(diào)用這三個函數(shù)來生成泛领。
首先看后面這五個foreach循環(huán),以第一個為例:取GEN_LIBS的值代入$(eval $(call ShortcutRule,$(lib),$(LIBODIR)))執(zhí)行敛惊,ShortcutRule函數(shù)為生成一個規(guī)則渊鞋,第一個參數(shù)依賴于.subdirs和第二個參數(shù)和第一個參數(shù)組成的文件路徑,即:$(lib): .subdirs $(LIBODIR)/$(lib)瞧挤,$(eval text)函數(shù)是將text放到Makefile中重新解析锡宋,也就是說lib這個目標(biāo)依賴于輸出目錄下的同名文件,再簡化一下就是lib這個目標(biāo)就是要生成輸出目錄下的同名lib文件(這個保留懷疑特恬,因為測試輸出在/和$(lib)之間會出現(xiàn)一個空格)执俩。這里還依賴一個.subdirs目標(biāo):

.subdirs:
    @set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);)

set -e表示后面操作如果出錯就停止編譯,取出SUBDIRS后執(zhí)行make -C癌刽,SUBDIRS是在我們?nèi)肟贛akefile(app目錄下的Makefile)中定義的役首。make -C dir表示在dir目錄下執(zhí)行make尝丐。簡單的說就是在SUBDIRS目錄各執(zhí)行一次make息楔。
MakeLibrary和MakeImage類似的株旷,有一點就是$$表示轉(zhuǎn)義一個$星爪。結(jié)果就是生成GEN_LIBS男翰、GEN_IMAGES和GEN_BINS中存放的文件风秤。具體生成原理可以推敲一下那三個函數(shù)窿克。
最后看一下目標(biāo)規(guī)則部分

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

ifeq ($(APP), 0)
    @$(RM) -r $(BIN_PATH)/eagle.S $(BIN_PATH)/eagle.dump
    @$(OBJDUMP) -x -s $< > $(BIN_PATH)/eagle.dump
    @$(OBJDUMP) -S $< > $(BIN_PATH)/eagle.S
else
    @mkdir -p $(BIN_PATH)/upgrade
    @$(RM) -r $(BIN_PATH)/upgrade/$(BIN_NAME).S $(BIN_PATH)/upgrade/$(BIN_NAME).dump
    @$(OBJDUMP) -x -s $< > $(BIN_PATH)/upgrade/$(BIN_NAME).dump
    @$(OBJDUMP) -S $< > $(BIN_PATH)/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 $(ODIR)/$(TARGET)/$(FLAVOR)

clobber: $(SPECIAL_CLOBBER)
    $(foreach d, $(SUBDIRS), $(MAKE) -C $(d) clobber;)
    $(RM) -r $(ODIR)

這是我們編譯要生成的目標(biāo)苔咪,直接make默認(rèn)是生成all這個目標(biāo)漓概,原因是默認(rèn)生成第一個目標(biāo)乏屯,也許你會問第一個目標(biāo)不是最前面的$(BINODIR)/%.bin么根时?這個我還查了一下,百度無果辰晕,在GNU官網(wǎng)上找到這樣一段話:

默認(rèn)目標(biāo)

劃紅線的意思是蛤迎,默認(rèn)取第一個目標(biāo),但有兩個除外含友,一個是模式規(guī)則(pattern rule)的目標(biāo)替裆。$(BINODIR)/%.bin正是模式規(guī)則,所以這里默認(rèn)目標(biāo)為all窘问。
我們看一下這個這個目標(biāo)all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)
.subdirs 上面已經(jīng)說過了辆童,是到子目錄(SUBDIRS)下進(jìn)行make操作;$(OBJS) $(OLIBS) $(OIMAGES)使用對應(yīng)的規(guī)則生成對應(yīng)的文件惠赫;
$(OBINS) 使用$(BINODIR)/%.bin: $(IMAGEODIR)/%.out這個規(guī)則生成最終的bin文件:
($@--目標(biāo)文件把鉴,$^--所有的依賴文件,$<--第一個依賴文件)
首先創(chuàng)建bin目錄儿咱,將生成文件進(jìn)行反編譯(反編譯這個做啥庭砍。。混埠。)怠缸,拷貝bin文件。
記住拷貝的這四個bin文件eagle.app.v6.text.bin钳宪、eagle.app.v6.data.bin揭北、eagle.app.v6.rodata.bin和eagle.app.v6.irom0text.bin
然后調(diào)用gen_appbin.py(天啊,又要看一門語音--Python)這個腳本(傳入依賴文件吏颖、app搔体、mode、freqdiv和size_map)
最后重命名bin文件侦高。至此整個編譯工作已經(jīng)結(jié)束了嫉柴,接下來我們來看一下子目錄里的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 $(PDIR)include
INCLUDES += -I ./
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

看注釋實際上就差不多知道怎么弄了,剛開始PDIR并沒有定義瞧壮,所以定義了GEN_LIBS登馒,然后添加頭文件路徑定義PDIR,最后再展開主Makefile咆槽,主Makefile就會對GEN_LIBS進(jìn)行編譯陈轿。
簡單是說就是GEN_LIBS賦值為我們要的lib名字,將寫好的源文件放到子目錄里面秦忿,然后編譯就可以了麦射。

大蟒蛇
天啊,分析個代碼結(jié)構(gòu)居然要看這么多語言灯谣,來吧潜秋,讓暴風(fēng)雨來的更猛烈些吧,打開上面提到的tools/gen_appbin.py:
注意:這個文件我們從主Makefile中傳了5個參數(shù)進(jìn)來(依賴文件胎许、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函數(shù)钩述,將data數(shù)據(jù)追加文件末尾,這么簡單的代碼如果看不懂不要跟我說你是干程序的喲穆碎。

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函數(shù)牙勘,從名字上看就知道是合并兩個bin文件的,這里是從file_name的start_offset_addr地址開始拷貝到dest_file_name末尾所禀,need_chk表示是否進(jìn)行四字節(jié)對齊檢查谜悟。

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函數(shù),生成文件的CRC校驗值

def gen_appbin():
    global chk_sum
    global crc_sum
    global blocks
    if len(sys.argv) != 6: # 判斷參數(shù)北秽,默認(rèn)一個加傳遞的五個
        print 'Usage: gen_appbin.py eagle.app.out boot_mode flash_mode flash_clk_div flash_size_map'
        sys.exit(0)
    # 保存參數(shù)
    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' # 要生成的目標(biāo)文件

    BIN_MAGIC_FLASH  = 0xE9 # 魔數(shù)(沒寫錯別字呦)
    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)$') # 編譯正則表達(dá)式
    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)
    # 數(shù)據(jù)區(qū)起始地址
    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
    # 常量數(shù)據(jù)起始地址
    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這就是我們的入口函數(shù)了葡幸,為什么?我們看最后的兩行

if __name__=='__main__':
    gen_appbin()

當(dāng)我們執(zhí)行一個python腳本的時候name就會是'main'贺氓,這里直接調(diào)用了gen_appbin()所以gen_appbin()就算是我們的入口函數(shù)了蔚叨。代碼中加了少量的注釋,這個就是合并生產(chǎn)的幾個bin文件為eagle.app.flash.bin辙培。主Makefile最后會把這個文件重命名為對應(yīng)的文件名蔑水。可見這個腳本是通用的腳本扬蕊,沒有太多深究的價值搀别。

視乎漏了什么
入口Makefile的LD_FILE漏了有沒有,這個在主Makefile的MakeImage函數(shù)使用了尾抑,這個是連接腳本用于生成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"

這里先普及一下我們的內(nèi)存結(jié)構(gòu),ARM架構(gòu)的芯片地址是4個字節(jié)榜苫,也就是最大尋址為4GB护戳。ram和rom共用這4GB的地址范圍,所以這里要對這些地址進(jìn)行分配垂睬,分配依據(jù)不是隨便分的媳荒,要根據(jù)硬件實際掛載位置進(jìn)行分配,不然訪問就出錯了驹饺。
ram這里分為兩部分钳枕,一個是iram為內(nèi)部的內(nèi)存,只有32KB(0x8000)赏壹,另一個是dram為掛載的內(nèi)存鱼炒,比較大(速度會慢點),有96KB卡儒,所以ESP8266整個ram就只有128KB(感覺好小啊)分別掛載在0x40100000和0x3FFE8000地址田柔,dport是什么鬼我也不知道。
從注釋中可以知道我們可以修改irom0骨望,它的掛載地址為0x40201010硬爆,沒猜錯的話實際Flash的首地址應(yīng)該是0x40200000,因為前面有boot區(qū)擎鸠,這個鏈接文件是放user1.bin的缀磕,0x1010+0x6B000=432KB,這個腳本是1MB的Flash分兩個區(qū)(512+512)劣光,剩余的就是數(shù)據(jù)區(qū)(參考我們最上面的Flash布局)袜蚕,所以這個調(diào)整最多調(diào)整到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)燎悍。

總結(jié)

原本就想寫稍微長點,寫著寫著也忒長了敷待。主要是講了Flash分配以及編譯過程间涵。涉及較多語音仁热,自己也是一知半解榜揖,我覺得Makefile寫的也不好,使用類似遞歸的方式一直調(diào)用主Makefile來編譯感覺很混亂(也可能是我比較菜b)抗蠢,用樹形方式調(diào)用就好多了举哟,不過不用擔(dān)心,這些都不是必須的迅矛,只要知道這幾點就夠了:

  • 入口Makefile(app/Makefile)中的FLAVOR用于控制軟件是debug還是release
  • 入口Makefile中的SUBDIRS用于選擇編譯的模塊
  • 入口Makefile中的COMPONENTS_eagle.app.v6用于選擇加入最終的bin文件的模塊(和上一條對應(yīng))
  • 入口Makefile中的LINKFLAGS_eagle.app.v6用于選擇SDK提供是庫加入最終的bin文件
  • 添加模塊代碼只要在app中創(chuàng)建一個文件夾和拷貝一份對應(yīng)的Makefile修改GEN_LIBS的模塊名
  • 模塊文件夾內(nèi)的所有源文件都會自動加入編譯
  • 公共的頭文件放在app/include下
  • 需要修改Flash分配時可以修改eagle.app.v6.xxx.xxx.xxx.ld內(nèi)的對應(yīng)數(shù)據(jù)(irom)妨猩,一般不要修改
  • 清除編譯使用make clean
  • 編譯時使用make COMPILE=gcc BOOT=new APP=1 SPI_SPEED=40 SPI_MODE=DIO SPI_SIZE_MAP=6(使用gen_misc.sh根本無法彰顯我們的逼格)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秽褒,隨后出現(xiàn)的幾起案子壶硅,更是在濱河造成了極大的恐慌,老刑警劉巖销斟,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庐椒,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚂踊,警方通過查閱死者的電腦和手機约谈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犁钟,“玉大人棱诱,你說我怎么就攤上這事±远” “怎么了迈勋?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長醋粟。 經(jīng)常有香客問我靡菇,道長,這世上最難降的妖魔是什么昔穴? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任镰官,我火速辦了婚禮,結(jié)果婚禮上吗货,老公的妹妹穿的比我還像新娘泳唠。我一直安慰自己,他們只是感情好宙搬,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布笨腥。 她就那樣靜靜地躺著拓哺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脖母。 梳的紋絲不亂的頭發(fā)上士鸥,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音谆级,去河邊找鬼烤礁。 笑死,一個胖子當(dāng)著我的面吹牛肥照,可吹牛的內(nèi)容都是我干的脚仔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼舆绎,長吁一口氣:“原來是場噩夢啊……” “哼鲤脏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吕朵,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猎醇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后努溃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硫嘶,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年茅坛,在試婚紗的時候發(fā)現(xiàn)自己被綠了音半。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡贡蓖,死狀恐怖曹鸠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斥铺,我是刑警寧澤彻桃,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站晾蜘,受9級特大地震影響邻眷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剔交,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一肆饶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧岖常,春花似錦驯镊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橄镜。三九已至,卻和暖如春冯乘,著一層夾襖步出監(jiān)牢的瞬間洽胶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工裆馒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姊氓,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓领追,卻偏偏與公主長得像他膳,于是被迫代替她去往敵國和親响逢。 傳聞我的和親對象是個殘疾皇子绒窑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 上一周講了ESP8266的初步開發(fā),也就是AT模式工作舔亭,這種模式是最適合初學(xué)者使用的些膨,因為wifi模塊內(nèi)部的函數(shù)都...
    JaydenOnly閱讀 3,852評論 1 6
  • Ubuntu的發(fā)音 Ubuntu矛洞,源于非洲祖魯人和科薩人的語言洼哎,發(fā)作 oo-boon-too 的音。了解發(fā)音是有意...
    螢火蟲de夢閱讀 99,366評論 9 467
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,322評論 0 18
  • 鳥兒的歌聲總和黎明一起上升沼本,一起膨大渾圓鮮活水靈靈時間要長出會歌唱的喙啄破永遠(yuǎn)不回來的十四五歲 那歌聲像二十一年的...
    萬象更新_f742閱讀 252評論 0 3
  • 這周我們重新認(rèn)識了差異噩峦,而且是與有錢人之間的差異。 一直以來抽兆,對有錢人存在偏見识补,特別是有錢人的子女,俗稱富二代辫红。認(rèn)...
    廣羽三的時間磨合閱讀 308評論 0 0