【ESP-IDF】基于ESP8266库的从0开发(一)Hello World!
ESP-IDF开发
Mac/Linux的编译环境搭建
- 参考https://github.com/espressif/ESP8266_RTOS_SDK在mac搭建编译环境
其中,会遇到找不到ncurses的情况,参考:https://blogs.lebenito.net/posts/os/busybox-make-menuconfig-ncurses-error,修改文件。 - vscode环境:
{
"configurations": [
{
"name": "Esp8266",
"includePath": [
"${workspaceFolder}/**",
"${env:IDF_PATH}/components/**"
],
"compilerPath": "/Users/chengzhichao/esp/xtensa-lx106-elf/bin/xtensa-lx106-elf-gcc",
"browse": {
"path": [
"${workspaceFolder}",
"${IDF_PATH}/components"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
从0开始的ESP-IDF
hello world
快速烧录和观察
参考:https://github.com/espressif/ESP8266_RTOS_SDK
- 在esp8266上,注意将GPIO0接地进入烧录状态
- 使用example目录下的
hello_world工程,执行make flash - 执行
make monitor进行串口想看,也可使用自己的串口助手查看,注意串口速率为74880
工程文件描述
hello_world
├─ CMakeLists.txt
├─ Makefile
├─ README.md
├─ build
│ ├─ CMakeFiles
│ ├─ app_update
│ ├─ bootloader
│ ├─ bootloader_support
│ ├─ cmake_install.cmake
│ ├─ coap
│ ├─ console
│ ├─ esp-tls
│ ├─ esp-wolfssl
│ ├─ esp8266
│ ├─ esp_common
│ ├─ esp_event
│ ├─ esp_gdbstub
│ ├─ esp_http_client
│ ├─ esp_http_server
│ ├─ esp_https_ota
│ ├─ esp_ringbuf
│ ├─ esptool_py
│ ├─ fatfs
│ ├─ freemodbus
│ ├─ freertos
│ ├─ heap
│ ├─ hello-world.bin
│ ├─ hello-world.elf
│ ├─ hello-world.map
│ ├─ http_parser
│ ├─ include
│ ├─ jsmn
│ ├─ json
│ ├─ kconfig_bin
│ ├─ ldgen_libraries
│ ├─ libsodium
│ ├─ log
│ ├─ lwip
│ ├─ main
│ ├─ mbedtls
│ ├─ mconf-idf-prefix
│ ├─ mdns
│ ├─ mqtt
│ ├─ newlib
│ ├─ nvs_flash
│ ├─ openssl
│ ├─ partition_table
│ ├─ partitions_singleapp.bin
│ ├─ protobuf-c
│ ├─ protocomm
│ ├─ pthread
│ ├─ spi_flash
│ ├─ spi_ram
│ ├─ spiffs
│ ├─ tcp_transport
│ ├─ tcpip_adapter
│ ├─ vfs
│ ├─ wear_levelling
│ ├─ wifi_provisioning
│ └─ wpa_supplicant
├─ main
│ ├─ CMakeLists.txt
│ ├─ component.mk
│ └─ hello_world_main.c
├─ sdkconfig
├─ sdkconfig.ci.2MB
├─ sdkconfig.ci.4MB
└─ sdkconfig.old
与STM32工程不同的是,这里的工程文件里不包含ESP-IDF库。在编译时,通过makefile/cmake多层嵌套引用和链接生成中间文件(.o和.d)以及最后的二进制文件。
CMakeLists.txt/Makefile:二者相似,编译的配置文件,对应CMake和Makefile工具build: 通过上面两种编译工具生成的.o 文件(目标文件)和.d 文件(依赖文件),这两个文件的关系类似于.c和.hmain:应用主文件sdkconfig: ESP-IDF的配置文件,用于存储项目的配置选项sdkconfig.old: 上一次的配置备份sdkconfig.ci.4MB和sdkconfig.ci.2MB: CI测试文件
Makefile文件
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := hello-world
include $(IDF_PATH)/make/project.m
- 定义一个
PROJECT_NAME,字面意思 - 引用
project.m,这个是makefile类型的文件,该文件包括命令的定义,自动查找项目依赖目录。
该文件定义了各种环境变量,比如PROJECT_PATH,BUILD_DIR_BASE,COMPONENT_DIRS。通过该文件整合各文件之间的依赖。当需要添加组件文件夹时,涉及到这个文件,下一节进行说明
添加自定义目录
通过查看${IDF_PATH}/ESP8266_RTOS_SDK/make/project.mk文件可以看到ESP_IDF是怎么使用makefile工具处理各个文件的依赖,这里说明一下几个关键定义
找到下面的定义,定义了工程路径:
ifndef PROJECT_PATH
PROJECT_PATH := $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
export PROJECT_PATH # 将变量从当前 shell 环境导出到子进程环境
endif
$(MAKEFILE_LIST)- Make 的内置变量,包含所有被加载的 Makefile 文件列表$(firstword $(MAKEFILE_LIST))- 取列表中的第一个 Makefile(最初被调用的那个)$(dir ...)- 获取该 Makefile 所在的目录路径$(abspath ...)- 转换为绝对路径
这里EXTRA_COMPONENT_DIRS是用户定义的组件目录,COMPONENT_DIRS包括了三个部分:
$(IDF_PATH)/components:IDF提供的官方组件目录$(PROJECT_PATH)/components
$(PROJECT_PATH)/main:用户工程目录下的main和components$(EXTRA_COMPONENT_DIRS):用户自定义目录
可以发现EXTRA_COMPONENT_DIRS是弱定义,我们添加目录就是在这里添加
ifndef COMPONENT_DIRS
EXTRA_COMPONENT_DIRS ?=
COMPONENT_DIRS := $(PROJECT_PATH)/components $(EXTRA_COMPONENT_DIRS) $(IDF_PATH)/components $(PROJECT_PATH)/main
endif
添加目录分两种情况,一种是目标文件只包含一个组件(即一对.c和.h),另一种是一个文件夹中包含多个组件,即包含多个子文件夹。
两种情况均只需添加上层路径即可,比如在工程目录下的Makefile文件,添加目录:
PROJECT_NAME := hello-world
EXTRA_COMPONENT_DIRS = $(PROJECT_PATH)/BSP # 添加工程目录下BSP文件
include $(IDF_PATH)/make/project.mk
则BSP可为下面两种目录结构:
BSP
├─ component.mk
├─ led.c
└─ led.h
BSP
├─ led
│ ├─ component.mk
│ ├─ led.c
│ └─ led.h
└─ pwm
├─ component.mk
├─ pwm.c
└─ pwm.h
注意,两种目录结构不能混合,这与project.mk检索目录的方式有关
# List of component directories, i.e. directories which contain a component.mk file
SINGLE_COMPONENT_DIRS := $(abspath $(dir $(dir $(foreach cd,$(COMPONENT_DIRS),\
$(wildcard $(cd)/component.mk)))))
# List of components directories, i.e. directories which may contain components
MULTI_COMPONENT_DIRS := $(filter-out $(SINGLE_COMPONENT_DIRS),$(COMPONENT_DIRS))
# The project Makefile can define a list of components, but if it does not do this
# we just take all available components in the component dirs.
# A component is COMPONENT_DIRS directory, or immediate subdirectory,
# which contains a component.mk file.
#
# Use the "make list-components" target to debug this step.
ifndef COMPONENTS
# Find all component names. The component names are the same as the
# directories they're in, so /bla/components/mycomponent/component.mk -> mycomponent.
# We need to do this for MULTI_COMPONENT_DIRS only, since SINGLE_COMPONENT_DIRS
# are already known to contain component.mk.
COMPONENTS := $(dir $(foreach cd,$(MULTI_COMPONENT_DIRS),$(wildcard $(cd)/*/component.mk))) \
$(SINGLE_COMPONENT_DIRS)
COMPONENTS := $(sort $(foreach comp,$(COMPONENTS),$(lastword $(subst /, ,$(comp)))))
endif
在SINGLE_COMPONENT_DIRS的定义中,当对第一层目录检索到component.mk文件时,该文件就被标记为SINGLE_COMPONENT_DIRS ,而在后面的两个检索行为中被排除:
filter-out $(SINGLE_COMPONENT_DIRS)
Hello World!
下面进入主应用程序的构建方法
关于程序入口
我们可以看到,主程序的名称是app_main,而不是main。为什么呢?
可以通过查看.map文件来寻找线索:
/Users/Username/esp/ESP8266_RTOS_SDK/examples/get-started/hello_world/build/esp8266/libesp8266.a(startup.o)
(call_start_cpu)
在开头就能看到这一句,这个文件中包含了实际的入口函数call_start_cpu,而又是怎么知道它是入口函数呢?这就涉及程序编译时的链接步骤。链接步骤将变量和代码块按照实际内存表进行拼接,这个过程相当于出餐时的摆盘工作。在进行摆盘前,需要一个摆盘的“方法指导”,这就是.ld文件的用处。下面是esp8266.bootloader.ld的片断
MEMORY
{
dport0_seg : org = 0x3FF00000, len = 0x10
/* All .data/.bss/heap are in this segment. Reserve 1KB for old boot or ROM boot */
dram_seg : org = 0x3FFE8000, len = 0x18000 - 0x400
/* Functions which are critical should be put in this segment. */
iram_seg : org = 0x40100000, len = 0x8000
}
/* Default entry point: */
ENTRY(call_start_cpu);
SECTIONS
{
.text :
{
_stext = .;
_text_start = ABSOLUTE(.);
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.iram1 .iram1.*) /* catch stray IRAM_ATTR */
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
} > iram_seg
/* 下略 */
文件规定了入口call_start_cpu,可以在esp8266.project.ld.in 找到相同的声明。通过下面的命令可以得到call_start_cpu程序本体的地址。(地址不一定是402109dc,会变化)
> xtensa-lx106-elf-nm build/hello-world.elf | grep call_start_cpu
402109dc T call_start_cpu
使用下面命令也可说明这个地址就是入口地址
> esptool image-info build/hello-world.bin
esptool v5.1.0
Image size: 168032 bytes
Detected image type: ESP8266
Warning: Suspicious segment 0x40210010, length 117708
Warning: Suspicious segment 0x4022cbdc, length 27976
ESP8266 Image Header
====================
Image version: 1
Entry point: 0x402109dc
Segments: 5
Flash size: 256KB
Flash freq: 40m
Flash mode: DIO
Segments Information
====================
Segment Length Load addr File offs Memory types
------- ------- ---------- ---------- ------------
0 0x1cbcc 0x40210010 0x00000008 IROM
1 0x06d48 0x4022cbdc 0x0001cbdc IROM
2 0x00544 0x3ffe8000 0x0002392c DRAM
3 0x00080 0x40100000 0x00023e78 IRAM
4 0x0512c 0x40100080 0x00023f00 IRAM
ESP8266 Image Footer
====================
Checksum: 0x0a (valid)
回到.map文件,可以看到这个入口函数在startup.c这个文件中。(./ESP8266_RTOS_SDK/components/esp8266/source/startup.c)
在call_start_cpu中,对寄存器进行初始化后,能看到下面这段:
assert(xTaskCreate(user_init_entry, "uiT", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL) == pdPASS);
vTaskStartScheduler();
它开启了一个任务user_init_entry,在同一个文件的前面能看到这个函数,这个函数对系统级进行初始化,最后调用了app_main !
以上的探索可以加深对SDK构建的理解。
更多推荐


所有评论(0)