diff --git a/doc/函数调用指南.md b/doc/函数调用指南.md index 137ce94..6e7ea47 100644 --- a/doc/函数调用指南.md +++ b/doc/函数调用指南.md @@ -204,6 +204,115 @@ void OnConfigUpdated(const ConfigCommon_t* common, const Config2D_t* cfg2d, cons Preprocess_Settings_Change(cfg2d, cfg1d, common); } +## 4.6 TcpLogic_InjectTestConfig(测试模式配置注入) + +功能:在不依赖服务器的情况下,直接向 TCP 逻辑模块注入配置参数。注入后走与服务器下发完全相同的缓存 + 回调路径。仅非 NULL 的参数会被更新。 + +原型: + +```c +void TcpLogic_InjectTestConfig(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d); +``` + +入参: +- `common`:通用配置指针,NULL 表示不更新 +- `cfg2d`:2D 配置指针,NULL 表示不更新 +- `cfg1d`:1D 配置指针,NULL 表示不更新 + +内部行为: +1. 互斥锁保护下写入内部缓存 +2. 设置 `has_valid_config = 1` +3. 触发已注册的 `ConfigUpdateCallback` + +使用场景: +- 测试模式(`TEST_PATTERN_MODE=1`)启动时注入默认 2D 配置,使自主触发管线无需服务器即可工作 +- 注入后若服务器连接并下发新配置,新配置会覆盖注入的默认值 + +示例: +```c +Config2D_t test_cfg2d; +memset(&test_cfg2d, 0, sizeof(test_cfg2d)); +test_cfg2d.Enabled = 1; +test_cfg2d.TriggerMode = 1; // 内部触发 +test_cfg2d.TargetWidth = 64; +test_cfg2d.TargetHeight = 64; +test_cfg2d.TriggerTemperatureThreshold = 800; // 80.0°C +test_cfg2d.TriggerBurstCount = 3; +TcpLogic_InjectTestConfig(NULL, &test_cfg2d, NULL); +``` + +## 8. MCU 实际业务调用流程(CH32V307 实现) + +以下为 MCU 中 main.c 的实际初始化和业务循环流程,作为第 7 节伪代码的具体实现参考: + +```c +// ===== 初始化阶段 (main 函数) ===== + +// 1. 硬件初始化 +USART_Printf_Init(921600); +Config_Flash_SRAM(FLASH_128_SRAM_192); // 128K Flash + 192K SRAM +#if !TEST_PATTERN_MODE +DVP_Init(); // 正常模式初始化 DVP +#endif +TIM2_Init(); // WCHNET 10ms 定时基准 +ExtTrigger_GPIO_Init(); // PA15/EXTI15 外部触发 +NG_GPIO_Init(); // PA8 NG 输出 + +// 2. 网络初始化 +ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr); +qdx_port_init(); + +// 3. 库初始化 + 回调注册 +Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT); +TcpLogic_Init(MACAddr, NULL); +TcpLogic_RegisterConfigCallback(OnConfigUpdate); +TcpLogic_RegisterDetectionCallback(OnDetectionResult); +TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest); + +// 4. 测试模式注入默认配置 +#if TEST_PATTERN_MODE +TcpLogic_InjectTestConfig(NULL, &test_cfg2d, NULL); +#endif + +// 5. 启动 TCP 引擎 + RTOS 任务 +TcpLogic_Start(); +xTaskCreate(task_wchnet_entry, "wchnet", 512, NULL, 6, NULL); +xTaskCreate(task_business_entry, "business", 512, NULL, 5, NULL); +xTaskCreate(task_heartbeat_entry,"hb", 256, NULL, 3, NULL); +#if TEST_PATTERN_MODE +xTaskCreate(task_test_pattern_entry, "testpat", 256, NULL, 4, NULL); +#endif +vTaskStartScheduler(); + +// ===== 业务循环 (task_business_entry) ===== +// 全部行为由运行时 Config 驱动,无编译期功能开关 + +while (1) { + // NG 脉冲超时检查 + if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) + GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); + + #if !TEST_PATTERN_MODE + DVP_Task(); // 行 DMA 搬运 + #endif + + // 辅助通道:TEMP_REQ 服务器按需截图 + if (g_temp_req_pending) { + // → 2D: Preprocess_Execute → TcpLogic_BuildAndSendTemperatureFrame + // → 1D: send_1d_snapshot (30点空间采样) + } + + // 主管线:自主触发 + if (Frame_Ready_Flag) { + TcpLogic_GetLatestConfig(&tc, &t2, &t1); + if (t2.Enabled) + handle_2d_trigger(&raw, &t2, &use_buf); // 2D 状态机 + else if (t1.Enabled) + handle_1d_trigger(&raw, &t1, &use_buf); // 1D 状态机 + } +} +``` + # Page 5 6. 函数调⽤时序图 (Control Flow) @@ -235,3 +344,56 @@ TCP 数据流返回 Defect Result (NG) 拉⾼ DO1 定时执⾏剔除动作 主机/中断(Main) TcpLogic (⽹络库) Preprocess (预处理库) ConfigServer (上位机) +# Page 7 + +## 7. 多级调试打印系统 + +### 7.1 概述 + +调试打印通过 `qdx_port.h` 中的宏体系实现,支持 **5 个级别** 和 **7 个分类**。仅需修改一个宏即可全局控制输出量: + +```c +#define DBG_LEVEL DBG_LEVEL_NORMAL // 修改此值控制输出详细度 +``` + +### 7.2 级别定义 + +| 值 | 宏名 | 说明 | 适用场景 | +|---|---|---|---| +| 0 | `DBG_LEVEL_NONE` | 完全关闭 | 量产固件 | +| 1 | `DBG_LEVEL_ERR` | 仅错误 | 最小输出排障 | +| 2 | `DBG_LEVEL_BRIEF` | + 服务器配置/网络事件 | **推荐日常使用** | +| 3 | `DBG_LEVEL_NORMAL` | + 触发/数据/初始化 | 开发调试(默认) | +| 4 | `DBG_LEVEL_VERBOSE` | + 心跳/TLV详情 | 深度调试 | + +### 7.3 分类宏及打印级别 + +| 宏 | 级别阈值 | 前缀 | 用途 | +|---|---|---|---| +| `DBG_ERR` | ≥1 (ERR) | `[ERR]` | HardFault、CRC校验失败、连接失败、PP失败 | +| `DBG_CFG` | ≥2 (BRIEF) | `[CFG]` | **服务器下发的所有配置**(Config2D/1D/Common、DevID、ACK、DetResult、TempReq) | +| `DBG_NET` | ≥2 (BRIEF) | `[NET]` | TCP连接/断开、PHY状态、Socket事件 | +| `DBG_TRIG` | ≥3 (NORMAL) | `[TRIG]` | 外部/内部触发、防抖、Burst启停 | +| `DBG_DATA` | ≥3 (NORMAL) | `[DATA]` | 2D/1D帧发送结果、TEMP_REQ发送 | +| `DBG_INIT` | ≥3 (NORMAL) | `[INIT]` | 开机初始化(传感器、DVP、WCHNET、RTOS) | +| `DBG_HB` | ≥4 (VERBOSE) | `[HB]` | 心跳打印、TLV包头详情(高频) | + +### 7.4 使用示例 + +```c +DBG_ERR("CRC fail: calc=0x%04X recv=0x%04X\r\n", calc, recv); +DBG_CFG("<< Config2D: En=%d %dx%d Trig=%d\r\n", en, w, h, trig); +DBG_NET("[Control] Connected to %s:%d\r\n", ip, port); +DBG_TRIG("2D ext trigger\r\n"); +DBG_DATA("2D SEND frm=%d ret=%d\r\n", frm, ret); +DBG_INIT("Sensor: Y16 OK\r\n"); +DBG_HB("%d tick=%d frm=%d\r\n", cnt, tick, frm); +``` + +### 7.5 各级别输出预期 + +- **NONE (0)**:静默,仅 HardFault 的 raw printf 输出 +- **ERR (1)**:错误信息 — 约 13 条打印点 +- **BRIEF (2)**:+ 服务器配置全量打印 + 网络事件 — 上位机交互一目了然 +- **NORMAL (3)**:+ 触发事件、数据发送、初始化 — 标准开发模式 +- **VERBOSE (4)**:+ 心跳(每2秒)、每个TLV包详情 — 仅深度调试使用 \ No newline at end of file diff --git a/doc/模式配置与功能说明.md b/doc/模式配置与功能说明.md index ddb88a5..dd3afdc 100644 --- a/doc/模式配置与功能说明.md +++ b/doc/模式配置与功能说明.md @@ -1,24 +1,32 @@ # CH32V307 TCPClient 模式配置与功能说明 +> **版本**: v2.0 — 自主触发架构 +> **更新日期**: 2026-03 +> **适用代码**: main.c 自主触发架构(已移除所有 `TEST_ENABLE_*` 编译开关) + ## 目录 1. [系统架构概述](#1-系统架构概述) 2. [运行模式说明](#2-运行模式说明) -3. [宏开关配置总表](#3-宏开关配置总表) -4. [测试模式详解](#4-测试模式详解) -5. [正常模式详解](#5-正常模式详解) -6. [网络与服务器配置](#6-网络与服务器配置) -7. [配置下发流程](#7-配置下发流程) -8. [RTOS 任务架构](#8-rtos-任务架构) -9. [切换模式操作指南](#9-切换模式操作指南) -10. [常见配置场景](#10-常见配置场景) -11. [已知注意事项](#11-已知注意事项) +3. [编译期常量总表](#3-编译期常量总表) +4. [自主触发架构详解](#4-自主触发架构详解) +5. [2D 触发状态机](#5-2d-触发状态机) +6. [1D 采集状态机](#6-1d-采集状态机) +7. [TEMP_REQ 辅助通道](#7-temp_req-辅助通道) +8. [测试模式详解](#8-测试模式详解) +9. [正常模式详解](#9-正常模式详解) +10. [网络与服务器配置](#10-网络与服务器配置) +11. [配置下发流程](#11-配置下发流程) +12. [RTOS 任务架构](#12-rtos-任务架构) +13. [GPIO 引脚分配](#13-gpio-引脚分配) +14. [功能完整性对照表](#14-功能完整性对照表) +15. [已知注意事项](#15-已知注意事项) --- ## 1. 系统架构概述 -本系统基于 CH32V307WCU6 (RISC-V) 微控制器,运行 FreeRTOS 实时操作系统,通过 WCHNET TCP/IP 协议栈与上位机通信。系统支持两种运行模式:**测试模式**(软件生成模拟热成像数据)和**正常模式**(通过 DVP 接口采集 Mini212G2 红外传感器真实数据)。 +本系统基于 CH32V307WCU6 (RISC-V) 微控制器,运行 FreeRTOS 实时操作系统,通过 WCHNET TCP/IP 协议栈与上位机通信。系统行为完全由运行时服务器配置驱动——服务器下发 Config2D/Config1D 并设置 `Enabled=1` 后,MCU 自主执行触发检测和数据采集上报。 ### 硬件资源 @@ -37,57 +45,63 @@ |------|------| | FreeRTOS v202112.00 | 实时操作系统,Heap=16KB | | WCHNET | 万瑞 TCP/IP 协议栈 | -| QDX 协议栈 | 自定义 TLV 二进制通信协议 | -| 预处理模块 | 滑窗 ROI 搜索 + 温度统计 | +| QDX 协议栈 | 自定义 TLV 二进制通信协议 (0x55AA, v2.0) | +| 预处理模块 | 滑窗 ROI 搜索 + 温度统计 + 自动降尺寸 | --- ## 2. 运行模式说明 +系统仅保留一个编译期宏开关 `TEST_PATTERN_MODE`,控制数据来源。所有业务功能(触发、采集、NG 输出、心跳)在两种模式下均始终启用,行为由服务器运行时配置驱动。 + ### 两种模式对比 | 对比项 | 测试模式 (`TEST_PATTERN_MODE=1`) | 正常模式 (`TEST_PATTERN_MODE=0`) | |--------|------|------| | **数据来源** | 软件生成模拟热图 (~10 FPS) | Mini212G2 传感器 DVP/DMA 采集 | | **硬件依赖** | 仅需网口,无需传感器 | 需连接 Mini212G2 + DVP 线缆 | -| **初始化** | 跳过 `Mini212G2_Init()` 和 `DVP_Init()` | 执行传感器初始化 + DVP 配置 | +| **初始化** | 跳过 `DVP_Init()` | 执行 DVP 初始化 | | **帧生成** | `task_test_pattern_entry` 任务周期填充 | DVP 行中断逐行 DMA 搬运 | -| **心跳显示** | `[HB] N tick=X TEST_MODE frm=Y` | `[HB] N tick=X dvp_frm=Y row_irq=Z` | -| **默认配置** | 自动加载测试配置(64×64, 80°C 阈值) | 等待服务器下发,默认 1×1 | -| **适用场景** | 软件调试、协议验证、无硬件开发 | 生产环境、实际传感器采集 | +| **默认配置** | 自动注入测试配置(2D Enabled=1, 内部触发, 80°C) | 等待服务器下发,默认不使能 | +| **自主触发** | ✅ 立即工作(无需服务器) | ✅ 需服务器下发 Enabled=1 后激活 | +| **TEMP_REQ** | ✅ 可用(需对应模式 Enabled=1) | ✅ 可用 | +| **NG GPIO** | ✅ PA8 始终初始化 | ✅ PA8 始终初始化 | +| **外部触发 GPIO** | ✅ PA0/EXTI0 始终初始化 | ✅ PA0/EXTI0 始终初始化 | --- -## 3. 宏开关配置总表 +## 3. 编译期常量总表 所有宏定义位于 `prj/TCPClient/User/main.c` 文件头部。 ### 3.1 主模式开关 -| 宏名 | 文件位置 | 当前值 | 说明 | -|------|---------|--------|------| -| `TEST_PATTERN_MODE` | main.c:18 | **1** | **主开关**:1=测试模式,0=正常模式 | +| 宏名 | 当前值 | 说明 | +|------|--------|------| +| `TEST_PATTERN_MODE` | **1** | **唯一编译开关**:1=测试模式,0=正常模式 | -### 3.2 功能子开关 - -| 宏名 | 当前值 | 控制功能 | 依赖条件 | -|------|--------|---------|---------| -| `TEST_ENABLE_HEARTBEAT` | **1** | 周期心跳调试打印(2秒间隔) | 无 | -| `TEST_ENABLE_TRIGGER` | **0** | 内部触发 + 连拍上传 | 需配合阈值配置 | -| `TEST_ENABLE_NG_GPIO` | **0** | PA8 NG GPIO 脉冲输出 | 需硬件连接 | -| `TEST_ENABLE_TEMP_REQ` | **1** | 服务器按需帧请求响应 | 需服务器发送请求 | -| `TEST_ENABLE_TCP_SEND` | **1** | TCP 数据通道发送 | 需网络连接 | - -### 3.3 其他编译期常量 +### 3.2 业务常量 | 宏名 | 值 | 说明 | |------|-----|------| -| `KEEPALIVE_ENABLE` | 1 | TCP KeepAlive 保持(20s 探测) | -| `DEFAULT_BURST_COUNT` | 3 | 默认连拍帧数 | -| `DEFAULT_BURST_INTERVAL_MS` | 200 | 默认连拍帧间隔 (ms) | +| `DEFAULT_BURST_COUNT` | 3 | 服务器未配置时的默认连拍帧数 | +| `DEFAULT_BURST_INTERVAL_MS` | 200 | 服务器未配置时的默认连拍间隔 (ms) | | `MAX_TCP_PAYLOAD_SIZE` | 10240 | TCP 发送缓冲区大小 (字节) | -| `TEST_FPS_DELAY_MS` | 100 | 测试模式帧间隔 (~10 FPS) | -| `TEST_1D_POINTS` | 30 | 1D 模式采样点数 | +| `KEEPALIVE_ENABLE` | 1 | TCP KeepAlive(20s 空闲 / 15s 探测 / 9 次) | +| `TEST_FPS_DELAY_MS` | 100 | 测试模式帧生成间隔 (~10 FPS) | +| `SNAPSHOT_1D_POINTS` | 30 | TEMP_REQ 1D 空间采样点数 | +| `MAX_1D_POINTS` | 512 | 1D 采集缓冲最大样本数 | +| `NG_PULSE_MS` | 200 | NG 脉冲默认宽度(服务器可通过 NGioDelay 覆盖) | + +### 3.3 GPIO 引脚宏 + +| 宏名 | 值 | 说明 | +|------|-----|------| +| `EXT_TRIG_GPIO_PORT` | GPIOA | 外部触发输入端口 | +| `EXT_TRIG_GPIO_PIN` | GPIO_Pin_15 | PA15 | +| `EXT_TRIG_EXTI_LINE` | EXTI_Line15 | 外部中断线 | +| `NG_GPIO_PORT` | GPIOA | NG 输出端口 | +| `NG_GPIO_PIN` | GPIO_Pin_8 | PA8 | ### 3.4 传感器相关宏 (dvp.h) @@ -104,15 +118,222 @@ | 宏名 | 当前值 | 说明 | |------|--------|------| -| `SENSOR_USE_USART3` | 0 | 启用后必须为 1(USART2 不可用) | -| `SENSOR_UART_ENABLE` | **0 (已禁用)** | MCU UART 配置已禁用,传感器通过 USB 预配置 | -| `SENSOR_UART_BAUD` | 115200 | 传感器 UART 波特率(未使用) | +| `SENSOR_UART_ENABLE` | **0 (已禁用)** | 传感器通过 USB 预配置 | --- -## 4. 测试模式详解 +## 4. 自主触发架构详解 -### 4.1 启用方式 +### 4.1 核心设计原则 + +系统的所有业务行为由运行时服务器配置驱动,不再使用编译期功能开关: + +``` +服务器下发 Config2D (Enabled=1) → MCU 自主执行 2D 触发/连拍/上报 +服务器下发 Config1D (Enabled=1) → MCU 自主执行 1D 采集/上报 +两者互斥:Config2D.Enabled 优先级高于 Config1D.Enabled +``` + +### 4.2 主循环逻辑流程 + +``` +task_business_entry (优先级 5, 2ms 轮询) + │ + ├── [1] NG 脉冲超时检查 → 到期则拉低 PA8 + │ + ├── [2] DVP_Task() (仅正常模式) + │ ├── 行 DMA 完成 → 复制到 FrameBuffer + │ └── 最后一行 → Frame_Ready_Flag=1 + │ + ├── [3] TEMP_REQ 辅助通道(服务器按需截图) + │ ├── 前提:对应模式 Enabled=1 + │ ├── is2D=1 → Preprocess_Execute() → TCP 发送 (frameType=0x02) + │ └── is2D=0 → send_1d_snapshot() → 30 点空间采样 + │ + └── [4] 自主触发主管线 + │ 前提:Frame_Ready_Flag=1 && TcpLogic_GetLatestConfig OK + │ + ├── Config2D.Enabled=1 → handle_2d_trigger() + │ (2D 触发状态机:消抖 → 延迟 → 连拍) + │ + └── Config1D.Enabled=1 → handle_1d_trigger() + (1D 采集状态机:IDLE → DEBOUNCE → COLLECTING) +``` + +### 4.3 回调注册 + +| 回调 | 注册函数 | 触发时机 | 处理 | +|------|---------|---------|------| +| `OnConfigUpdate` | `TcpLogic_RegisterConfigCallback` | 服务器下发配置 | → `Preprocess_Settings_Change()` | +| `OnDetectionResult` | `TcpLogic_RegisterDetectionCallback` | 服务器返回检测结果 (resultStatus=0 为 NG) | → PA8 NG 脉冲 | +| `OnTempFrameRequest` | `TcpLogic_RegisterTempFrameRequestCallback` | 服务器请求截图 | → 设置 `g_temp_req_pending` | + +--- + +## 5. 2D 触发状态机 + +### 5.1 状态机流程 + +``` + ┌─────────────┐ + │ IDLE │ + │ 等待触发 │ + └──────┬──────┘ + │ + ┌────────────┼────────────┐ + │ TriggerMode=0 │ TriggerMode=1 + │ (外部触发) │ (内部触发) + ▼ ▼ + PA0/EXTI0 上升沿 Preprocess_CheckInternalTrigger2D() + │ │ (TriggerRoi 区域 Max/Avg > Threshold) + ▼ │ + ┌──────────────┐ │ + │ DEBOUNCE │ │ + │ DebounceMs │ │ + └──────┬───────┘ │ + │ │ + └─────────┬───────────────┘ + ▼ + ┌──────────────┐ + │ DELAY │ + │ DelayMs │ + └──────┬───────┘ + ▼ + ┌──────────────┐ + │ BURST │──┐ + │ 发送第1帧 │ │ 剩余 BurstCount-1 帧 + └──────────────┘ │ 间隔 InternalIntervalMs + ▲ │ + └──────────┘ + │ + ▼ + Burst 完成 → 回到 IDLE +``` + +### 5.2 关键 Config2D 字段 + +| 字段 | 类型 | 2D 触发中的作用 | +|------|------|----------------| +| `Enabled` | u8 | 1=启用 2D 管线 | +| `TriggerMode` | u8 | 0=外部(GPIO), 1=内部(温度) | +| `TriggerDebounceIntervalMs` | u16 | 外部触发消抖等待 (ms) | +| `TriggerDelayMs` | u16 | 触发确认后延迟采集 (ms) | +| `TriggerBurstCount` | u8 | 连拍帧数 | +| `TriggerInternalIntervalMs` | u16 | 连拍帧间隔 (ms) | +| `TriggerCondition` | u8 | 内部触发判定:0=平均温度, 1=最大温度 | +| `TriggerTemperatureThreshold` | i16 | 内部触发温度阈值 (0.1°C/LSB) | +| `TriggerRoiX/Y/W/H` | u16×4 | 内部触发检测区域 | +| `TargetWidth` / `TargetHeight` | u16 | 预处理输出尺寸 | +| `NGioDelay` | u16 | NG 脉冲宽度 (ms) | + +### 5.3 2D 帧发送路径 + +``` +handle_2d_trigger() → start_2d_burst() + → do_2d_capture_send() + → Preprocess_Execute(raw, tx_buf, &meta) // 滑窗 ROI 搜索 + → TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1) + // frameType=TRIGGER, is2D=1 +``` + +--- + +## 6. 1D 采集状态机 + +### 6.1 状态机流程 + +``` + ┌─────────────┐ + │ S1D_IDLE │ (RunMode 必须=1) + │ 等待触发 │ + └──────┬──────┘ + │ + ┌────────────┼────────────┐ + │ TriggerType=0 │ TriggerType=1 + │ (外部触发) │ (内部触发) + ▼ ▼ + PA0/EXTI0 上升沿 前置环形缓冲(3样本) + │ 连续3个 ≥ TriggerTempLimit + ▼ │ + ┌───────────────┐ │ + │ S1D_DEBOUNCE │ │ + │ HighTimerLimit │ │ + └──────┬────────┘ │ + │ │ + └─────────┬───────────────┘ + ▼ + ┌────────────────┐ + │ S1D_COLLECTING │ + │ 逐帧采集温度点 │ + │ (中心行最大值) │ + └───────┬────────┘ + │ + ┌────────┼────────┐ + │ 停止条件1 │ 停止条件2 + │ BufferSize 满 │ 已触发 + NgCountLimit 连续冷点 + │ │ + └────────┬────────┘ + ▼ + 切片 [LSizeStart, -RSizeStart] + │ + ▼ + send_1d_collection() → TCP 发送 + │ + ▼ + 回到 S1D_IDLE +``` + +### 6.2 1D 温度采样方式 + +每个 DVP 帧产生一个温度样本:取中心行所有像素的最大温度值(`get_1d_sample()`)。 + +### 6.3 内部触发预环形缓冲 + +内部触发模式维护长度为 3 的环形缓冲: +- 每帧写入一个样本到环形缓冲 +- 当 3 个样本全部 ≥ `TriggerTempLimit` → 进入 COLLECTING 状态 +- 环形缓冲内容(最旧在前)写入采集数组前 3 位 +- 从第 4 位起继续采集 + +### 6.4 关键 Config1D 字段 + +| 字段 | 类型 | 1D 采集中的作用 | +|------|------|----------------| +| `Enabled` | u8 | 1=启用 1D 管线 | +| `RunMode` | u8 | 0=STOP, 1=RUN | +| `TriggerType` | u8 | 0=外部(GPIO), 1=内部(温度) | +| `BufferSize` | u16 | 单次采集最大样本数(停止条件) | +| `TriggerTempLimit` | i16 | 触发/采集温度阈值 (0.1°C/LSB) | +| `HighTimerLimit` | u16 | 外部触发消抖时间 (ms) | +| `NgCountLimit` | u8 | 连续冷点数→停止采集 | +| `LSizeStart` | u16 | 切片左起始偏移 | +| `RSizeStart` | u16 | 切片右侧移除点数 | + +### 6.5 1D 数据打包格式 + +每个样本点 4 字节 (Little-Endian): + +| 偏移 | 类型 | 说明 | +|------|------|------| +| 0-1 | u16 LE | TimeOffset (距采集开始的毫秒偏移) | +| 2-3 | u16 LE | Temperature (0.1°C/LSB) | + +--- + +## 7. TEMP_REQ 辅助通道 + +TEMP_REQ 是服务器主动请求的按需截图通道,与自主触发管线独立工作: + +- **2D 截图**:`Preprocess_Execute()` → 滑窗搜索 → 发送 `frameType=0x02`(MASKED) +- **1D 截图**:`send_1d_snapshot()` → 从中心行等间距采样 30 点 → 发送 + +前提:对应模式 `Enabled=1`,否则请求被忽略并打印 `TEMP_REQ ignored`。 + +--- + +## 8. 测试模式详解 + +### 8.1 启用方式 ```c #define TEST_PATTERN_MODE 1 @@ -120,7 +341,7 @@ 编译烧录即可,**无需连接任何传感器硬件**,仅需以太网连接。 -### 4.2 测试图案 +### 8.2 测试图案 系统自动循环生成 4 种测试图案(256×192 Y16 格式): @@ -133,65 +354,50 @@ 总周期:40 帧 ≈ 4 秒循环一次。 -### 4.3 默认配置参数 +### 8.3 自动注入测试配置 -测试模式启动时自动加载以下配置(无需服务器下发即可工作): +测试模式启动时通过 `TcpLogic_InjectTestConfig()` 注入以下配置(走标准缓存+回调路径): ``` -TargetWidth = 64 # 预处理输出宽度 -TargetHeight = 64 # 预处理输出高度 -TriggerRoiX = 0 # 触发检测区域起点 X -TriggerRoiY = 0 # 触发检测区域起点 Y -TriggerRoiW = 256 # 触发检测区域宽度(全幅) -TriggerRoiH = 192 # 触发检测区域高度(全幅) -TriggerCondition = 1 # 触发条件:1=最大温度 -TriggerTemperatureThreshold = 800 # 80.0°C (0.1°C/LSB) -TriggerBurstCount = 3 # 连拍帧数 -TriggerInternalIntervalMs = 200 # 连拍间隔 (ms) -NGioDelay = 200 # NG 脉冲宽度 (ms) +Config2D: + Enabled = 1 # 自动启用 2D 管线 + TriggerMode = 1 # 内部触发(温度检测) + TargetWidth = 64 # 预处理输出 64×64 + TargetHeight = 64 + TriggerRoiX/Y = 0, 0 # 全幅扫描 + TriggerRoiW/H = 256, 192 + TriggerCondition = 1 # 最大温度 + TriggerTemperatureThreshold = 800 # 80.0°C + TriggerBurstCount = 3 # 3 帧连拍 + TriggerInternalIntervalMs = 200 + NGioDelay = 200 ``` -> **注意**:如果服务器连接后下发了 Config2D,将覆盖上述默认配置。 +> **注入路径**:`TcpLogic_InjectTestConfig()` → 内部缓存 + `has_valid_config=1` → 触发 `OnConfigUpdate` 回调 → `Preprocess_Settings_Change()`。与服务器下发配置走完全相同的路径。 -### 4.4 测试模式数据流 +### 8.4 启动串口输出示例 ``` -task_test_pattern_entry (优先级 4, 100ms 周期) - │ 生成 256×192 Y16 测试图案 - │ 写入 FrameBuffer[] - │ 设置 Frame_Ready_Flag=1 - ▼ -task_business_entry (优先级 5) - ├── 检查 g_temp_req_pending (服务器请求) - │ ├── is2D=1 → Preprocess_Execute() → 滑窗搜索 → 发送 2D 帧 - │ └── is2D=0 → send_1d_frame_from_raw() → 采样中心行 → 发送 1D 帧 - │ - └── 检查 Frame_Ready_Flag (仅 TRIGGER 启用时) - └── Preprocess_CheckInternalTrigger2D() - └── 触发 → 连拍发送 -``` - -### 4.5 启动串口输出示例 - -``` -TCPClient Test +TCPClient SystemClk:144000000 -=== Feature Switches === - PATTERN=1 TRIGGER=0 NG_GPIO=0 TEMP_REQ=1 TCP_SEND=1 HB=1 + TEST_PATTERN=1 UserByte: c0 === TEST PATTERN MODE === No sensor/DVP hardware needed net version:1c WCHNET_LibInit Success -Test default config loaded: trigger thresh=800 ROI=full burst=3 +Test config injected: En=1 TrigMode=Internal thresh=800 Tgt=64x64 burst=3 [HB] 0 tick=1010 TEST_MODE frm=9 -[HB] 1 tick=3010 TEST_MODE frm=29 +2D internal trigger +TRIGGER frm=21 +2D SEND frm=21 64x64 ret=0 +Burst start: 3 frames, interval=200 ms ``` --- -## 5. 正常模式详解 +## 9. 正常模式详解 -### 5.1 启用方式 +### 9.1 启用方式 ```c #define TEST_PATTERN_MODE 0 @@ -199,9 +405,9 @@ Test default config loaded: trigger thresh=800 ROI=full burst=3 需要: 1. Mini212G2 传感器正确连接 DVP 接口 -2. 传感器已通过 USB 或 UART 预配置为 CMOS/DVP Y16 输出 +2. 传感器已通过 USB 预配置为 CMOS/DVP Y16 输出 -### 5.2 硬件连接 +### 9.2 硬件连接 #### DVP 引脚定义 @@ -231,20 +437,7 @@ Test default config loaded: trigger thresh=800 ROI=full burst=3 > 注意:启用 USART3 传感器通信会占用 printf 调试端口,需通过 JTAG 调试。 -### 5.3 传感器配置选项 - -修改 `mini212g2.h`: - -```c -// 当前状态:MCU UART 配置已禁用(#if 0) -// 模组已通过 USB 预配置,无需 MCU 配置 - -// 如需重新启用 MCU UART 配置,将 mini212g2.h 中的 #if 0 改为 #if 1 -// 仅可使用 USART3 (PB10/PB11),USART2 引脚已被占用 -// ⚠ 启用后 printf 串口不可用,需通过 JTAG 调试 -``` - -### 5.4 正常模式数据流 +### 9.3 正常模式数据流 ``` DVP 硬件中断 (DVP_IRQHandler) @@ -257,40 +450,23 @@ task_business_entry (优先级 5) │ ├── 复制 DMA 行缓冲 → FrameBuffer[line_idx] │ └── 最后一行 → Frame_Ready_Flag=1 ▼ - ├── 检查 g_temp_req_pending (服务器请求) - │ ├── is2D=1 → Preprocess_Execute() → 发送 - │ └── is2D=0 → 1D 采样 → 发送 + ├── [TEMP_REQ] 服务器按需截图 │ - └── 检查 Frame_Ready_Flag (TRIGGER 启用时) - └── Preprocess_CheckInternalTrigger2D() + └── [自主触发] Config2D.Enabled → 2D 状态机 + Config1D.Enabled → 1D 状态机 ``` -### 5.5 正常模式注意事项 +### 9.4 正常模式注意事项 -- **无默认预处理参数**:正常模式不加载测试默认配置,`Preprocess_Init()` 仅设置 `TargetWidth=1, TargetHeight=1`。必须等服务器下发 Config2D 后才能获取正确的目标尺寸。 -- **传感器初始化检查**:可通过 JTAG 查看 `sensor_init_ok` / `sensor_init_fail` 变量确认初始化结果。 -- **帧率取决于传感器**:Mini212G2 在 DVP 模式下的帧率由传感器内部配置决定,不通过软件控制。 - -### 5.6 启动串口输出示例(预期) - -``` -TCPClient Test -SystemClk:144000000 -=== Feature Switches === - PATTERN=0 TRIGGER=0 NG_GPIO=0 TEMP_REQ=1 TCP_SEND=1 HB=1 -UserByte: c0 -[DVP] Init done CR0=0x2C CR1=0x04 ROW=1 COL=512 -[DVP] DMA_BUF0=0x200XXXXX DMA_BUF1=0x200XXXXX -net version:1c -WCHNET_LibInit Success -[HB] 0 tick=1010 dvp_frm=0 row_irq=0 -``` +- **无默认配置**:正常模式不注入测试配置,`Preprocess_Init()` 仅设置 `TargetWidth=1, TargetHeight=1`。必须等服务器下发 Config2D/Config1D 后才能工作。 +- **传感器初始化**:模组已通过 USB 预配置,`mini212g2.h` 中 UART 配置已关闭。 +- **帧率取决于传感器**:Mini212G2 在 DVP 模式下的帧率由传感器内部配置决定。 --- -## 6. 网络与服务器配置 +## 10. 网络与服务器配置 -### 6.1 MCU 网络参数 +### 10.1 MCU 网络参数 定义位置:`main.c` @@ -301,7 +477,7 @@ WCHNET_LibInit Success | 子网掩码 | 255.255.255.0 | C 类子网 | | MAC 地址 | 由芯片自动获取 | 硬件 MAC | -### 6.2 服务器连接参数 +### 10.2 服务器连接参数 定义位置:`qdx_tcp_logic.c` @@ -311,47 +487,62 @@ WCHNET_LibInit Success | `CONTROL_PORT` | 5511 | 控制通道端口 | | `DATA_PORT` | 5512 | 数据通道端口 | -> **修改方法**:直接编辑 `qdx_tcp_logic.c` 中的常量值,重新编译。 - -### 6.3 TCP KeepAlive +### 10.3 TCP KeepAlive ```c #define KEEPALIVE_ENABLE 1 // main.c // 探测配置: 20s 空闲后每 15s 探测一次,最多 9 次 ``` +### 10.4 TCP 内部参数 + +| 参数 | 值 | 说明 | +|------|-----|------| +| `HEARTBEAT_INTERVAL_MS` | 2000 | 心跳发送间隔 | +| `SERVER_TIMEOUT_MS` | 6000 | 服务器超时断连 | +| `RECONNECT_DELAY_MS` | 3000 | 断连后重连等待 | +| `MAX_FRAGMENT_PAYLOAD` | 1400 | 分片最大载荷 | + --- -## 7. 配置下发流程 +## 11. 配置下发流程 -### 7.1 TLV 配置类型 +### 11.1 TLV 消息类型 -| TLV Type | 名称 | 说明 | -|----------|------|------| -| 0x20 | TYPE_CONFIG_COMMON | 通用配置(模式、严格标记等) | -| 0x22 | TYPE_CONFIG_2D | 2D 配置(目标尺寸、触发、连拍等) | -| 0x23 | TYPE_CONFIG_1D | 1D 配置 | +| TLV Type | 名称 | 方向 | 说明 | +|----------|------|------|------| +| 0x01 | TYPE_HANDSHAKE | ↔ | 握手(UUID、版本) | +| 0x02 | TYPE_HEARTBEAT | ↔ | 心跳(上行/下行) | +| 0x05 | TYPE_DEVID_ASSIGN | ← | 设备 ID 分配(触发重连) | +| 0x10 | TYPE_TEMP_FRAME | ← | 请求温度帧(TEMP_REQ) | +| 0x20 | TYPE_CONFIG_COMMON | ← | 通用配置 | +| 0x22 | TYPE_CONFIG_2D | ← | 2D 配置 | +| 0x23 | TYPE_CONFIG_1D | ← | 1D 配置 | +| 0x30 | TYPE_ACK_PAYLOAD | ← | ACK 响应 | +| 0x40 | TYPE_DETECTION_RESULT | ← | 检测结果(OK/NG) | -### 7.2 Config2D 关键字段 +### 11.2 Config2D 关键字段 | 字段 | 偏移 | 类型 | 说明 | |------|------|------|------| -| Width | 4 | u16 LE | 设备/传感器分辨率宽度(仅标记,不影响预处理) | -| Height | 6 | u16 LE | 设备/传感器分辨率高度(仅标记,不影响预处理) | -| **TargetWidth** | **23** | **u16 LE** | **预处理输出宽度(直接控制帧大小)** | -| **TargetHeight** | **25** | **u16 LE** | **预处理输出高度(直接控制帧大小)** | -| Fps | 8 | u16 LE | 帧率 | -| TriggerCondition | 27 | u8 | 0=平均, 1=最大 | -| TriggerTemperatureThreshold | 28 | u16 LE | 触发温度阈值 (0.1°C/LSB) | +| Enabled | 0 | u8 | **使能开关** | +| Width | 4 | u16 LE | 设备分辨率宽度(仅标记) | +| Height | 6 | u16 LE | 设备分辨率高度(仅标记) | +| **TargetWidth** | **23** | **u16 LE** | **预处理输出宽度** | +| **TargetHeight** | **25** | **u16 LE** | **预处理输出高度** | +| TriggerMode | 21 | u8 | 0=外部, 1=内部 | +| TriggerCondition | 37 | u8 | 0=平均, 1=最大 | +| TriggerTemperatureThreshold | 28 | i16 LE | 触发阈值 (0.1°C/LSB) | | TriggerBurstCount | 30 | u8 | 连拍帧数 | -| TriggerInternalIntervalMs | 31 | u16 LE | 连拍帧间隔 (ms) | -| TriggerRoiX/Y/W/H | 33-40 | u16 LE ×4 | 触发检测 ROI 区域 | +| TriggerInternalIntervalMs | 31 | u16 LE | 连拍间隔 (ms) | +| TriggerDebounceIntervalMs | 35 | u16 LE | 外部触发消抖 (ms) | +| TriggerDelayMs | 23 | u16 LE | 触发后延迟 (ms) | +| TriggerRoiX/Y/W/H | 33-40 | u16 LE ×4 | 内部触发检测 ROI | +| NGioDelay | 41 | u16 LE | NG 脉冲宽度 (ms) | -> **重要**:Width/Height 与 TargetWidth/TargetHeight 是两组不同的字段! -> - `Width/Height`:设备分辨率标记(偏移 4-7),**不影响预处理** -> - `TargetWidth/TargetHeight`:预处理实际输出尺寸(偏移 23-26),**直接决定帧数据大小** +> **重要**:`Width/Height` 与 `TargetWidth/TargetHeight` 是两组不同的字段!前者仅做标识,后者直接控制帧大小。 -### 7.3 缓冲区容量约束 +### 11.3 缓冲区容量约束 ``` MAX_TCP_PAYLOAD_SIZE = 10240 字节 @@ -360,189 +551,119 @@ HeadOffset = 64 字节(协议头预留) 最大像素数 = 10176 / 2 = 5088 像素 推荐安全目标尺寸: - 64 × 64 = 4096 像素 = 8192 字节 ✓ (< 10176) - 71 × 71 = 5041 像素 = 10082 字节 ✓ (< 10176) - 72 × 72 = 5184 像素 = 10368 字节 ✗ (> 10176,会触发降尺寸) + 64 × 64 = 4096 像素 = 8192 字节 ✓ + 71 × 71 = 5041 像素 = 10082 字节 ✓ + 72 × 72 = 5184 像素 = 10368 字节 ✗ (触发降尺寸) ``` -### 7.4 自动降尺寸保护 +### 11.4 自动降尺寸保护 -当服务器下发的 TargetWidth × TargetHeight × 2 超过可用缓冲区时,MCU 会自动等比减半目标尺寸直到适合: +TargetWidth × TargetHeight × 2 超过可用缓冲区时,自动等比减半: ``` -示例:服务器设 TargetWidth=185, TargetHeight=70 - 185 × 70 × 2 = 25,900 > 10,176 → 减半 - 93 × 35 × 2 = 6,510 < 10,176 → 使用 93×35 -日志输出:PP: target clamped 185x70 -> 93x35 (buf=10176) +示例:服务器设 185×70 → 185×70×2=25900 > 10176 → 减半 → 93×35 +日志:PP: target clamped 185x70 -> 93x35 (buf=10176) ``` -### 7.5 配置传递链 +### 11.5 配置传递链 ``` 服务器 TCP 下发 TLV → qdx_tcp_logic.c: parse_and_dispatch_tlv() - → 反序列化 → cached_cfg2d / cached_cfg1d / cached_common + → 反序列化 → cached_cfg{2d,1d,common} → OnConfigUpdate() 回调 → Preprocess_Settings_Change() 互斥锁更新 - → 下次 Preprocess_Execute() 使用新配置 + → 下次帧处理使用新配置 ``` --- -## 8. RTOS 任务架构 +## 12. RTOS 任务架构 | 任务名 | 优先级 | 栈 (words) | 条件 | 功能 | |--------|--------|-----------|------|------| -| `wchnet` | 6 (最高) | 512 | 始终创建 | WCHNET 协议栈轮询 | -| `business` | 5 | 512 | `TEST_ENABLE_TCP_SEND=1` | DVP/预处理/发送主循环 | -| `testpat` | 4 | 256 | `TEST_PATTERN_MODE=1` | 测试图案生成 | -| `hb` | 3 (最低) | 256 | `TEST_ENABLE_HEARTBEAT=1` | 心跳调试打印 | -| TcpLogic 内部 | - | - | 始终创建 | 管理线程 (连接/心跳) | - -> 测试模式创建 4 个任务,正常模式创建 3 个(无 testpat)。 +| `wchnet` | 6 (最高) | 512 | 始终创建 | WCHNET 协议栈轮询 (5ms) | +| `business` | 5 | 512 | 始终创建 | DVP + 触发状态机 + 发送 | +| `testpat` | 4 | 256 | `TEST_PATTERN_MODE=1` | 测试图案生成 (~10 FPS) | +| `hb` | 3 (最低) | 256 | 始终创建 | 心跳调试打印 (2s) | +| TcpLogic 内部 | - | - | 始终创建 | 管理线程 (连接/心跳/重连) | --- -## 9. 切换模式操作指南 +## 13. GPIO 引脚分配 -### 9.1 从测试模式 → 正常模式 - -1. **修改宏**:打开 `prj/TCPClient/User/main.c`,将: - ```c - #define TEST_PATTERN_MODE 1 - ``` - 改为: - ```c - #define TEST_PATTERN_MODE 0 - ``` - -2. **检查传感器配置**:当前模组已通过 USB 预配置,`mini212g2.h` 中 UART 配置已用 `#if 0` 关闭,无需修改。如需重新启用,将 `#if 0` 改为 `#if 1`。 - -3. **检查功能开关**:根据需要启用/禁用各子功能: - ```c - #define TEST_ENABLE_TRIGGER 1 // 启用内部触发 - #define TEST_ENABLE_NG_GPIO 1 // 启用 NG 输出(需接 PA8) - ``` - -4. **编译烧录**:使用 MRS IDE 编译并烧录。 - -### 9.2 从正常模式 → 测试模式 - -1. **修改宏**:`TEST_PATTERN_MODE` 改为 `1` -2. **无需断开传感器**:测试模式跳过所有硬件初始化,传感器接线不影响 -3. **编译烧录** - -### 9.3 功能开关独立配置 - -各功能子开关在两种模式下均可独立启用/禁用,无互相依赖: - -| 开关组合 | 行为 | -|---------|------| -| PATTERN=1, TRIGGER=1 | 测试模式 + 内部触发连拍(测试热点图案会触发) | -| PATTERN=1, TRIGGER=0, TEMP_REQ=1 | 测试模式 + 服务器按需请求(当前配置) | -| PATTERN=0, TRIGGER=1, TEMP_REQ=1 | 正常模式 + 自动触发 + 服务器请求(完整功能) | -| PATTERN=0, TRIGGER=0, TEMP_REQ=1 | 正常模式 + 仅服务器请求(最简运行) | -| TCP_SEND=0 | 禁用 business 任务,系统仅运行网络协议但不处理/发送帧 | +| GPIO | 功能 | 方向 | 配置 | 说明 | +|------|------|------|------|------| +| PA15 | 外部触发输入 | 输入 | 下拉 + EXTI15 上升沿 | 2D/1D 共用(互斥) | +| PA8 | NG 输出 | 推挽输出 | 低速 2MHz | 检测 NG 时脉冲拉高 | +| PA4-PA6, PA9-PA10, PB3, PB8-PB9 | DVP 数据线 | 输入 | 浮空 | 仅正常模式使用 | +| PC8, PC9, PC11 | DVP HSYNC/VSYNC/PCLK | 输入 | - | 仅正常模式使用 | +| PA2, PA3 | **已占用** | - | - | USART2,不可用 | --- -## 10. 常见配置场景 +## 14. 功能完整性对照表 -### 场景 A:纯软件协议调试(无硬件) +### 协议规范 vs MCU 实现 -```c -#define TEST_PATTERN_MODE 1 -#define TEST_ENABLE_HEARTBEAT 1 -#define TEST_ENABLE_TRIGGER 0 -#define TEST_ENABLE_NG_GPIO 0 -#define TEST_ENABLE_TEMP_REQ 1 -#define TEST_ENABLE_TCP_SEND 1 -``` - -**适用**:验证 TLV 协议、握手流程、Config 下发、帧分片发送等。 - -### 场景 B:测试内部触发 + 连拍 - -```c -#define TEST_PATTERN_MODE 1 -#define TEST_ENABLE_TRIGGER 1 // ← 打开 -#define TEST_ENABLE_NG_GPIO 0 -#define TEST_ENABLE_TEMP_REQ 1 -#define TEST_ENABLE_TCP_SEND 1 -``` - -**适用**:验证 80°C 触发阈值、3 帧连拍、连拍间隔。热点和棋盘格图案会触发。 - -### 场景 C:正常模式完整功能 - -```c -#define TEST_PATTERN_MODE 0 -#define TEST_ENABLE_TRIGGER 1 -#define TEST_ENABLE_NG_GPIO 1 // ← PA8 接报警 -#define TEST_ENABLE_TEMP_REQ 1 -#define TEST_ENABLE_TCP_SEND 1 -``` - -**适用**:生产运行,传感器实时采集 + 自动触发 + NG 报警 + 服务器请求。 - -### 场景 D:正常模式最小功能验证 - -```c -#define TEST_PATTERN_MODE 0 -#define TEST_ENABLE_TRIGGER 0 -#define TEST_ENABLE_NG_GPIO 0 -#define TEST_ENABLE_TEMP_REQ 1 // 仅响应服务器请求 -#define TEST_ENABLE_TCP_SEND 1 -``` - -**适用**:验证传感器数据采集 + 网络发送,无自动触发逻辑干扰。 - -### 场景 E:网络连接诊断 - -```c -#define TEST_PATTERN_MODE 1 -#define TEST_ENABLE_HEARTBEAT 1 -#define TEST_ENABLE_TRIGGER 0 -#define TEST_ENABLE_NG_GPIO 0 -#define TEST_ENABLE_TEMP_REQ 0 // 关闭帧请求 -#define TEST_ENABLE_TCP_SEND 0 // 关闭 business 任务 -``` - -**适用**:仅测试 TCP 连接/断开/重连,不涉及数据帧处理。心跳打印确认系统活跃。 +| 协议功能 | 实现状态 | 备注 | +|---------|---------|------| +| 握手 (Handshake) | ✅ | UUID + HW/FW 版本 | +| 心跳 (Heartbeat) | ✅ | 2s 间隔,6s 超时断连 | +| 设备 ID 分配 (DevID Assign) | ✅ | 触发自动重连 | +| Config Common/2D/1D 下发 | ✅ | 解析+缓存+回调 | +| ACK 响应 | ✅ | 发送+接收 | +| TEMP_REQ 截图 | ✅ | 2D 预处理 / 1D 空间采样 | +| Detection Result | ✅ | NG → PA8 脉冲 | +| 2D 外部触发 | ✅ | GPIO → 消抖 → 延迟 → 连拍 | +| 2D 内部触发 | ✅ | ROI Max/Avg → 延迟 → 连拍 | +| 1D 外部触发 | ✅ | GPIO → 消抖 → 采集 → 切片 | +| 1D 内部触发 | ✅ | 3 样本预环形 → 采集 → 切片 | +| 温度矩阵分片发送 | ✅ | 每片 ≤1400 字节 | +| TCP KeepAlive | ✅ | 20s/15s/9次 | +| CRC16-MODBUS 校验 | ✅ | 每帧校验 | +| IsLive 实时流 | ❌ | MCU 资源不足,触发场景不需要 | +| Training 训练模式 | ❌ | 服务器端完成 | +| SyncTime 时间同步 | ❌ | 协议已定义 (0x03),未解析 | +| StatusEvent 状态上报 | ❌ | 服务器通过心跳判断在线 | +| Alarm GPIO | ❌ | 结构体有字段,硬件未接线 | +| 1D StartPointsToRemove / ReferenceLength / TimerCLimit | ❌ | 已解析到结构体,业务逻辑未使用 | --- -## 11. 已知注意事项 +## 15. 已知注意事项 -### 11.1 温度单位 +### 15.1 温度单位 系统统一使用 **0.1°C/LSB**: - 温度值 350 = 35.0°C - 温度值 900 = 90.0°C - `TEMP_RAW(deg_c) = (uint16_t)(deg_c * 10)` -### 11.2 TriggerTemperatureThreshold 值 +### 15.2 Config2D 字段区分 -温度阈值使用 0.1°C/LSB 单位,与系统所有温度值一致。当前测试默认 `TriggerTemperatureThreshold = 800`(80.0°C),测试热点图案 90.0°C(值 900)可正常触发。 +- **Width / Height**:设备分辨率标识(如 256×192),不影响 MCU 预处理 +- **TargetWidth / TargetHeight**:预处理实际输出尺寸,**必须正确设置** -### 11.3 服务器 Config2D 字段区分 +### 15.3 正常模式默认目标尺寸 -- **Width / Height**(偏移 4-7):设备分辨率标识(如 256×192),不影响 MCU 预处理 -- **TargetWidth / TargetHeight**(偏移 23-26):预处理输出目标尺寸,**必须正确设置** +`TargetWidth=1, TargetHeight=1`(最小默认),必须等服务器下发 Config2D 后才能正常输出。 -如果服务器只设了 Width/Height=64×64 而未设 TargetWidth/TargetHeight,MCU 仍会使用超大目标尺寸(或上次缓存值),导致 `PP fail ret=-3` 或触发自动降尺寸。 +### 15.4 IP 与端口硬编码 -### 11.4 正常模式默认目标尺寸 - -正常模式启动时 `TargetWidth=1, TargetHeight=1`(最小默认),必须等服务器下发 Config2D 后才会更新为正确值。测试模式自动加载 64×64。 - -### 11.5 IP 与端口硬编码 - -当前网络参数(IP、端口)为编译期常量,更改需修改源码并重新编译: +网络参数为编译期常量,更改需修改源码并重新编译: - MCU IP:`main.c` → `IPAddr[]` - 服务器 IP:`qdx_tcp_logic.c` → `SERVER_IP` - 端口号:`qdx_tcp_logic.c` → `CONTROL_PORT` / `DATA_PORT` -### 11.6 printf 限制 +### 15.5 printf 限制 -使用 newlib-nano,仅支持 `%d, %x, %s` 格式。所有 `uint32_t` 变量必须强制转换 `(int)` 后打印,否则可能 HardFault。不支持 `%u, %lu, %f`。 +使用 newlib-nano,仅支持 `%d, %x, %s` 格式。所有 `uint32_t` 必须 `(int)` 强转后打印,否则 HardFault。不支持 `%u, %lu, %f`。 + +### 15.6 双缓冲发送 + +系统使用 A/B 交替 `TcpTxBuffer_t`,避免发送过程中缓冲区被覆盖。`use_buffer_A` 标志在每次发送后翻转。 + +### 15.7 2D/1D 互斥 + +主循环中 `Config2D.Enabled` 优先级高于 `Config1D.Enabled`。若两者同时 Enabled=1,仅 2D 管线运行。 diff --git a/prj/TCPClient/Debug/dvp.c b/prj/TCPClient/Debug/dvp.c index b21c4aa..e1f3651 100644 --- a/prj/TCPClient/Debug/dvp.c +++ b/prj/TCPClient/Debug/dvp.c @@ -2,6 +2,7 @@ #include "ch32v30x_dvp.h" #include "eth_driver.h" #include "string.h" +#include "qdx_port.h" __attribute__((aligned(4))) uint8_t DMA_LineBuf0[BYTES_PER_LINE]; __attribute__((aligned(4))) uint8_t DMA_LineBuf1[BYTES_PER_LINE]; @@ -63,10 +64,10 @@ void DVP_Init(void) DVP->CR1 = RB_DVP_DMA_EN; /* DMA on, CM=0 continuous, no reset bits */ DVP->CR0 |= RB_DVP_ENABLE; - printf("[DVP] Init done CR0=0x%02x CR1=0x%02x ROW=%d COL=%d\r\n", + DBG_INIT("DVP CR0=0x%02x CR1=0x%02x ROW=%d COL=%d\r\n", (int)(DVP->CR0 & 0xFF), (int)(DVP->CR1 & 0xFF), (int)DVP->ROW_NUM, (int)DVP->COL_NUM); - printf("[DVP] DMA_BUF0=0x%08x DMA_BUF1=0x%08x\r\n", + DBG_INIT("DVP DMA_BUF0=0x%08x DMA_BUF1=0x%08x\r\n", (int)DVP->DMA_BUF0, (int)DVP->DMA_BUF1); } diff --git a/prj/TCPClient/Debug/mini212g2.c b/prj/TCPClient/Debug/mini212g2.c index 903494e..e9a87a0 100644 --- a/prj/TCPClient/Debug/mini212g2.c +++ b/prj/TCPClient/Debug/mini212g2.c @@ -1,6 +1,7 @@ #include "mini212g2.h" #include "debug.h" #include "string.h" +#include "qdx_port.h" #if SENSOR_UART_ENABLE @@ -165,7 +166,7 @@ int Mini212G2_SendCmd(const uint8_t *cmd, uint8_t len) if (n == ACK_LEN && memcmp(resp, ACK_PATTERN, ACK_LEN) == 0) return 0; - printf("[Sensor] resp(%d):", n); + DBG_INIT("Sensor resp(%d):", n); for (int j = 0; j < n; j++) printf(" %02x", resp[j]); printf("\r\n"); return -1; @@ -176,18 +177,18 @@ int Mini212G2_SendCmd(const uint8_t *cmd, uint8_t len) int Mini212G2_Init(void) { #if !SENSOR_UART_ENABLE - printf("[Sensor] UART disabled, assuming pre-configured via USB\r\n"); - printf("[Sensor] Expected: CMOS output, CMOS8(MSB), Y16, 30Hz\r\n"); + DBG_INIT("Sensor: UART disabled, pre-configured via USB\r\n"); + DBG_INIT("Sensor: CMOS output, CMOS8(MSB), Y16, 30Hz\r\n"); return 0; #else int ok = 0, fail = 0; Sensor_UART_Init(); - printf("[Sensor] UART init %d, baud=%d\r\n", + DBG_INIT("Sensor: UART%d baud=%d\r\n", (int)(SENSOR_UART == USART3 ? 3 : 2), (int)SENSOR_UART_BAUD); /* Sensor needs several seconds to boot after power-on */ - printf("[Sensor] Waiting 3s for sensor boot...\r\n"); + DBG_INIT("Sensor: Waiting 3s for boot...\r\n"); Delay_Ms(3000); /* === Communication test: send status query === */ @@ -204,51 +205,51 @@ int Mini212G2_Init(void) } Delay_Ms(200); - printf("[Sensor] Configuring CMOS/DVP output...\r\n"); + DBG_INIT("Sensor: Configuring CMOS/DVP output...\r\n"); /* 1. Shutter compensation */ if (Mini212G2_SendCmd(CMD_SHUTTER, sizeof(CMD_SHUTTER)) == 0) - { ok++; printf("[Sensor] Shutter OK\r\n"); } + { ok++; DBG_INIT("Sensor: Shutter OK\r\n"); } else - { fail++; printf("[Sensor] Shutter FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: Shutter FAIL\r\n"); } Delay_Ms(200); /* 2. Digital port → CMOS */ if (Mini212G2_SendCmd(CMD_DIGITAL_CMOS, sizeof(CMD_DIGITAL_CMOS)) == 0) - { ok++; printf("[Sensor] Digital->CMOS OK\r\n"); } + { ok++; DBG_INIT("Sensor: Digital->CMOS OK\r\n"); } else - { fail++; printf("[Sensor] Digital->CMOS FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: Digital->CMOS FAIL\r\n"); } Delay_Ms(200); /* 3. CMOS interface → 8-bit MSB */ if (Mini212G2_SendCmd(CMD_CMOS8_MSB, sizeof(CMD_CMOS8_MSB)) == 0) - { ok++; printf("[Sensor] CMOS8(MSB) OK\r\n"); } + { ok++; DBG_INIT("Sensor: CMOS8(MSB) OK\r\n"); } else - { fail++; printf("[Sensor] CMOS8(MSB) FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: CMOS8(MSB) FAIL\r\n"); } Delay_Ms(200); /* 4. CMOS content → Y16 (raw 16-bit thermal) */ if (Mini212G2_SendCmd(CMD_CONTENT_Y16, sizeof(CMD_CONTENT_Y16)) == 0) - { ok++; printf("[Sensor] Y16 OK\r\n"); } + { ok++; DBG_INIT("Sensor: Y16 OK\r\n"); } else - { fail++; printf("[Sensor] Y16 FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: Y16 FAIL\r\n"); } Delay_Ms(200); /* 5. Frame rate → 30Hz */ if (Mini212G2_SendCmd(CMD_FPS_30HZ, sizeof(CMD_FPS_30HZ)) == 0) - { ok++; printf("[Sensor] 30Hz OK\r\n"); } + { ok++; DBG_INIT("Sensor: 30Hz OK\r\n"); } else - { fail++; printf("[Sensor] 30Hz FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: 30Hz FAIL\r\n"); } Delay_Ms(200); /* 6. Save settings to flash */ if (Mini212G2_SendCmd(CMD_SAVE, sizeof(CMD_SAVE)) == 0) - { ok++; printf("[Sensor] Save OK\r\n"); } + { ok++; DBG_INIT("Sensor: Save OK\r\n"); } else - { fail++; printf("[Sensor] Save FAIL\r\n"); } + { fail++; DBG_ERR("Sensor: Save FAIL\r\n"); } Delay_Ms(500); - printf("[Sensor] Config result: %d ok, %d fail\r\n", ok, fail); + DBG_INIT("Sensor: %d ok, %d fail\r\n", ok, fail); sensor_init_ok = (uint8_t)ok; sensor_init_fail = (uint8_t)fail; @@ -268,7 +269,7 @@ void Mini212G2_PrintDigitalVideoStatus(void) int n = Sensor_ReadResp(resp, sizeof(resp), 500); if (n < 24) { - printf("[Sensor] Query failed (got %d bytes)\r\n", n); + DBG_ERR("Sensor: Query failed (got %d bytes)\r\n", n); return; } @@ -292,16 +293,16 @@ void Mini212G2_PrintDigitalVideoStatus(void) uint8_t fps = resp[9]; uint8_t clk_phase = resp[11]; - printf("[Sensor] Digital Video Status:\r\n"); - printf(" Port: %s (%d)\r\n", + DBG_INIT("Sensor Digital Video Status:\r\n"); + DBG_INIT(" Port: %s (%d)\r\n", (port_type < 10) ? port_names[port_type] : "?", (int)port_type); - printf(" Content: %s (%d)\r\n", + DBG_INIT(" Content: %s (%d)\r\n", (content < 6) ? content_names[content] : "?", (int)content); - printf(" Interface: %s (%d)\r\n", + DBG_INIT(" Interface: %s (%d)\r\n", (iface < 3) ? iface_names[iface] : "?", (int)iface); - printf(" FPS: %s (%d)\r\n", + DBG_INIT(" FPS: %s (%d)\r\n", (fps < 4) ? fps_names[fps] : "?", (int)fps); - printf(" ClkPhase: %s (%d)\r\n", + DBG_INIT(" ClkPhase: %s (%d)\r\n", (clk_phase == 0) ? "Rising" : "Falling", (int)clk_phase); } diff --git a/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.c b/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.c index f5ab1fa..c9bf1b2 100644 --- a/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.c +++ b/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.c @@ -176,7 +176,7 @@ void qdx_port_sock_recv_notify(uint8_t sockid) total += len; if (err != WCHNET_ERR_SUCCESS) break; } - DBG_PORT("recv_notify sock%d: %u bytes, ring=%u\r\n", + DBG_HB("recv_notify sock%d: %u bytes, ring=%u\r\n", sockid, (unsigned)total, (unsigned)ring_available(&ctx->rx_ring)); /* Wake blocking recv thread */ xSemaphoreGive(ctx->rx_sem); @@ -187,7 +187,7 @@ void qdx_port_sock_connect_notify(uint8_t sockid) SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid); if (!ctx) return; ctx->connected = 1; - DBG_PORT("connect_notify sock%d\r\n", sockid); + DBG_NET("connect_notify sock%d\r\n", sockid); xSemaphoreGive(ctx->connect_sem); } @@ -196,7 +196,7 @@ void qdx_port_sock_disconnect_notify(uint8_t sockid) SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid); if (!ctx) return; ctx->connected = 0; - DBG_PORT("disconnect_notify sock%d\r\n", sockid); + DBG_NET("disconnect_notify sock%d\r\n", sockid); /* Wake recv thread so it can detect disconnect */ xSemaphoreGive(ctx->rx_sem); } @@ -209,7 +209,7 @@ void qdx_port_init(void) { memset(g_sock_ctx, 0, sizeof(g_sock_ctx)); g_wchnet_mutex = xSemaphoreCreateMutex(); - DBG_PORT("init done, mutex=%p\r\n", g_wchnet_mutex); + DBG_INIT("qdx_port init done, mutex=%p\r\n", g_wchnet_mutex); } /* ============================================================ @@ -268,7 +268,7 @@ int8_t qdx_port_thread_create(const char *name, qdx_thread_entry_t entry, BaseType_t ret = xTaskCreate((TaskFunction_t)entry, name, (uint16_t)stack_words, arg, (UBaseType_t)priority, NULL); - DBG_PORT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n", + DBG_INIT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n", name, (int)stack_words, (int)priority, (ret == pdPASS) ? "OK" : "FAIL"); return (ret == pdPASS) ? 0 : -1; } @@ -281,15 +281,15 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) { uint8_t dest_ip[4]; if (parse_ip(ip, dest_ip) != 0) { - DBG_PORT("bad IP \"%s\"\r\n", ip); + DBG_ERR("bad IP \"%s\"\r\n", ip); return NULL; } - DBG_PORT("connecting to %s:%d\r\n", ip, port); + DBG_NET("connecting to %s:%d\r\n", ip, port); SocketCtx_t *ctx = alloc_sock_ctx(); if (!ctx) { - DBG_PORT("no free SocketCtx\r\n"); + DBG_ERR("no free SocketCtx\r\n"); return NULL; } @@ -300,7 +300,7 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) sock_inf.DesPort = port; memcpy(sock_inf.IPAddr, dest_ip, 4); - DBG_PORT("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n", + DBG_NET("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n", sock_inf.ProtoType, sock_inf.IPAddr[0], sock_inf.IPAddr[1], sock_inf.IPAddr[2], sock_inf.IPAddr[3], @@ -313,11 +313,11 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) xSemaphoreGive(g_wchnet_mutex); if (err != WCHNET_ERR_SUCCESS) { - DBG_PORT("SocketCreat fail %02X\r\n", err); + DBG_ERR("SocketCreat fail %02X\r\n", err); free_sock_ctx(ctx); return NULL; } - DBG_PORT("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id); + DBG_NET("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id); ctx->wchnet_sock_id = wchnet_id; @@ -330,12 +330,12 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) xSemaphoreGive(g_wchnet_mutex); if (err != WCHNET_ERR_SUCCESS) { - DBG_PORT("SocketConnect fail %02X\r\n", err); + DBG_ERR("SocketConnect fail %02X\r\n", err); WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST); free_sock_ctx(ctx); return NULL; } - DBG_PORT("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err); + DBG_NET("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err); /* Block until SINT_STAT_CONNECT or 5s timeout */ uint32_t t0 = xTaskGetTickCount(); @@ -343,14 +343,14 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) uint32_t elapsed = (xTaskGetTickCount() - t0) * portTICK_PERIOD_MS; if (sem_ret != pdTRUE) { - DBG_PORT("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n", + DBG_ERR("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n", (int)elapsed, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port); - DBG_PORT(" ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id); + DBG_ERR(" ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id); WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST); free_sock_ctx(ctx); return NULL; } - DBG_PORT("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected); + DBG_NET("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected); if (!ctx->connected) { WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST); @@ -362,7 +362,7 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) WCHNET_SocketSetKeepLive(wchnet_id, ENABLE); #endif - DBG_PORT("connected sock %d -> %d.%d.%d.%d:%d\r\n", + DBG_NET("connected sock %d -> %d.%d.%d.%d:%d\r\n", wchnet_id, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port); return (qdx_socket_t)ctx; } @@ -388,7 +388,7 @@ int32_t qdx_port_tcp_send(qdx_socket_t sock, const uint8_t *data, uint32_t len) if (err != WCHNET_ERR_SUCCESS && send_len == 0) { /* WCHNET send buffer full — yield so wchnet task can flush */ if (++retries > 50) { - DBG_PORT("send fail after retries, err=0x%02X\r\n", err); + DBG_ERR("send fail after retries, err=0x%02X\r\n", err); return -1; } vTaskDelay(pdMS_TO_TICKS(2)); diff --git a/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.h b/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.h index 2897df8..de17303 100644 --- a/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.h +++ b/prj/TCPClient/Middle/QDXnetworkStack/qdx_port.h @@ -18,29 +18,48 @@ extern "C" { #endif /* ============================================================ - * Debug Print Macros (set to 0 to disable, 1 to enable) + * Multi-Level Debug Print System + * + * Verbosity levels (set DBG_LEVEL to control output volume): + * 0 = NONE — all debug prints disabled + * 1 = ERR — errors only + * 2 = BRIEF — + server config / network events (recommended) + * 3 = NORMAL — + trigger / data / init events (default) + * 4 = VERBOSE — + heartbeat / detailed internals + * + * Category macros — each prints when DBG_LEVEL >= its threshold: + * DBG_ERR (>=1) Errors, HardFault, CRC fail, bad frames + * DBG_CFG (>=2) Server-received config (Config2D/1D/Common, DevID, ACK, DetResult) + * DBG_NET (>=2) TCP connect/disconnect, PHY change, socket events + * DBG_TRIG (>=3) Trigger events (ext/int), burst start/complete + * DBG_DATA (>=3) Frame send results (2D/1D/TEMP_REQ) + * DBG_INIT (>=3) Boot-time init messages (sensor, DVP, WCHNET) + * DBG_HB (>=4) Heartbeat prints (high-frequency, debug only) * ============================================================ */ -#define QDX_DEBUG_PORT 1 /* qdx_port.c: socket/mutex/thread ops */ -#define QDX_DEBUG_LOGIC 1 /* qdx_tcp_logic.c: protocol/TLV parsing */ -#define QDX_DEBUG_APP 1 /* main.c: application-level events */ +#define DBG_LEVEL_NONE 0 +#define DBG_LEVEL_ERR 1 +#define DBG_LEVEL_BRIEF 2 +#define DBG_LEVEL_NORMAL 3 +#define DBG_LEVEL_VERBOSE 4 -#if QDX_DEBUG_PORT - #define DBG_PORT(fmt, ...) printf("[PORT] " fmt, ##__VA_ARGS__) -#else - #define DBG_PORT(fmt, ...) ((void)0) -#endif +/* >>> Change this single value to control output volume <<< */ +#define DBG_LEVEL DBG_LEVEL_NORMAL -#if QDX_DEBUG_LOGIC - #define DBG_LOGIC(fmt, ...) printf("[LOGIC] " fmt, ##__VA_ARGS__) -#else - #define DBG_LOGIC(fmt, ...) ((void)0) -#endif +#define DBG_PRINT_(threshold, tag, fmt, ...) \ + do { if (DBG_LEVEL >= (threshold)) printf(tag fmt, ##__VA_ARGS__); } while(0) -#if QDX_DEBUG_APP - #define DBG_APP(fmt, ...) printf("[APP] " fmt, ##__VA_ARGS__) -#else - #define DBG_APP(fmt, ...) ((void)0) -#endif +#define DBG_ERR(fmt, ...) DBG_PRINT_(DBG_LEVEL_ERR, "[ERR] ", fmt, ##__VA_ARGS__) +#define DBG_CFG(fmt, ...) DBG_PRINT_(DBG_LEVEL_BRIEF, "[CFG] ", fmt, ##__VA_ARGS__) +#define DBG_NET(fmt, ...) DBG_PRINT_(DBG_LEVEL_BRIEF, "[NET] ", fmt, ##__VA_ARGS__) +#define DBG_TRIG(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[TRIG] ", fmt, ##__VA_ARGS__) +#define DBG_DATA(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[DATA] ", fmt, ##__VA_ARGS__) +#define DBG_INIT(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[INIT] ", fmt, ##__VA_ARGS__) +#define DBG_HB(fmt, ...) DBG_PRINT_(DBG_LEVEL_VERBOSE, "[HB] ", fmt, ##__VA_ARGS__) + +/* Legacy compatibility aliases — avoid using in new code */ +#define DBG_PORT(fmt, ...) DBG_INIT(fmt, ##__VA_ARGS__) +#define DBG_LOGIC(fmt, ...) DBG_CFG(fmt, ##__VA_ARGS__) +#define DBG_APP(fmt, ...) DBG_DATA(fmt, ##__VA_ARGS__) /* ============================================================ * Time & Delay diff --git a/prj/TCPClient/Middle/QDXnetworkStack/qdx_preprocess.c b/prj/TCPClient/Middle/QDXnetworkStack/qdx_preprocess.c index bde1d27..48b8669 100644 --- a/prj/TCPClient/Middle/QDXnetworkStack/qdx_preprocess.c +++ b/prj/TCPClient/Middle/QDXnetworkStack/qdx_preprocess.c @@ -109,7 +109,7 @@ int8_t Preprocess_Execute(const RawImageBuffer_t *input, tgt_w = (tgt_w + 1) / 2; tgt_h = (tgt_h + 1) / 2; } - DBG_PORT("PP: target clamped %dx%d -> %dx%d (buf=%d)\r\n", + DBG_INIT("PP: target clamped %dx%d -> %dx%d (buf=%d)\r\n", (int)orig_w, (int)orig_h, (int)tgt_w, (int)tgt_h, (int)max_payload); } diff --git a/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.c b/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.c index ca84863..9675126 100644 --- a/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.c +++ b/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.c @@ -69,7 +69,7 @@ static void tcp_stream_init(TcpStreamCtx_t *ctx, const char *label) { } static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) { - DBG_LOGIC("!! [%s] Disconnected\r\n", ctx->label); + DBG_NET("[%s] Disconnected\r\n", ctx->label); ctx->is_connected = 0; if (ctx->sock) { qdx_port_tcp_close(ctx->sock); @@ -79,10 +79,10 @@ static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) { static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip, uint16_t port) { - DBG_LOGIC("[%s] connecting %s:%d...\r\n", ctx->label, ip, port); + DBG_NET("[%s] connecting %s:%d...\r\n", ctx->label, ip, port); ctx->sock = qdx_port_tcp_connect(ip, port); if (ctx->sock == NULL) { - DBG_LOGIC("[%s] connect FAILED\r\n", ctx->label); + DBG_ERR("[%s] connect FAILED\r\n", ctx->label); return -1; } @@ -90,7 +90,7 @@ static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip, ctx->last_activity_ms = qdx_port_get_tick_ms(); ctx->last_heartbeat_ms = ctx->last_activity_ms; ctx->recv_len = 0; - DBG_LOGIC(">> [%s] Connected to %s:%d\r\n", ctx->label, ip, port); + DBG_NET("[%s] Connected to %s:%d\r\n", ctx->label, ip, port); return 0; } @@ -121,7 +121,7 @@ static int32_t tcp_send_frame(TcpStreamCtx_t *ctx, uint8_t msg_class, } static void tcp_send_handshake(TcpStreamCtx_t *ctx) { - DBG_LOGIC(">> [%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id); + DBG_NET("[%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id); uint8_t payload[54]; memset(payload, 0, sizeof(payload)); qdx_write_u16_le(payload + 0, 0x0200); @@ -250,17 +250,17 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, uint8_t cfg_updated = 0; - DBG_LOGIC("[%s] TLV pkt: Seq=%d, PayloadLen=%d\r\n", ctx->label, + DBG_HB("[%s] TLV pkt: Seq=%d Len=%d\r\n", ctx->label, hdr_seq, payload_len); while (parsed_len <= payload_len - 3) { uint8_t type = packet[offset]; uint16_t len = qdx_read_u16_le(packet + offset + 1); - DBG_LOGIC("[%s] TLV: Type=0x%02X, Len=%d\r\n", ctx->label, type, len); + DBG_HB("[%s] TLV: Type=0x%02X Len=%d\r\n", ctx->label, type, len); if (parsed_len + 3 + len > payload_len) { - DBG_LOGIC("[%s] ! TLV truncated (need %d, have %d)\r\n", + DBG_ERR("[%s] TLV truncated (need %d, have %d)\r\n", ctx->label, len, payload_len - parsed_len - 3); break; /* Malformed */ } @@ -278,16 +278,16 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, uint16_t ack_seq = qdx_read_u16_le(value); uint8_t status = value[2]; uint16_t err_code = (len >= 5) ? qdx_read_u16_le(value + 3) : 0; - DBG_LOGIC("<< [%s] ACK: seq=%d status=%d err=0x%04x\r\n", - ctx->label, (int)ack_seq, (int)status, (int)err_code); + DBG_CFG("<< ACK: seq=%d status=%d err=0x%04x\r\n", + (int)ack_seq, (int)status, (int)err_code); } break; } case TYPE_DEVID_ASSIGN: { if (len >= sizeof(DevIDAssignment_t)) { uint16_t new_id = qdx_read_u16_le(value); - DBG_LOGIC("<< [%s] DevID assigned: %d -> %d\r\n", - ctx->label, (int)g_TcpLogic.dev_id, (int)new_id); + DBG_CFG("<< DevID assigned: %d -> %d\r\n", + (int)g_TcpLogic.dev_id, (int)new_id); g_TcpLogic.pending_new_dev_id = new_id; tcp_send_ack(ctx, hdr_seq, 0, 0); } @@ -300,8 +300,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, g_TcpLogic.has_valid_config = 1; cfg_updated = 1; qdx_port_mutex_unlock(g_TcpLogic.config_mutex); - DBG_LOGIC("<< [%s] ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n", - ctx->label, + DBG_CFG("<< ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n", (int)g_TcpLogic.cached_common.WorkMode, (int)g_TcpLogic.cached_common.ConfigTag, (int)g_TcpLogic.cached_common.StrictnessLevel); @@ -315,10 +314,9 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, g_TcpLogic.has_valid_config = 1; cfg_updated = 1; qdx_port_mutex_unlock(g_TcpLogic.config_mutex); - DBG_LOGIC("<< [%s] Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d " + DBG_CFG("<< Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d " "Trig=%d Burst=%d Intv=%d Thresh=%d " "ROI(%d,%d,%d,%d) NGio=%d\r\n", - ctx->label, (int)g_TcpLogic.cached_cfg2d.Enabled, (int)g_TcpLogic.cached_cfg2d.Width, (int)g_TcpLogic.cached_cfg2d.Height, @@ -335,8 +333,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, (int)g_TcpLogic.cached_cfg2d.TriggerRoiH, (int)g_TcpLogic.cached_cfg2d.NGioDelay); } else { - DBG_LOGIC("<< [%s] Config2D bad len=%d (need %d)\r\n", - ctx->label, len, (int)sizeof(Config2D_t)); + DBG_ERR("<< Config2D bad len=%d (need %d)\r\n", + len, (int)sizeof(Config2D_t)); } break; } @@ -347,9 +345,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, g_TcpLogic.has_valid_config = 1; cfg_updated = 1; qdx_port_mutex_unlock(g_TcpLogic.config_mutex); - DBG_LOGIC("<< [%s] Config1D: En=%d RunMode=%d TrigType=%d " + DBG_CFG("<< Config1D: En=%d RunMode=%d TrigType=%d " "BufSz=%d TempLim=%d NGio=%d\r\n", - ctx->label, (int)g_TcpLogic.cached_cfg1d.Enabled, (int)g_TcpLogic.cached_cfg1d.RunMode, (int)g_TcpLogic.cached_cfg1d.TriggerType, @@ -360,13 +357,13 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, break; } case TYPE_TEMP_FRAME: { - DBG_LOGIC("<< [%s] TempFrame request (len=%d)\r\n", ctx->label, (int)len); + DBG_CFG("<< TempFrame request (len=%d)\r\n", (int)len); if (g_TcpLogic.temp_req_cb) { uint8_t is2d = 0; if (len >= 18) { is2d = value[18]; } - DBG_LOGIC(" -> callback: is2D=%d\r\n", (int)is2d); + DBG_CFG(" -> callback: is2D=%d\r\n", (int)is2d); g_TcpLogic.temp_req_cb(is2d); } break; @@ -375,8 +372,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, if (len >= sizeof(DetectionResult_t)) { uint32_t frame_num = qdx_read_u32_le(value); uint8_t result_status = value[4]; - DBG_LOGIC("<< [%s] DetectionResult: frm=%d result=%d\r\n", - ctx->label, (int)frame_num, (int)result_status); + DBG_CFG("<< DetectionResult: frm=%d result=%d\r\n", + (int)frame_num, (int)result_status); if (g_TcpLogic.detect_cb) g_TcpLogic.detect_cb(frame_num, result_status); } @@ -386,8 +383,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, /* Server echoes handshake info — safe to ignore */ break; default: - DBG_LOGIC("<< [%s] Unknown TLV type=0x%02x len=%d\r\n", - ctx->label, (int)type, (int)len); + DBG_ERR("Unknown TLV type=0x%02x len=%d\r\n", (int)type, (int)len); break; } @@ -396,7 +392,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet, } if (cfg_updated && g_TcpLogic.config_cb && g_TcpLogic.has_valid_config) { - DBG_LOGIC("<< [%s] Config updated -> notify app\r\n", ctx->label); + DBG_CFG("Config updated -> notify app\r\n"); qdx_port_mutex_lock(g_TcpLogic.config_mutex); g_TcpLogic.config_cb(&g_TcpLogic.cached_common, &g_TcpLogic.cached_cfg2d, &g_TcpLogic.cached_cfg1d); @@ -438,7 +434,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) { uint16_t length = qdx_read_u16_le(ctx->recv_buffer + 3); if (version != PROTO_VERSION || length < HEADER_SIZE + CRC_SIZE) { - DBG_LOGIC("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n", + DBG_ERR("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n", ctx->label, version, PROTO_VERSION, length); memmove(ctx->recv_buffer, ctx->recv_buffer + 2, ctx->recv_len - 2); ctx->recv_len -= 2; @@ -446,7 +442,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) { } if (length > sizeof(ctx->recv_buffer)) { - DBG_LOGIC("[%s] frame too large: %d > %d\r\n", ctx->label, + DBG_ERR("[%s] frame too large: %d > %d\r\n", ctx->label, length, (int)sizeof(ctx->recv_buffer)); ctx->recv_len = 0; break; @@ -464,7 +460,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) { /* 3. Dispatch */ parse_and_dispatch_tlv(ctx, ctx->recv_buffer, length); } else { - DBG_LOGIC("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n", + DBG_ERR("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n", ctx->label, calculated_crc, received_crc, length); } @@ -642,6 +638,22 @@ void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb) { g_TcpLogic.temp_req_cb = cb; } +void TcpLogic_InjectTestConfig(const ConfigCommon_t *common, + const Config2D_t *cfg2d, + const Config1D_t *cfg1d) { + qdx_port_mutex_lock(g_TcpLogic.config_mutex); + if (common) memcpy(&g_TcpLogic.cached_common, common, sizeof(*common)); + if (cfg2d) memcpy(&g_TcpLogic.cached_cfg2d, cfg2d, sizeof(*cfg2d)); + if (cfg1d) memcpy(&g_TcpLogic.cached_cfg1d, cfg1d, sizeof(*cfg1d)); + g_TcpLogic.has_valid_config = 1; + qdx_port_mutex_unlock(g_TcpLogic.config_mutex); + if (g_TcpLogic.config_cb) { + g_TcpLogic.config_cb(&g_TcpLogic.cached_common, + &g_TcpLogic.cached_cfg2d, + &g_TcpLogic.cached_cfg1d); + } +} + /* ============================================================ * Zero-Copy Frame Building & Fragmentation for Temperature Data * ============================================================ */ @@ -658,7 +670,7 @@ TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer, g_TcpLogic.frame_count++; - DBG_LOGIC(">> [Data] TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n", + DBG_DATA(">> TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n", (int)processMeta->FrameNumber, (int)processMeta->ValidWidth, (int)processMeta->ValidHeight, (int)frameType, (int)is2D, (int)io_buffer->ValidPayloadLen); @@ -725,7 +737,7 @@ TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer, uint32_t frag_count = (total_tlv_len + MAX_FRAGMENT_PAYLOAD - 1) / MAX_FRAGMENT_PAYLOAD; - DBG_LOGIC(">> [Data] Fragmented: %d frags, total=%d\r\n", + DBG_DATA(">> Fragmented: %d frags, total=%d\r\n", (int)frag_count, (int)total_tlv_len); for (uint32_t i = 0; i < frag_count; i++) { diff --git a/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.h b/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.h index f786e1a..3fea0f7 100644 --- a/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.h +++ b/prj/TCPClient/Middle/QDXnetworkStack/qdx_tcp_logic.h @@ -105,6 +105,16 @@ typedef void (*TempFrameRequestCallback_t)(uint8_t is2dRequest); */ void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb); +/** + * @brief Inject configuration directly (for test mode without server). + * + * Sets the internal config cache and fires the config callback. + * Only non-NULL parameters are updated. + */ +void TcpLogic_InjectTestConfig(const ConfigCommon_t *common, + const Config2D_t *cfg2d, + const Config1D_t *cfg1d); + #ifdef __cplusplus } #endif diff --git a/prj/TCPClient/User/main.c b/prj/TCPClient/User/main.c index 4e365ee..c57f835 100644 --- a/prj/TCPClient/User/main.c +++ b/prj/TCPClient/User/main.c @@ -17,20 +17,32 @@ * ============================================================ */ #define TEST_PATTERN_MODE 1 -/* ============================================================ - * Feature Test Switches — set to 1 to enable, 0 to disable. - * Allows testing each feature independently. - * ============================================================ */ -#define TEST_ENABLE_HEARTBEAT 1 /* Periodic heartbeat debug print task */ -#define TEST_ENABLE_TRIGGER 0 /* Internal trigger + burst upload */ -#define TEST_ENABLE_NG_GPIO 0 /* NG GPIO pulse output on detection */ -#define TEST_ENABLE_TEMP_REQ 1 /* On-demand frame upload from server */ -#define TEST_ENABLE_TCP_SEND 1 /* TCP data channel sending */ - /* Default burst parameters — used when server hasn't sent config yet */ #define DEFAULT_BURST_COUNT 3 #define DEFAULT_BURST_INTERVAL_MS 200 +/* ============================================================ + * External Trigger DI Input + * PA15 / EXTI15 / Rising Edge + * Shared by 2D and 1D external trigger (mutually exclusive). + * ============================================================ */ +#define EXT_TRIG_GPIO_PORT GPIOA +#define EXT_TRIG_GPIO_PIN GPIO_Pin_15 +#define EXT_TRIG_GPIO_CLK RCC_APB2Periph_GPIOA +#define EXT_TRIG_EXTI_LINE EXTI_Line15 +#define EXT_TRIG_EXTI_PORT GPIO_PortSourceGPIOA +#define EXT_TRIG_EXTI_PIN GPIO_PinSource15 +#define EXT_TRIG_IRQn EXTI15_10_IRQn + +/* NG output GPIO: PA8 push-pull, active-high when NG detected */ +#define NG_GPIO_PORT GPIOA +#define NG_GPIO_PIN GPIO_Pin_8 +#define NG_GPIO_CLK RCC_APB2Periph_GPIOA +#define NG_PULSE_MS 200 + +/* ============================================================ + * TX Buffers (double-buffered for zero-copy) + * ============================================================ */ #define MAX_TCP_PAYLOAD_SIZE 10240 uint8_t g_TxNetBuffer_A_Mem[MAX_TCP_PAYLOAD_SIZE]; uint8_t g_TxNetBuffer_B_Mem[MAX_TCP_PAYLOAD_SIZE]; @@ -48,68 +60,94 @@ TcpTxBuffer_t g_TxNetBuffer_B = { .ValidPayloadLen = 0 }; +/* ============================================================ + * Runtime State + * ============================================================ */ +extern volatile uint32_t sys_tick_ms; +/* External trigger flag — set in EXTI ISR */ +static volatile uint8_t g_ext_trigger_flag = 0; +static volatile uint32_t g_ext_trigger_time_ms = 0; +/* TEMP_REQ auxiliary channel (server on-demand screenshot) */ +static volatile uint8_t g_temp_req_pending = 0; +static volatile uint8_t g_temp_req_is2d = 1; + +/* NG output pulse timer */ +static volatile uint32_t g_ng_off_time = 0; + +/* ============================================================ + * 2D Burst State Machine + * ============================================================ */ +static uint8_t burst_active = 0; +static uint8_t burst_remaining = 0; +static uint32_t burst_next_time_ms = 0; +static uint8_t burst_delay_pending = 0; /* waiting for DelayMs */ +static uint32_t burst_delay_until_ms = 0; +static uint8_t burst_debounce_pending = 0; /* waiting for DebounceMs */ +static uint32_t burst_debounce_until = 0; + +/* ============================================================ + * 1D Collection State Machine + * + * Protocol flow: + * External: GPIO → debounce(HighTimerLimit) → collect temp points + * → threshold start/stop → slice [LSizeStart, -RSizeStart] → send + * Internal: continuous scan → 3 consecutive hot → collect + * → threshold stop → slice → send + * + * Each DVP frame provides one temperature sample (center row max). + * ============================================================ */ +typedef enum { + S1D_IDLE = 0, + S1D_DEBOUNCE, + S1D_COLLECTING, +} State1D_t; + +#define MAX_1D_POINTS 512 + +static State1D_t s1d_state = S1D_IDLE; +static uint16_t s1d_temp_buf[MAX_1D_POINTS]; +static uint16_t s1d_time_buf[MAX_1D_POINTS]; +static uint16_t s1d_count = 0; +static uint8_t s1d_consec_hot = 0; +static uint8_t s1d_consec_cold = 0; +static uint32_t s1d_start_time = 0; +static uint32_t s1d_debounce_until = 0; +static uint8_t s1d_triggered = 0; + +/* Pre-trigger ring buffer (3 samples) for internal 1D trigger */ +static uint16_t s1d_pre_ring[3]; +static uint8_t s1d_pre_idx = 0; +static uint8_t s1d_pre_count = 0; + +/* ============================================================ + * Callbacks + * ============================================================ */ void OnConfigUpdate(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d) { Preprocess_Settings_Change(cfg2d, cfg1d, common); } -extern volatile uint32_t sys_tick_ms; - -#if TEST_ENABLE_NG_GPIO -/* NG output GPIO: PA8 push-pull, active-high when NG detected */ -#define NG_GPIO_PORT GPIOA -#define NG_GPIO_PIN GPIO_Pin_8 -#define NG_GPIO_CLK RCC_APB2Periph_GPIOA -#define NG_PULSE_MS 200 /* default NG pulse width */ - -static volatile uint32_t g_ng_off_time = 0; - -static void NG_GPIO_Init(void) -{ - GPIO_InitTypeDef gpio = {0}; - RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE); - gpio.GPIO_Pin = NG_GPIO_PIN; - gpio.GPIO_Mode = GPIO_Mode_Out_PP; - gpio.GPIO_Speed = GPIO_Speed_2MHz; - GPIO_Init(NG_GPIO_PORT, &gpio); - GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); -} -#endif /* TEST_ENABLE_NG_GPIO */ - -#if TEST_ENABLE_TEMP_REQ -/* Flag set by TempFrameRequest callback; consumed by business task */ -static volatile uint8_t g_temp_req_pending = 0; -static volatile uint8_t g_temp_req_is2d = 1; - void OnTempFrameRequest(uint8_t is2dRequest) { g_temp_req_is2d = is2dRequest; g_temp_req_pending = 1; - DBG_APP("TempFrameReq is2d=%d\r\n", (int)is2dRequest); + DBG_CFG("TempFrameReq is2d=%d\r\n", (int)is2dRequest); } -#endif /* TEST_ENABLE_TEMP_REQ */ -#if TEST_ENABLE_NG_GPIO void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus) { (void)frameNumber; - /* resultStatus: 0 = NG, 1 = OK */ if (resultStatus == 0) { - ConfigCommon_t tmp_common; - Config2D_t tmp_cfg2d; - Config1D_t tmp_cfg1d; - uint32_t pulse_ms = NG_PULSE_MS; /* fallback default */ - if (TcpLogic_GetLatestConfig(&tmp_common, &tmp_cfg2d, &tmp_cfg1d) == 0 - && tmp_cfg2d.NGioDelay > 0) { - pulse_ms = tmp_cfg2d.NGioDelay; - } + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint32_t pulse_ms = NG_PULSE_MS; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 && t2.NGioDelay > 0) + pulse_ms = t2.NGioDelay; GPIO_SetBits(NG_GPIO_PORT, NG_GPIO_PIN); g_ng_off_time = sys_tick_ms + pulse_ms; } } -#endif /* TEST_ENABLE_NG_GPIO */ #define KEEPALIVE_ENABLE 1 @@ -144,7 +182,7 @@ static void Config_Flash_SRAM(FLASH_SRAM_DEFIN mode) FLASH_Unlock(); FLASH_ProgramOptionByteData(0x1FFFF802, newByte); FLASH_Lock(); - printf("Flash/SRAM config changed to %d, resetting...\r\n", mode); + DBG_INIT("Flash/SRAM changed to %d, resetting...\r\n", mode); NVIC_SystemReset(); } @@ -162,7 +200,7 @@ u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN]; void mStopIfError(u8 iError) { if (iError == WCHNET_ERR_SUCCESS) return; - printf("Error: %02X\r\n", (u16)iError); + DBG_ERR("WCHNET: %02X\r\n", (u16)iError); } void TIM2_Init(void) @@ -181,9 +219,6 @@ void TIM2_Init(void) } - - - /* qdx_port.c notification hooks */ extern void qdx_port_sock_recv_notify(uint8_t sockid); extern void qdx_port_sock_connect_notify(uint8_t sockid); @@ -195,7 +230,7 @@ extern void qdx_port_net_unlock(void); void WCHNET_HandleSockInt(u8 socketid, u8 intstat) { - DBG_APP("SockInt: id=%d stat=0x%02X\r\n", socketid, intstat); + DBG_NET("SockInt: id=%d stat=0x%02X\r\n", socketid, intstat); if (intstat & SINT_STAT_RECV) { qdx_port_sock_recv_notify(socketid); @@ -204,17 +239,17 @@ void WCHNET_HandleSockInt(u8 socketid, u8 intstat) { WCHNET_ModifyRecvBuf(socketid, (u32)SocketRecvBuf[socketid], RECE_BUF_LEN); qdx_port_sock_connect_notify(socketid); - DBG_APP("TCP Connected, socket %d\r\n", socketid); + DBG_NET("TCP Connected, socket %d\r\n", socketid); } if (intstat & SINT_STAT_DISCONNECT) { qdx_port_sock_disconnect_notify(socketid); - DBG_APP("TCP Disconnected, socket %d\r\n", socketid); + DBG_NET("TCP Disconnected, socket %d\r\n", socketid); } if (intstat & SINT_STAT_TIM_OUT) { qdx_port_sock_disconnect_notify(socketid); - DBG_APP("TCP Timeout, socket %d\r\n", socketid); + DBG_NET("TCP Timeout, socket %d\r\n", socketid); } } @@ -224,12 +259,12 @@ void WCHNET_HandleGlobalInt(void) u16 i; u8 socketint; intstat = WCHNET_GetGlobalInt(); - DBG_APP("GlobalInt: 0x%02X\r\n", intstat); - if (intstat & GINT_STAT_UNREACH) DBG_APP("GINT_STAT_UNREACH\r\n"); - if (intstat & GINT_STAT_IP_CONFLI) DBG_APP("GINT_STAT_IP_CONFLI\r\n"); + DBG_NET("GlobalInt: 0x%02X\r\n", intstat); + if (intstat & GINT_STAT_UNREACH) DBG_NET("UNREACH\r\n"); + if (intstat & GINT_STAT_IP_CONFLI) DBG_ERR("IP_CONFLICT\r\n"); if (intstat & GINT_STAT_PHY_CHANGE) { i = WCHNET_GetPHYStatus(); - DBG_APP("PHY_CHANGE: status=0x%04X %s\r\n", i, + DBG_NET("PHY: status=0x%04X %s\r\n", i, (i & PHY_Linked_Status) ? "LINK_UP" : "LINK_DOWN"); } if (intstat & GINT_STAT_SOCKET) { @@ -240,9 +275,55 @@ void WCHNET_HandleGlobalInt(void) } } -#if TEST_ENABLE_HEARTBEAT /* ============================================================ - * RTOS Task: Heartbeat (periodic print for debug) + * Hardware Init: External Trigger GPIO (PA15 / EXTI15) + * ============================================================ */ +static void ExtTrigger_GPIO_Init(void) +{ + GPIO_InitTypeDef gpio = {0}; + EXTI_InitTypeDef exti = {0}; + + RCC_APB2PeriphClockCmd(EXT_TRIG_GPIO_CLK | RCC_APB2Periph_AFIO, ENABLE); + + gpio.GPIO_Pin = EXT_TRIG_GPIO_PIN; + gpio.GPIO_Mode = GPIO_Mode_IPD; /* Pull-down: idle low */ + GPIO_Init(EXT_TRIG_GPIO_PORT, &gpio); + + GPIO_EXTILineConfig(EXT_TRIG_EXTI_PORT, EXT_TRIG_EXTI_PIN); + + exti.EXTI_Line = EXT_TRIG_EXTI_LINE; + exti.EXTI_Mode = EXTI_Mode_Interrupt; + exti.EXTI_Trigger = EXTI_Trigger_Rising; + exti.EXTI_LineCmd = ENABLE; + EXTI_Init(&exti); + + NVIC_EnableIRQ(EXT_TRIG_IRQn); +} + +static void NG_GPIO_Init(void) +{ + GPIO_InitTypeDef gpio = {0}; + RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE); + gpio.GPIO_Pin = NG_GPIO_PIN; + gpio.GPIO_Mode = GPIO_Mode_Out_PP; + gpio.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_Init(NG_GPIO_PORT, &gpio); + GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); +} + +/* EXTI15 ISR — external trigger rising edge (PA15) */ +void EXTI15_10_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +void EXTI15_10_IRQHandler(void) +{ + if (EXTI_GetITStatus(EXT_TRIG_EXTI_LINE) != RESET) { + EXTI_ClearITPendingBit(EXT_TRIG_EXTI_LINE); + g_ext_trigger_flag = 1; + g_ext_trigger_time_ms = sys_tick_ms; + } +} + +/* ============================================================ + * RTOS Task: Heartbeat (periodic debug print) * ============================================================ */ static void task_heartbeat_entry(void *pvParameters) { @@ -251,18 +332,17 @@ static void task_heartbeat_entry(void *pvParameters) while (1) { #if TEST_PATTERN_MODE - printf("[HB] %d tick=%d TEST_MODE frm=%d\r\n", + DBG_HB("%d tick=%d TEST_MODE frm=%d\r\n", (int)cnt++, (int)xTaskGetTickCount(), (int)dvp_frame_count); #else - printf("[HB] %d tick=%d dvp_frm=%d row_irq=%d\r\n", + DBG_HB("%d tick=%d dvp_frm=%d row_irq=%d\r\n", (int)cnt++, (int)xTaskGetTickCount(), (int)dvp_frame_count, (int)dvp_row_irq_cnt); #endif vTaskDelay(pdMS_TO_TICKS(2000)); } } -#endif /* TEST_ENABLE_HEARTBEAT */ /* ============================================================ * RTOS Task: WCHNET protocol stack driver (highest priority) @@ -281,18 +361,6 @@ static void task_wchnet_entry(void *pvParameters) } } -/* ============================================================ - * RTOS Task: Business logic (DVP + preprocess + send) - * ============================================================ */ -#if TEST_ENABLE_TRIGGER -/* ============================================================ - * Burst capture state machine - * ============================================================ */ -static uint8_t burst_active = 0; /* 1 = currently in burst */ -static uint8_t burst_remaining = 0; /* frames left to capture */ -static uint32_t burst_next_time_ms = 0; /* next burst frame time */ -#endif /* TEST_ENABLE_TRIGGER */ - #if TEST_PATTERN_MODE /* ============================================================ * Test Pattern Generator @@ -310,11 +378,9 @@ static uint32_t burst_next_time_ms = 0; /* next burst frame time */ static void test_fill_gradient(uint16_t *buf, uint16_t w, uint16_t h) { - /* Diagonal gradient: 25°C at top-left → 45°C at bottom-right - * Both X and Y contribute so 1D center-row also shows variation */ for (uint16_t y = 0; y < h; y++) { for (uint16_t x = 0; x < w; x++) { - uint32_t pos = (uint32_t)y * w + (uint32_t)x * h; /* combined weight */ + uint32_t pos = (uint32_t)y * w + (uint32_t)x * h; uint16_t temp = TEMP_RAW(25.0) + (uint16_t)(pos * TEMP_RAW(20.0) / ((uint32_t)w * h)); buf[y * w + x] = temp; } @@ -323,7 +389,6 @@ static void test_fill_gradient(uint16_t *buf, uint16_t w, uint16_t h) static void test_fill_hotspot(uint16_t *buf, uint16_t w, uint16_t h) { - /* Background 30°C with slight X variation, center 32x32 block at 90°C */ for (uint16_t y = 0; y < h; y++) for (uint16_t x = 0; x < w; x++) buf[y * w + x] = TEMP_RAW(28.0) + (uint16_t)((uint32_t)x * TEMP_RAW(4.0) / w); @@ -342,7 +407,6 @@ static void test_fill_uniform(uint16_t *buf, uint16_t w, uint16_t h) static void test_fill_checker(uint16_t *buf, uint16_t w, uint16_t h) { - /* 32x32 checkerboard: alternating 30°C and 85°C blocks */ for (uint16_t y = 0; y < h; y++) for (uint16_t x = 0; x < w; x++) { uint8_t cell = ((y / 32) + (x / 32)) & 1; @@ -384,25 +448,120 @@ static void task_test_pattern_entry(void *pvParameters) } #endif /* TEST_PATTERN_MODE */ -#if TEST_ENABLE_TEMP_REQ /* ============================================================ - * 1D mode: build and send a single 1D temperature frame from - * the current raw image. Scans the center row and packs - * TempPoint1D_t (2B time_offset + 2B temp, little-endian). + * Business Logic Helpers * ============================================================ */ -#define TEST_1D_POINTS 30 /* 1D test: sample 30 points from center row */ -static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *tx_buf) +/* + * Extract one representative temperature from a raw frame for 1D. + * Uses max temperature of the center row. + */ +static uint16_t get_1d_sample(const RawImageBuffer_t *raw) +{ + uint16_t w = raw->Width; + uint16_t h = raw->Height; + uint16_t row = h / 2; + uint16_t *src = raw->pData; + uint16_t max_temp = 0; + for (uint16_t x = 0; x < w; x++) { + uint16_t t = src[row * w + x]; + if (t > max_temp) max_temp = t; + } + return max_temp; +} + +/* + * Reset 1D state machine to idle. + */ +static void reset_1d_state(void) +{ + s1d_state = S1D_IDLE; + s1d_count = 0; + s1d_consec_hot = 0; + s1d_consec_cold = 0; + s1d_triggered = 0; + s1d_pre_idx = 0; + s1d_pre_count = 0; +} + +/* + * Pack collected 1D samples and send via TCP. + * Applies slicing: [LSizeStart, count - RSizeStart] + */ +static void send_1d_collection(const Config1D_t *cfg, uint8_t *use_buf_a) +{ + uint16_t start = cfg->LSizeStart; + uint16_t end = s1d_count; + if (cfg->RSizeStart > 0 && cfg->RSizeStart < end) + end -= cfg->RSizeStart; + if (start >= end) { + DBG_ERR("1D: slice empty start=%d end=%d\r\n", (int)start, (int)end); + return; + } + uint16_t n = end - start; + + TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + *use_buf_a = !(*use_buf_a); + tx_buf->ValidPayloadLen = 0; + uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset; + uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset; + if ((uint32_t)n * 4 > capacity) + n = (uint16_t)(capacity / 4); + + int16_t min_t = 32767, max_t = -32768; + int32_t sum_t = 0; + + for (uint16_t i = 0; i < n; i++) { + uint16_t idx = start + i; + uint16_t temp = s1d_temp_buf[idx]; + uint16_t toff = s1d_time_buf[idx]; + int16_t t = (int16_t)temp; + if (t < min_t) min_t = t; + if (t > max_t) max_t = t; + sum_t += t; + dest[0] = (uint8_t)(toff & 0xFF); + dest[1] = (uint8_t)((toff >> 8) & 0xFF); + dest[2] = (uint8_t)(temp & 0xFF); + dest[3] = (uint8_t)((temp >> 8) & 0xFF); + dest += 4; + } + tx_buf->ValidPayloadLen = (uint32_t)n * 4; + + PreprocessResult_t meta; + memset(&meta, 0, sizeof(meta)); + meta.pValidData = tx_buf->pBuffer + tx_buf->HeadOffset; + meta.DataLength = tx_buf->ValidPayloadLen; + meta.ValidWidth = n; + meta.ValidHeight = 1; + meta.MinTemp = min_t; + meta.MaxTemp = max_t; + meta.AvgTemp = (int16_t)(sum_t / (n > 0 ? n : 1)); + meta.RoiTemp = meta.AvgTemp; + meta.FrameNumber = Ready_Frame_Count; + + int8_t ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0); + DBG_DATA("1D SEND n=%d slice=[%d,%d) ret=%d\r\n", + (int)n, (int)start, (int)end, (int)ret); +} + +/* + * Spatial 1D snapshot for TEMP_REQ — samples points across center row. + */ +#define SNAPSHOT_1D_POINTS 30 + +static void send_1d_snapshot(const RawImageBuffer_t *raw, uint8_t *use_buf_a) { uint16_t w = raw->Width; uint16_t h = raw->Height; uint16_t *src = raw->pData; - uint16_t row = h / 2; /* center row */ + uint16_t row = h / 2; + TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + *use_buf_a = !(*use_buf_a); tx_buf->ValidPayloadLen = 0; uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset; uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset; - uint16_t points = TEST_1D_POINTS; + uint16_t points = SNAPSHOT_1D_POINTS; if (points > w) points = w; if ((uint32_t)points * 4 > capacity) points = (uint16_t)(capacity / 4); @@ -411,7 +570,6 @@ static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *t int32_t sum_t = 0; for (uint16_t i = 0; i < points; i++) { - /* Evenly sample across the full row width */ uint16_t col = (uint16_t)((uint32_t)i * (w - 1) / (points > 1 ? points - 1 : 1)); uint16_t temp = src[row * w + col]; uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1)); @@ -439,10 +597,261 @@ static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *t meta.RoiTemp = meta.AvgTemp; meta.FrameNumber = raw->FrameNumber; - TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0 /* IS_1D */); + int8_t ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0); + DBG_DATA("1D snapshot n=%d ret=%d\r\n", (int)points, (int)ret); } -#endif /* TEST_ENABLE_TEMP_REQ */ +/* + * Capture and send one 2D frame (used during burst sequence). + */ +static void do_2d_capture_send(const RawImageBuffer_t *raw, uint8_t *use_buf_a) +{ + TcpTxBuffer_t *tx_buf = (*use_buf_a) ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + *use_buf_a = !(*use_buf_a); + tx_buf->ValidPayloadLen = 0; + + PreprocessResult_t meta; + int8_t pp_ret = Preprocess_Execute(raw, tx_buf, &meta); + if (pp_ret == 0) { + int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1); + DBG_DATA("2D SEND frm=%d %dx%d ret=%d\r\n", + (int)meta.FrameNumber, (int)meta.ValidWidth, + (int)meta.ValidHeight, (int)tx_ret); + } else { + DBG_ERR("2D PP fail ret=%d\r\n", (int)pp_ret); + } +} + +/* + * Start a 2D burst: capture first frame, then queue remaining. + */ +static void start_2d_burst(const RawImageBuffer_t *raw, const Config2D_t *t2, uint8_t *use_buf_a) +{ + DBG_TRIG("TRIGGER frm=%d\r\n", (int)raw->FrameNumber); + do_2d_capture_send(raw, use_buf_a); + + uint8_t total = DEFAULT_BURST_COUNT; + uint16_t interval = DEFAULT_BURST_INTERVAL_MS; + if (t2->TriggerBurstCount >= 1) + total = t2->TriggerBurstCount; + if (t2->TriggerInternalIntervalMs > 0) + interval = t2->TriggerInternalIntervalMs; + + if (total > 1) { + burst_active = 1; + burst_remaining = total - 1; + burst_next_time_ms = sys_tick_ms + interval; + DBG_TRIG("Burst start: %d frames, interval=%d ms\r\n", + (int)total, (int)interval); + } +} + +/* ============================================================ + * 2D Trigger Handler — called once per frame when Config2D enabled + * + * State machine phases: + * 1. Debounce wait (external trigger: DebounceIntervalMs) + * 2. Delay wait (DelayMs after trigger confirmation) + * 3. Burst capture (BurstCount frames at InternalIntervalMs) + * 4. Idle — check for new trigger (external GPIO or internal temp) + * ============================================================ */ +static void handle_2d_trigger(const RawImageBuffer_t *raw, const Config2D_t *t2, uint8_t *use_buf_a) +{ + /* Phase 1: debounce wait (external trigger only) */ + if (burst_debounce_pending) { + if (sys_tick_ms < burst_debounce_until) + return; + burst_debounce_pending = 0; + if (t2->TriggerDelayMs > 0) { + burst_delay_pending = 1; + burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs; + } else { + start_2d_burst(raw, t2, use_buf_a); + } + return; + } + + /* Phase 2: delay wait */ + if (burst_delay_pending) { + if (sys_tick_ms < burst_delay_until_ms) + return; + burst_delay_pending = 0; + start_2d_burst(raw, t2, use_buf_a); + return; + } + + /* Phase 3: ongoing burst — send remaining frames */ + if (burst_active) { + if (sys_tick_ms >= burst_next_time_ms) { + do_2d_capture_send(raw, use_buf_a); + burst_remaining--; + if (burst_remaining == 0) { + burst_active = 0; + DBG_TRIG("Burst complete\r\n"); + } else { + uint16_t interval = DEFAULT_BURST_INTERVAL_MS; + if (t2->TriggerInternalIntervalMs > 0) + interval = t2->TriggerInternalIntervalMs; + burst_next_time_ms = sys_tick_ms + interval; + } + } + return; + } + + /* Phase 4: check for new trigger */ + if (t2->TriggerMode == 0) { + /* --- External trigger --- */ + if (g_ext_trigger_flag) { + g_ext_trigger_flag = 0; + DBG_TRIG("2D ext trigger\r\n"); + if (t2->TriggerDebounceIntervalMs > 0) { + burst_debounce_pending = 1; + burst_debounce_until = sys_tick_ms + t2->TriggerDebounceIntervalMs; + } else if (t2->TriggerDelayMs > 0) { + burst_delay_pending = 1; + burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs; + } else { + start_2d_burst(raw, t2, use_buf_a); + } + } + } else { + /* --- Internal trigger: temperature threshold in TriggerRoi --- */ + if (Preprocess_CheckInternalTrigger2D(raw) == 1) { + DBG_TRIG("2D internal trigger\r\n"); + if (t2->TriggerDelayMs > 0) { + burst_delay_pending = 1; + burst_delay_until_ms = sys_tick_ms + t2->TriggerDelayMs; + } else { + start_2d_burst(raw, t2, use_buf_a); + } + } + } +} + +/* ============================================================ + * 1D Trigger Handler — called once per frame when Config1D enabled + * + * Each DVP frame yields one temperature sample (center row max). + * The state machine collects samples over time, starting/stopping + * based on temperature threshold crossings. + * ============================================================ */ +static void handle_1d_trigger(const RawImageBuffer_t *raw, const Config1D_t *t1, uint8_t *use_buf_a) +{ + /* RunMode: 0=STOP, 1=RUN. Only collect when running. */ + if (t1->RunMode != 1) + return; + + uint16_t sample = get_1d_sample(raw); + int16_t thresh = t1->TriggerTempLimit; + uint8_t is_hot = ((int16_t)sample >= thresh) ? 1 : 0; + + switch (s1d_state) { + case S1D_IDLE: + if (t1->TriggerType == 0) { + /* --- External trigger --- */ + if (g_ext_trigger_flag) { + g_ext_trigger_flag = 0; + s1d_debounce_until = sys_tick_ms + t1->HighTimerLimit; + s1d_state = S1D_DEBOUNCE; + DBG_TRIG("1D: ext trigger -> debounce %d ms\r\n", + (int)t1->HighTimerLimit); + } + } else { + /* --- Internal trigger: scan with pre-ring buffer --- */ + s1d_pre_ring[s1d_pre_idx] = sample; + s1d_pre_idx = (s1d_pre_idx + 1) % 3; + if (s1d_pre_count < 3) s1d_pre_count++; + + if (s1d_pre_count >= 3) { + uint8_t all_hot = 1; + for (uint8_t i = 0; i < 3; i++) { + if ((int16_t)s1d_pre_ring[i] < thresh) { + all_hot = 0; + break; + } + } + if (all_hot) { + /* 3 consecutive hot samples → start collecting */ + s1d_count = 0; + s1d_start_time = sys_tick_ms; + /* Copy pre-ring into buffer (oldest first) */ + for (uint8_t i = 0; i < 3; i++) { + uint8_t ri = (s1d_pre_idx + i) % 3; + s1d_temp_buf[s1d_count] = s1d_pre_ring[ri]; + s1d_time_buf[s1d_count] = 0; + s1d_count++; + } + s1d_consec_cold = 0; + s1d_consec_hot = 3; + s1d_triggered = 1; + s1d_state = S1D_COLLECTING; + DBG_TRIG("1D: internal trigger start\r\n"); + } + } + } + break; + + case S1D_DEBOUNCE: + if (sys_tick_ms >= s1d_debounce_until) { + s1d_count = 0; + s1d_start_time = sys_tick_ms; + s1d_consec_hot = 0; + s1d_consec_cold = 0; + s1d_triggered = 0; + s1d_state = S1D_COLLECTING; + DBG_TRIG("1D: debounce done -> collecting\r\n"); + } + break; + + case S1D_COLLECTING: { + if (s1d_count < MAX_1D_POINTS) { + s1d_temp_buf[s1d_count] = sample; + s1d_time_buf[s1d_count] = (uint16_t)(sys_tick_ms - s1d_start_time); + s1d_count++; + } + + if (is_hot) { + s1d_consec_hot++; + s1d_consec_cold = 0; + if (!s1d_triggered && s1d_consec_hot >= 3) + s1d_triggered = 1; + } else { + s1d_consec_cold++; + s1d_consec_hot = 0; + } + + /* Stop conditions */ + uint8_t stop = 0; + if (s1d_count >= MAX_1D_POINTS) + stop = 1; + if (t1->BufferSize > 0 && s1d_count >= t1->BufferSize) + stop = 1; + if (s1d_triggered && t1->NgCountLimit > 0 && + s1d_consec_cold >= t1->NgCountLimit) + stop = 1; + + if (stop) { + if (s1d_triggered) { + send_1d_collection(t1, use_buf_a); + } else { + DBG_DATA("1D: buf full, not triggered — discard %d pts\r\n", + (int)s1d_count); + } + reset_1d_state(); + } + break; + } + } +} + +/* ============================================================ + * RTOS Task: Business logic (DVP + trigger + send) + * + * Main pipeline — behavior is entirely driven by server config: + * Config2D.Enabled → 2D autonomous trigger pipeline (priority) + * Config1D.Enabled → 1D autonomous collection pipeline + * TEMP_REQ → auxiliary on-demand screenshot (always available) + * ============================================================ */ static void task_business_entry(void *pvParameters) { (void)pvParameters; @@ -450,51 +859,61 @@ static void task_business_entry(void *pvParameters) while (1) { -#if TEST_ENABLE_NG_GPIO /* NG pulse off check */ if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) { GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); g_ng_off_time = 0; } -#endif #if !TEST_PATTERN_MODE DVP_Task(); #endif -#if TEST_ENABLE_TEMP_REQ - /* Handle on-demand frame request from server */ + /* -------- Auxiliary: TEMP_REQ (server on-demand screenshot) -------- */ if (g_temp_req_pending) { g_temp_req_pending = 0; - RawImageBuffer_t raw_img; - raw_img.pData = (uint16_t *)FrameBuffer; - raw_img.Width = SENSOR_WIDTH; - raw_img.Height = SENSOR_HEIGHT; - raw_img.FrameNumber = Ready_Frame_Count; - TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; - use_buffer_A = !use_buffer_A; - tx_buf->ValidPayloadLen = 0; + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint8_t ok = 0; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) { + if (g_temp_req_is2d && t2.Enabled) ok = 1; + if (!g_temp_req_is2d && t1.Enabled) ok = 1; + } - if (g_temp_req_is2d) { - PreprocessResult_t meta; - int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta); - if (pp_ret == 0) { - int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x02 /* REQUEST */, 1); - DBG_APP("TEMP_REQ SEND 2D frm=%d %dx%d ret=%d\r\n", - (int)meta.FrameNumber, (int)meta.ValidWidth, - (int)meta.ValidHeight, (int)tx_ret); - } else { - DBG_APP("TEMP_REQ PP fail ret=%d\r\n", (int)pp_ret); - } + if (!ok) { + DBG_ERR("TEMP_REQ ignored: %s not enabled\r\n", + g_temp_req_is2d ? "2D" : "1D"); } else { - send_1d_frame_from_raw(&raw_img, tx_buf); - DBG_APP("TEMP_REQ SEND 1D frm=%d\r\n", (int)raw_img.FrameNumber); + RawImageBuffer_t raw_img; + raw_img.pData = (uint16_t *)FrameBuffer; + raw_img.Width = SENSOR_WIDTH; + raw_img.Height = SENSOR_HEIGHT; + raw_img.FrameNumber = Ready_Frame_Count; + + if (g_temp_req_is2d) { + TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + use_buffer_A = !use_buffer_A; + tx_buf->ValidPayloadLen = 0; + PreprocessResult_t meta; + int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta); + if (pp_ret == 0) { + int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame( + tx_buf, &meta, 0x02, 1); + DBG_DATA("TEMP_REQ 2D frm=%d %dx%d ret=%d\r\n", + (int)meta.FrameNumber, (int)meta.ValidWidth, + (int)meta.ValidHeight, (int)tx_ret); + } else { + DBG_ERR("TEMP_REQ PP fail ret=%d\r\n", (int)pp_ret); + } + } else { + send_1d_snapshot(&raw_img, &use_buffer_A); + DBG_DATA("TEMP_REQ 1D frm=%d\r\n", (int)raw_img.FrameNumber); + } } } -#endif /* TEST_ENABLE_TEMP_REQ */ + /* -------- Main pipeline: autonomous trigger -------- */ if (Frame_Ready_Flag) { Frame_Ready_Flag = 0; @@ -505,74 +924,16 @@ static void task_business_entry(void *pvParameters) raw_img.Height = SENSOR_HEIGHT; raw_img.FrameNumber = Ready_Frame_Count; -#if TEST_ENABLE_TRIGGER - if (burst_active) - { - /* Burst mode: wait for interval, then capture & send */ - if (sys_tick_ms >= burst_next_time_ms) - { - PreprocessResult_t meta; - TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; - use_buffer_A = !use_buffer_A; - tx_buf->ValidPayloadLen = 0; + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + int8_t has_cfg = TcpLogic_GetLatestConfig(&tc, &t2, &t1); - if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0) - { - TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1); - } - burst_remaining--; - if (burst_remaining == 0) { - burst_active = 0; - DBG_APP("Burst complete\r\n"); - } else { - ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; - uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS; - if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 - && t2.TriggerInternalIntervalMs > 0) - interval_ms = t2.TriggerInternalIntervalMs; - burst_next_time_ms = sys_tick_ms + interval_ms; - } - } + if (has_cfg == 0 && t2.Enabled) { + /* 2D mode — autonomous trigger pipeline */ + handle_2d_trigger(&raw_img, &t2, &use_buffer_A); + } else if (has_cfg == 0 && t1.Enabled) { + /* 1D mode — autonomous collection pipeline */ + handle_1d_trigger(&raw_img, &t1, &use_buffer_A); } - else if (Preprocess_CheckInternalTrigger2D(&raw_img) == 1) - { - /* Trigger hit! Start burst: this frame is frame #0 */ - DBG_APP("TRIGGER frm=%d\r\n", (int)raw_img.FrameNumber); - PreprocessResult_t meta; - TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; - use_buffer_A = !use_buffer_A; - tx_buf->ValidPayloadLen = 0; - - int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta); - if (pp_ret == 0) - { - int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1); - DBG_APP("SEND frm=%d %dx%d ret=%d\r\n", - (int)meta.FrameNumber, (int)meta.ValidWidth, - (int)meta.ValidHeight, (int)tx_ret); - } else { - DBG_APP("PP fail ret=%d\r\n", (int)pp_ret); - } - - /* Determine burst count from config (fall back to defaults) */ - ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; - uint8_t total_burst = DEFAULT_BURST_COUNT; - uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS; - if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) { - if (t2.TriggerBurstCount >= 1) - total_burst = t2.TriggerBurstCount; - if (t2.TriggerInternalIntervalMs > 0) - interval_ms = t2.TriggerInternalIntervalMs; - } - if (total_burst > 1) { - burst_active = 1; - burst_remaining = total_burst - 1; /* frame #0 already sent */ - burst_next_time_ms = sys_tick_ms + interval_ms; - DBG_APP("Burst start: %d frames, interval=%d ms\r\n", - (int)total_burst, (int)interval_ms); - } - } -#endif /* TEST_ENABLE_TRIGGER */ } else { @@ -587,36 +948,31 @@ int main(void) SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(921600); - printf("TCPClient Test\r\nSystemClk:%d\r\n", SystemCoreClock); - printf("=== Feature Switches ===\r\n"); - printf(" PATTERN=%d TRIGGER=%d NG_GPIO=%d TEMP_REQ=%d TCP_SEND=%d HB=%d\r\n", - TEST_PATTERN_MODE, TEST_ENABLE_TRIGGER, TEST_ENABLE_NG_GPIO, - TEST_ENABLE_TEMP_REQ, TEST_ENABLE_TCP_SEND, TEST_ENABLE_HEARTBEAT); - printf("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF); + DBG_INIT("TCPClient SystemClk:%d\r\n", SystemCoreClock); + DBG_INIT("TEST_PATTERN=%d\r\n", TEST_PATTERN_MODE); + DBG_INIT("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF); Config_Flash_SRAM(FLASH_128_SRAM_192); - printf("net version:%x\n", WCHNET_GetVer()); - if (WCHNET_LIB_VER != WCHNET_GetVer()) printf("version error.\n"); + DBG_INIT("net version:%x\r\n", WCHNET_GetVer()); + if (WCHNET_LIB_VER != WCHNET_GetVer()) DBG_ERR("net version mismatch\r\n"); WCHNET_GetMacAddr(MACAddr); - printf("mac addr:"); - for (i = 0; i < 6; i++) printf("%x ", MACAddr[i]); - printf("\n"); + DBG_INIT("mac addr: %x:%x:%x:%x:%x:%x\r\n", + MACAddr[0], MACAddr[1], MACAddr[2], + MACAddr[3], MACAddr[4], MACAddr[5]); #if TEST_PATTERN_MODE - printf("=== TEST PATTERN MODE === No sensor/DVP hardware needed\r\n"); - /* Skip Mini212G2_Init() and DVP_Init() */ + DBG_INIT("=== TEST PATTERN MODE ===\r\n"); #else /* 模组已通过 USB 预配置,无需 MCU UART 配置,直接初始化 DVP */ DVP_Init(); #endif TIM2_Init(); -#if TEST_ENABLE_NG_GPIO + ExtTrigger_GPIO_Init(); NG_GPIO_Init(); -#endif i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr); mStopIfError(i); - if (i == WCHNET_ERR_SUCCESS) printf("WCHNET_LibInit Success\r\n"); + if (i == WCHNET_ERR_SUCCESS) DBG_INIT("WCHNET_LibInit OK\r\n"); #if KEEPALIVE_ENABLE { @@ -626,55 +982,49 @@ int main(void) #endif qdx_port_init(); - Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT); + TcpLogic_Init(MACAddr, NULL); + TcpLogic_RegisterConfigCallback(OnConfigUpdate); + TcpLogic_RegisterDetectionCallback(OnDetectionResult); + TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest); + #if TEST_PATTERN_MODE - /* Set default preprocess config so trigger fires with test patterns. - * Without this, all Config2D_t fields are 0 and trigger never fires - * (roi_w==0 → immediate return 0). */ + /* Inject test config so autonomous trigger works without server. + * TcpLogic_InjectTestConfig → caches config + fires OnConfigUpdate + * → Preprocess_Settings_Change. */ { Config2D_t test_cfg2d; memset(&test_cfg2d, 0, sizeof(test_cfg2d)); - test_cfg2d.TargetWidth = 64; /* ROI: 64x64 = 8KB, fits in 10KB buffer */ + test_cfg2d.Enabled = 1; + test_cfg2d.TriggerMode = 1; /* Internal trigger */ + test_cfg2d.TargetWidth = 64; test_cfg2d.TargetHeight = 64; - test_cfg2d.TriggerRoiX = 0; /* Full-frame trigger area */ + test_cfg2d.TriggerRoiX = 0; test_cfg2d.TriggerRoiY = 0; test_cfg2d.TriggerRoiW = SENSOR_WIDTH; test_cfg2d.TriggerRoiH = SENSOR_HEIGHT; test_cfg2d.TriggerCondition = 1; /* Max temperature */ - test_cfg2d.TriggerTemperatureThreshold = 800; /* 80.0°C (0.1°C/LSB) */ + test_cfg2d.TriggerTemperatureThreshold = 800; /* 80.0°C */ test_cfg2d.TriggerBurstCount = 3; test_cfg2d.TriggerInternalIntervalMs = 200; test_cfg2d.NGioDelay = 200; - Preprocess_Settings_Change(&test_cfg2d, NULL, NULL); - printf("Test default config loaded: trigger thresh=800 ROI=full burst=3\r\n"); + TcpLogic_InjectTestConfig(NULL, &test_cfg2d, NULL); + DBG_INIT("TestCfg injected: En=1 IntTrig thresh=800 Tgt=64x64 burst=3\r\n"); } #endif - TcpLogic_Init(MACAddr, NULL); - TcpLogic_RegisterConfigCallback(OnConfigUpdate); -#if TEST_ENABLE_NG_GPIO - TcpLogic_RegisterDetectionCallback(OnDetectionResult); -#endif -#if TEST_ENABLE_TEMP_REQ - TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest); -#endif - DBG_APP("TcpLogic_Start...\r\n"); + DBG_INIT("TcpLogic_Start...\r\n"); TcpLogic_Start(); - DBG_APP("Creating RTOS tasks...\r\n"); - xTaskCreate(task_wchnet_entry, "wchnet", 512, NULL, 6, NULL); -#if TEST_ENABLE_TCP_SEND - xTaskCreate(task_business_entry, "business", 512, NULL, 5, NULL); -#endif -#if TEST_ENABLE_HEARTBEAT - xTaskCreate(task_heartbeat_entry, "hb", 256, NULL, 3, NULL); -#endif + DBG_INIT("Creating RTOS tasks...\r\n"); + xTaskCreate(task_wchnet_entry, "wchnet", 512, NULL, 6, NULL); + xTaskCreate(task_business_entry, "business", 512, NULL, 5, NULL); + xTaskCreate(task_heartbeat_entry, "hb", 256, NULL, 3, NULL); #if TEST_PATTERN_MODE xTaskCreate(task_test_pattern_entry, "testpat", 256, NULL, 4, NULL); #endif - DBG_APP("Starting scheduler\r\n"); + DBG_INIT("Starting scheduler\r\n"); vTaskStartScheduler(); /* Should never reach here */ diff --git a/prj/TCPClient/User/main.c.bak b/prj/TCPClient/User/main.c.bak new file mode 100644 index 0000000..baf67a3 --- /dev/null +++ b/prj/TCPClient/User/main.c.bak @@ -0,0 +1,709 @@ +#include "ch32v30x.h" +#include "string.h" +#include "eth_driver.h" +#include "dvp.h" +#include "mini212g2.h" +#include "qdx_port.h" +#include "qdx_preprocess.h" +#include "qdx_tcp_logic.h" + +#include "FreeRTOS.h" +#include "task.h" + +/* ============================================================ + * TEST MODE: Generate simulated sensor data without real hardware. + * Set to 1 to enable test pattern generation (no sensor/DVP needed). + * Set to 0 for normal operation with real Mini212G2 sensor. + * ============================================================ */ +#define TEST_PATTERN_MODE 1 + +/* ============================================================ + * Feature Test Switches — set to 1 to enable, 0 to disable. + * Allows testing each feature independently. + * ============================================================ */ +#define TEST_ENABLE_HEARTBEAT 1 /* Periodic heartbeat debug print task */ +#define TEST_ENABLE_TRIGGER 0 /* Internal trigger + burst upload */ +#define TEST_ENABLE_NG_GPIO 0 /* NG GPIO pulse output on detection */ +#define TEST_ENABLE_TEMP_REQ 1 /* On-demand frame upload from server */ +#define TEST_ENABLE_TCP_SEND 1 /* TCP data channel sending */ + +/* Default burst parameters — used when server hasn't sent config yet */ +#define DEFAULT_BURST_COUNT 3 +#define DEFAULT_BURST_INTERVAL_MS 200 + +#define MAX_TCP_PAYLOAD_SIZE 10240 +uint8_t g_TxNetBuffer_A_Mem[MAX_TCP_PAYLOAD_SIZE]; +uint8_t g_TxNetBuffer_B_Mem[MAX_TCP_PAYLOAD_SIZE]; + +TcpTxBuffer_t g_TxNetBuffer_A = { + .pBuffer = g_TxNetBuffer_A_Mem, + .TotalCapacity = MAX_TCP_PAYLOAD_SIZE, + .HeadOffset = 64, + .ValidPayloadLen = 0 +}; +TcpTxBuffer_t g_TxNetBuffer_B = { + .pBuffer = g_TxNetBuffer_B_Mem, + .TotalCapacity = MAX_TCP_PAYLOAD_SIZE, + .HeadOffset = 64, + .ValidPayloadLen = 0 +}; + + + +void OnConfigUpdate(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d) +{ + Preprocess_Settings_Change(cfg2d, cfg1d, common); +} + +extern volatile uint32_t sys_tick_ms; + +#if TEST_ENABLE_NG_GPIO +/* NG output GPIO: PA8 push-pull, active-high when NG detected */ +#define NG_GPIO_PORT GPIOA +#define NG_GPIO_PIN GPIO_Pin_8 +#define NG_GPIO_CLK RCC_APB2Periph_GPIOA +#define NG_PULSE_MS 200 /* default NG pulse width */ + +static volatile uint32_t g_ng_off_time = 0; + +static void NG_GPIO_Init(void) +{ + GPIO_InitTypeDef gpio = {0}; + RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE); + gpio.GPIO_Pin = NG_GPIO_PIN; + gpio.GPIO_Mode = GPIO_Mode_Out_PP; + gpio.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_Init(NG_GPIO_PORT, &gpio); + GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); +} +#endif /* TEST_ENABLE_NG_GPIO */ + +#if TEST_ENABLE_TEMP_REQ +/* Flag set by TempFrameRequest callback; consumed by business task */ +static volatile uint8_t g_temp_req_pending = 0; +static volatile uint8_t g_temp_req_is2d = 1; + +void OnTempFrameRequest(uint8_t is2dRequest) +{ + g_temp_req_is2d = is2dRequest; + g_temp_req_pending = 1; + DBG_APP("TempFrameReq is2d=%d\r\n", (int)is2dRequest); +} +#endif /* TEST_ENABLE_TEMP_REQ */ + +#if TEST_ENABLE_NG_GPIO +void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus) +{ + (void)frameNumber; + /* resultStatus: 0 = NG, 1 = OK */ + if (resultStatus == 0) { + ConfigCommon_t tmp_common; + Config2D_t tmp_cfg2d; + Config1D_t tmp_cfg1d; + uint32_t pulse_ms = NG_PULSE_MS; /* fallback default */ + if (TcpLogic_GetLatestConfig(&tmp_common, &tmp_cfg2d, &tmp_cfg1d) == 0 + && tmp_cfg2d.NGioDelay > 0) { + pulse_ms = tmp_cfg2d.NGioDelay; + } + GPIO_SetBits(NG_GPIO_PORT, NG_GPIO_PIN); + g_ng_off_time = sys_tick_ms + pulse_ms; + } +} +#endif /* TEST_ENABLE_NG_GPIO */ + +#define KEEPALIVE_ENABLE 1 + +/* ============================================================ + * FLASH / SRAM 分配配置 (Option Bytes RAM_CODE_MOD[2:0]) + * 修改后需复位才生效 + * ============================================================ */ +typedef enum { + FLASH_192_SRAM_128 = 0, /* 00x 默认 */ + FLASH_224_SRAM_96, /* 01x */ + FLASH_256_SRAM_64, /* 10x */ + FLASH_128_SRAM_192, /* 110 */ + FLASH_288_SRAM_32 /* 111 */ +} FLASH_SRAM_DEFIN; + +static void Config_Flash_SRAM(FLASH_SRAM_DEFIN mode) +{ + uint8_t UserByte = FLASH_GetUserOptionByte() & 0xFF; + uint8_t newByte = UserByte & ~0xE0; /* clear bits [7:5] */ + + switch (mode) { + case FLASH_192_SRAM_128: break; /* 000 */ + case FLASH_224_SRAM_96: newByte |= 0x40; break; /* 010 */ + case FLASH_256_SRAM_64: newByte |= 0x80; break; /* 100 */ + case FLASH_128_SRAM_192: newByte |= 0xC0; break; /* 110 */ + case FLASH_288_SRAM_32: newByte |= 0xE0; break; /* 111 */ + default: return; + } + + if (newByte == UserByte) return; /* already configured */ + + FLASH_Unlock(); + FLASH_ProgramOptionByteData(0x1FFFF802, newByte); + FLASH_Lock(); + printf("Flash/SRAM config changed to %d, resetting...\r\n", mode); + NVIC_SystemReset(); +} + +u8 MACAddr[6]; +u8 IPAddr[4] = {192, 168, 7, 10}; +u8 GWIPAddr[4] = {192, 168, 7, 1}; +u8 IPMask[4] = {255, 255, 255, 0}; +u8 DESIP[4] = {192, 168, 7, 50}; +u16 desport = 5512; +u16 srcport = 5511; + +u8 SocketId; +u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN]; + +void mStopIfError(u8 iError) +{ + if (iError == WCHNET_ERR_SUCCESS) return; + printf("Error: %02X\r\n", (u16)iError); +} + +void TIM2_Init(void) +{ + TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = {0}; + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); + TIM_TimeBaseStructure.TIM_Period = WCHNETTIMERPERIOD * 1000 - 1; + TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock / 1000000 - 1; + TIM_TimeBaseStructure.TIM_ClockDivision = 0; + TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; + TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); + TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); + TIM_Cmd(TIM2, ENABLE); + TIM_ClearITPendingBit(TIM2, TIM_IT_Update); + NVIC_EnableIRQ(TIM2_IRQn); +} + + + + + +/* qdx_port.c notification hooks */ +extern void qdx_port_sock_recv_notify(uint8_t sockid); +extern void qdx_port_sock_connect_notify(uint8_t sockid); +extern void qdx_port_sock_disconnect_notify(uint8_t sockid); +extern void qdx_port_init(void); +extern void qdx_port_net_lock(void); +extern void qdx_port_net_unlock(void); + + +void WCHNET_HandleSockInt(u8 socketid, u8 intstat) +{ + DBG_APP("SockInt: id=%d stat=0x%02X\r\n", socketid, intstat); + if (intstat & SINT_STAT_RECV) + { + qdx_port_sock_recv_notify(socketid); + } + if (intstat & SINT_STAT_CONNECT) + { + WCHNET_ModifyRecvBuf(socketid, (u32)SocketRecvBuf[socketid], RECE_BUF_LEN); + qdx_port_sock_connect_notify(socketid); + DBG_APP("TCP Connected, socket %d\r\n", socketid); + } + if (intstat & SINT_STAT_DISCONNECT) + { + qdx_port_sock_disconnect_notify(socketid); + DBG_APP("TCP Disconnected, socket %d\r\n", socketid); + } + if (intstat & SINT_STAT_TIM_OUT) + { + qdx_port_sock_disconnect_notify(socketid); + DBG_APP("TCP Timeout, socket %d\r\n", socketid); + } +} + +void WCHNET_HandleGlobalInt(void) +{ + u8 intstat; + u16 i; + u8 socketint; + intstat = WCHNET_GetGlobalInt(); + DBG_APP("GlobalInt: 0x%02X\r\n", intstat); + if (intstat & GINT_STAT_UNREACH) DBG_APP("GINT_STAT_UNREACH\r\n"); + if (intstat & GINT_STAT_IP_CONFLI) DBG_APP("GINT_STAT_IP_CONFLI\r\n"); + if (intstat & GINT_STAT_PHY_CHANGE) { + i = WCHNET_GetPHYStatus(); + DBG_APP("PHY_CHANGE: status=0x%04X %s\r\n", i, + (i & PHY_Linked_Status) ? "LINK_UP" : "LINK_DOWN"); + } + if (intstat & GINT_STAT_SOCKET) { + for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) { + socketint = WCHNET_GetSocketInt(i); + if (socketint) WCHNET_HandleSockInt(i, socketint); + } + } +} + +#if TEST_ENABLE_HEARTBEAT +/* ============================================================ + * RTOS Task: Heartbeat (periodic print for debug) + * ============================================================ */ +static void task_heartbeat_entry(void *pvParameters) +{ + (void)pvParameters; + uint32_t cnt = 0; + while (1) + { +#if TEST_PATTERN_MODE + printf("[HB] %d tick=%d TEST_MODE frm=%d\r\n", + (int)cnt++, (int)xTaskGetTickCount(), + (int)dvp_frame_count); +#else + printf("[HB] %d tick=%d dvp_frm=%d row_irq=%d\r\n", + (int)cnt++, (int)xTaskGetTickCount(), + (int)dvp_frame_count, (int)dvp_row_irq_cnt); +#endif + vTaskDelay(pdMS_TO_TICKS(2000)); + } +} +#endif /* TEST_ENABLE_HEARTBEAT */ + +/* ============================================================ + * RTOS Task: WCHNET protocol stack driver (highest priority) + * ============================================================ */ +static void task_wchnet_entry(void *pvParameters) +{ + (void)pvParameters; + while (1) + { + qdx_port_net_lock(); + WCHNET_MainTask(); + if (WCHNET_QueryGlobalInt()) + WCHNET_HandleGlobalInt(); + qdx_port_net_unlock(); + vTaskDelay(pdMS_TO_TICKS(5)); + } +} + +/* ============================================================ + * RTOS Task: Business logic (DVP + preprocess + send) + * ============================================================ */ +#if TEST_ENABLE_TRIGGER +/* ============================================================ + * Burst capture state machine + * ============================================================ */ +static uint8_t burst_active = 0; /* 1 = currently in burst */ +static uint8_t burst_remaining = 0; /* frames left to capture */ +static uint32_t burst_next_time_ms = 0; /* next burst frame time */ +#endif /* TEST_ENABLE_TRIGGER */ + +#if TEST_PATTERN_MODE +/* ============================================================ + * Test Pattern Generator + * Generates simulated 256x192 Y16 thermal images at ~10 FPS. + * Pattern cycles through several types so all pipeline paths + * can be exercised. + * + * Pattern 0: Gradient (25.00~45.00°C) — baseline, no trigger + * Pattern 1: Hot center spot (90.00°C) — should trigger alarm + * Pattern 2: Uniform warm (35.00°C) — no trigger + * Pattern 3: Checkerboard with hot cells — tests ROI search + * ============================================================ */ +#define TEST_FPS_DELAY_MS 100 /* ~10 FPS */ +#define TEMP_RAW(deg_c) ((uint16_t)((deg_c) * 10)) /* e.g. 35.0°C → 350, 0.1°C/LSB */ + +static void test_fill_gradient(uint16_t *buf, uint16_t w, uint16_t h) +{ + /* Diagonal gradient: 25°C at top-left → 45°C at bottom-right + * Both X and Y contribute so 1D center-row also shows variation */ + for (uint16_t y = 0; y < h; y++) { + for (uint16_t x = 0; x < w; x++) { + uint32_t pos = (uint32_t)y * w + (uint32_t)x * h; /* combined weight */ + uint16_t temp = TEMP_RAW(25.0) + (uint16_t)(pos * TEMP_RAW(20.0) / ((uint32_t)w * h)); + buf[y * w + x] = temp; + } + } +} + +static void test_fill_hotspot(uint16_t *buf, uint16_t w, uint16_t h) +{ + /* Background 30°C with slight X variation, center 32x32 block at 90°C */ + for (uint16_t y = 0; y < h; y++) + for (uint16_t x = 0; x < w; x++) + buf[y * w + x] = TEMP_RAW(28.0) + (uint16_t)((uint32_t)x * TEMP_RAW(4.0) / w); + uint16_t cx = w / 2, cy = h / 2; + for (uint16_t y = cy - 16; y < cy + 16; y++) + for (uint16_t x = cx - 16; x < cx + 16; x++) + buf[y * w + x] = TEMP_RAW(90.0); +} + +static void test_fill_uniform(uint16_t *buf, uint16_t w, uint16_t h) +{ + for (uint16_t y = 0; y < h; y++) + for (uint16_t x = 0; x < w; x++) + buf[y * w + x] = TEMP_RAW(35.0); +} + +static void test_fill_checker(uint16_t *buf, uint16_t w, uint16_t h) +{ + /* 32x32 checkerboard: alternating 30°C and 85°C blocks */ + for (uint16_t y = 0; y < h; y++) + for (uint16_t x = 0; x < w; x++) { + uint8_t cell = ((y / 32) + (x / 32)) & 1; + buf[y * w + x] = cell ? TEMP_RAW(85.0) : TEMP_RAW(28.0); + } +} + +static void task_test_pattern_entry(void *pvParameters) +{ + (void)pvParameters; + uint32_t frame_num = 0; + uint8_t pattern_idx = 0; + /* Cycle: gradient x20, hotspot x5, uniform x10, checker x5 */ + const uint8_t pattern_seq[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3}; + const uint8_t seq_len = sizeof(pattern_seq); + + while (1) + { + uint16_t *buf = (uint16_t *)FrameBuffer; + switch (pattern_seq[pattern_idx % seq_len]) { + case 0: test_fill_gradient(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break; + case 1: test_fill_hotspot(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break; + case 2: test_fill_uniform(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break; + case 3: test_fill_checker(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break; + } + pattern_idx++; + if (pattern_idx >= seq_len) pattern_idx = 0; + + frame_num++; + dvp_frame_count = frame_num; + Ready_Frame_Count = frame_num; + Frame_Ready_Flag = 1; + + vTaskDelay(pdMS_TO_TICKS(TEST_FPS_DELAY_MS)); + } +} +#endif /* TEST_PATTERN_MODE */ + +#if TEST_ENABLE_TEMP_REQ +/* ============================================================ + * 1D mode: build and send a single 1D temperature frame from + * the current raw image. Scans the center row and packs + * TempPoint1D_t (2B time_offset + 2B temp, little-endian). + * ============================================================ */ +#define TEST_1D_POINTS 30 /* 1D test: sample 30 points from center row */ + +static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *tx_buf) +{ + uint16_t w = raw->Width; + uint16_t h = raw->Height; + uint16_t *src = raw->pData; + uint16_t row = h / 2; /* center row */ + + tx_buf->ValidPayloadLen = 0; + uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset; + uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset; + uint16_t points = TEST_1D_POINTS; + if (points > w) points = w; + if ((uint32_t)points * 4 > capacity) + points = (uint16_t)(capacity / 4); + + int16_t min_t = 32767, max_t = -32768; + int32_t sum_t = 0; + + for (uint16_t i = 0; i < points; i++) { + /* Evenly sample across the full row width */ + uint16_t col = (uint16_t)((uint32_t)i * (w - 1) / (points > 1 ? points - 1 : 1)); + uint16_t temp = src[row * w + col]; + uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1)); + int16_t t = (int16_t)temp; + if (t < min_t) min_t = t; + if (t > max_t) max_t = t; + sum_t += t; + dest[0] = (uint8_t)(time_offset & 0xFF); + dest[1] = (uint8_t)((time_offset >> 8) & 0xFF); + dest[2] = (uint8_t)(temp & 0xFF); + dest[3] = (uint8_t)((temp >> 8) & 0xFF); + dest += 4; + } + tx_buf->ValidPayloadLen = (uint32_t)points * 4; + + PreprocessResult_t meta; + memset(&meta, 0, sizeof(meta)); + meta.pValidData = tx_buf->pBuffer + tx_buf->HeadOffset; + meta.DataLength = tx_buf->ValidPayloadLen; + meta.ValidWidth = points; + meta.ValidHeight = 1; + meta.MinTemp = min_t; + meta.MaxTemp = max_t; + meta.AvgTemp = (int16_t)(sum_t / (points > 0 ? points : 1)); + meta.RoiTemp = meta.AvgTemp; + meta.FrameNumber = raw->FrameNumber; + + TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0 /* IS_1D */); +} +#endif /* TEST_ENABLE_TEMP_REQ */ + +static void task_business_entry(void *pvParameters) +{ + (void)pvParameters; + static uint8_t use_buffer_A = 1; + + while (1) + { +#if TEST_ENABLE_NG_GPIO + /* NG pulse off check */ + if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) { + GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); + g_ng_off_time = 0; + } +#endif + +#if !TEST_PATTERN_MODE + DVP_Task(); +#endif + +#if TEST_ENABLE_TEMP_REQ + /* Handle on-demand frame request from server */ + if (g_temp_req_pending) + { + g_temp_req_pending = 0; + + /* 检查对应模式是否已由服务器使能 */ + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint8_t mode_enabled = 0; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) { + if (g_temp_req_is2d && t2.Enabled) + mode_enabled = 1; + else if (!g_temp_req_is2d && t1.Enabled) + mode_enabled = 1; + } + + if (!mode_enabled) { + DBG_APP("TEMP_REQ ignored: %s not enabled\r\n", + g_temp_req_is2d ? "2D" : "1D"); + } else { + RawImageBuffer_t raw_img; + raw_img.pData = (uint16_t *)FrameBuffer; + raw_img.Width = SENSOR_WIDTH; + raw_img.Height = SENSOR_HEIGHT; + raw_img.FrameNumber = Ready_Frame_Count; + + TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + use_buffer_A = !use_buffer_A; + tx_buf->ValidPayloadLen = 0; + + if (g_temp_req_is2d) { + PreprocessResult_t meta; + int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta); + if (pp_ret == 0) { + int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x02 /* REQUEST */, 1); + DBG_APP("TEMP_REQ SEND 2D frm=%d %dx%d ret=%d\r\n", + (int)meta.FrameNumber, (int)meta.ValidWidth, + (int)meta.ValidHeight, (int)tx_ret); + } else { + DBG_APP("TEMP_REQ PP fail ret=%d\r\n", (int)pp_ret); + } + } else { + send_1d_frame_from_raw(&raw_img, tx_buf); + DBG_APP("TEMP_REQ SEND 1D frm=%d\r\n", (int)raw_img.FrameNumber); + } + } /* mode_enabled */ + } +#endif /* TEST_ENABLE_TEMP_REQ */ + + if (Frame_Ready_Flag) + { + Frame_Ready_Flag = 0; + + RawImageBuffer_t raw_img; + raw_img.pData = (uint16_t *)FrameBuffer; + raw_img.Width = SENSOR_WIDTH; + raw_img.Height = SENSOR_HEIGHT; + raw_img.FrameNumber = Ready_Frame_Count; + +#if TEST_ENABLE_TRIGGER + /* 触发逻辑仅在服务器下发 Config2D 且 Enabled=1 后才激活 */ + { + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint8_t trigger_armed = 0; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 && t2.Enabled) + trigger_armed = 1; + + if (trigger_armed) + { + if (burst_active) + { + /* Burst mode: wait for interval, then capture & send */ + if (sys_tick_ms >= burst_next_time_ms) + { + PreprocessResult_t meta; + TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + use_buffer_A = !use_buffer_A; + tx_buf->ValidPayloadLen = 0; + + if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0) + { + TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1); + } + burst_remaining--; + if (burst_remaining == 0) { + burst_active = 0; + DBG_APP("Burst complete\r\n"); + } else { + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 + && t2.TriggerInternalIntervalMs > 0) + interval_ms = t2.TriggerInternalIntervalMs; + burst_next_time_ms = sys_tick_ms + interval_ms; + } + } + } + else if (Preprocess_CheckInternalTrigger2D(&raw_img) == 1) + { + /* Trigger hit! Start burst: this frame is frame #0 */ + DBG_APP("TRIGGER frm=%d\r\n", (int)raw_img.FrameNumber); + PreprocessResult_t meta; + TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + use_buffer_A = !use_buffer_A; + tx_buf->ValidPayloadLen = 0; + + int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta); + if (pp_ret == 0) + { + int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1); + DBG_APP("SEND frm=%d %dx%d ret=%d\r\n", + (int)meta.FrameNumber, (int)meta.ValidWidth, + (int)meta.ValidHeight, (int)tx_ret); + } else { + DBG_APP("PP fail ret=%d\r\n", (int)pp_ret); + } + + /* Determine burst count from config (fall back to defaults) */ + ConfigCommon_t tc; Config2D_t t2; Config1D_t t1; + uint8_t total_burst = DEFAULT_BURST_COUNT; + uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS; + if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) { + if (t2.TriggerBurstCount >= 1) + total_burst = t2.TriggerBurstCount; + if (t2.TriggerInternalIntervalMs > 0) + interval_ms = t2.TriggerInternalIntervalMs; + } + if (total_burst > 1) { + burst_active = 1; + burst_remaining = total_burst - 1; /* frame #0 already sent */ + burst_next_time_ms = sys_tick_ms + interval_ms; + DBG_APP("Burst start: %d frames, interval=%d ms\r\n", + (int)total_burst, (int)interval_ms); + } + } + } /* if trigger_armed */ + } /* trigger scope */ +#endif /* TEST_ENABLE_TRIGGER */ + } + else + { + vTaskDelay(pdMS_TO_TICKS(2)); + } + } +} + +int main(void) +{ + u8 i; + SystemCoreClockUpdate(); + Delay_Init(); + USART_Printf_Init(921600); + printf("TCPClient Test\r\nSystemClk:%d\r\n", SystemCoreClock); + printf("=== Feature Switches ===\r\n"); + printf(" PATTERN=%d TRIGGER=%d NG_GPIO=%d TEMP_REQ=%d TCP_SEND=%d HB=%d\r\n", + TEST_PATTERN_MODE, TEST_ENABLE_TRIGGER, TEST_ENABLE_NG_GPIO, + TEST_ENABLE_TEMP_REQ, TEST_ENABLE_TCP_SEND, TEST_ENABLE_HEARTBEAT); + printf("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF); + Config_Flash_SRAM(FLASH_128_SRAM_192); + printf("net version:%x\n", WCHNET_GetVer()); + if (WCHNET_LIB_VER != WCHNET_GetVer()) printf("version error.\n"); + + WCHNET_GetMacAddr(MACAddr); + printf("mac addr:"); + for (i = 0; i < 6; i++) printf("%x ", MACAddr[i]); + printf("\n"); + +#if TEST_PATTERN_MODE + printf("=== TEST PATTERN MODE === No sensor/DVP hardware needed\r\n"); + /* Skip Mini212G2_Init() and DVP_Init() */ +#else + /* 模组已通过 USB 预配置,无需 MCU UART 配置,直接初始化 DVP */ + DVP_Init(); +#endif + TIM2_Init(); +#if TEST_ENABLE_NG_GPIO + NG_GPIO_Init(); +#endif + + i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr); + mStopIfError(i); + if (i == WCHNET_ERR_SUCCESS) printf("WCHNET_LibInit Success\r\n"); + +#if KEEPALIVE_ENABLE + { + struct _KEEP_CFG cfg = {20000, 15000, 9}; + WCHNET_ConfigKeepLive(&cfg); + } +#endif + + qdx_port_init(); + + Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT); + +#if TEST_PATTERN_MODE + /* Set default preprocess config so trigger fires with test patterns. + * Without this, all Config2D_t fields are 0 and trigger never fires + * (roi_w==0 → immediate return 0). */ + { + Config2D_t test_cfg2d; + memset(&test_cfg2d, 0, sizeof(test_cfg2d)); + test_cfg2d.TargetWidth = 64; /* ROI: 64x64 = 8KB, fits in 10KB buffer */ + test_cfg2d.TargetHeight = 64; + test_cfg2d.TriggerRoiX = 0; /* Full-frame trigger area */ + test_cfg2d.TriggerRoiY = 0; + test_cfg2d.TriggerRoiW = SENSOR_WIDTH; + test_cfg2d.TriggerRoiH = SENSOR_HEIGHT; + test_cfg2d.TriggerCondition = 1; /* Max temperature */ + test_cfg2d.TriggerTemperatureThreshold = 800; /* 80.0°C (0.1°C/LSB) */ + test_cfg2d.TriggerBurstCount = 3; + test_cfg2d.TriggerInternalIntervalMs = 200; + test_cfg2d.NGioDelay = 200; + Preprocess_Settings_Change(&test_cfg2d, NULL, NULL); + printf("Test default config loaded: trigger thresh=800 ROI=full burst=3\r\n"); + } +#endif + + TcpLogic_Init(MACAddr, NULL); + TcpLogic_RegisterConfigCallback(OnConfigUpdate); +#if TEST_ENABLE_NG_GPIO + TcpLogic_RegisterDetectionCallback(OnDetectionResult); +#endif +#if TEST_ENABLE_TEMP_REQ + TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest); +#endif + DBG_APP("TcpLogic_Start...\r\n"); + TcpLogic_Start(); + + DBG_APP("Creating RTOS tasks...\r\n"); + xTaskCreate(task_wchnet_entry, "wchnet", 512, NULL, 6, NULL); +#if TEST_ENABLE_TCP_SEND + xTaskCreate(task_business_entry, "business", 512, NULL, 5, NULL); +#endif +#if TEST_ENABLE_HEARTBEAT + xTaskCreate(task_heartbeat_entry, "hb", 256, NULL, 3, NULL); +#endif +#if TEST_PATTERN_MODE + xTaskCreate(task_test_pattern_entry, "testpat", 256, NULL, 4, NULL); +#endif + DBG_APP("Starting scheduler\r\n"); + vTaskStartScheduler(); + + /* Should never reach here */ + while (1) {} +}