ESP-IDF 工程能力 / 工程结构发布 内容更新

ESP-IDF 项目由什么组成?CMake、组件、menuconfig 和 defaults

用一个真实滑动窗口组件分清公开接口、本地配置与构建产物;从干净目录改参重建,再用缺失组件和非法输入检查失败路径。

读完能做什么
得到可从公开defaults重建的最小自定义组件项目,并能定位配置与依赖错误。
开始之前
已完成E01,能进入固定5.5.5环境并辨认项目根目录。
所需器材
指定S3板与USB数据线、电脑与ESP-IDF5.5.5
版本组合
SDK:ESP-IDF5.5.5 / esp32s3组件:本站demo_window / 最多16个int32样本配置:默认4/500ms;改参2/1000ms
适用范围

官方 ESP32-S3-DevKitC-1 v1.1 / WROOM-1-N8R8;ESP-IDF5.5.5、esp32s3、8MB Flash、不启用PSRAM;桥接UART115200。只使用USB,不接外部GPIO;输入为固定合成整数。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/engineering-expanded.md#e02;具体上游章节、来源与修正记录。验证日期:
构建 / 配置已通过
docs/esp32-evidence/logs/e02-build.log;真实Exit0,干净重建与源/固件SHA另见engineering-expanded-builds.json。对象:官方 ESP32-S3-DevKitC-1 v1.1 / WROOM-1-N8R8版本:ESP-IDF5.5.5验证日期:
仿真不适用
未执行器件或ESP32调度仿真;主机纯C输入/依赖求解检查单独留证,不能证明电气与硬件时序。
本站实板待验证
本站尚无这套工程烧录、串口、实体LED/按钮或重启测试;观察CSV为空。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 工程结构

用一个有实际输出的小工程认识目录

这一篇不再只看hello_world目录名。完整工程把固定合成序列[-2,0,7,3,1]送入自己写的demo_window组件,输出窗口的数量、和、最小、最大与均值;串口明确标记SYNTHETIC_INPUT,不是传感器数据。

保持E01同一v1.1 N8R8板、IDF5.5.5和桥接UART115200,只接USB、不接GPIO。这样组件和配置出错时,不会同时怀疑一组新接线。下载包同时给实现、公开头、配置、lock和主机检查,不只给一张目录截图。

做到这里,你应该看到
能指出应用入口、自己的组件和生成目录;知道输出数字的输入来源。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[2] ESP-IDF v5.5.5组件管理:manifest、lock与managed_components[3] ESP-IDF v5.5.5 hello_world根CMake与完整工程[4] 指定DevKitC-1 v1.1连接器、USB与板载RGB资料

02 / 工程结构

CMake注册源码,组件公开接口

根CMakeLists载入SDK的project.cmake并命名项目,组件自己的idf_component_register列出SRCS和INCLUDE_DIRS。组件不是“任意位置的.c自动被编译”;main通过demo_window.h使用公开接口,不包含另一个.c来拼程序。

公开头依赖其他组件的头时用REQUIRES;仅实现中用到的头或链接依赖用PRIV_REQUIRES。main是IDF的特殊组件,默认自动依赖其他已发现组件;本例仍显式写私有依赖表达意图,不把“删掉这行必然报错”当成教学证据。纯C窗口公开头没有SDK类型,所以自身没有额外公开SDK依赖。

路径职责
根CMakeLists.txt工程名、载入SDK构建入口
main/main.c选择合成输入与调度周期
components/demo_window/include公开的结构与函数声明
demo_window.c容量、滚动替换和统计实现
Kconfig.projbuild在顶层菜单定义公开可选项
build / sdkconfig本地生成产物 / 本地完整配置
代码 / 配置
idf_component_register(SRCS "main.c" INCLUDE_DIRS "." PRIV_REQUIRES demo_window esp_timer)
做到这里,你应该看到
用公开头编译调用,接口与实现的依赖不会靠全局目录碰巧找到。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[2] ESP-IDF v5.5.5组件管理:manifest、lock与managed_components

03 / 工程结构

menuconfig与defaults不是同一层东西

Kconfig.projbuild定义窗口1…16、周期100…5000ms与日志标签,menuconfig保存这一次完整配置到sdkconfig。sdkconfig.defaults保存少量可公开默认值,适合版本库与新机器;它不是当前每个配置项的最终真相。

已有sdkconfig的已设置键优先。只改defaults或仅删除build,原有参数可能仍在;fullclean也主要清构建。要验证公开默认值,先保存本地修改与可能的私有配置,再用不带sdkconfig/build的新目录,不能对别人的项目直接清理。多份defaults按顺序应用,后文件覆盖前文件的重复默认键。

做到这里,你应该看到
能解释“改了defaults但没变化”该先看哪份文件,而不是重新接线。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults

04 / 工程结构

先构建默认,再从新目录构建改参版本

进入E01的5.5.5环境,在项目根目录执行默认build。应在生成配置中看到window4、period500、label=baseline;实板运行后还要对照启动字段,构建文件中的参数不等于已经刷到板上。

第二份干净目录叠加sdkconfig.alt.defaults,得到window2、period1000、label=changed-defaults。本站实际完成两次构建并核对生成header;正文不填假串口样本。Flash仍8MB、不启用PSRAM,不为配置课顺便改变硬件。

代码 / 配置
idf.py --version
idf.py build
# 在另一份不带sdkconfig/build的新目录
idf.py -D 'SDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.alt.defaults' build
# 需要实板时,使用实际串口替换PORT
idf.py -p PORT flash monitor
做到这里,你应该看到
新目录可以只凭公开源文件/defaults/lock生成固件,改参后生成配置确实不同。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[2] ESP-IDF v5.5.5组件管理:manifest、lock与managed_components[3] ESP-IDF v5.5.5 hello_world根CMake与完整工程[4] 指定DevKitC-1 v1.1连接器、USB与板载RGB资料

05 / 工程结构

组件函数先验证输入,再更新有界状态

窗口固定最多16个int32样本,sum使用int64承受边界;不动态申请内存。未满时统计已收到样本,满后替换最旧样本;空窗或非法参数返回false,不输出一个伪造正常均值。

公开接口不读sdkconfig.h,容量由调用方传入,所以同一C实现可以做主机输入检查,也可以由另一个IDF应用复用。测试纯逻辑不意味着移植完成,更不意味着真实采样周期已测量。

代码 / 配置
// SPDX-License-Identifier: MIT
#include "demo_window.h"
#include <string.h>
static bool valid(const demo_window_t *w) {
    return w && w->capacity >= 1 && w->capacity <= DEMO_WINDOW_MAX && w->count <= w->capacity && w->next < w->capacity;
}
bool demo_window_init(demo_window_t *w, size_t capacity) {
    if (!w || capacity < 1 || capacity > DEMO_WINDOW_MAX) return false;
    memset(w, 0, sizeof(*w)); w->capacity = capacity; return true;
}
bool demo_window_push(demo_window_t *w, int32_t sample) {
    if (!valid(w)) return false;
    if (w->count == w->capacity) w->sum -= w->values[w->next];
    else ++w->count;
    w->values[w->next] = sample; w->sum += sample;
    w->next = (w->next + 1) % w->capacity; return true;
}
bool demo_window_snapshot(const demo_window_t *w, demo_window_result_t *out) {
    if (!valid(w) || !out || !w->count) return false;
    demo_window_result_t r = {.count = w->count, .min = w->values[0], .max = w->values[0], .sum = w->sum, .mean = (double)w->sum / w->count};
    for (size_t i=1; i<w->count; ++i) { if (w->values[i]<r.min) r.min=w->values[i]; if (w->values[i]>r.max) r.max=w->values[i]; }
    *out = r; return true;
}
做到这里,你应该看到
能从返回值判断何时有有效统计;不把空窗口的0当成正常读数。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults

06 / 工程结构

业务入口只组织输入、组件与周期

下方main.c与下载包一致。程序每轮选择一个合成整数,只有push和snapshot都成功才打印结果;周期等待发生在app_main任务中,没有无限等待外设的初始化。

组件的window/count与任务的sequence用途不同;序号不表示真实传感器采样数量。显示浮点均值只是算术结果,没有物理单位或测量准确度声明。

代码 / 配置
// SPDX-License-Identifier: MIT
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "sdkconfig.h"
#include "demo_window.h"
void app_main(void) {
    const char *tag = "E02";
    const int32_t synthetic[] = {-2, 0, 7, 3, 1};
    demo_window_t window;
    if (!demo_window_init(&window, CONFIG_DEMO_WINDOW_SIZE)) { ESP_LOGE(tag, "CONFIG_INVALID; no samples accepted"); return; }
    ESP_LOGI(tag, "IDF=%s target=%s label=%s window=%d period_ms=%d SYNTHETIC_INPUT", esp_get_idf_version(), CONFIG_IDF_TARGET, CONFIG_DEMO_LABEL, CONFIG_DEMO_WINDOW_SIZE, CONFIG_DEMO_PERIOD_MS);
    for (uint32_t sequence=0;;++sequence) {
        demo_window_result_t result;
        const int32_t input = synthetic[sequence % (sizeof(synthetic)/sizeof(synthetic[0]))];
        if (demo_window_push(&window, input) && demo_window_snapshot(&window, &result))
            ESP_LOGI(tag, "seq=%" PRIu32 " input=%" PRIi32 " count=%u sum=%" PRIi64 " min=%" PRIi32 " max=%" PRIi32 " mean=%.2f (not a sensor)", sequence, input, (unsigned)result.count, result.sum, result.min, result.max, result.mean);
        else ESP_LOGE(tag, "MODEL_ERROR; no fabricated output");
        vTaskDelay(pdMS_TO_TICKS(CONFIG_DEMO_PERIOD_MS));
    }
}

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[3] ESP-IDF v5.5.5 hello_world根CMake与完整工程

07 / 工程结构

用缺失组件和非法容量各验一次失败

在单独副本把main私有依赖拼成demo_window_missing,reconfigure会在依赖解析阶段拒绝;恢复正确名称后配置成功。这个错误发生在USB之前,不应换数据线或复制旧build来躲过它。头文件错误先核对组件名、SRCS、INCLUDE_DIRS和依赖方向。

host_check实际覆盖NULL、容量0/17、空窗不改输出、未满/满窗/覆盖及int32极值求和。它执行同一纯C组件,但不验证ESP32调度、电气或串口。本站已实际默认/改参构建、失败/恢复与主机检查;真实烧录和输出仍pending,observations.csv保持空白。

代码 / 配置
clang -std=c11 -Wall -Wextra -Werror -I components/demo_window/include host_check.c components/demo_window/demo_window.c -o /tmp/e02-host-check
/tmp/e02-host-check
做到这里,你应该看到
失败有实际输入和错误,恢复有对应改动;未执行硬件步骤不写成通过。

参考:[1] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[2] ESP-IDF v5.5.5组件管理:manifest、lock与managed_components

BACK TO THE SOURCE

资料来源与版本说明

按固定SDK、组件源码与指定板级核对;实际构建和主机输入检查另有证据,没有本站烧录、GPIO或串口运行结果。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults
  2. ESP-IDF v5.5.5组件管理:manifest、lock与managed_components
  3. ESP-IDF v5.5.5 hello_world根CMake与完整工程
  4. 指定DevKitC-1 v1.1连接器、USB与板载RGB资料