【ESP32笔记】工程目录解析

本节目标:补齐环境细节(USB、串口权限、免密登录),看懂 helloworld 工程每个文件的用途,理解 CMake 构建系统,会编译、配置、烧录、监控

一、本节要解决的问题

上一节环境已经能编译,但还有一批"用小毛病换大麻烦"的细节没处理:

  1. C/C++ 插件在 ubuntu 端占着 CPU 不放,虚拟机越用越卡;
  2. 开发板插上 Type-C 线后,ubuntu 里经常连不上串口设备;
  3. 执行 idf.py flash 报 Permission denied: '/dev/ttyUSB0';
  4. 每次用 VSCode 远程登录、打开工程都要输两三次密码。

更核心的问题是:helloworld 工程里每个文件是干什么的?编译出来的 bin 放在哪、烧到哪里?不搞清楚这些,就没法自己新建和修改工程------而"从现有工程拷贝来改"正是教程推荐的高效做法。

二、相关知识:环境搭建补充

2.1 降低 C/C++ 插件 CPU 占用

C/C++ 插件安装在 ubuntu 端(不是 Windows 本地那份)。登录远程后,点击插件管理里 ubuntu 端那一份,按教程截图设置,减少 IntelliSense 的后台解析负担。

提示 :仓库自带的 helloworld/.vscode/settings.json 里已包含 "C_Cpp.intelliSenseEngine": "default" 一类配置,就是本地调优后的结果,可作参考。

2.2 虚拟机 USB 设置

开发板通过 Type-C 转 USB 连到电脑,为了下载和调试,我们要把它接到虚拟机上:

  • 插上线后虚拟机会弹出提示框,选择"连接到虚拟机",虚拟机右下角出现 USB 已连接图标,也可以手动点该图标连接/断开;
  • 按默认设置使用时会经常连不上串口设备,按教程截图在虚拟机 USB 设置里调整,设置完成后重启虚拟机系统。

2.3 USB 串口权限问题

烧录的前提是开发板已连到虚拟机。执行 idf.py flash 时可能报:

复制代码
Permission denied: '/dev/ttyUSB0'

临时解决(重插或重启后失效,且 777 权限开得过大,仅应急):

bash 复制代码
sudo chmod 777 /dev/ttyUSB0

一劳永逸(把当前用户加入串口设备所属的 dialout 组,然后重启系统):

bash 复制代码
sudo usermod -aG dialout 你的用户名

提示 :教程原文命令写的是 usrname,要换成你自己的 ubuntu 用户名。另外,若使用原生 USB 的 ESP32-S3/C3 板子,串口设备名可能是 /dev/ttyACM0 而不是 /dev/ttyUSB0,具体以 ls /dev/tty* 看到的为准。

2.4 使用证书 SSH 登录 ubuntu

每次远程登录都要输密码很烦,可以改用密钥免密:

  1. Windows 下打开 PowerShell,执行 ssh-keygen -t ed25519 -C youremail@xxx.com(邮箱随便填,一路回车);
  2. 在 C:\Users\你的用户名\.ssh 目录下找到 id_ed25519.pub,用文本打开并复制内容;
  3. 用 VSCode 远程登录 ubuntu,打开 .ssh 文件夹里的 authorized_keys,把公钥内容粘贴进去;
  4. 如仍要输密码,再在 VSCode 的 SSH 配置里加一项 IdentityFile 指向密钥,然后重启 VSCode。

注意 :教程原文的 IdentityFile 写的是 id_ed25519.pub(公钥)。按 OpenSSH 的规范这里通常应指向私钥 id_ed25519(不带 .pub);教程也注明"这一步不做也可以",以你本地实际生效的写法为准。

三、知识点:ESP-IDF 的构建系统与工程组成

3.1 两层 CMakeLists 的分工

ESP-IDF 用 CMake 构建,一个工程有两级 CMake 脚本:

  • 顶层 CMakeLists.txt(项目级):声明最低 CMake 版本、引入 ESP-IDF 的构建逻辑、指定工程名;
  • 组件级 CMakeLists.txt(main/ 目录下):登记本组件有哪些源码文件、头文件目录在哪。

ESP-IDF 以"组件 "为构建单元:esp-idf/components 下每个组件(FreeRTOS、driver、wifi...)独立编译成静态库,最后和你的应用链接成 main.bin。main 是存放用户代码的特殊组件。

3.2 build/ 里的产物与烧录地址

编译后生成的 bin 不止一个。看 build/flasher_args.json 就能知道各自烧到哪里(本仓库实际内容节选):

文件 烧录地址 作用
bootloader/bootloader.bin 0x0 应用程序启动前运行的一段代码,负责引导
partition_table/partition-table.bin 0x8000 分区表,描述各分区分别存放什么
main.bin 0x10000 我们的应用代码

此外该文件还记录了烧录参数(flash 模式、频率、大小)和目标芯片信息,后续讲分区表时会再回来看它。

sdkconfig 是工程配置文件,记录所有可配置项的值:

  • 如果不存在,idf 会先生成一份默认配置;
  • 执行 idf.py menuconfig(图形化配置界面,用键盘上下左右和回车操作),保存后自动写回 sdkconfig,旧文件备份为 sdkconfig.old;
  • sdkconfig 属于自动生成的配置产物,不建议手动编辑。

教程要求先改三项(其余保持默认,以后用到哪个配哪个):

配置项 教程值 menuconfig 路径
工作主频 160MHz 改为 240MHz Component config → ESP System Settings → CPU frequency
Flash 大小 2MB 改为 4MB Serial flasher config → Flash size
PSRAM 启用 Component config → ESP PSRAM → Support for external, SPI-connected RAM

修改后按 s 保存、q 退出,再 idf.py build 重新编译。

3.4 新建工程的两种方式

  • 拷贝现有工程 :教程最推荐,把 helloworld 整个复制一份改名,改改就能用,效率最高;
  • 命令行创建 :idf.py create-project 工程名 会自动生成一个最小工程骨架。

四、代码分析

4.1 工程结构

复制代码
helloworld/
├── CMakeLists.txt          # 顶层 CMake 脚本:工程名与构建入口
├── main/
│   ├── CMakeLists.txt      # 组件 CMake 脚本:登记源文件
│   └── main.c              # 应用入口 app_main()
├── sdkconfig               # 工程配置(menuconfig 生成)
├── sdkconfig.old           # 上一次保存的配置备份
├── build/                  # 编译产物与中间文件(自动生成)
├── .vscode/                # 本地 VSCode 配置(端口、IntelliSense、调试)
├── .clangd                 # clangd 代码索引配置
└── README.md               # ESP-IDF 模板自动生成,无实际内容,可忽略

4.2 顶层 CMakeLists.txt

cmake 复制代码
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

三句有效代码各有分工:

  • cmake_minimum_required(VERSION 3.16):要求 CMake 至少 3.16;
  • include($ENV{IDF_PATH}/tools/cmake/project.cmake):引入 ESP-IDF 的构建规则,靠环境变量 IDF_PATH 定位(这就是必须先 source export.sh 的原因之一);
  • project(main):声明工程名。要改工程名就改这里的 main (注意:它只是工程名,和 main/ 目录名是两回事,模板里两者恰好同名,容易混淆)。

注释原文强调这几行样板代码"必须按此顺序出现",顺序错了 CMake 直接报错。

4.3 main/CMakeLists.txt

cmake 复制代码
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS ".")
  • SRCS "main.c":本组件的源文件清单。以后新增 .c 文件,必须加到这里,否则不参与编译;
  • INCLUDE_DIRS ".":头文件搜索路径为当前目录。新增头文件目录同样要登记在这里。

main/ 目录是必须存在的,否则编译不过。

4.4 main/main.c

c 复制代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char* TAG = "main";

void app_main(void)
{
    while(1)
    {
        ESP_LOGI(TAG,"Hello world!");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
  • app_main() 是应用的入口函数,相当于 PC 程序的 main;
  • TAG 是日志标签,串口打印时用它标识这条日志来自哪个模块;
  • ESP_LOGI(TAG,"Hello world!") 以 INFO 级别输出日志,比 printf 多了时间戳、级别、标签,且能按级别整体过滤,是 ESP-IDF 里推荐的打印方式;
  • vTaskDelay(pdMS_TO_TICKS(1000)) 延时 1000ms。vTaskDelay 的单位是 FreeRTOS 的 tick,pdMS_TO_TICKS 负责把毫秒换算成 tick;延时会阻塞当前任务并让出 CPU,空闲任务和其他任务才有机会运行;
  • while(1) 让任务常驻循环:app_main 返回后会被系统删除,这里不返回,日志便每秒打印一次。

4.5 sdkconfig 关键配置

sdkconfig 有两千多行,初学只需关注开头几项。本仓库当前的实际值(节选):

复制代码
CONFIG_IDF_TARGET="esp32s3"
CONFIG_IDF_TARGET_ESP32S3=y
CONFIG_IDF_INIT_VERSION="5.3.4"
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160
# CONFIG_SPIRAM is not set

含义:目标芯片是 esp32s3 ,Flash 还保持默认的 2MB ,CPU 主频 160MHz ,PSRAM 未启用。

与教程不一致,务必注意 :教程第 3 节按"原版 ESP32 开发板"讲解------模组为 4MB Flash + 2MB PSRAM、主频可开到 240MHz,所以让你在 menuconfig 里改这三项。但仓库里的 helloworld 工程已被切到 esp32s3 目标,上面的三项配置仍是默认值(build/flasher_args.json 里的 "chip": "esp32s3"、"flash_size": "2MB" 也印证了这一点);而且 sdkconfig 由本地 ESP-IDF 5.3.4 生成,与教程的 v5.2 不完全同源。以自己开发板的原理图和实际工程为准 :若你的板子是原版 ESP32,先 idf.py set-target esp32,再按 3.3 配置这三项;换 target 会重置 sdkconfig,配置要重做。

五、关键 API 与命令速查

常用 idf.py 命令:

命令 作用
idf.py create-project 工程名 新建一个最小工程骨架
idf.py set-target esp32 设置目标芯片(会重置 sdkconfig 和 build)
idf.py build 编译工程,生成 bin 文件
idf.py menuconfig 图形化配置工程,保存后写回 sdkconfig
idf.py -p /dev/ttyUSB0 flash 烧录编译产物到开发板
idf.py -p /dev/ttyUSB0 monitor 打开串口监视器查看日志
idf.py flash monitor 烧录完成后直接进入监视器
idf.py fullclean 清除 build 目录的全部产物

本节用到的 API:

API 作用 所在头文件
ESP_LOGI(TAG, fmt, ...) 以 INFO 级别打印日志,自带时间戳与标签 esp_log.h
vTaskDelay(ticks) 阻塞当前任务指定 tick 数并让出 CPU freertos/task.h
pdMS_TO_TICKS(ms) 把毫秒换算成 FreeRTOS tick freertos/FreeRTOS.h

关键文件速查:

文件/目录 作用
CMakeLists.txt 顶层构建脚本,声明工程名与构建入口
main/CMakeLists.txt 组件构建脚本,登记源码文件与头文件目录
main/main.c 应用入口 app_main()
sdkconfig / sdkconfig.old 工程配置及其上一次保存的备份
build/flasher_args.json 记录各 bin 文件与烧录地址
.vscode/ 本地 VSCode 配置(串口、IntelliSense、调试)

六、编译与现象验证

bash 复制代码
cd ~/esp32/esp32-board/helloworld
idf.py menuconfig          # 按需改配置:s 保存、q 退出
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
  • 编译成功后 build/ 目录下出现 main.bin、bootloader/bootloader.bin、partition_table/partition-table.bin;
  • 烧录:板子通过 Type-C 线连到电脑并选择连接虚拟机,板上有一键下载电路,无需按任何按键;
  • 串口现象:每秒打印一行日志,形如 I (1234) main: Hello world!;
  • 退出串口监视器:按 Ctrl+]。

提示 :仓库 .vscode/settings.json 里的 "idf.portWin": "COM8" 是 Windows 本地 ESP-IDF 插件的端口配置,和 ubuntu 虚拟机里的 /dev/ttyUSB0 不是一回事,别照着 COM8 去烧录。

相关推荐
华允物联-HUAIOT1 小时前
串口服务器支持 WiFi 吗?有线与无线串口联网科普
运维·服务器·单片机
ss2731 小时前
PG实战笔记 | 07 PostgreSQL常见编译项及其作用
数据库·笔记·postgresql
沪上企服通1 小时前
规则引擎版本化:把代账科目映射当 policy-as-code 管(上海 5 家样本对照)
笔记·架构
志尊宝1 小时前
Vue3 零基础每日笔记(085):项目上线前 Checklist——环境变量、报错监控、安全自查
前端·javascript·笔记·安全·信息可视化·echarts
霖霖7141 小时前
【ESP32笔记】ADC获取NTC温度
笔记·单片机·mcu·物联网
志尊宝1 小时前
Vue3 零基础每日笔记(088·完结):从零到一——一个完整 Vue3 项目的成长复盘
前端·javascript·vue.js·笔记·ecmascript·#vue.js
Capricorn19881 小时前
一段内容查不出对错时:知芽 Notebook Skill 与 SynthID 检测器的边界
大数据·论文阅读·人工智能·笔记
ClinicTech1 小时前
蠕动泵的隔离式传输原理与关键控制功能解析
单片机·嵌入式硬件
奋斗的阿狸_19861 小时前
ESP32-S3 DevKit 模拟量采集调试报告
单片机·嵌入式硬件