跳到主要内容

02. 创建第一个标准 ESP-IDF 工程

学习形式工程实操
硬件要求ESP32-C3
配套 Demo
本课动作仅烧录基础程序

这节课要完成什么

这一课先不写 CAN。我们要使用 ESP-IDF 自带的标准命令,从一个空目录创建工程,然后完成(如果以前已经学会下面的操作,可以跳过该章):

创建工程 → 设置ESP32-C3目标 → 编译 → 烧录 → 查看串口日志

从标准工程到开发板运行日志的完整路径

图里的六步就是本课顺序。后面每一份 ESP32 实验也会重复这条路径,因此现在应当逐步执行,不要只记住最后一条 flash monitor 命令。

只有这条基础链路通过,后面出现问题时才能把“开发环境故障”和“CAN 故障”分开。

开始前检查

# 在当前终端加载 ESP-IDF 环境;新开终端后需要重新执行。
source "$HOME/esp/esp-idf-v5.5.4/export.sh"
echo "$IDF_PATH"
idf.py --version

必须确认:

IDF_PATH=<你的主目录>/esp/esp-idf-v5.5.4
ESP-IDF v5.5.4

1. 先认识标准工程的最小结构

一个最小 ESP-IDF 工程通常是:

can_hello/
├── CMakeLists.txt
└── main/
├── CMakeLists.txt
└── can_hello.c

三个文件分别负责:

文件用途
顶层 CMakeLists.txt声明 ESP-IDF 工程和工程名称
main/CMakeLists.txt告诉构建系统需要编译哪些源文件
main/can_hello.c包含 ESP-IDF 应用入口 app_main()

标准 ESP-IDF 工程中各文件怎样进入 build 目录

初学时最容易混淆的是两个 CMakeLists.txt:顶层文件声明整个工程,main/ 中的文件声明应用组件要编译哪些源码。新增 .c 文件后若忘记修改后者,代码写得再正确也不会进入固件。

2. 查看命令帮助

# 创建一个全新的练习工程,并进入工程目录核对生成结果。
idf.py create-project --help

在 ESP-IDF v5.5.4 中,标准语法是:

idf.py create-project [OPTIONS] NAME

-p--path 可以指定目标目录。如果目标目录非空,命令会拒绝覆盖。

3. 创建课程工作目录

课程建议把阶段工程放在独立目录,不修改最终 demo:

# 创建课程工作目录并进入该目录。
mkdir -p "$HOME/esp/can_course_workspace"
cd "$HOME/esp/can_course_workspace"

检查目标工程是否存在:

# 先确认目标路径不存在,避免覆盖之前的工程或源码。
test -e "$HOME/esp/can_course_workspace/can_hello"
echo $?

只有返回 1,表示路径不存在,才继续创建。

4. 用 idf.py 创建标准工程

# 创建一个全新的练习工程,并进入工程目录核对生成结果。
idf.py create-project \
-p "$HOME/esp/can_course_workspace/can_hello" \
can_hello

预期输出:

Executing action: create-project
The project was created in .../can_hello

列出文件:

# 列出实际生成的文件,名称和层级应与正文给出的结构一致。
find "$HOME/esp/can_course_workspace/can_hello" \
-maxdepth 3 -type f | sort

应该看到三个最小文件。

标准工程与示例工程的区别create-project 创建空白标准骨架;create-project-from-example 从组件注册表下载并复制一个官方示例。后面创建 CANopenNode heartbeat 工程时会使用第二种命令。

5. 看懂生成的入口

生成的 main/can_hello.c 是:

// ESP-IDF 工程不会使用桌面程序常见的 main(),而是由系统启动流程调用 app_main()。
// 这个空入口暂时不做任何事情,只用于确认 CMake、工具链和工程目录能够正常工作。
#include <stdio.h>

void app_main(void)
{
// 第一轮先保持为空。能够编译通过,就说明最小工程骨架没有问题。
}

ESP-IDF 应用入口叫 app_main(),不是普通桌面 C 程序中的 main()。ESP-IDF 启动系统和 FreeRTOS 后,会创建主任务并调用它。

6. 加入第一条日志

main/can_hello.c 修改为:

// esp_log.h 提供 ESP_LOGI() 等日志宏,比 printf() 多出级别、时间和 TAG 信息。
#include "esp_log.h"

// TAG 会显示在每条日志前面。后续日志变多时,可以用它判断输出来自哪个模块。
static const char *TAG = "can_course";

void app_main(void)
{
// app_main() 被调用且能看到这两行,才能证明固件已经真正运行,而不只是编译成功。
ESP_LOGI(TAG, "ESP-IDF environment is ready");
ESP_LOGI(TAG, "Next step: learn generic CAN before using TWAI");
}

第一行日志验证 ESP-IDF 应用运行成功。第二行提醒:TWAI 是后续 ESP32 实现课的内容,不是 CAN 标准本身。

7. 设置目标芯片

# 进入课程统一使用的 ~/esp 工作目录,后续相对路径都从这里计算。
cd "$HOME/esp/can_course_workspace/can_hello"
idf.py set-target esp32c3

这一步会:

  • 把构建目标设置为 ESP32-C3;
  • 重新生成项目配置;
  • 创建或更新 sdkconfig
  • 清理与旧目标不兼容的构建配置。

验证:

# 从 sdkconfig 读回目标芯片,结果应为 esp32c3。
rg 'CONFIG_IDF_TARGET=' sdkconfig

预期:

CONFIG_IDF_TARGET="esp32c3"

8. 编译

# 编译当前工程,先处理出现的第一条错误。
idf.py build

编译成功后会生成:

build/bootloader/
build/partition_table/
build/can_hello.bin
build/can_hello.elf

看到 Project build complete 只证明代码和环境能够生成固件,还没有验证开发板、USB 或硬件连接。

9. 烧录和监视串口

连接 ESP32-C3 后,直接执行:

# 烧录固件并打开串口监视器;按 Ctrl+] 退出监视器。
idf.py flash monitor

含义:

部分作用
flash自动寻找串口,把固件写入 ESP32-C3 Flash
monitor烧录完成后继续在当前终端查看串口日志

电脑只连接一块开发板时,一般不需要填写串口设备名。ESP-IDF Monitor 打开后会显示它实际使用的端口和波特率,例如:

--- idf_monitor on /dev/ttyACM0 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---

以后只想重新查看已经烧录好的程序,可以执行:

# 只重新打开串口监视器,不会再次烧录固件。
idf.py monitor

预期应用日志:

I (...) can_course: ESP-IDF environment is ready
I (...) can_course: Next step: learn generic CAN before using TWAI

退出 monitor:按 Ctrl+]。如果已经错过启动日志,依次按 Ctrl+TCtrl+R 复位开发板。

10. 常见问题

idf.py: command not found

当前终端没有加载 export.sh

目标不是 esp32c3

重新确认当前工程目录,再执行 idf.py set-target esp32c3

自动识别不到串口,或者电脑连接了多个串口设备

先检查 USB 线、开发板供电和 Linux 串口设备:

# 列出实际生成的文件,名称和层级应与正文给出的结构一致。
ls -l /dev/ttyUSB* /dev/ttyACM* 2>/dev/null

确定实际设备后再通过 -p 指定,例如:

# 烧录固件并打开串口监视器;按 Ctrl+] 退出监视器。
idf.py -p /dev/ttyACM0 flash monitor

-p 用于自动识别失败或多个设备无法区分的情况,不是每次烧录都必须填写。

Permission denied

当前用户没有串口访问权限,或串口被另一个 monitor 占用。不要同时打开多个串口监视器。

回头看一下刚才做了什么

我们先用 create-project 得到一个标准空白工程,再用 set-target 选择 ESP32-C3。build 负责生成固件,flash 把固件写入开发板,monitor 则用来查看程序实际输出。

这条链路跑通以后,下一课再加入 CAN。这样后面即使通信失败,也已经知道 ESP-IDF 环境、编译、烧录和串口本身可以正常工作。

离开这一课前

逐项确认;在 Markdown 编辑器中可以把已经完成的 [ ] 改成 [x]

  • idf.py --version 显示 ESP-IDF v5.5.4
  • sdkconfig 中存在 CONFIG_IDF_TARGET="esp32c3"
  • idf.py build 结束时出现 Project build complete,并生成 build/can_hello.bin
  • idf.py flash monitor 已自动打开开发板串口,或在多设备时通过 -p 指定了正确端口。
  • 串口中出现 ESP-IDF environment is readyNext step: learn generic CAN before using TWAI

这五项都能确认,就可以继续下一课。

资料来源

  • ESP-IDF v5.5.4 idf.py create-project --help 实际输出。
  • ESP-IDF 标准工程生成结果:顶层 CMake、main component 和 app_main()