2. 固件编译与首次烧录:Hello World工程创建、编译流程解析、使用esptool.py进行首次烧录、串口监视器查看日志

好,咱们正式开始动手了。这一章的目标很明确——让ESP32跑起来,在串口里看到“Hello World”。听起来简单,但这里面藏着不少坑。我当年第一次烧录时,折腾了整整一个下午才看到那行字。嗯,咱们争取半小时搞定。

2.1 创建Hello World工程

我个人习惯用ESP-IDF的命令行工具来创建工程。别怕敲命令,其实就一行的事。

idf.py create-project hello_world

这条命令会在当前目录下生成一个 hello_world 文件夹。进去看看,结构很简单:

  • main/ — 存放源代码的目录
  • CMakeLists.txt — 顶层构建文件
  • main/CMakeLists.txt — 组件构建文件
  • main/hello_world_main.c — 主程序文件

打开 hello_world_main.c,把默认代码替换成下面这个:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

void app_main(void)
{
    printf("Hello World!\n");

    /* 打印芯片信息 */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("芯片型号: %s, 核心数: %d\n",
           CONFIG_IDF_TARGET,
           chip_info.cores);

    /* 循环打印,方便观察 */
    for (int i = 10; i >= 0; i--) {
        printf("倒计时: %d 秒\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("重启中...\n");
}

这里有个小细节:app_main 是ESP32的入口函数,不是 main。我第一次写的时候直接写了 main(),编译报错半天没反应过来。

小提示: 如果你用的是VS Code + ESP-IDF插件,也可以直接通过图形界面创建工程。但我还是推荐命令行,后面做自动化脚本时你会感谢我的。

2.2 编译流程解析

编译ESP32固件,说白了就是执行一条命令:

idf.py build

但这条命令背后干了什么?我拆开给你看:

步骤 工具 作用
1. 配置 cmake 读取CMakeLists.txt,生成构建系统文件
2. 编译 gcc / xtensa-esp32-elf-gcc 将 .c 文件编译为 .o 目标文件
3. 链接 ld 将所有 .o 文件和库链接成 .elf 文件
4. 生成二进制 esptool.py / objcopy 将 .elf 转换为 .bin 烧录文件

你可能会问:为什么要有这么多步骤?直接生成 .bin 不行吗?

其实每个步骤都有它的意义。比如链接阶段,ESP-IDF会把FreeRTOS、WiFi协议栈、蓝牙协议栈全部链接进来。我曾经遇到一个坑:编译通过了,但烧录后芯片不停重启。查了半天,发现是链接时某个库版本不匹配。

关键输出文件: 编译完成后,在 build/ 目录下你会看到:
  • hello_world.elf — 带调试信息的可执行文件
  • hello_world.bin — 纯二进制固件,烧录用
  • flash_args — 烧录参数文件,后面会用到

2.3 使用esptool.py进行首次烧录

esptool.py 是乐鑫官方提供的烧录工具。其实 idf.py flash 底层调用的就是它。但我建议你直接学esptool.py,因为后面做OTA升级时,你大概率需要手动调用它。

先确认一下你的ESP32连接状态。把开发板通过USB连到电脑,然后:

esptool.py --port /dev/ttyUSB0 flash_id

如果返回芯片型号和Flash大小,说明连接正常。如果报错,八成是驱动没装好,或者端口号不对。Windows用户一般是 COM3、COM4 这样的。

接下来正式烧录:

esptool.py --chip esp32 \
           --port /dev/ttyUSB0 \
           --baud 921600 \
           write_flash \
           0x1000 build/bootloader/bootloader.bin \
           0x8000 build/partition_table/partition-table.bin \
           0x10000 build/hello_world.bin

这三个地址分别对应:

  • 0x1000 — Bootloader,引导程序
  • 0x8000 — 分区表,告诉芯片Flash怎么划分
  • 0x10000 — 应用程序,我们的Hello World
注意: 烧录时ESP32需要处于下载模式。大部分开发板会自动进入,但如果你用的是裸板,需要按住 BOOT 键,再按一下 EN 键,然后松开BOOT。我曾经因为没按BOOT键,反复报“连接超时”,折腾了半小时才发现。

2.4 串口监视器查看日志

烧录完成后,芯片会自动重启。如果没看到输出,别急,打开串口监视器:

idf.py monitor

或者直接用 screen 或 putty:

screen /dev/ttyUSB0 115200

波特率是 115200,这是ESP32默认的日志输出速率。你应该会看到类似这样的输出:

Hello World!
芯片型号: esp32, 核心数: 2
倒计时: 10 秒
倒计时: 9 秒
...
倒计时: 0 秒
重启中...

看到这行字,恭喜你,ESP32已经跑起来了!

调试技巧: 按 Ctrl+] 退出monitor。如果日志刷得太快看不清,可以在 printf 里加个延时,或者用 ESP_LOGI 替代 printf,它自带时间戳和日志级别过滤。

2.5 本章知识结构图

下面这张图把整个流程串起来了,你可以对照着回顾一下:

固件编译与首次烧录流程 创建工程 idf.py create-project 编译固件 idf.py build 烧录固件 esptool.py CMake → 编译 → 链接 → 生成bin 烧录地址 0x1000: bootloader 0x8000: 分区表 0x10000: 应用 串口监视 idf.py monitor / screen ⚠️ 常见问题 • 烧录失败:检查BOOT按键、端口号、驱动 • 无日志输出:检查波特率是否为115200,或芯片未正确复位 编译输出文件 hello_world.elf(调试) → hello_world.bin(烧录) → flash_args(烧录参数)

2.6 避坑指南

最后,分享几个我踩过的坑:

  • 波特率不匹配: 烧录时用921600,监视时用115200。这两个不一样,别搞混了。
  • Flash加密: 如果你买的模组开启了Flash加密,直接用esptool.py烧录会失败。需要先关闭加密,或者用加密烧录模式。
  • 电源不足: ESP32瞬间电流能到500mA。我曾经用电脑USB口供电,结果烧录到一半掉线了。换个带外接电源的USB Hub就好了。
  • 日志乱码: 如果看到一堆乱码,八成是波特率设错了。检查一下你的串口工具配置。

好了,到这里你已经完成了ESP32的首次烧录。从创建工程到编译,再到烧录和查看日志,整个流程都走了一遍。后面我们会在这个基础上,一步步加入OTA升级的功能。