CogletESP-camera-version/main/uart_component.cc
Rdzleo e95d0c414e Phase 01 批次 1-3: 单摄像头人脸追踪基础设施
实现 ESP32-S3 上单摄像头人脸追踪的核心代码骨架,替代 Grove Vision AI V2
模块,通过 UART 发送人脸坐标驱动 RP2040 控制的眼球/YAW 舵机。

## 规划文档(docs/phase-01-face-tracking/)

- GOAL.md       Phase 目标与 5 大成功标准
- RESEARCH.md   esp-dl v3.2/3.3 + human_face_detect 0.4.1 技术调研
- PLAN.md       15 个原子任务的执行计划(T01-T15)
- PLAN_CHECK.md 计划审查报告(PASS_WITH_NOTES)
- PROGRESS.md   执行进度追踪(批次 1-3 已完成)

## 批次 1:依赖与开关(T01-T03)

- main/idf_component.yml
  新增 esp-dl ~3.3.0 + human_face_detect 0.4.1(仅 S3/P4)
  esp-sr 从 ~2.2.0 升级到 ~2.3.1,解决 esp-dsp 1.6/1.7 版本冲突
- main/Kconfig.projbuild
  新增 CONFIG_XIAOZHI_ENABLE_FACE_TRACKING 开关(默认 y,depends on S3)
  新增 CONFIG_XIAOZHI_FACE_TRACKING_FPS_CHOICE(5/10/15)
- main/boards/common/esp32_camera.{h,cc}
  新增 ProbeFrameCapture() 最小 V4L2 DQBUF/QBUF 探针(T01)
- main/application.cc
  Start() 末尾调用 probe 验证摄像头硬件链路

## 批次 2:人脸检测核心(T04-T06)

- main/boards/common/esp32_camera.{h,cc}
  新增 FrameRef 结构体 + CaptureForDetection/ReleaseDetectionFrame
  双超时 mutex 策略:face_tracker 10ms timeout 跳帧,Capture() RAII guard
- main/face_tracker.{h,cc}(新建)
  Core 0 / 优先级 2 / 栈 8KB 独立任务
  集成 esp-dl HumanFaceDetect 推理
  坐标归一化 cx*224/W-112,匹配 RP2040 pixel_centre=112
  多人脸遍历挑 score 最高,避免多脸时眼球摇摆
  三重保护:Kconfig depends on S3 + 源文件 #if 守卫 + CMake 条件排除
- main/CMakeLists.txt
  非 S3 目标从 SOURCES 移除 face_tracker.cc

## 批次 3:UART 协议扩展(T07)

- main/uart_component.{h,cc}
  新增 uart_send_face(x,y) 发送 face:x,y\r\n 协议
  extern "C" 链接名配合 face_tracker 的弱符号声明
  全局 TX mutex 保护所有 UART 写入,防并发帧交织
  uart_send_string 同步加锁保持一致性

## 编译验证

idf.py build 通过,固件 2.51MB / 剩余 1.46MB (36% free)
当前 face_tracker 未被 application 激活(留到 T11),
UART/摄像头现有功能零影响。

## 未完成(下次继续)

- T01 硬件 probe 实机验证
- T08-T10 RP2040 端 parse_face + facetrack 双数据源改造
- T11-T15 application 接入 + 端到端联调 + 性能调优 + 最终验收

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:24:27 +08:00

76 lines
2.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "uart_component.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <string.h>
#include <stdio.h>
// T07: UART TX 全局互斥锁
// 保护所有 uart_write_bytes 调用,防止 face_tracker 任务与 application 任务并发
// 写入造成帧交织RESEARCH A3
static SemaphoreHandle_t s_uart_tx_mutex = nullptr;
// 初始化 ESP32 → RP2040 的 UART 通信
// 波特率 1152008 数据位无校验1 停止位,无流控
void uart_init_component() {
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
uart_param_config(UART_PORT_NUM, &uart_config);
// GPIO17=TX发送到 RP2040 的 GP5/RXGPIO18=RX接收 RP2040 的 GP4/TX
uart_set_pin(UART_PORT_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(UART_PORT_NUM, BUF_SIZE, 0, 0, NULL, 0);
// T07: 创建 TX 全局互斥锁
if (s_uart_tx_mutex == nullptr) {
s_uart_tx_mutex = xSemaphoreCreateMutex();
}
}
// 发送状态字符串给 RP2040末尾自动添加 \r\n
// RP2040 的 main.py 通过 coms.esp_read() 按 \n 分割解析
// 支持的状态字符串idle / listening / speaking / thinking / neutral / happy 等
// T07: 加锁,防与 uart_send_face 并发撕包
void uart_send_string(const char* str) {
if (s_uart_tx_mutex != nullptr) {
xSemaphoreTake(s_uart_tx_mutex, portMAX_DELAY);
}
uart_write_bytes(UART_PORT_NUM, str, strlen(str));
uart_write_bytes(UART_PORT_NUM, "\r\n", 2);
if (s_uart_tx_mutex != nullptr) {
xSemaphoreGive(s_uart_tx_mutex);
}
}
// 发送说话开始信号预留接口RP2040 当前未使用)
// 注意:经由 uart_send_string 间接加锁
void uart_signal_start() {
uart_send_string("[SPEAK_START]\n");
}
// 发送说话停止信号预留接口RP2040 当前未使用)
// 注意:经由 uart_send_string 间接加锁
void uart_signal_stop() {
uart_send_string("[SPEAK_STOP]\n");
}
// T07: 发送人脸检测坐标到 RP2040
// 格式:"face:<x>,<y>\r\n"x/y ∈ [-112, +112]RP2040 pixel_centre=112
// 由 face_tracker 任务以 Kconfig FPS 频率调用(默认 10 FPS
// 必须是 C 链接extern "C"——face_tracker.cc 用 weak 符号前置声明,
// 链接时本 strong 实现自动覆盖 weak。
extern "C" void uart_send_face(int x_offset, int y_offset) {
if (s_uart_tx_mutex == nullptr) return; // UART 未初始化,直接丢弃
char buf[24];
int n = snprintf(buf, sizeof(buf), "face:%d,%d", x_offset, y_offset);
if (n <= 0 || n >= (int)sizeof(buf)) return; // 格式化失败/截断
xSemaphoreTake(s_uart_tx_mutex, portMAX_DELAY);
uart_write_bytes(UART_PORT_NUM, buf, n);
uart_write_bytes(UART_PORT_NUM, "\r\n", 2);
xSemaphoreGive(s_uart_tx_mutex);
}