用一个有实际输出的小工程认识目录
这一篇不再只看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资料
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
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
先构建默认,再从新目录构建改参版本
进入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资料
组件函数先验证输入,再更新有界状态
窗口固定最多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
业务入口只组织输入、组件与周期
下方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与完整工程
用缺失组件和非法容量各验一次失败
在单独副本把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
资料来源与版本说明
按固定SDK、组件源码与指定板级核对;实际构建和主机输入检查另有证据,没有本站烧录、GPIO或串口运行结果。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。