ESP32 固件(PlatformIO:优先 Arduino,按需 ESP-IDF)
同时加载 embedded-c-cpp 以获得编码规范、架构、单元测试和通用调试。本技能仅包含 ESP32 特定部分。
首先,识别项目
| 查找 | 位置 | 为什么重要 |
|---|---|---|
| 确切的芯片和模块(ESP32-WROOM-32E、ESP32-S3-WROOM-1-N16R8、ESP32-C3-MINI-1...) | platformio.ini 中的 board =,用户,板子丝印 | 不同变体的核心、引脚、PSRAM、USB、ADC 差异很大 |
| 框架 | framework = arduino、espidf 或 arduino, espidf | API 和配置模型 |
| Arduino core 版本(2.x 或 3.x) | 构建日志中的 framework-arduinoespressif32 @ x.y.z 行,platform = 行,或 ESP_ARDUINO_VERSION_MAJOR | 3.x 中多个 API 已更改(LEDC、定时器)——为错误版本编写的代码无法编译 |
| 闪存 / PSRAM 大小 | 模块名称(N16R8 = 16 MB 闪存,8 MB 八进制 PSRAM),board_upload.flash_size | 分区、内存布局 |
| 板子接线 | 用户/原理图 | 避免引导引脚以及被闪存/PSRAM/USB/LED 占用的引脚 |
如果变体未知,且任务涉及引脚、外设、电源或内存,请在编写代码前查明——在 S3 上正常的引脚可能会阻止经典 ESP32 启动。
Arduino 还是 ESP-IDF?
| 坚持使用 Arduino(默认) | 切换到 ESP-IDF(在 PlatformIO 中:framework = espidf)或 Arduino + IDF(framework = arduino, espidf) |
|---|---|
| 原型和大多数产品 | 需要更改 Arduino 预编译核心未暴露的 sdkconfig 选项(回滚 OTA、安全启动、闪存加密、PM 自动浅睡眠、核心转储到闪存、FreeRTOS 设置) |
| 大量可用库 | 最新 IDF 特性,对内存/性能的严格控制 |
| 快速迭代 | 生产环境加固 |
ESP32 上的 Arduino 已经运行在 FreeRTOS + ESP-IDF 之上,因此 IDF API(esp_*、xTaskCreatePinnedToCore、nvs_*、esp_sleep_*、heap_caps_*)可以直接在 Arduino 代码中使用。“Arduino 作为 IDF 组件”(arduino, espidf)保留了 Arduino 库并增加了 sdkconfig 控制——这是常见的迁移路径。
设置、platformio.ini 模板以及 2.x 与 3.x API 变更:请参考 references/platformio-setup.md。
防止典型 ESP32 故障的规则
- 引脚:绝不要使用闪存引脚(ESP32 上的 GPIO6–11)、PSRAM 引脚(WROVER 上的 16/17;S3 八进制 PSRAM 模块上的 33–37)或使用 USB 时的 USB 引脚;将引导引脚(ESP32:0、2、5、12、15)视为“需谨慎使用”——GPIO12 在启动时被拉高会导致 3.3V 闪存模块无法启动。Wi-Fi 开启时 ADC2 不可用(经典 ESP32):请使用 ADC1 引脚。各变体的完整表格:
references/pins-and-variants.md。 - 不要永久阻塞
loop()或任务:长时间while循环不包含delay()/vTaskDelay()会触发任务看门狗;while (WiFi.status() != WL_CONNECTED)需要超时。 - ISR:标记为
IRAM_ATTR,保持短小,严禁调用Serial.print、delay或 Wi-Fi 函数;将工作传递给任务(队列 / 任务通知 / 标志)。共享变量使用volatile+ 临界区(portENTER_CRITICAL_ISR自旋锁——ESP32 是双核的)。 - 任务栈的大小以字节为单位(在 ESP-IDF/Arduino 中,不同于原生 FreeRTOS 的“字”)。使用
uxTaskGetStackHighWaterMark(NULL)(返回字节)测量。Arduinoloop()默认有 8 KB 栈(使用SET_LOOP_TASK_STACK_SIZE(16 * 1024)来更改)。 - 电源:Wi-Fi 发送峰值电流约 300–500 mA。“Brownout detector was triggered”意味着需要改善供电(稳压器、大电容、线缆)——不要禁用 brownout 检测器。
- 堆:避免在循环中拼接
String和频繁new/malloc(长时间运行后会产生内存碎片);预先分配。定期检查ESP.getFreeHeap()/heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)。 - NVS/flash 磨损:不要在每次循环迭代中都写入
Preferences/NVS;仅在数据变化时写入,并限速。 - 不可逆操作:烧写 eFuse、安全启动、生产模式下的闪存加密是永久性的。未经用户明确确认,绝不运行
espefuse.py burn_*、在sdkconfig中启用这些功能,或者对含有数据的设备执行erase_flash。
构建、烧录、监视(PlatformIO)
pio run -e <env> # 构建
pio run -e <env> -t upload # 通过 USB/串口烧录
pio device monitor -e <env> # 串口监视器(使用 monitor_speed / monitor_filters)
pio run -e <env> -t upload -t monitor # 同时进行
pio run -e <env> -t uploadfs # 将 data/ 目录以 LittleFS/SPIFFS 镜像上传
pio run -e <env> -t erase # 擦除整个闪存(会先提示确认:会清除 NVS/WiFi 凭据)
pio run -e <env> -t menuconfig # 仅限 ESP-IDF / arduino+espidf
pio test -e native # 主机单元测试(参见 embedded-c-cpp)
上传失败,提示“Failed to connect / Wrong boot mode detected”→ 按住 BOOT(GPIO0 / C3 上是 GPIO9),轻按 EN/RST,松开 BOOT;检查线缆是否支持数据传输;关闭任何其他占用该串口的监视器。在尝试上传前,确认板子已连接并确定端口(pio device list)。
当崩溃或重启时
可粘贴的串口输出是关键证据。启用 monitor_filters = esp32_exception_decoder 和 build_type = debug,以便将回溯信息解码为文件:行。然后参考 references/panics-and-debug.md 了解:Guru Meditation 类型(LoadProhibited、StoreProhibited、IllegalInstruction、RISC-V 上的 Load/Store 访问错误)、栈金丝雀、任务/中断看门狗、rst: 代码、brownout、启动循环、JTAG 调试(S3/C3/C6 上使用 debug_tool = esp-builtin)。
参考文件
| 文件 | 何时查阅 |
|---|---|
references/platformio-setup.md | 创建/修复 platformio.ini、选择平台/核心版本、Arduino 2.x→3.x API 变更、PSRAM、USB-CDC 串口、分区与文件系统、在 PlatformIO 中使用 ESP-IDF、sdkconfig |
references/pins-and-variants.md | 选择或审查引脚、比较 ESP32/S2/S3/C3/C6、ADC、触摸、深度睡眠唤醒引脚 |
references/rtos-memory-power.md | 任务、核心、ISR、队列、看门狗、栈/堆/PSRAM、IRAM、深度/浅睡眠、电池 |
references/panics-and-debug.md | 任何崩溃、复位、看门狗、brownout、启动循环、上传失败、JTAG 调试、核心转储、复位原因 |
references/connectivity-ota-security.md | Wi-Fi/BLE 鲁棒性、NVS/Preferences、OTA 更新与回滚、安全启动/闪存加密决策 |