📝 docs(design): 新增系统设计文档集

✨ feat(system-overview): 创建系统总览文档
- 描述项目背景与硬件平台配置
- 提供 FreeRTOS 任务拓扑表(任务优先级、栈大小、职责)
- 详细说明系统启动序列和初始化依赖关系
- 绘制 2D/1D 状态机完整流程图
- 解释 TEMP_REQ 辅助通道工作机制
- 说明任务间同步机制(Frame_Ready_Flag、双缓冲 TX)

✨ feat(dvp-module-design): 创建 DVP 模块设计文档
- 提供 DVP 硬件连接引脚映射表
- 描述 DVP 时序配置(信号极性、工作模式)
- 解释 DMA ping-pong 行缓冲机制和切换逻辑
- 说明 DVP IRQ 帧组装流程(STR_FRM/ROW_DONE)
- 定义 FrameBuffer 数据格式和像素访问方式
- 说明 TMP 模式温度换算公式和字节序要求

✨ feat(qdx-protocol-design): 创建 QDX 协议设计文档
- 描述完整 TLV 帧结构(FrameHeader + TLV + CRC)
- 列出所有 Class/Type 映射表和用途说明
- 解释零拷贝 TX 缓冲区架构(HeadOffset 机制)
- 说明分片机制和最大载荷限制
- 定义 Flags 字段各位含义和使用场景

✨ feat(tcp-module-design): 创建 TCP 通信模块设计文档
- 描述双流连接架构(控制流 5511 / 数据流 5512)
- 说明握手流程和连接建立时序
- 解释心跳机制和 TCP Keepalive 配置
- 描述配置下发与缓存机制
- 说明数据发送队列和背压处理策略
- 解释 WCHNET 网络栈驱动任务工作机制

✨ feat(integration-guide): 创建对接集成指南
- 提供网络接入参数表(IP、端口、协议)
- 详细说明握手流程和配置下发格式
- 提供 2D/1D 温度帧解析方法和示例代码
- 说明检测结果上报和 NG 响应机制
- 解释 TEMP_REQ 按需截图工作方式
- 列出错误码表和对接故障排查步骤
This commit is contained in:
2026-03-15 19:17:41 +08:00
parent c347c988f2
commit b69717b964
19 changed files with 1818 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
# DVP 模块设计文档
> 文档版本:1.0 · 日期:2026-03-15 · 状态:已发布
---
## 1. 模块概述
DVP(Digital Video Port)模块负责将 Mini212G2 传感器输出的 8-bit CMOS 并行数据转换为内存中可访问的帧缓冲(FrameBuffer)。整个采集链路通过硬件 DMA ping-pong 和快速中断实现,CPU 占用极低。
源文件:`Debug/dvp.c` / `Debug/dvp.h`
---
## 2. DVP 硬件连接与引脚映射
所有 DVP 引脚配置为 **浮空输入(GPIO_Mode_IN_FLOATING)**。
| 引脚 | DVP 信号 | 说明 |
|------|----------|------|
| PA4 | D0 | 数据位 0 |
| PA5 | D1 | 数据位 1 |
| PA6 | D2 | 数据位 2 |
| PA9 | D3 | 数据位 3 |
| PA10 | D4 | 数据位 4 |
| PC8 | D5 | 数据位 5 |
| PC9 | D6 | 数据位 6 |
| PC11 | D7 | 数据位 7 |
| PB3 | VSYNC | 帧同步信号 |
| PB8 | HSYNC | 行同步信号 |
| PB9 | PIXCLK | 像素时钟 |
---
## 3. DVP 时序配置
在 `DVP_Init()` 中完成以下配置:
### 3.1 工作模式
| 配置项 | 值 | 说明 |
|--------|-----|------|
| 数据宽度 | `RB_DVP_D8_MOD` | 8-bit 并行数据 |
| 采集模式 | `Video_Mode` | 连续帧采集(非快照模式) |
| 捕获控制 | 持续捕获(CR1 CM=0) | 不限帧数 |
### 3.2 信号极性
| 信号 | 寄存器位 | 配置值 | 说明 |
|------|----------|--------|------|
| VSYNC | `RB_DVP_V_POLAR` | **1(高有效)** | DIGITAL_FIELD_VALID 高电平期间为有效帧行 |
| HSYNC | `RB_DVP_H_POLAR` | 0(高有效)| 标准高有效行同步 |
| PCLK | `RB_DVP_P_POLAR` | 0(上升沿采样)| 正常时钟极性 |
> **与手册对应**:Mini212G2 FIELD_VALID 高电平为有效期,因此 V_POLAR 置 1(高有效),与硬件手册时序图一致。
### 3.3 图像尺寸配置
| 寄存器 | 值 | 说明 |
|--------|-----|------|
| `DVP->COL_NUM` | 512(`BYTES_PER_LINE`) | 每行字节数 = 256 像素 × 2 字节 |
| `DVP->ROW_NUM` | 1 | 每行触发一次 ROW_DONE 中断 |
---
## 4. DMA ping-pong 行缓冲机制
### 4.1 缓冲区配置
```c
__attribute__((aligned(4))) uint8_t DMA_LineBuf0[512]; /* DMA_BUF0 */
__attribute__((aligned(4))) uint8_t DMA_LineBuf1[512]; /* DMA_BUF1 */
```
两个缓冲区分别挂载在 `DVP->DMA_BUF0` 和 `DVP->DMA_BUF1`,DVP 硬件在两者之间自动切换,无需 CPU 干预。
### 4.2 缓冲切换逻辑
ROW_DONE 中断触发时,硬件已切换到 **下一个缓冲**开始写入。因此正确读取方法为:
```c
/* BUF_TOG=1 表示 DMA 正在写 BUF0,此时应读 BUF1(刚写完的那个) */
uint8_t *src = (DVP->CR1 & RB_DVP_BUF_TOG) ? DMA_LineBuf0 : DMA_LineBuf1;
```
即:`BUF_TOG` 状态位指示**当前正在写入的缓冲**,应读取**另一个**刚刚完成写入的缓冲。
### 4.3 尺寸约束
修改传感器分辨率时,必须同时更新以下三个宏(`dvp.h`):
```c
#define SENSOR_WIDTH 256 /* 像素列数 */
#define SENSOR_HEIGHT 192 /* 像素行数 */
#define BYTES_PER_LINE 512 /* SENSOR_WIDTH × 2 字节/像素 */
```
---
## 5. DVP IRQ 帧组装逻辑
中断处理函数声明:
```c
void DVP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
```
`WCH-Interrupt-fast` 属性使中断跳过通用寄存器入栈,显著降低中断延迟,确保在下一行像素时钟到来(Mini212G2 @ 25 Hz,每行约 42.67 µs)前完成 512 B 的 `memcpy`。
### 5.1 帧同步(STR_FRM 中断)
VSYNC 上升沿触发,是帧同步的**唯一锚点**:
```c
if (DVP->IFR & RB_DVP_IF_STR_FRM) {
DVP->IFR = RB_DVP_IF_STR_FRM;
current_line_idx = 0; /* 重置行计数器 */
dvp_frame_count++;
}
```
### 5.2 行完成(ROW_DONE 中断)
每行像素传输完成后触发:
```c
if (DVP->IFR & RB_DVP_IF_ROW_DONE) {
DVP->IFR = RB_DVP_IF_ROW_DONE;
uint8_t *src = (DVP->CR1 & RB_DVP_BUF_TOG) ? DMA_LineBuf0 : DMA_LineBuf1;
uint32_t idx = current_line_idx;
current_line_idx++;
if (idx < SENSOR_HEIGHT) {
memcpy(FrameBuffer[idx], src, BYTES_PER_LINE);
if (idx == SENSOR_HEIGHT - 1) { /* 第 191 行完成 */
Frame_Ready_Flag = 1; /* 通知业务任务 */
Ready_Frame_Count = dvp_frame_count; /* 记录帧号 */
}
}
}
```
> **注意:先读 `idx` 再递增**,避免 off-by-one。STR_FRM 将 `idx` 置 0,首次 ROW_DONE 时 `idx=0`,将数据写入 `FrameBuffer[0]`,符合预期。
---
## 6. FrameBuffer 数据格式
```c
__attribute__((aligned(4))) uint8_t FrameBuffer[SENSOR_HEIGHT][BYTES_PER_LINE];
/* = uint8_t[192][512] */
```
### 6.1 像素访问方式
FrameBuffer 声明为 `uint8_t` 数组,实际存储 `uint16_t` 小端序温度值:
```c
/* 访问第 row 行、第 col 列的温度 */
uint16_t *pixels = (uint16_t *)FrameBuffer;
uint16_t raw_val = pixels[row * SENSOR_WIDTH + col];
```
### 6.2 温度换算(TMP 模式)
```
温度(°C)= raw_val × 0.1
```
例:`raw_val = 370` → `37.0°C`
### 6.3 字节序说明
CH32V307 为**小端序**(Little-Endian):
| 偏移 | 内容 |
|------|------|
| `FrameBuffer[row][col*2 + 0]` | 温度低字节 |
| `FrameBuffer[row][col*2 + 1]` | 温度高字节 |
传感器必须配置为 **CMOS8(LSB) 模式**(低字节先发),否则字节顺序反转,温度值错误。
### 6.4 内存布局摘要
| 参数 | 值 |
|------|-----|
| 总大小 | 192 × 512 = 98,304 字节 ≈ 96 KB |
| 每行 | 512 字节 = 256 像素 |
| 像素格式 | uint16_t,小端,0.1°C/LSB,TMP 模式绝对温度 |
| 对齐 | 4 字节对齐(`__attribute__((aligned(4)))`) |
---
## 7. 相关文档
| 文档 | 内容 |
|------|------|
| [系统总览.md](系统总览.md) | FreeRTOS 任务拓扑、Frame_Ready_Flag 同步机制 |
| [Mini212G2预配置指南.md](../Mini212G2预配置指南.md) | CMOS8(LSB) + TMP 模式配置命令 |
+202
View File
@@ -0,0 +1,202 @@
# QDX 协议设计文档
> 文档版本:1.0 · 日期:2026-03-15 · 状态:已发布
---
## 1. 模块概述
QDXnetworkStack 是本固件使用的应用层通信协议栈,基于 TLV(Type-Length-Value)帧格式,运行在 TCP/IP 之上。协议设计重点:
- **零拷贝 TX**:预留帧头空间(HeadOffset),像素数据直接写入发送缓冲区,帧头在发送前原地写入
- **双流架构**:控制流(5511)与数据流(5512)职责分离
- **平台无关**:所有结构体 `#pragma pack(push, 1)`,1 字节对齐,可在任意平台解析
源文件:`Middle/QDXnetworkStack/`
---
## 2. 帧结构
### 2.1 完整帧布局
```
┌─────────────────────────────────────────────────────────┐
│ FrameHeader(16 字节) │
│ TLV Header(3 字节) │
│ Value(N 字节) │
│ CRC16(2 字节,Modbus CRC16,覆盖 FrameHeader+TLV+Value)│
└─────────────────────────────────────────────────────────┘
```
总帧长 = 16 + 3 + N + 2 = **N + 21** 字节
### 2.2 FrameHeader(16 字节)
```c
typedef struct {
uint16_t Magic; /* [0-1] 0x55AA — 帧同步魔数 */
uint8_t Version; /* [2] 0x20 — 协议版本 v2.0 */
uint16_t Length; /* [3-4] 整帧字节数(含 Header,不含 CRC) */
uint16_t Sequence; /* [5-6] 单调递增帧序号(LE) */
uint32_t Timestamp; /* [7-10] Unix 时间戳或 ms 计数(LE) */
uint8_t Source; /* [11] 0x01=MCU,0x02=Server */
uint16_t DevID; /* [12-13]设备 ID(LE) */
uint8_t Class; /* [14] 消息类别 */
uint8_t Flags; /* [15] 控制标志位 */
} FrameHeader_t; /* 共 16 字节,1 字节对齐 */
```
### 2.3 TLV Header(3 字节)
```c
typedef struct {
uint8_t Type; /* [0] 帧类型 */
uint16_t Length; /* [1-2] Value 区域字节数(LE) */
} TLV_t;
```
---
## 3. Class 与 Type 映射表
### 3.1 消息类别(Class)
| 常量 | 值 | 说明 |
|------|----|------|
| `CLASS_CONTROL` | `0x01` | 配置 / 控制命令(服务器 → MCU) |
| `CLASS_DATA` | `0x02` | 实时数据上报(MCU → 服务器) |
| `CLASS_RESPONSE` | `0x03` | ACK / NACK / 错误响应 |
| `CLASS_SYSTEM` | `0x04` | 握手 / 心跳 / 系统同步 |
### 3.2 帧类型(Type)
| 常量 | 值 | 方向 | 说明 |
|------|----|------|------|
| `TYPE_HANDSHAKE` | `0x01` | 双向 | 握手,Class=SYSTEM |
| `TYPE_HEARTBEAT` | `0x02` | 双向 | 心跳保活,Class=SYSTEM |
| `TYPE_SYNC_TIME` | `0x03` | Server→MCU | 时间同步 |
| `TYPE_DEVID_ASSIGN` | `0x05` | Server→MCU | 设备 ID 分配 |
| `TYPE_TEMP_FRAME` | `0x10` | MCU→Server | 温度帧(2D/1D),Class=DATA |
| `TYPE_RAW_FRAME` | `0x11` | MCU→Server | 原始帧(预留) |
| `TYPE_CONFIG_COMMON` | `0x20` | Server→MCU | 公共配置 |
| `TYPE_CONFIG_2D` | `0x22` | Server→MCU | 2D 模式配置 |
| `TYPE_CONFIG_1D` | `0x23` | Server→MCU | 1D 模式配置 |
| `TYPE_ACK_PAYLOAD` | `0x30` | 双向 | ACK 响应体 |
| `TYPE_DETECTION_RESULT` | `0x40` | Server→MCU | 检测结果(含 NG 判定) |
### 3.3 Flags 字段位定义
| 位掩码 | 常量 | 说明 |
|--------|------|------|
| `0x03` | `FLAG_PRIORITY_MASK` | 优先级 0-3(0=最低) |
| `0x04` | `FLAG_COMPRESSED` | 压缩标志(当前固件未使用) |
| `0x08` | `FLAG_ENCRYPTED` | 加密标志(当前固件未使用) |
| `0x10` | `FLAG_ACK_REQ` | 要求接收方回复 ACK |
| `0x20` | `FLAG_LAST_FRAGMENT` | 分片末片标志 |
---
## 4. 零拷贝 TX 架构
### 4.1 TcpTxBuffer_t 结构
```c
typedef struct {
uint8_t *pBuffer; /* 分配的内存起点 */
uint32_t TotalCapacity; /* 总容量 = 9216 字节 */
uint32_t HeadOffset; /* 预留帧头空间 = 64 字节 */
uint32_t ValidPayloadLen;/* 当前有效 Value 数据长度 */
} TcpTxBuffer_t;
```
### 4.2 缓冲区内存布局
```
pBuffer
│
├──[0 ... 63]────────────────── HeadOffset(64 字节预留区)
│ ← 发送前:写入 FrameHeader(16B) + TLV_Header(3B) = 19 字节
│ 从 HeadOffset - 19 = 偏移 45 处开始写入(帧头向前填充)
│
├──[64 ... 64+ValidPayloadLen-1]── Value 数据(Preprocess 写入)
│ ← 图像像素 / 1D 样本 / 其他 payload
│
└──[64+ValidPayloadLen ... 9215] ── 未使用空间
+ [尾部 2 字节] CRC16(追加在 Value 之后)
```
### 4.3 有效载荷容量计算
```
最大可用 Value 大小
= TotalCapacity(9216) - HeadOffset(64) - CRC(2) - TLV_Header(3) - FrameHeader(16)
= 9131 字节
对于 2D 温度帧(每像素 2 字节):
最多传输 9131 / 2 = 4565 个像素(约 67×67 ROI)
```
### 4.4 双缓冲设计
```c
/* main.c 中声明 */
uint8_t g_TxNetBuffer_A_Mem[9216];
uint8_t g_TxNetBuffer_B_Mem[9216];
TcpTxBuffer_t g_TxNetBuffer_A = { .pBuffer = g_TxNetBuffer_A_Mem, .TotalCapacity = 9216, .HeadOffset = 64 };
TcpTxBuffer_t g_TxNetBuffer_B = { .pBuffer = g_TxNetBuffer_B_Mem, .TotalCapacity = 9216, .HeadOffset = 64 };
```
切换逻辑:`use_buffer_A` 标志在每次调用发送函数时取反,保证本帧发送入队后下一帧可立即填写另一个缓冲区。
---
## 5. 分片机制
### 5.1 分片限制
| 参数 | 值 | 说明 |
|------|-----|------|
| `MAX_FRAGMENT_PAYLOAD` | 1400 字节 | 受 TCP MSS=1460 制约(1460 - HEADER=1400) |
| TX 缓冲区容量 | 9216 字节 | 单次发送通常不超过此限制,无需分片 |
### 5.2 分片标志
末片帧的 `Flags` 字段 `FLAG_LAST_FRAGMENT(0x20)` 置位,接收端以此判断分片结束。中间片段该位为 0。
> **当前固件实践**:绝大多数 2D 温度帧(最大 9131 字节)直接作为单个 TCP send 调用发送。WCHNET 底层在必要时分拆为多个 TCP 段,但协议层不显式分片。
---
## 6. Sequence 字段规则
- `Sequence` 字段为 `uint16_t`,每发送一帧单调递增
- 溢出后自然回滚(65535 → 0)
- 接收端 **不强制要求连续**,但可通过 Sequence 检测丢包(非连续 = 有帧丢失)
- 不同流(控制流 / 数据流)的 Sequence 互相独立计数
---
## 7. 错误码
| 常量 | 值 | 触发场景 |
|------|----|---------|
| `ERR_NONE` | `0x0000` | 无错误 |
| `ERR_CRC` | `0x1001` | CRC 校验失败 |
| `ERR_VERSION` | `0x1002` | 协议版本不匹配 |
| `ERR_LENGTH` | `0x1003` | 帧长度字段与实际不符 |
| `ERR_AUTH` | `0x2001` | 认证失败(AuthToken 不匹配) |
| `ERR_BUSY` | `0x2002` | 设备忙,拒绝处理 |
| `ERR_DEV_ID_CONFLICT` | `0x2003` | DevID 冲突 |
| `ERR_PARAM` | `0x3001` | 参数非法(配置值越界等) |
---
## 8. 相关文档
| 文档 | 内容 |
|------|------|
| [系统总览.md](系统总览.md) | 双缓冲 TX 策略、任务调度 |
| [TCP通信模块设计.md](TCP通信模块设计.md) | `TcpLogic_BuildAndSendTemperatureFrame` 调用链 |
| [对接集成指南.md](对接集成指南.md) | 外部接入方协议帧构造示例 |
+228
View File
@@ -0,0 +1,228 @@
# TCP 通信模块设计文档
> 文档版本:1.0 · 日期:2026-03-15 · 状态:已发布
---
## 1. 模块概述
TCP 通信模块(`TcpLogic`)管理与上位机(ConfigServer)的所有网络连接,包括连接建立、握手认证、配置接收与缓存、温度帧发送和断线重连。模块基于 WCHNET 协议栈构建,通过 QDX TLV 协议封装数据。
源文件:`Middle/QDXnetworkStack/qdx_tcp_logic.c` / `qdx_tcp_logic.h`
---
## 2. 双流连接架构
### 2.1 职责分工
| 流 | 端口 | 方向 | 职责 |
|----|------|------|------|
| 控制流 | srcport=**5511** | MCU 主动连接 Server | 握手、配置下发、心跳、TEMP_REQ、DetectionResult 上报 |
| 数据流 | desport=**5512** | MCU 主动连接 Server | 温度帧数据上报(TYPE_TEMP_FRAME) |
两流独立管理,数据流断开不影响控制流的配置和心跳功能。
```c
/* main.c 中的地址配置 */
u8 IPAddr[4] = {192, 168, 7, 10}; /* MCU IP */
u8 DESIP[4] = {192, 168, 7, 50}; /* Server IP */
u16 desport = 5512; /* 数据流目标端口 */
u16 srcport = 5511; /* 控制流源端口 */
```
### 2.2 连接建立时序
```
TcpLogic_Start()
│
├─ 1. 控制流 TCP Connect → Server:5511
│ 等待 SINT_STAT_CONNECT 事件
│
├─ 2. 发送 TYPE_HANDSHAKE(Class=CLASS_SYSTEM)
│ 含 ProtocolVersion=0x0200,DeviceUUID=MAC 地址(6 字节扩展为 UUID 格式)
│ AuthToken=全零(当前不鉴权)
│
├─ 3. 等待服务器握手响应
│ 收到响应后标记控制流"就绪"
│
└─ 4. 数据流 TCP Connect → Server:5512
连接成功后数据流"就绪"
```
---
## 3. 心跳与 Keepalive 机制
### 3.1 应用层心跳
`TcpLogic` 内部定时通过控制流发送 `TYPE_HEARTBEAT` 帧,包含:
```c
typedef struct {
uint32_t UpTime; /* 设备运行时间(ms) */
uint8_t CpuLoad; /* CPU 负载(预留,当前填 0) */
uint8_t MemUsage; /* 内存使用率(预留,当前填 0) */
} Heartbeat_t; /* 6 字节 */
```
### 3.2 TCP Keepalive
在 `main()` 中配置:
```c
struct _KEEP_CFG cfg = {
.idle = 20000, /* 空闲 20 秒后开始发送探测包 */
.interval = 15000, /* 每次探测间隔 15 秒 */
.count = 9 /* 最多 9 次无响应后关闭连接 */
};
WCHNET_ConfigKeepLive(&cfg);
```
| 参数 | 值 | 说明 |
|------|-----|------|
| idle | 20000 ms | 无数据交换后多久开始 Keepalive 探测 |
| interval | 15000 ms | 相邻两次探测的间隔 |
| count | 9 | 探测无响应的最大次数,超过则断链 |
**总体断线判定时间**(最坏情况):20 + 9×15 = **155 秒**
---
## 4. 配置下发与缓存机制
### 4.1 配置接收流程
```
Server 控制流发送配置帧(可一次发三种):
├─ TYPE_CONFIG_COMMON(ConfigCommon_t)
├─ TYPE_CONFIG_2D(Config2D_t)
└─ TYPE_CONFIG_1D(Config1D_t)
│
↓
TcpLogic 解析后缓存至内部 shadow 寄存器
│
↓
触发 ConfigUpdateCallback(OnConfigUpdate)
│
↓
回调内调用 Preprocess_Settings_Change(cfg2d, cfg1d, common)
→ 更新预处理模块的 ROI、阈值、目标尺寸等参数
```
### 4.2 读取当前配置
业务任务每帧调用:
```c
ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
int8_t has_cfg = TcpLogic_GetLatestConfig(&tc, &t2, &t1);
```
返回值:
- `0`:有有效配置,`tc`/`t2`/`t1` 已填充
- `-1`:服务器尚未下发配置
### 4.3 无配置时的默认行为
当 `TcpLogic_GetLatestConfig` 返回 `-1` 时,`task_business_entry` 跳过 2D/1D 处理,仅响应 TEMP_REQ(如有)。
默认 Burst 参数(代码中 fallback 常量):
| 参数 | 默认值 |
|------|--------|
| `DEFAULT_BURST_COUNT` | 3 帧 |
| `DEFAULT_BURST_INTERVAL_MS` | 200 ms |
---
## 5. 数据发送队列与背压处理
### 5.1 发送接口
```c
int8_t TcpLogic_BuildAndSendTemperatureFrame(
TcpTxBuffer_t *tx_buf,
PreprocessResult_t *meta,
uint8_t socket_type, /* 0x01=数据流,0x02=控制流(TEMP_REQ 响应) */
uint8_t is2d /* 1=2D矩阵,0=1D时序 */
);
```
返回值:
- `0`:成功入队发送
- `< 0`:失败(连接断开或队列满)
### 5.2 发送失败处理
当前固件对发送失败仅打印 `DBG_ERR`,**不重试**,帧直接丢弃。原因:热像数据具有实时性,重试旧帧无意义。
### 5.3 双缓冲保证
业务任务通过 `use_buffer_A` 标志交替使用 `g_TxNetBuffer_A` / `g_TxNetBuffer_B`:
```
帧 N → g_TxNetBuffer_A → BuildAndSend(入队)
↓ 切换
帧 N+1 → g_TxNetBuffer_B → BuildAndSend(入队)
↓ 切换
帧 N+2 → g_TxNetBuffer_A ...
```
帧 N 入队后立即切换缓冲区,即使 WCHNET 尚未完成实际 TCP 发送,帧 N+1 的填充也不会覆盖帧 N。
---
## 6. WCHNET 网络栈驱动任务
### 6.1 任务职责
`task_wchnet_entry`(优先级 6)每 5 ms 执行一次:
```c
static void task_wchnet_entry(void *pvParameters) {
while (1) {
qdx_port_net_lock(); /* 获取互斥量 */
WCHNET_MainTask(); /* 协议栈心跳(TCP 超时检测、重传计时等) */
if (WCHNET_QueryGlobalInt())
WCHNET_HandleGlobalInt(); /* 分发 socket 事件 */
qdx_port_net_unlock(); /* 释放互斥量 */
vTaskDelay(pdMS_TO_TICKS(5));
}
}
```
### 6.2 事件分发路径
```
WCHNET_HandleGlobalInt()
└─ WCHNET_HandleSockInt(socketid, intstat)
├─ SINT_STAT_RECV → qdx_port_sock_recv_notify()
├─ SINT_STAT_CONNECT → WCHNET_ModifyRecvBuf() + qdx_port_sock_connect_notify()
├─ SINT_STAT_DISCONNECT → qdx_port_sock_disconnect_notify()
└─ SINT_STAT_TIM_OUT → qdx_port_sock_disconnect_notify()(视为断连)
```
### 6.3 互斥保护
`qdx_port_net_lock/unlock` 使用 FreeRTOS 互斥量保护所有 WCHNET API 调用,防止 `task_wchnet_entry` 和 `task_business_entry` 并发访问网络栈导致竞态。
> **设计说明**:WCHNET 不是线程安全的,所有 WCHNET 调用(包括业务任务中的发送接口)均须在锁保护下进行。`task_wchnet_entry` 持锁 5 ms,`task_business_entry` 在发送时短暂竞争该锁,实际竞争窗口极短。
---
## 7. 断线重连
- 控制流或数据流断开(`SINT_STAT_DISCONNECT` / `SINT_STAT_TIM_OUT`)后,`qdx_tcp_logic` 内部状态机自动重置并重新尝试连接
- 重连成功后重新发送握手帧
- 配置 shadow 寄存器在断线期间**保留**,重连后无需服务器重新下发配置即可继续工作
---
## 8. 相关文档
| 文档 | 内容 |
|------|------|
| [系统总览.md](系统总览.md) | TcpLogic_Start 在启动序列中的位置 |
| [QDX协议设计.md](QDX协议设计.md) | 零拷贝 TX 缓冲区、帧结构 |
| [对接集成指南.md](对接集成指南.md) | 上位机侧握手和配置下发操作 |
+317
View File
@@ -0,0 +1,317 @@
# 对接集成指南
> 文档版本:1.0 · 日期:2026-03-15 · 适用对象:ConfigServer 开发方
>
> 本文档描述如何对接 CH32V307 红外采集端,内容直接来源于固件代码,无需阅读源码即可完成对接开发。
---
## 1. 网络接入参数
### 1.1 固定参数
| 参数 | 值 |
|------|-----|
| MCU IP | `192.168.7.10` |
| MCU 子网掩码 | `255.255.255.0` |
| MCU 网关 | `192.168.7.1` |
| 以太网规格 | 1000M RGMII(标准以太网帧) |
### 1.2 服务端监听要求
Server 端需在 **192.168.7.50** 上监听两个 TCP 端口(MCU 主动连入):
| 端口 | 用途 | 连接方向 |
|------|------|---------|
| **5511** | 控制流(握手/配置/心跳) | MCU → Server |
| **5512** | 数据流(温度帧上报) | MCU → Server |
> 两个连接均为 MCU 主动发起,Server 侧需以 TCP Server(Listen)模式等待。
---
## 2. 握手流程
MCU 上电并完成内部初始化后,按以下顺序建立连接:
```
1. MCU 建立控制流 TCP 连接(Server 5511)
│
↓
2. MCU 发送 TYPE_HANDSHAKE(Class=CLASS_SYSTEM=0x04)
Value 结构(Handshake_t,46 字节):
├─ ProtocolVersion: uint16_t = 0x0200(协议 v2.0)
├─ DeviceUUID[16]: 6 字节 MAC 地址填入前 6 字节,其余填 0
├─ AuthToken[16]: 全零(当前版本不鉴权)
├─ HardwareVersion[8]: "CH32V307"(参考)
└─ FirmwareVersion[8]: 固件版本号
│
↓
3. Server 回复握手响应(同类型或 TYPE_ACK_PAYLOAD)
MCU 收到响应后标记控制流"就绪"
│
↓
4. MCU 建立数据流 TCP 连接(Server 5512)
│
↓
5. 系统就绪,开始接受配置并上报数据
```
**重要**:握手完成前,MCU 不会上报任何温度帧。如 Server 长时间未收到数据,请检查握手响应是否正确发送。
---
## 3. 配置下发
配置通过**控制流(5511)**发送,支持三种配置类型,可单独或组合发送。
### 3.1 Config2D — 2D 触发模式配置
**帧格式**:FrameHeader(Class=0x01, Type=0x22)+ TLV + Config2D_t
Config2D_t 全字段(按结构体内存顺序,均小端序):
| 字段 | 类型 | 偏移 | 说明 | 推荐初始值 |
|------|------|------|------|-----------|
| `Enabled` | uint8_t | 0 | 1=启用 2D 模式,0=禁用 | 1 |
| `IsLive` | uint8_t | 1 | 保留,填 0 | 0 |
| `DeviceId` | uint16_t | 2 | 设备 ID | 0 |
| `Width` | uint16_t | 4 | 传感器宽度(256) | 256 |
| `Height` | uint16_t | 6 | 传感器高度(192) | 192 |
| `Fps` | uint8_t | 8 | 期望帧率(参考值,DVP 硬件控制) | 25 |
| `Exposure` | uint32_t | 9 | 曝光时间(传感器支持时有效) | 0 |
| `AutoExposure` | uint8_t | 13 | 自动曝光(0/1) | 0 |
| `MaskEnabled` | uint8_t | 14 | 掩码使能 | 0 |
| `MaskThreshold` | int16_t | 15 | 掩码阈值(0.1°C/LSB) | 0 |
| `MaskWidth` | uint16_t | 17 | 掩码宽度 | 0 |
| `MaskHeight` | uint16_t | 19 | 掩码高度 | 0 |
| `Angle` | int16_t | 21 | 旋转角度(当前未使用) | 0 |
| `TargetWidth` | uint16_t | 23 | 输出 ROI 宽度(像素) | 64 |
| `TargetHeight` | uint16_t | 25 | 输出 ROI 高度(像素) | 64 |
| `TriggerMode` | uint8_t | 27 | **0=内部温度触发,1=外部 GPIO 触发** | 1 |
| `TriggerGpioLine` | uint8_t | 28 | 外部触发 GPIO 编号(参考,MCU 固定 PA15) | 0 |
| `TriggerDelayMs` | uint16_t | 29 | 触发后延迟采集的时间(ms) | 0 |
| `TriggerBurstCount` | uint8_t | 31 | 每次触发连拍帧数 | 3 |
| `TriggerInternalIntervalMs` | uint16_t | 32 | 连拍帧间隔(ms) | 200 |
| `TriggerTemperatureThreshold` | int16_t | 34 | 内部触发温度阈值(0.1°C/LSB),如 `800` = 80.0°C | 800 |
| `TriggerDebounceIntervalMs` | uint16_t | 36 | 外部触发防抖时间(ms) | 50 |
| `TriggerCondition` | uint8_t | 38 | 内部触发判据:**0=ROI 均值,1=ROI 最大值** | 1 |
| `TriggerRoiX` | uint16_t | 39 | 触发检测 ROI 左上角 X | 0 |
| `TriggerRoiY` | uint16_t | 41 | 触发检测 ROI 左上角 Y | 0 |
| `TriggerRoiW` | uint16_t | 43 | 触发检测 ROI 宽度(0=全帧) | 256 |
| `TriggerRoiH` | uint16_t | 45 | 触发检测 ROI 高度(0=全帧) | 192 |
| `NGioDelay` | uint16_t | 47 | NG 输出 GPIO 脉宽(ms) | 200 |
| `OutputGpioLine` | uint8_t | 49 | 输出 GPIO 编号(固定 PA8) | 0 |
| `AlarmGpioLine` | uint8_t | 50 | 告警 GPIO(当前未使用) | 0 |
| `AlarmHoldMs` | uint16_t | 51 | 告警持续时间(当前未使用) | 0 |
| `StoreNgImagesOnly` | uint8_t | 53 | 仅存储 NG 图像(当前未使用) | 0 |
| `TrainingEnabled` | uint8_t | 54 | 训练模式(当前未使用) | 0 |
| `TrainingSampleThreshold` | uint16_t | 55 | 训练样本阈值(当前未使用) | 0 |
| `ProcessingTimeoutMs` | uint16_t | 57 | 处理超时(ms,当前未使用) | 0 |
| `MaxProcessingQueueSize` | uint8_t | 59 | 最大处理队列(当前未使用) | 0 |
| `Reserved` | uint8_t | 60 | 保留,填 0 | 0 |
### 3.2 Config1D — 1D 采集模式配置
**帧格式**:FrameHeader(Class=0x01, Type=0x23)+ TLV + Config1D_t
Config1D_t 全字段:
| 字段 | 类型 | 偏移 | 说明 | 推荐初始值 |
|------|------|------|------|-----------|
| `Enabled` | uint8_t | 0 | 1=启用 1D 模式,0=禁用 | 0 |
| `RunMode` | uint8_t | 1 | **0=停止采集,1=运行** | 1 |
| `TriggerType` | uint8_t | 2 | **1=内部(连续热帧),2=外部(PA15 GPIO)** | 1 |
| `BufferSize` | uint16_t | 3 | 采集缓冲区大小(样本数,最大 512) | 200 |
| `TriggerTempLimit` | int16_t | 5 | 触发温度阈值(0.1°C/LSB) | 500 |
| `StartPointsToRemove` | uint16_t | 7 | 保留 | 0 |
| `ReferenceLength` | uint16_t | 9 | 参考长度(保留) | 0 |
| `HighTimerLimit` | uint16_t | 11 | 外部触发防抖等待时间(ms) | 100 |
| `TimerCLimit` | uint16_t | 13 | 保留 | 0 |
| `NgCountLimit` | uint8_t | 15 | 触发后连续低温帧数达此值则停止采集 | 5 |
| `LSizeStart` | uint16_t | 16 | 发送时去掉头部样本数(切片) | 0 |
| `RSizeStart` | uint16_t | 18 | 发送时去掉尾部样本数(切片) | 0 |
| `NGioDelay` | uint16_t | 20 | NG 输出脉宽(ms,1D 使用) | 200 |
| `OutputGpioLine` | uint8_t | 22 | 输出 GPIO(固定 PA8) | 0 |
| `AlarmGpioLine` | uint8_t | 23 | 告警 GPIO(未使用) | 0 |
| `AlarmHoldMs` | uint16_t | 24 | 告警持续(未使用) | 0 |
> **2D 与 1D 互斥**:`Config2D.Enabled=1` 时 MCU 进入 2D 模式;`Config1D.Enabled=1` 且 `Config2D.Enabled=0` 时进入 1D 模式。同时置 1 时 2D 优先。
---
## 4. 温度帧接收
温度帧通过**数据流(5512)**接收,帧类型为 `TYPE_TEMP_FRAME(0x10)`,Class=`CLASS_DATA(0x02)`。
### 4.1 2D 温度帧解析
Value 布局(TemperatureFrameHeader_t + 像素数组):
```
[TemperatureFrameHeader_t] 18 字节
├─ FrameNumber: uint32_t LE — 帧序号(与 DVP 帧计数对应)
├─ Width: uint16_t LE — 输出 ROI 宽度(TargetWidth 或有效宽度)
├─ Height: uint16_t LE — 输出 ROI 高度
├─ MinTemp: int16_t LE — ROI 内最低温度(0.1°C/LSB)
├─ MaxTemp: int16_t LE — ROI 内最高温度(0.1°C/LSB)
├─ AvgTemp: int16_t LE — ROI 内平均温度(0.1°C/LSB)
├─ RoiTemp: int16_t LE — TriggerRoi 区域特征温度(0.1°C/LSB)
├─ FrameType: uint8_t — 帧类型标识(0x01=触发帧,0x02=TEMP_REQ 帧)
├─ Status: uint8_t — 状态(0=正常)
├─ Is2D: uint8_t — 1=2D 矩阵帧
└─ Reserved: uint8_t — 填 0
[像素数组] Width × Height × 2 字节
每个像素:uint16_t 小端序
排列:行优先(row0 col0, row0 col1, ..., row0 colW-1, row1 col0, ...)
单位:0.1°C/LSB,转为摄氏度 = 原始值 × 0.1
```
**Python 解析示例**:
```python
import struct
def parse_2d_frame(data: bytes):
hdr_fmt = '<IHHhhhhhBBBB' # TemperatureFrameHeader_t
hdr_size = struct.calcsize(hdr_fmt)
frame_num, w, h, min_t, max_t, avg_t, roi_t, _, ft, st, is2d, _ = struct.unpack_from(hdr_fmt, data)
pixels_raw = struct.unpack_from(f'<{w*h}H', data, hdr_size)
pixels_celsius = [v * 0.1 for v in pixels_raw]
# pixels_celsius[row * w + col] = 第 row 行、第 col 列的温度(°C)
return {
'frame_num': frame_num, 'width': w, 'height': h,
'max_temp': max_t * 0.1, 'min_temp': min_t * 0.1,
'pixels': pixels_celsius
}
```
### 4.2 1D 温度帧解析
当 `Is2D=0` 时,像素数组替换为 1D 样本序列。
Value 布局(同 TemperatureFrameHeader_t,但 Width=点数,Height=1):
```
[TemperatureFrameHeader_t] 18 字节
├─ Width: 实际有效样本点数 N
├─ Height: 1
└─ Is2D: 0
[样本数组] N × 4 字节
每个样本(TempPoint1D_t):
├─ TimeOffset: uint16_t LE — 相对采集开始的 ms 偏移
└─ Temperature: uint16_t LE — 对应时刻温度(0.1°C/LSB)
```
**Python 解析示例**:
```python
def parse_1d_frame(data: bytes):
hdr_fmt = '<IHHhhhhhBBBB'
hdr_size = struct.calcsize(hdr_fmt)
frame_num, n, _, min_t, max_t, avg_t, _, _, ft, st, is2d, _ = struct.unpack_from(hdr_fmt, data)
points = []
for i in range(n):
t_off, temp = struct.unpack_from('<HH', data, hdr_size + i*4)
points.append({'time_ms': t_off, 'temp_c': temp * 0.1})
return {'frame_num': frame_num, 'points': points}
```
---
## 5. 检测结果下发与 NG 输出
### 5.1 下发检测结果
Server 在图像处理完成后,通过**控制流(5511)**发送 `TYPE_DETECTION_RESULT(0x40)`(Class=CLASS_CONTROL=0x01)。
Value 结构(DetectionResult_t,8 字节):
| 字段 | 类型 | 说明 |
|------|------|------|
| `FrameNumber` | uint32_t LE | 对应触发该结果的帧号 |
| `Result` | uint8_t | **0=NG(不合格),1=OK(合格)** |
| `Reserved[3]` | uint8_t[3] | 填 0 |
### 5.2 MCU NG 动作
收到 `Result=0`(NG)时:
- `PA8` 输出高电平
- 持续 `Config2D.NGioDelay`(默认 200 ms)后自动恢复低电平
- `NGioDelay` 可通过 Config2D 配置调整(1D 模式使用 Config1D.NGioDelay)
> 外部报警装置应接在 PA8,高电平有效,持续时间可配置。
---
## 6. TEMP_REQ 按需截图
Server 可随时通过控制流请求当前热场快照,无需等待触发。
**请求帧**:TYPE_CONFIG_COMMON(0x20,Class=CLASS_CONTROL=0x01)中包含 TEMP_REQ 字段,或通过专用命令触发(具体字段由 `TcpLogic` 内部解析),携带 `is2dRequest` 标志(1=请求 2D 帧,0=请求 1D 帧)。
**MCU 响应**:
- 下一个业务循环取当前 FrameBuffer 的最新帧
- `is2d=1`:执行 `Preprocess_Execute`,返回 ROI 裁切后的 2D 温度矩阵(FrameType=0x02)
- `is2d=0`:返回中心行 **30 个等间距采样点**,时间偏移仿真 0~600 ms(非真实时间序列)
- 仅在对应模式(`Config2D.Enabled` / `Config1D.Enabled`)为 1 时响应,否则忽略
---
## 7. 错误码参考
| 错误码 | 值 | 触发场景 | 建议处理 |
|--------|-----|---------|---------|
| `ERR_CRC` | `0x1001` | 帧 CRC 校验失败 | 检查发送数据是否正确,重发 |
| `ERR_VERSION` | `0x1002` | 协议版本不匹配(非 0x20) | 检查 Version 字段 |
| `ERR_LENGTH` | `0x1003` | FrameHeader.Length 与实际帧长不符 | 检查帧构造逻辑 |
| `ERR_AUTH` | `0x2001` | AuthToken 不匹配 | 当前版本 AuthToken 全零,不应触发 |
| `ERR_BUSY` | `0x2002` | 设备忙,拒绝处理 | 等待 100 ms 后重试 |
| `ERR_DEV_ID_CONFLICT` | `0x2003` | DevID 与已注册设备冲突 | 检查 DevID 分配 |
| `ERR_PARAM` | `0x3001` | 配置字段值非法 | 检查配置结构体字段范围 |
---
## 8. 对接故障排查
### 8.1 MCU 上电后无连接
- 检查 MCU IP 是否为 `192.168.7.10`,Server 是否在 `192.168.7.50`
- 确保 Server 在 5511 和 5512 端口上已 Listen
- 检查以太网连接(MCU 使用 1000M RGMII,需 1 Gbps 交换机或兼容网卡)
- 调试串口(USART3,921600)会打印 `TCP Connected, socket X` 确认连接
### 8.2 握手成功但无数据帧
- 确认已通过控制流下发 `Config2D`(`Enabled=1`)或 `Config1D`(`Enabled=1`)
- 查看调试串口是否打印 `TRIGGER frm=N`(2D 模式)或 `1D: internal trigger start`(1D 模式)
- 若无触发,检查 `TriggerTemperatureThreshold` 是否过高
### 8.3 数据帧偶发丢失
- 检查 Server 是否及时 ACK,TCP 窗口是否足够
- 调试串口打印 `2D TX fail` 或 `1D SEND ... ret=-1` 表示 MCU 侧发送失败(队列满或连接异常)
### 8.4 温度值异常(偏高或偏低约 10 倍)
- 确认传感器已配置为 **TMP 模式**(非 Y16 模式)。参见 `Doc/Mini212G2预配置指南.md`
- TMP 模式输出值已是 0.1°C/LSB,解析时乘以 0.1 即为摄氏度
### 8.5 NG 输出不动作
- 确认发送的 DetectionResult.Result = 0(NG),而非 1(OK)
- 检查 `Config2D.NGioDelay` 是否已配置(0 时默认 200 ms)
- 核查 PA8 GPIO 连接是否正确
---
## 9. 相关文档
| 文档 | 内容 |
|------|------|
| [系统总览.md](系统总览.md) | MCU 固件架构总览 |
| [QDX协议设计.md](QDX协议设计.md) | 帧结构详细说明(供深度对接参考) |
| [TCP通信模块设计.md](TCP通信模块设计.md) | MCU 侧连接管理实现细节 |
| [Mini212G2预配置指南.md](../Mini212G2预配置指南.md) | 传感器 TMP 模式配置 |
+233
View File
@@ -0,0 +1,233 @@
# CH32V307 固件系统总览
> 文档版本:1.0 · 日期:2026-03-15 · 状态:已发布
---
## 1. 项目背景与硬件平台
### 1.1 项目概述
本固件运行于 CH32V307WCU6 微控制器,连接 Mini212G2(256×192)红外热像传感器,通过 1000M RGMII 以太网实时向上位机(ConfigServer)上报温度数据。
系统支持两种主要工作模式,均在 FreeRTOS 多任务框架下运行:
- **2D 模式**:矩阵温度帧触发采集(外部 GPIO 触发或内部温度阈值触发)
- **1D 模式**:时间轴温度序列采集(外部 GPIO 触发或内部连续热帧检测)
### 1.2 硬件配置
| 项目 | 规格 |
|------|------|
| MCU | CH32V307WCU6,RISC-V,144 MHz |
| FLASH | 128 KB(Option Bytes 配置 `FLASH_128_SRAM_192`) |
| SRAM | 192 KB |
| FreeRTOS Heap | 16 KB(Heap_4) |
| 传感器 | Mini212G2,256×192,TMP 模式(0.1°C/LSB) |
| 传感器接口 | DVP 8-bit CMOS,硬件 DMA ping-pong |
| 以太网 | 1000M RGMII |
| 调试串口 | USART3(PB10 TX / PB11 RX),921600 baud |
| 外部触发输入 | PA15 / EXTI15,上升沿,内部下拉 |
| NG 输出 | PA8,推挽,高电平有效 |
> **TMP 模式说明**:传感器必须预配置为 TMP 模式(非 Y16 原始 ADC),输出值为 0.1°C/LSB 的绝对温度,无需 MCU 换算。配置方法见 `Doc/Mini212G2预配置指南.md`。
---
## 2. FreeRTOS 任务拓扑
### 2.1 任务列表
| 任务名 | 优先级 | 栈(words) | 条件 | 职责 |
|--------|--------|-------------|------|------|
| `task_wchnet_entry` | 6(最高) | 512 | 始终创建 | WCHNET 协议栈驱动,每 5 ms 轮询 |
| `task_business_entry` | 5 | 512 | 始终创建 | DVP 帧消费、2D/1D 状态机、TCP 发送 |
| `task_test_pattern_entry` | 4 | 256 | `TEST_PATTERN_MODE=1` 时 | 仿真传感器数据(不含真实DVP) |
| `task_heartbeat_entry` | 3(最低) | 256 | 始终创建 | 调试心跳打印,每 2 s 一次 |
### 2.2 任务间数据流
```
传感器(DVP 硬件)
│ 每行 512 B,DMA ping-pong
↓
DVP_IRQHandler(WCH-Interrupt-fast)
│ ① STR_FRM → current_line_idx = 0
│ ② ROW_DONE → memcpy → FrameBuffer[idx]
│ ③ idx == 191 → Frame_Ready_Flag = 1
↓
task_business_entry(优先级 5)
│ 轮询 Frame_Ready_Flag
│ 读取 FrameBuffer(共享内存)
│ 执行 Preprocess_Execute / get_1d_sample
↓
TcpLogic_BuildAndSendTemperatureFrame
│ 零拷贝写入帧头(HeadOffset 空间复用)
↓
task_wchnet_entry(优先级 6)
│ WCHNET_MainTask / WCHNET_HandleGlobalInt
↓
以太网 → ConfigServer
```
**关键同步机制**:
- `Frame_Ready_Flag`:`volatile uint8_t`,IRQ 置 1,业务任务读取后清 0
- `FrameBuffer`:`uint8_t[192][512]`,IRQ 写入,业务任务只读
- 双缓冲 TX:`g_TxNetBuffer_A` / `g_TxNetBuffer_B`,`use_buffer_A` 标志交替,保证一帧发送未完时下一帧可立即写入
- `qdx_port_net_lock/unlock`:互斥量保护 WCHNET API,防止业务任务和网络任务并发
---
## 3. 系统启动序列
`main()` 按以下顺序执行初始化,各步骤依赖前序完成:
```
SystemCoreClockUpdate() → 更新 SystemCoreClock(144 MHz)
Delay_Init() → SysTick 初始化,提供 sys_tick_ms 计时
USART_Printf_Init(921600) → USART3 调试串口(PB10/PB11)
Config_Flash_SRAM(FLASH_128_SRAM_192) → Option Bytes:FLASH=128K,SRAM=192K
(若已配置跳过;否则写入后系统复位)
DVP_Init() → GPIO 配置、DMA ping-pong 缓冲、IRQ 使能
TIM2_Init() → WCHNET 定时器(1 ms 周期)
ExtTrigger_GPIO_Init() → PA15 EXTI15,上升沿中断
NG_GPIO_Init() → PA8 推挽输出,默认低
ETH_LibInit(IP, GW, Mask, MAC) → WCHNET 协议栈初始化,1000M RGMII
WCHNET_ConfigKeepLive(20000,15000,9) → TCP Keepalive
qdx_port_init() → QDX 网络层 socket/互斥量初始化
Preprocess_Init(256, 192) → 图像预处理模块
TcpLogic_Init(MACAddr, NULL) → TCP 逻辑层,注册 DeviceUUID=MAC
TcpLogic_Register*Callback() → 注册 OnConfigUpdate / OnDetectionResult / OnTempFrameRequest
TcpLogic_Start() → 启动后台连接状态机(控制流 → 握手 → 数据流)
xTaskCreate(×3 或 ×4) → 创建 RTOS 任务
vTaskStartScheduler() → 启动调度器(不返回)
```
> **Option Bytes 说明**:首次上电若 Flash/SRAM 分配不符,`Config_Flash_SRAM` 会写入 Option Bytes 并触发 `NVIC_SystemReset()`,复位后以新分配重新启动。正常运行时该函数直接返回。
---
## 4. 2D 状态机
2D 模式在 `task_business_entry` 中,每帧调用 `handle_2d_trigger()`,由 4 个状态变量组成隐式状态机:
### 4.1 状态转换图
```
┌─────────────────── IDLE ──────────────────────────┐
│ │
│ TriggerMode=1(外部) │ TriggerMode=0(内部)
│ PA15 上升沿 → g_ext_trigger_flag=1 │ Preprocess_CheckInternalTrigger2D()==1
↓ ↓
DEBOUNCE (直接)
等待 DebounceIntervalMs │
│ │
└──────────────────────────────────────────────────→┤
↓
DELAY
等待 TriggerDelayMs
│
↓
BURST(循环)
发送第 1 帧(立即)
+ 剩余 BurstCount-1 帧
@ TriggerInternalIntervalMs 间隔
│
burst_remaining == 0
│
↓
→ IDLE
```
### 4.2 状态参数
| 配置字段 | 默认值 | 说明 |
|----------|--------|------|
| `TriggerMode` | 0 | 0=内部温度阈值,1=外部 GPIO |
| `TriggerDebounceIntervalMs` | 0 | 外部触发防抖等待时间(ms) |
| `TriggerDelayMs` | 0 | 触发确认到第一帧采集的延迟(ms) |
| `TriggerBurstCount` | 3 | 每次触发发送帧数(DEFAULT_BURST_COUNT=3) |
| `TriggerInternalIntervalMs` | 200 | 连拍帧间隔(DEFAULT_BURST_INTERVAL_MS=200 ms) |
| `TriggerCondition` | 0 | 内部触发判据:0=Avg,1=Max |
| `TriggerTemperatureThreshold` | — | 内部触发温度阈值(0.1°C/LSB) |
| `TriggerRoiX/Y/W/H` | — | 内部触发检测 ROI 区域 |
### 4.3 双缓冲 TX 策略
每次 `do_2d_capture_send()` 交替选择 `g_TxNetBuffer_A` / `g_TxNetBuffer_B`,通过 `use_buffer_A` 标志取反实现切换。TCP 发送入队后立即切换,保证下一帧填充不覆盖正在传输的缓冲区。
---
## 5. 1D 状态机
1D 模式以每帧全帧最大温度作为一个采样点,在 `task_business_entry` 中每帧调用 `handle_1d_trigger()`。
### 5.1 状态图
```
S1D_IDLE
│
├─ TriggerType=2(外部):PA15 上升沿
│ → S1D_DEBOUNCE(等待 HighTimerLimit ms)
│ → S1D_COLLECTING
│
└─ TriggerType=1(内部):维护 3 帧滑动窗口
连续 3 帧最大温度 ≥ TriggerTempLimit
→ S1D_COLLECTING(直接,含预触发 3 帧数据)
S1D_COLLECTING(每帧采一个样本)
├─ 记录 sample = 全帧最大温度,time_offset = ms - start_time
├─ 热帧(≥ thresh):s1d_consec_hot++,consec_cold=0
│ 满足 3 次热 → s1d_triggered=1
└─ 停止条件(任意一满足):
① s1d_count ≥ MAX_1D_POINTS(512)
② s1d_count ≥ BufferSize
③ s1d_triggered && s1d_consec_cold ≥ NgCountLimit
└─ 若 triggered → send_1d_collection()
否则 → 丢弃,reset
→ S1D_IDLE
```
### 5.2 数据格式
每个样本 **4 字节**,小端序:
| 字节 | 字段 | 类型 | 说明 |
|------|------|------|------|
| 0–1 | `TimeOffset` | uint16_t LE | 相对采集开始的 ms 偏移 |
| 2–3 | `Temperature` | uint16_t LE | 全帧最大温度,0.1°C/LSB |
### 5.3 切片参数
发送前对采集缓冲区切片:有效区间 = `[LSizeStart, count - RSizeStart)`,去除头尾噪声点。若切片越界则退化为全量发送并打印告警。
---
## 6. TEMP_REQ 辅助通道
服务器可通过控制流随时请求单帧快照,无需等待自主采集触发:
```
服务器发送 TEMP_REQ(含 is2dRequest 字段)
→ OnTempFrameRequest() 回调
→ g_temp_req_pending = 1,g_temp_req_is2d = is2dRequest
task_business_entry 业务循环检测到 g_temp_req_pending:
→ TcpLogic_GetLatestConfig() 确认对应模式 Enabled
→ is2d=1:Preprocess_Execute() + TcpLogic_BuildAndSendTemperatureFrame(SocketType=0x02)
is2d=0:send_1d_snapshot()(中心行 30 个等间距采样点,时间偏移仿真 0~600 ms)
```
**限制**:仅在对应模式(`t2.Enabled` / `t1.Enabled`)为 1 时响应;若未 Enabled,打印 DBG_ERR 并忽略请求。
---
## 7. 相关文档
| 文档 | 内容 |
|------|------|
| [DVP模块设计.md](DVP模块设计.md) | DVP 硬件初始化、DMA、IRQ、FrameBuffer 格式 |
| [QDX协议设计.md](QDX协议设计.md) | TLV 帧结构、零拷贝 TX、分片机制 |
| [TCP通信模块设计.md](TCP通信模块设计.md) | 双流连接、心跳、配置缓存 |
| [对接集成指南.md](对接集成指南.md) | 外部对接方接入手册 |
| [Mini212G2预配置指南.md](../Mini212G2预配置指南.md) | 传感器 TMP 模式配置 HEX 命令 |