先安装VSCode(官网下载Lunix系统)
指令如下:
sudo dpkg -i xxx.deb
code .(打开目标文件下打开VScode)
开发过程:
(1) 首先安装编译好的ESP-IDF所需的包:
指令为 sudo apt-get install gcc git wget make libncurses-dev flex bison gperf python python-pip python-setuptools python-serial
(2) 然后下载脚本和执行脚本:
指令为
cd $HOME #进入HOME目录
wget https://esphuifeng.github.io/build_development_environment.sh #下载脚本文件
chmod u+x build development environment.sh #为脚本添加可执行权限
./build_development_environment.sh(这个过程需要等待下载时间)
(3) 配置
使用make menuconfig 配置编译参数。设置serial port为实际COM口,对于Ubuntu系统,可以使用ls /dev/tty*指令进行查看串口设备名称,该指令要进行两次命令,第一次是断开开发板,第二次是连接开发板。既可以看出来所使用的串口设备名称,然后运行make menuconfig去修改串口名称并保存。指令为(此对于Linux系统)
ls /dev/tty*(未连接开发板)
ls /dev/tty*(连接开发板)
make menuconfig(设置串口名称)
sudo chmod 777 /dev/ttyUSB0
修改权限为可读可写可执行,但是这种设置电脑重启后,又会出现这种问题,还要重新设置.因此查询资料,可以用下面这条指令:
sudo usermod -aG dialout xxx
其中xxx是我的用户名,换成你想用USB的用户名即可.把此用户名加入dialout用户组,然后注销下电脑,即可.这样下次重启也不用修改权限了
(4) 验证开发环境(esp-idf中的实例)指令为 (启用监视器:使用传统 GNU Make 编译系统)
cd ~/esp/esp-idf/examples/get-started/hello_world/
export IDF_PATH="$HOME/esp/esp-idf" #环境变量
make -j all #全速编译
make flash #烧录刚编译好的应用
make monitor #打开 monitor 工具观察串口输出
(5)若使用 CMake 编译系统,开发环境如下:
先安装Python,然后搭建cmake环境:
cd ~/esp
python2.7 -m pip install --user -r $IDF_PATH/requirements.txt
cd ~/esp/hello_world
idf.py menuconfig
idf.py build
idf.py -p /dev/ttyUSB0 monitor
在用户配置文件中添加 IDF_PATH 和 idf.py PATH
使用基于 CMake 的构建系统和 idf.py 工具,用户需修改两处系统环境变量:
要设置 IDF_PATH,并在 PATH 中添加 idf.py,请将以下两行代码添加至你的 ~/.profile 文件中:
export IDF_PATH=~/esp/esp-idf
export PATH="$IDF_PATH/tools:$PATH"
~/.profile 表示在你的电脑用户主目录中,后缀为 .profile 的文件。(~ 为 shell 中的缩写)。
用户配置文件
这里先介绍第一种调试方法-JTAG调试,JTAG是从STM32转过来的开发者的第一反应,这里放在最前面介绍。但是对于双核多任务的ESP32应用程序,并非最优的调试方法。
关于调试环境的搭建,参考乐鑫官方文档即可,有中文版详细配置过程:
ESP-WROVER-KIT板载了JTAG功能(FT2232),但是需要连接4个跳线帽接通线路,如下图:
FT2232 是多协议 USB 转串口桥接器。在这里替换了cp2102,并且可以同时提供 USB-to-JTAG ,USB-to-Serial 接口功能,便利开发人员的应用开发与调试。具体配置信息可以查阅 ESP-WROVER-KIT V4.1 入门指南
ESP32 Core Board V2 没有JTAG插口,如果需要硬件调试,需要准备:
按照如下方式连接:
| | ESP32 引脚 | JTAG 信号 |
| 1 | CHIP_PU | TRST_N |
| 2 | MTDO / GPIO15 | TDO |
| 3 | MTDI / GPIO12 | TDI |
| 4 | MTCK / GPIO13 | TCK |
| 5 | MTMS / GPIO14 | TMS |
| 6 | GND | GND |
ESP32 使用标准的 JTAG 接口,支持STM32常用的J-Link,如何使用 J-Link 调试 ESP32,但是不支持ST-Link,也不支持SWD接口:
官方描述: 在软件方面,OpenOCD 支持相当多数量的 JTAG 适配器,可以参阅 OpenOCD 支持的适配器列表 (尽管上面显示的器件不太完整),这个页面还列出了兼容 SWD 接口的适配器,但是请注意,ESP32 目前并不支持 SWD。此外那些被硬编码为只支持特定产品线的 JTAG 适配器也不能在 ESP32 上工作,比如用于 STM32 产品家族的 ST-LINK 适配器。
cd ~/esp/openocd-esp32
bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg #在openocd安装目录执行,开启openOCD server
或者
cd ~/esp/openocd-esp32
export OPENOCD_SCRIPTS=$PWD/tcl
sudo openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
或
src/openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
注意如果不是用sudo可能会出现失败的问题,启动 OpenOCD 之后要保持此窗口打开
补充:openocd需要使用usb,建议把/usr/local/share/openocd/contrib/60-openocd.rules拷贝到/etc/udev/rules.d,这样openocd就有使用usb调试设备的权限了,不用每次sudo (配置完成后需要重启生效)
连接好开发版,将JTAG相关的跳线帽连接好,正常下载启动,使用make -j4 flash monitor下载工程并打开的监视器,查看运行状态。
在你要调试的工程下面 创建一个初始化配置文件
当启动调试器时,通常需要提供几个配置参数和命令,为了避免每次都在命令行中逐行输入这些命令,我们可以新建一个配置文件,并将其命名为 gdbinit:
1. target remote :3333
2. set remote hardware-watchpoint-limit 2
3. mon reset halt
4. flushregs
5. thb app_main
6. c
然后运行在终端中输入以下内容,启动 GDB:(这里的 build/blink.elf 请切换到你工程的 elf 文件路径)
cd ~/esp/esp-idf-v3.2.2/examples/get-started/blink
xtensa-esp32-elf-gdb -x gdbinit build/blink.elf #在工程目录执行,连接openOCD
其中对于配置GDB解释:
break N:在第N行设置断点
delete N:删除第N行的断点
info break:查看已设置的断点数量和位置
s(或step):单步调试,可以跳入函数内部
n(或next):单步调试,不跳入函数内部
c:Continue,继续运行,到断点处停止
Ctrl+C:随机停止
q:结束会话
x /1wx 0x3FF44004 #查看0x3FF44004内存位置内容
0x3ff44004: 0x00000000
set {unsigned int}0x3FF44004=0x000010 #设置0x3FF44004内存位置内容
p i:print,打印当前i的值
watch i:在发生的代码处插入所谓的“观察点”
i threads:查看当前所有线程
thread N:查看编号N线程的代码
bt(或backtrace):查看上一层,查看当前函数调用处(仅查看,未跳出)
l/list:打印停止点处代码
l 30, 40:打印第30-40行代码
help xxx:查看指令xxx的帮助
ESP32 WROVER KIT 自带RGB指示灯,分别接入了IO0、IO2、IO4,注意IO0、IO2的特殊作用。
ESP32管脚 | RGB LED |
---|---|
GPIO0 | 红色 |
GPIO2 | 绿色 |
GPIO4 | 蓝色 |
该寄存器可以用来控制(设置或者清除)某个 GPIO 的电平
查询当前寄存器值:
(gdb) x /1wx 0x3FF44004
0x3ff44004: 0x00000000 #返回0x00000000
设置GPIO2对应的寄存器标志位为1,点亮LED:
(gdb) set {unsigned int}0x3FF44004=0x00000004
遗留问题: 只有写入0x00000004(对应IO2)才能正常点亮,IO0、IO4直接写GPIO_OUT_REG(GPIO 0-31 output register: 0x3FF44004 ),无法实现灯的操作。怀疑受到其他寄存器钳制。
无论先使用命令行还是eclipse调试,都需要先打开openOCD,关系图如下:
cd ~/esp/openocd-esp32
bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
- openOCD是硬件层次的调试,gdb是源码层次的调试。
- 在gdb中可以使用monitor发送openOCD的命令,例如monitor reset;halt
日志库有两种管理日志详细程度的方法:编译阶段,通过菜单设置;运行阶段,使用esp_log_level_set()函数设置。
日志等级有:错误,警告,信息,调试和详细(详细度从最低到最高)
在编译阶段,使用CONFIG_LOG_DEFAULT_LEVEL选项过滤。所有等级状态高于CONFIG_LOG_DEFAULT_LEVELD的日志将会被处理器移除。
在运行阶段,所有低于 CONFIG_LOG_DEFAULT_LEVEL 的日志被默认使能。esp_log_level_set() 函数可以用来减少每个模块的日志等级。模块通过标签识别,这些标签是可读的零结尾的ASCII字符串。
注意 esp_log_level_set() 函数不能提高到超过 CONFIG_LOG_DEFAULT_LEVEL 设置的等级。在编译阶段,为了给特殊文件提高日志等级,可以使用LOG_LOCAL_LEVEL宏(详见下)。
void esp_log_level_set(const char* tag, esp_log_level_t level);
typedef {
ESP_LOG_NONE,/ *!<无日志输出* /
ESP_LOG_ERROR,/ *!<严重错误,软件模块无法自行恢复* /
ESP_LOG_WARN,/ *!<采取恢复措施的错误条件* /
ESP_LOG_INFO,/ *!<描述正常事件流的信息消息* /
ESP_LOG_DEBUG,/ *!<正常使用不需要的其他信息(值,指针,大小等)。*/
ESP_LOG_VERBOSE / *!<较大的调试信息块或频繁出现的消息,有可能淹没输出。*/
} esp_log_level_t;
使用的方式如下形式,在运行时配置每个模块的日志记录输出,向函数添加调用:
esp_log_level_set("*", ESP_LOG_ERROR); // set all components to ERROR level
esp_log_level_set("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack
esp_log_level_set("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client
下面是
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
va_list list;
va_start(list, format);
(*s_log_print_func)(format, list);
va_end(list);
参考资料Logginglibrary
ESP-IDF 提供了 console 组件,它包含了开发基于串口的交互式控制终端所需要的所有模块,主要支持以下功能:
筛选规则
在sdkconfig文件中CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
该功能默认启用。用户也可以禁用该功能,既将y去掉不使能,从而减小应用程序的二进制文件大小。注意,该功能对禁用并不影响 esp_err_to_name() 和 esp_err_to_name_r() 函数的定义,用户仍可调用这两个函数转化错误码。在这种情况下,esp_err_to_name() 函数在遇到无法匹配错误码的情况会返回 UNKNOWN ERROR,而 esp_err_to_name_r() 函数会返回 Unknown error 0xXXXX(YYYYY),其中 0xXXXX 和 YYYYY 分别代表错误代码的十六进制和十进制表示。
紧急处理程序
[外链图片转存中…(img-Qi3T0b5S-1574132788122)]
寄存器转储与回溯
回溯行包含了当前任务中每个堆栈帧的 PC:SP 对(PC 是程序计数器,SP 是堆栈指针), 若要查找发生严重错误的代码位置,请查看 “Backtrace” 的后面几行,发生严重错误的代码显示在顶行,后续几行显示的是调用堆栈。
在minicom调试中,如果之后Backtrace后面的字符串,对Backtrace的后面进行解析可以使用该命令xtensa-esp32-elf-addr2line -fiap -e build/hello_world.elf Backtrace: 0x400d2182:0x3ffb4c70 0x400d09b2:0x3ffb4ca0 0x40084b95:0x3ffb4cc0
如果使用sudo minicom /dev/ttyUSB0中,可以用Ctrl+A之后再按Q就可以退出。
自动解码地址详细说明
##1. ESP32 Core Dump
ESP-IDF 支持在不可恢复的软件错误上生成 Core Dump。这个技术可以对软件发生故障时的软件状态进行事后分析。在系统崩溃进入 Panic 状态时,根据配置打印一些信息并停止或重新启动。
ESP-IDF 提供特殊脚本 espcoredump.py,以帮助用户检索和分析 Core Dump。此工具提供两个用于 Core Dump 分析的命令:
有许多与Core Dump相关的配置选项,用户可以在应用程序的配置菜单中选择 (make menuconfig或者用)。
make -j all
make flash
make monitor
或者idf.py build flash monitor -p /dev/ttyUSB2 -b 912600
6. 与core dump配置选项,配置好;(将配置选择在USRT上)
7. 将程序进行编译
make -j all
make flash
make monitor
8. 用户应手动将Core Dump文本正文保存到某个文件(此处保存在了hello文件中),CORE DUMP START 和 CORE DUMP END 行不得包含在 Core Dump 文本文件中。
code hello
或者
vim hello
系统出现紧急情况时,base64编码的内核转储将打印在UART上的通用命令示例为
espcoredump.py info_corefile -t b64 -c
或者 espcoredump.py dbg_corefile -t b64 -c
python espcoredump.py dbg_corefile -t b64 -c ./hello ~/esp/esp-idf/examples/get-started/hello_world/build/hello-world.elf
注意: 如果在make menuconfig中,串口没有修改,只是还是选择的是core dump destination下的None选项,则编译会出现如下情况,不能得到想要的core dump文本,如下的文本将不能得到。
================= CORE DUMP START =================
================= CORE DUMP END ===================
Heap管理主要函数接口与数据结构
1.1 主要函数接口
ESP32的SDK对于heap部分管理的源码位于路径\esp-idf\components\heap下,可以简单的认为分为两层:heap_caps_init.c与heap_caps.c构成一层函数接口封装;multi_heap.c等文件内是具体函数接口的实现。主要接口:
Heap初始化: voidheap_caps_init(void)
Heap分配: void *heap_caps_malloc( size_t size,uint32_t caps )
Heap释放: void heap_caps_free(void *ptr)
ESP32 SDK中的malloc/calloc/free等系统调用,最终都是调用以上函数执行(参见syscall_stub_table)。
1.2 重要数据结构
Heap_t是Heap管理代码的核心数据结构。但是在heap_private.h与multi_heap.c下各有一个该类型定义。其实并不矛盾,前者是Heap管理中用于描述某个“Heap分区”的;后者定义仅限于multi_heap.c文件内部,作为“Heap分区”下”MetaData头”,示意如下:
由数据结构heap_t可以初步探知ESP32 SDK的Heap管理的结构:
(1) 整个Heap空间被作为多个Heap分区进行管理,描述Heap分区的结构体定义在heap_private.h文件下的heap_t,分区之间用链表关联;
(2) 每个Heap分区实体由MedaData头以及Data区构成:MedaData头定义在multi_heap.c文件下的heap_t;Data区分为已用空间(used space)以及可用空间(free space)。Data区以Heap_Block(heap_block_t)作为管理单元,通过链表关联。
heap_caps_init与Heap空间的初始化
2.1 Heap空间初始化
系统调用heap_caps_init实现Heap空间初始化的过程,实质就是初始化1.2节所示数据结构的过程。简单的看可以分为三个步骤:
(1) 遍历当前系统的可用内存区域,并对类型相同的相邻区域进行合并
(2) 将可用内存区域通过register_heap初始化为Heap分区的结构(建立Meta Data头以及数据区的链表)
(3) 将所有Heap分区信息通过链表连接到registered_heaps
2.2 heap_caps_init
比对2.1节三个步骤,截取主要代码如下:
(1) 遍历与合并当前系统内存可用区域,内存区域见数组soc_memory_regions
(2) 将所有可用内存区初始化为Heap分区
(3) 将所有Heap分区信息通过链表连接到registered_heaps
2.3 register_heap
register_heap将普通的内存区域初始化为Heap分区结构,multi_heap_register_impl 是其最终实现功能的接口。下图示意这个过程:
(1) 一段可用的连续内存被初始化为Heap分区时,分区起始地址会写入一个MetaData头(即是multi_heap_info类型)。MetaData 头后紧接着是第一个真正可以用来分配的内存块block0,block0包含一个信息头heap_block_t以及实际数据区。在Heap分区的最后,有一个heap_block_t的信息头作为整个分区的结尾。heap_block_t结构中有指向下个节点的指针(既*next_free),因此分区所有block组成链表用于访问。
(2) 当一段可用的连续内存被初始化Heap分区,它的实际可用空间是:对应如下图的“实际数据区”
heap_caps_malloc与Heap空间分配
Heap空间是系统运行期间交给用户申请的,如何分配与释放是关键问题。本节描述关于Heap空间如何分配的问题。
3.0 次要问题
为了突出“Heap空间分配机制”这个主干,先对几个Heap分配的次要问题进行说明:
(1) Malloc Cap:ESP32 SDK的Heap源码中,经常出现函数接口的参数是Malloc Caps。该参数的涵义是“待分配内存的特性”。例如:8/16/32位对齐,内存能否执行程序,是内部/外部内存等等。这个其实非常重要,与ESP32特性紧密相关。
(2) 内存临界区与跨界问题:某些时刻Heap的分配可能会涉及不同位置的内存空间。这里主要针对OTA升级功能。Over The Air Updates(OTA)
3.1 Heap空间分配(用户Heap空间申请)
对于Heap空间的分配过程,通过对代码的理解,可以简单的概括为三个步骤:
当用户向系统通过malloc申请Heap内存空间时:
(1) 系统会遍历每个Heap分区(通过registered_heaps),找到满足申请内存类型(例如:3.0所述Malloc Cap)的分区;再依次遍历分区下所有可用的Heap Block,找到可用空间满足申请内存大小的Heap Block。
(2) 遍历所有Heap Block期间,会出现几种可能:
A:若Heap Block的可用空间小于用户申请的内存大小,显然不能使用,继续查找下一个Heap Block;
B:若Heap Block的可用空间正好等于用户申请的内存大小,这时系统出现了最佳的分配选择,策略选择该Heap Block作为本次分配的Best Heap Block,停止遍历。
C:若Heap Block的可用空间大于用户申请的内存大小,则先行记录下这个Heap Block的位置以及它的实际可用空间大小,然后继续遍历查找;在找到下一个满足条件的Heap Block时,比较两者,选取可用空间较小的记录下来。策略的目的即是保证最终得到一个既满足用户申请需求又最不浪费空间的Best Heap Block。
(3) 对应步骤(2)选出的Best Heap Block:
A:若Best HeapBlock不存在,意味着用户申请失败;
B:若Best HeapBlock空间正好满足用户申请的需求,则无需另做处理,直接给到用户即可;
C:若Best HeapBlock的空间除去用户申请的需求还有盈余,则需要进行分割。盈余部分需要分割成为新的可用(空闲)Heap Block,等待下次内存申请。
下图是关于Heap空间分配的示意,体现出Heap Block的连接变化:
3.2 Heap 空间分配相关函数
ESP32 SDK Heap空间分配的相关函数以及调用过程:
Malloc ->
_malloc_r ->
heap_caps_malloc_default ->
heap_caps_malloc ->
multi_heap_malloc ->
multi_heap_malloc_impl
另外的重要函数,用于分割Best Heap Block: split_if_necessary
ESP32 SDK Heap空间释放的相关函数以及调用过程:
_free_r->
heap_caps_free->
multi_heap_free->
multi_heap_free_impl->
merge_adjacent
注意:
heap_caps_dump
heap_caps_get_minimum_free_size
heap_caps_get_free_size
heap_caps_print_heap_info
详细的说明链接Heap Memory Allocation
详细的说明链接Heap Memory Debugging
可以产生中断的外设可以分为两种类型:
每个Xtensa CPU内核都有自己的六个内部外设:
其余的中断源来自外部外设。这些在soc/soc.h中定义为ETS_*_INTR_SOURCE。
两个CPU内核中的非内部中断插槽连接到中断多路复用器,可用于将任何外部中断源路由到任何这些中断插槽。
从未固定到核心的任务调用esp_intr_alloc()时应小心。在任务切换期间,这些任务可以在核心之间迁移。因此,无法分辨哪个CPU分配了中断,这使得释放中断句柄变得困难,并且还可能导致调试困难。建议使用带有特定CoreID参数的xTaskCreatePinnedToCore()来创建将分配中断的任务。对于内部中断源,这是必需的。
详细使用说明