ESP32学习笔记(一):ESP-IDF配合VSCode的一些方法和技巧
1、idf的常用基本命令
(1). 查看芯片信息:esptool.py -p COM4 flash_id
1 | esptool.py -p COM4 flash_id |
(2). 选择芯片命令:idf.py set-target esp32
详细的命令格式为:idf.py set-target [OPTIONS] {esp32|esp32s2|esp32c3|esp32s3|esp32c2|esp32c6|esp32h2|linux}
所以我们如果选择ESP32S3,则可以:
1 | idf.py set-target esp32s3 |
(3). 编译命令:idf.py build
1 | idf.py build |
(4). 配置命令: idf.py menuconfig
1 | idf.py menuconfig |
(6). 下载命令: idf.py -p COM3 flash
1 | idf.py -p COM3 flash |
(7). console调试命令: idf.py -p COM3 monitor
1 | idf.py -p COM3 monitor |
(8). 退出调试模式: ctrl + ]
(9).更新esptool: pip install --upgrade esptool
1 | pip install --upgrade esptool |
(10). 关于idf版本相关内容
https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.3/esp32s3/versions.html
可以通过如下代码来下载idf的源码:
1 | cd $IDF_PATH |
2、idf和 vscode的一些说明和细节
(1). ESP-IDF CMD和 ESP-IDF PowerShell终端快捷方式
1. 终端快捷方式的解释
- cmd的目标内容:
C:\WINDOWS\system32\cmd.exe /k ""D:\Espressif\idf_cmd_init.bat" esp-idf-cc72132cd64ef413a5557253e3adc170"
- PowerShell的目标内容:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoExit -File "D:\Espressif/Initialize-Idf.ps1" -IdfId esp-idf-cc72132cd64ef413a5557253e3adc170
它们的起始位置:
D:\Espressif\frameworks\esp-idf-v5.1\
本质上idf就是创建了一个Python的虚拟环境,并针对该环境配置了环境变量,配置环境变量的内容可以详解idf安装目录下的
idf_cmd_init.bat
(针对cmd)和Initialize-Idf.ps1
(针对powershell)。而/k是cmd的参数,表示”保持命令行窗口打开”,表示在执行完idf_cmd_init.bat保持终端窗口打开,这样可以确保创建的虚拟环境在在终端中有效,也就是关闭了终端,虚拟环境就关闭了,环境变量也随之无效。
esp-idf-cc72132cd64ef413a5557253e3adc170
是idf软件的id,用来表示该idf软件,其在软件安装目录下的esp_idf.json
中定义,为idfSelectedId
的值。esp_idf.json
文件内容如下:
1 | { |
-ExecutionPolicy Bypass
目的是关闭powershell的安全策略,这是由于PowerShell有一个安全特性,可以限制未经签名的脚本的执行,所以需要配置Bypass选项,用来允许执行任何脚本,无视这些安全限制,以确保可以运行Initialize-Idf.ps1脚本。
-NoExit
与cmd终端中的/k
作用一样,指示PowerShell在脚本执行完毕后不要关闭窗口,允许用户在同一窗口继续执行更多命令。
-File "D:\Espressif/Initialize-Idf.ps1"
表示运行该脚本,用于设置ESP-IDF开发环境,与cmd终端的idf_cmd_init.bat
作用相同,详细可以看批处理和脚本的内容
-IdfId esp-idf-cc72132cd64ef413a5557253e3adc170
则表示在执行Initialize-Idf.ps1
脚本时,给脚本中的IdfId变量复制为esp-idf-cc72132cd64ef413a5557253e3adc170
这个值。
通过以上解释,我们大致理解了idf-env工具的作用了。这意味着,实际上,我们可以在任何终端窗口,输入如下内容就可以进行idf的使用:
2. 一种多版本安装方法:
根据前面的终端的解释,所以我这边进行一种多版本idf的安装方法,我首先给电脑安装一个默认版本的idf,这里选择了idf v5.1版本(至于原因,后面有讲),我是安装在D:\Espressif
目录下,然后再Espressif
目录下又创建了几个其他版本的idf,直接取名版本号如下图:
这个时候每安装完成一个版本,可以把该版本的终端快捷方式复制出来,放在桌面的一个文件夹内,这里我取名idf,就可以实现多有版本的终端编译功能。最后所有版本的快捷方式如下:
这里需要注意两点:
安装其他版本的时候,有可能导致,目录下
esp_idf.json
文件对应的目录内容改变,我记得好像是"gitPath"
变量的值会修改,可以修改为对应版本的idf的目录,详细json的值可以参考第一节对快捷方式的解释内容。安装可能会再用户环境变量中添加名称为
IDF_TOOLS_PATH
的环境变量,默认是安装最后一个版本的idf的目录,比如D:\Espressif\v5_1_3
,这里可以直接把该环境变量删除掉。否则可能会导致终端快捷方式运行出错(快捷方式运行的时候会首先读取IDF_TOOLS_PATH
的值,如果有,则默认定义该值为软件目录,没有则会自动创建批处理文件的目录为IDF_TOOLS_PATH
)这里给出idf_cmd_init.bat
这部分的定义如下:
1
2
3
4 if "%IDF_TOOLS_PATH%" == "" (
set IDF_TOOLS_PATH=%~dp0
echo IDF_TOOLS_PATH not set. Setting to %~dp0
)
此时VScode中默认绑定默认版本的idf。其他版本的编译有两种思路:
一种是直接再vscode中打开终端,然后输入语句来设置对应版本的idf的环境变量和配置,然后用idf的指令进行编译下载和调试;
另外一种方式是,针对每种版本的idf创建一个vscode工作区参数设置文件,直接把该文件复制到需要运行的idf工程中,此时,vscode会自动设置idf的工作区目录,并使用特定版本的idf来编译和下载程序。这个方法参考了B站UP主“第九个下弦月”的一个视频内容提供的 vscode 工作区的概念,详细视频地址为:https://www.bilibili.com/video/BV1bj421Z7z4/?spm_id_from=333.337.search-card.all.click&vd_source=50e88259c3b06cefc86b0480e57ce888
3. 终端环境变量配置语句:
首先是默认版本,我的是v5.1,直接再vscode终端中输入如下语句(一个采用cmd,一个采用powershell),既可再改终端之后,使用针对该版本的idf命令,实现编译和下载等功能:
1 | cmd /k ""D:\Espressif\idf_cmd_init.bat" esp-idf-cc72132cd64ef413a5557253e3adc170" |
或者
1 | powershell -ExecutionPolicy Bypass -NoExit -File "D:\Espressif/Initialize-Idf.ps1" -IdfId esp-idf-cc72132cd64ef413a5557253e3adc170 |
如果是V5.1.3版本,id号不一样,用改语句:
1 | cmd /k ""D:\Espressif\v5_1_3\idf_cmd_init.bat" esp-idf-2f332dae01e5300d41bb5887948c6ac3" |
或者
1 | powershell -ExecutionPolicy Bypass -NoExit -File "D:\Espressif\v5_1_3/Initialize-Idf.ps1" -IdfId esp-idf-2f332dae01e5300d41bb5887948c6ac3 |
如果是V5.2.1版本,id号不一样,用改语句:
1 | cmd /k ""D:\Espressif\v5_2_1\idf_cmd_init.bat" esp-idf-f2854e5176bb04b11fdfe329dc6d2d14" |
或者
1 | powershell -ExecutionPolicy Bypass -NoExit -File "D:\Espressif\v5_2_1/Initialize-Idf.ps1" -IdfId esp-idf-f2854e5176bb04b11fdfe329dc6d2d14 |
注意:安装位置不同,这个生成的码也不同,应该是根据版本号,安装位置等信息生成的唯一识别码。
4. vscode 工作区配置文件
如果不希望再vscode中用终端来编译和下载文件,而是希望直接用vscode下方的快捷键来操作,而且可以实现在一台电脑上随时切换不同版本的idf工程,可以针对每个版本创建一个 vscode 工作区配置文件,把该文件复制到工程目录下,文件后缀需要为:xxx.code-workspace
,比如,下面是我针对v5.2.1版本创建的文件,取名为idf_5.2.1.code-workspace
内容如下:
1 | { |
可以看到,其实它操作和终端配置环境变量差不多,就是设置idf工具的目录地址而已。其他版本可以参考这个来设置,这里不多赘述。需要做好的配置文件,可以直接到如下地址下载:https://gitee.com/SenySunny/picture/tree/master/idf_code-workspace
另外,经部分电脑
xxx.code-workspace
文件并不会生效,针对这种情况,请直接把"settings"
的内容(即所有的idf.xxx
相关的软件目录选项)复制到.vscode
目录下的settings.json
文件中,然后重新打开工程目录。
(2). vscode中的idf工程代码跳转问题
在vscode中建立idf工程,或者直接从idf中复制example
例程创建工程修改时,默认情况下我们点击使用的idf中的api函数是无法跳转到idf目录下,可以通过如下手段,实现代码的跳转(默认是按下Ctrl
+鼠标左击实现跳转)——注意需要vscode已经安装idf插件和C/C++
插件。
默认打开一个工程内的C文件,会在vscode的右下方出现win32的按钮图标(注意如下没有出现,可以尝试最大化vscode窗口),点击按钮,在弹出的选项栏中选择”编辑配置(JSON)”(也可以直接按住Ctrl+P
按钮,在弹出的输入框输入> 编辑配置(JSON)
,效果一样),此时,会自动为工程添加c_cpp_properties.json文件(在.vscode
目录下)。
在c_cpp_properties.json文件文件中的”configurations”属性添加一行代:码"compileCommands": "${workspaceFolder}/build/compile_commands.json"
, 注意json格式,需要在前面一行添加逗号,在正常添加之后的c_cpp_properties.json文件内容如下(不同环境下可能有所不同,只需要注意添加compileCommands属性即可):
1 | { |
此时在进行编译之后,会在build目录下产生一个compile_commands.json文件,此时,便可以按住ctrl键,鼠标点击特定函数,即使函数是在idf的内部,也可以实现跳转。
(3). 在下载或者其他使用串口的时候,出现无法打开串口的情况解决办法(被占用)
由于idf的操作都是在终端中进行的操作,如果它把串口占用了,可能会导致再次使用串口,出现无法打开串口的情况,此时可能会不清楚是哪个进程占用了串口的情况,导致无法使用串口(很多时候,即使把掉USB线,再插入也没有用,因为线程本身支持断线再插入的时候自动重新打开),可以通过如下方法来解除串口的占用。
打开注册表,在系统的左下角软件搜索栏输入“regedit”,或者右击右下角windows图标选择“运行”, 输入regedit,都可以打开注册表
打开如下注册表选项
“计算机\HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM”
,在里面找到无法打开的注册表,如下图,我电脑是COM4
- 复制它的名称,我的电脑为
\Device\USBSER000
.打开电脑资源管理器,选择“性能”选项卡, 点击下方的“打开资源监视器”,选择“CPU”,在关联的句柄中输入串口设备的名称,点击刷新,就可以看到占用串口的应用程序,选中右击,选择“结束进程”即可,此时,串口就又可以使用了,按照下图顺序操作即可。
(4). 关于USB-Serial-JTAG的一些问题(目前发现在v5.1中可以正常打印,其他版本可以用于log打印,但是直接用于stdin和stdout貌似有bug)
目前发现USB-Serial-JTAG,在打印的时候,会有缓冲机制,也就是只有当收到\n字符(或者超时相关)的时候才会进行打印和输入,而且不管是绑定esp_vfs虚拟文件系统还是通过fflush(stdout);
强制刷新输出缓冲流都没有用,这部分的内容可以参考: https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.1/esp32s3/api-guides/usb-serial-jtag-console.html (无关版本问题,在5.1版本也一样)和 https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-guides/usb-serial-jtag-console.html ,在文章中是这样说明的:
对于从 ESP32-S3 发送到 PC 终端的数据(例如 stdout、日志),ESP32-S3 会先写入一个小型内部缓冲区。如果该缓冲区达到最大容量(例如没有连接 PC 终端),ESP32-S3 会进行一次等待,时长约 50 ms。在此期间,PC 终端可以请求数据,但可能会导致应用程序出现非常短暂的暂停。
对于从 PC 终端发送到 ESP32-S3 的数据(如控制台命令),许多 PC 终端会等待 ESP32-S3 接收字节后,再允许发送更多数据。这与使用 USB 转串口 (URAT) 桥接芯片不同,URAT 桥接芯片始终接收字节,并将其发送到(可能未在监听)的 ESP32-S3。
在极少数情况下,从 ESP32-S3 发送到主机的数据可能会“阻塞”在主机内存中。继续发送数据即可“取消阻塞”,但如果应用程序未继续发送数据,则需要手动将这些数据刷新到主机。能否继续发送数据取决于驱动程序,默认配置的非阻塞驱动程序和 VFS 实现会在换行后自动刷新,而基于中断的阻塞驱动程序会在发送缓冲区变空时自动刷新。
目前发现在5.1版本中通过设置usb_serial_jtag缓冲区,然后绑定esp_vfs。然后用usb_serial_jtag_read_bytes
和usb_serial_jtag_write_bytes
来替代getchar
和putchar
貌似在部分环境下有效(也可以使用putchar+fflush(stdout)),但是我测试在5.1.3版本中无效。
1 | //初始化usb_serial_jtag缓冲区,并绑定esp_vfs |
如果直接绑定console做标准输入输出,printf
或者putchar
都会直接进行usb的缓冲区,直到接收到\n
字符(部分版本超时之后也会)之后才会一并通过usb_serial_jtag输出。
所以目前发现如果希望使用USB-Serial-JTAG即作为下载程序,又充当一个标准串口来使用,目前最好用v5.1版本,如果不是充当标准串口,而支持用作log打印或者不介意数据缓冲问题的情况下,可以采用任意版本的idf。
3、idf的工程和工具补充
(1). 一些常见的menuconfig配置问题
idf的工程 配置是通过menuconfig来实现的,menuconfig是一种工程代码管理工具,可以通过图形化配置界面,对工程的信息进行配置,这里对一些经常使用的menuconfig配置做一个说明
1. Flash大小和CPU频率
比如ESP32-S3默认的flash大小是2M,CPU工作频率为160MHz这个可以修改成你的flash大小和频率如下:
2. 修改主线程app_main的栈大小
当程序比较大时,可能会导致主线程堆栈太小,可以设置高一些。否则可能会给出***ERROR*** A stack overflow in task main has been detected.
的报错信息。
3. flash分区表
esp-idf默认分区表如下,只占用了系统1M+64K的空间:
1 | Name, Type, SubType, Offset, Size, Flags |
而程序稍微大一些,空间就可能不够,所以一般项目大一些情况,我们会自定义分区表,可以直接在工程下创建partitions.csv
文件,然后再menuconfig中选择自己创建的分区表信息,如下:
具体分区表partitions.csv
的内容可以参考
4. PSRAM的开启和使用
5. 指定默认console输出通道
这里面有几个值可以选择:
1 | Default:UART0: 选择系统默认底串口0作为log是输出。 |
6. 设置log输出模式
log输出包括两个部分底输出,一个式Bootloader config底log输出,也就是系统启动底时候Bootloader代码底log输出,一个代码的log输出,一般情况下我们只需要设置代码底log输出即可,Bootloader的log输出对我们代码几乎没有影响,还可以方便看到一些代码的配置信息,所以一般不需要设置,保持默认就好。这里把两个都给出来
Bootloader的log输出设置:
代码的log输出设置:
(2). 关于Kconfig.projbuild和sdkconfig.defaults
Kconfig.projbuild本质上是一个配置menuconfig的配置文件,可以同来配置和增加menuconfig相关的配置信息。
关于Kconfig更多的信息可以参考如下图片:
而sdkconfig.defaults可以定义一些默认的配置选项,即给menuconfig配置中的一些参数进行设定,这样可以使得我们不需要通过menuconfig交互式界面对设备信息进行配置,直接通过文本的形式进行配置(前提是我们已经知道了配置的名称和内容)。
实际上,我们在执行idf.py menuconfig的时候,会自动根据menuconfig中的定义,加载sdkconfig.defaults的内容,进行配置。所以如果sdkconfig.defaults中的定义已经存在,则会自动进行menuconfig的配置。
(3). 组件管理(managed_components
与components
)与官方组件管理工具: https://components.espressif.com/
idf的组件分为三种:idf官方组件, 第三方组件, 自定义组件,它们分别在“idf的frameworks库的components目录”, 工程的managed_components
目录, 工程的components
目录下,这里对这三个组件做个简单介绍
第一种为:idf官方组件,它跟随idf的发布而更新,在idf的frameworks库的components目录下,比如5.1版本的官方组件都在如下目录中
1 | frameworks\esp-idf-v5.1\components |
第二种为:第三方组件:一般指的是在官方组件管理工具: https://components.espressif.com/ 中可以搜索到的组件库,乐鑫有一个网络组件管理工具网站,在idf中也构建了组件管理工具。官方和某些第三方发布的组件可以在这里搜索到。它可以通过idf_component.yml来管理,一般情况下,在组件库的界面中会提供下载组件的方法如下图:
更新完成之后组件库会自动添加到工程的managed_components
目录下方。该组件在每次编译的时候会自动同步,以确保组件内容没有被修改,所以一般不允许修改组件的内容。
第三种为:自定义组件,自定义组件库,我们一般放在工程目录的components
目录下,用户可以自行修改,另外如果我们要对一些第三方组件进行修改定制,可以线同步到``managed_components目录,然后我们在剪贴到
components```目录下,再进行编辑使用。
(4). 组件配置文件idf_component.yml 和组件信息文件dependencies.lock
idf_component.yml
文件是一个YAML格式的文件,用于定义组件的元数据和依赖关系。这个文件通常位于组件的根目录下,作用包括:
- 定义依赖项:它列出了当前组件依赖的其他组件或库,包括这些依赖的版本信息。这有助于确保项目中使用的是兼容的组件版本。
- 指定组件属性:比如组件的描述、维护者信息、许可证信息等。
- 配置选项:提供对组件内部设置的配置,如使能或禁用特定的功能。
dependencies.lock
文件是一个自动生成的文件,通常在执行项目配置过程(如运行idf.py reconfigure
或首次构建项目时)生成。它的主要作用是:
- 锁定依赖版本:文件中详细记录了项目解析依赖时所确定的具体版本号。这确保了项目的再现性,即不同的开发者或在不同的环境中构建项目时,都将使用相同版本的依赖,避免由于依赖版本变化导致的问题。
- 依赖解析的结果:这个文件是项目依赖解析过程的直接产物,它包含了所有必需的、已解析的依赖组件及其版本,确保了构建系统能够获取和使用正确的组件版本。
idf_component.yml
和dependencies.lock
的区别
- 用途差异:
idf_component.yml
用于声明依赖和组件配置,而dependencies.lock
用于锁定这些依赖的具体版本,确保构建的一致性。 - 更新机制:
idf_component.yml
由开发者手动更新,以匹配组件需求和属性;dependencies.lock
通常在依赖关系有变化或首次解析依赖时自动更新。 - 目标用户:
idf_component.yml
面向项目和组件开发者,他们需要在这个文件中指定和管理组件的依赖关系;而dependencies.lock
主要是给构建系统使用,以保证项目的构建环境和条件的一致性。
idf_component.yml
文件内容大致如下:
1 | dependencies: |
dependencies.lock
文件内容大致如下:
1 | dependencies: |
下面是几个常用的针对idf_component.yml
组件描述符
1. ^ (Caret): 允许版本在当前主版本号范围内更新。
例如,
^1.0.6
允许使用1.0.6以上到2.0.0以下的版本。
2. ~ (Tilde): 允许次要版本的更新,如果指定了次版本,则只更新补丁版本。
例如,
~8.3.0
可以更新到8.3.x的任何版本,但不超过8.4.0。
3. >= (Greater Than or Equal To): 指定版本必须大于或等于特定版本。
例如,
>=4.4
表示版本必须是4.4或更高。
(5). idf中Cmake配置的一些关键词
1. 屏蔽一些不必要的警告
1 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-char-subscripts -Wno-maybe-uninitialized") |
set
: 这是CMake的命令,用于设置或修改变量的值。CMAKE_C_FLAGS
: 这是一个CMake变量,代表用于C语言编译器的编译选项。"${CMAKE_C_FLAGS}"
: 这部分是变量替换,意思是取出CMAKE_C_FLAGS
变量当前的值。-Wno-char-subscripts
和-Wno-maybe-uninitialized
是具体添加到CMAKE_C_FLAGS
变量中的编译器选项:
-Wno-char-subscripts
:这个选项用来禁止编译器发出警告,当数组下标是char类型时。通常这种情况下,char类型的下标会被隐式转换为整型,某些情况下可能会引发问题或警告。-Wno-maybe-uninitialized
:这个选项用来禁止编译器发出警告,当变量可能未初始化时。这可以防止编译器对可能未初始化的使用发出警告,尽管这样做有时可能隐藏实际的编程错误。
2. 自动扫描文件夹下的C文件自动添加(循环扫描目录以及子目录下的所有c文件)
1 | file(GLOB_RECURSE SOURCES *.c) |
注意,这可能导致增加或者删除文件,但是cmake并没有发现,导致其没有被编译仅二进制文件的可能性,所以这种情况下,建议增加或者删除文件的时候,可以先clean一下项目,然后再重新编译。
另一个常用的实践是尽量避免在配置文件中使用GLOB来指定源文件。可以考虑明确列出所有源文件,虽然这增加了维护成本,但可以提高项目的可预测性。
3. 静态库编译到二进制系统中关键字:WHOLE_ARCHIVE
1 | file(GLOB_RECURSE SOURCES *.c) |
这个意思是,有些函数,即使没有被系统调用(比如被main函数,或者中断函数,或者其他函数调用),这些函数仍然需要被编译到二进制文件中,这样做的目的是,一些脚本解析器,本身系统不会调用该函数,但是系统会执行来自外部的命令,或者文件系统中的脚本代码,根据脚本代码的执行情况会产生调用函数的可能性,如果不包含此关键字可能导致,该函数并未被编译到二进制文件中,而导致系统执行失败。
4. 组件依赖关键字:REQUIRES
1 | file(GLOB_RECURSE SOURCES *.c) |
REQUIRES关键字描述的是代码对一些其他组件库的依赖关系,也就是说,这部分代码可能需要依赖一些其他组件库一起运行(包括官方组件,第三方组件,自定义组件)如果没有相关的组件,可能会导致编译失败,加上这个组件库之后,系统会把需要的组件库一起添加到系统的编译选项里面进行编译。
5. 添加预处理器定义
1 | # add macro |
这会将
PIKA_CONFIG_ENABLE
定义添加到当前目标的编译定义中。