📝 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:
parent
c347c988f2
commit
b69717b964
198
doc/设计文档/DVP模块设计.md
Normal file
198
doc/设计文档/DVP模块设计.md
Normal 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
doc/设计文档/QDX协议设计.md
Normal file
202
doc/设计文档/QDX协议设计.md
Normal 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
doc/设计文档/TCP通信模块设计.md
Normal file
228
doc/设计文档/TCP通信模块设计.md
Normal 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
doc/设计文档/对接集成指南.md
Normal file
317
doc/设计文档/对接集成指南.md
Normal 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
doc/设计文档/系统总览.md
Normal file
233
doc/设计文档/系统总览.md
Normal 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 命令 |
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-03-15
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
**项目背景**:CH32V307WCU6 红外热像采集端固件,连接 Mini212G2 (256×192) 传感器,通过 1000M RGMII 以太网上报温度数据。核心功能已稳定运行,但缺少系统性设计文档。
|
||||||
|
|
||||||
|
**当前状态**:代码实现完整,包含 FreeRTOS 多任务、DVP+DMA 采集、QDX TLV 协议、双流 TCP 通信、2D/1D 双状态机。现有文档仅覆盖配置参数和协议接口,未形成模块级设计视图。
|
||||||
|
|
||||||
|
**读者**:内部开发工程师(理解实现逻辑、维护代码);外部对接方(ConfigServer/上位机开发团队,需接入协议)。
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- 为 5 个能力模块分别建立规范化的设计文档
|
||||||
|
- 通过 openspec specs 机制使文档可版本追踪和演进
|
||||||
|
- 提供外部对接方可直接使用的集成指南(无需阅读源码)
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- 不修改任何代码
|
||||||
|
- 不覆盖硬件电气原理图(属于硬件文档)
|
||||||
|
- 不重复协议 HEX 命令表(已在 `Doc/Mini212G2预配置指南.md`)
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### 决策 1:每个能力模块生成独立的 spec 文件,Doc/ 目录存放最终可交付文档
|
||||||
|
|
||||||
|
**理由**:openspec 的 `specs/<capability>/spec.md` 是规范锚点,实际可读性文档放在 `Doc/设计文档/` 下,分工清晰。每个 spec 描述 WHAT(需求),Doc 中文件描述 HOW(实现细节)。
|
||||||
|
|
||||||
|
**备选方案**:一个大 spec 文件 → 拒绝,spec 文件过大时 apply 阶段难以跟踪单个需求。
|
||||||
|
|
||||||
|
### 决策 2:集成指南(integration-guide)作为独立能力,单独维护
|
||||||
|
|
||||||
|
**理由**:外部对接方只需集成指南,不需要理解内部模块设计。独立文件避免外部文档被内部修改污染,版本可单独管控。
|
||||||
|
|
||||||
|
### 决策 3:文档覆盖既有代码的实际行为,不做超前设计
|
||||||
|
|
||||||
|
**理由**:这是文档补全变更,不是功能变更。spec 反映的是代码已实现的行为(SHALL = 当前实现保证),不引入新约束。
|
||||||
|
|
||||||
|
### 决策 4:2D 状态机和 1D 状态机各自独立描述,不合并
|
||||||
|
|
||||||
|
**理由**:两者触发逻辑、收集方式、发送格式完全不同(2D 矩阵 vs 1D 时间序列)。合并描述会造成混淆。两者通过 `Config2D.Enabled` / `Config1D.Enabled` 互斥使能。
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **风险**:文档编写时代码继续演进,导致文档过时 → **缓解**:在 spec 中使用 SHALL 约束当前行为,代码变更时触发 spec 更新 PR 流程。
|
||||||
|
- **风险**:integration-guide 中的协议字段描述与实际结构体不一致 → **缓解**:所有字段描述直接从 `qdx_protocol.h` 提取,source of truth 为代码。
|
||||||
|
- **权衡**:文档详细程度 vs 维护成本 → 选择"关键路径详细,边缘情况参考注释"策略,避免过度文档导致维护负担。
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Doc/ 下设计文档是否需要中英双语(供外部团队使用)?当前按中文优先。
|
||||||
|
- integration-guide 是否需要提供 Python/C# 示例解析代码片段?后续可作为独立变更补充。
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
# Proposal: CH32V307 固件系统软件设计文档
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
项目已完成核心功能开发,但缺乏系统级软件设计文档:内部无法快速理解任务拓扑和状态机逻辑,外部对接方缺乏集成参考。当前文档仅覆盖配置和协议层面,未形成完整的设计视图。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 新增 **系统总览文档**:FreeRTOS 任务拓扑(任务职责、优先级、通信方式)、2D/1D 双状态机完整流程图、网络栈初始化与运行流程
|
||||||
|
- 新增 **DVP 采集模块设计文档**:DVP 硬件配置原理、DMA ping-pong 机制、IRQ 帧组装逻辑、FrameBuffer 数据格式
|
||||||
|
- 新增 **QDX 协议模块设计文档**:TLV 帧结构、所有 Type 定义与用途、零拷贝 TX 缓冲区架构、分片机制
|
||||||
|
- 新增 **TCP 通信模块设计文档**:双流(5511 控制 / 5512 数据)连接管理、心跳机制、配置下发与缓存、数据发送队列
|
||||||
|
- 新增 **对接集成文档**:供上位机/ConfigServer 开发方参考的接入指南(握手流程、配置下发、数据帧解析、错误码)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `system-overview`: FreeRTOS 任务拓扑、2D 状态机、1D 状态机、系统启动序列
|
||||||
|
- `dvp-module-design`: DVP 硬件初始化、DMA ping-pong 行采集、帧组装、FrameBuffer 格式
|
||||||
|
- `qdx-protocol-design`: TLV 帧格式、Class/Type 定义、零拷贝 TX 架构、CRC、分片
|
||||||
|
- `tcp-module-design`: 双流连接管理、心跳、配置缓存、发送队列
|
||||||
|
- `integration-guide`: 外部对接方集成手册(握手、配置、数据解析、错误处理)
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
(无,本变更仅新增文档,不修改现有规范)
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- **新增文档目录**:`Doc/设计文档/` 或各模块独立文件
|
||||||
|
- **不涉及**任何代码修改
|
||||||
|
- 影响范围:内部开发参考、外部对接方(ConfigServer 开发团队)
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: DVP 硬件初始化配置描述
|
||||||
|
文档 SHALL 描述 DVP 外设的完整初始化配置,包含 GPIO 引脚映射、工作模式和信号极性。
|
||||||
|
|
||||||
|
#### Scenario: 开发者核查 DVP GPIO 引脚分配
|
||||||
|
- **WHEN** 开发者进行硬件调试时
|
||||||
|
- **THEN** 文档 SHALL 提供引脚映射表:PA4/5/6/9/10(数据线 D0-D4)、PC8/9/11(D5-D7 + PCLK)、PB3/8/9(VSYNC/HSYNC/PIXCLK),所有引脚配置为浮空输入
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 DVP 信号极性配置
|
||||||
|
- **WHEN** 开发者对接不同传感器时
|
||||||
|
- **THEN** 文档 SHALL 说明:VSYNC 高有效(RB_DVP_V_POLAR=1,对应 DIGITAL_FIELD_VALID 高电平有效帧)、HSYNC 高有效(RB_DVP_H_POLAR=0)、PCLK 上升沿采样(RB_DVP_P_POLAR=0),与 Mini212G2 手册时序图一致
|
||||||
|
|
||||||
|
### Requirement: DMA ping-pong 行缓冲机制描述
|
||||||
|
文档 SHALL 描述 DVP DMA 双缓冲的工作原理,包含缓冲切换逻辑和 IRQ 中的正确读取方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 DMA 缓冲切换逻辑
|
||||||
|
- **WHEN** 开发者排查行数据乱序问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:DVP 硬件在 DMA_BUF0/BUF1 间自动切换,ROW_DONE 中断触发时硬件已切换到下一个缓冲;BUF_TOG=1 表示 DMA 正在写 BUF0,此时应读 BUF1(公式:`src = (CR1 & BUF_TOG) ? DMA_LineBuf0 : DMA_LineBuf1`)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解行缓冲大小约束
|
||||||
|
- **WHEN** 开发者修改传感器分辨率时
|
||||||
|
- **THEN** 文档 SHALL 说明:DMA_LineBuf0/1 各 512 字节(256 像素 × 2 字节),COL_NUM=512,ROW_NUM=1(每行触发一次 IRQ),修改分辨率需同时更新 `BYTES_PER_LINE`、`SENSOR_WIDTH`、`SENSOR_HEIGHT`
|
||||||
|
|
||||||
|
### Requirement: DVP IRQ 帧组装逻辑描述
|
||||||
|
文档 SHALL 描述 `DVP_IRQHandler` 的完整逻辑,包含帧同步机制和 FrameBuffer 组装方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解帧同步锚点
|
||||||
|
- **WHEN** 开发者排查帧错位问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:STR_FRM 中断(VSYNC 上升沿)将 `current_line_idx` 清零,此为帧同步唯一锚点;ROW_DONE 中断依次将行数据 memcpy 至 `FrameBuffer[current_line_idx]` 并递增
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解帧完成标志
|
||||||
|
- **WHEN** 开发者需要知道何时帧数据可用时
|
||||||
|
- **THEN** 文档 SHALL 说明:当 `idx == SENSOR_HEIGHT - 1`(即第 191 行完成)时,`Frame_Ready_Flag` 置 1,`Ready_Frame_Count` 更新为当前帧号;业务任务轮询此标志,读取后将其清零
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 IRQ 使用 fast interrupt 属性的原因
|
||||||
|
- **WHEN** 开发者修改 IRQ 属性时
|
||||||
|
- **THEN** 文档 SHALL 说明:`__attribute__((interrupt("WCH-Interrupt-fast")))` 使中断处理跳过寄存器入栈,减少中断延迟,确保在下一行像素时钟到来前完成 memcpy(每行约 42.67µs @ 25Hz)
|
||||||
|
|
||||||
|
### Requirement: FrameBuffer 数据格式描述
|
||||||
|
文档 SHALL 描述 FrameBuffer 的内存布局和像素值含义。
|
||||||
|
|
||||||
|
#### Scenario: 开发者访问特定像素的温度值
|
||||||
|
- **WHEN** 开发者需要读取第 row 行第 col 列的温度时
|
||||||
|
- **THEN** 文档 SHALL 说明:`FrameBuffer` 声明为 `uint8_t[192][512]`,通过 `(uint16_t*)FrameBuffer` 访问,像素值 = `((uint16_t*)FrameBuffer)[row * 256 + col]`,单位 0.1°C/LSB(TMP 模式,需传感器预配置为 TMP)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解字节序要求
|
||||||
|
- **WHEN** 开发者移植到大端序平台时
|
||||||
|
- **THEN** 文档 SHALL 说明:CH32V307 为小端序,`uint8_t[0]` = 低字节,`uint8_t[1]` = 高字节;传感器必须配置为 CMOS8(LSB)(低字节先发),否则温度值字节反序
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 对接方网络接入参数说明
|
||||||
|
文档 SHALL 提供上位机(ConfigServer)与采集端建立 TCP 连接所需的全部参数。
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 TCP Server 监听端口
|
||||||
|
- **WHEN** 对接方开发服务端程序时
|
||||||
|
- **THEN** 文档 SHALL 提供:MCU 默认 IP=192.168.7.10,Subnet=255.255.255.0,Gateway=192.168.7.1;Server 需在 192.168.7.50 监听两个端口:5511(控制流,MCU 主动连入)和 5512(数据流,MCU 主动连入);以太网接口为 1000M RGMII
|
||||||
|
|
||||||
|
### Requirement: 握手流程说明
|
||||||
|
文档 SHALL 描述 MCU 上电后的握手过程,使对接方能正确识别采集端设备。
|
||||||
|
|
||||||
|
#### Scenario: 对接方接收第一个数据包并识别设备
|
||||||
|
- **WHEN** MCU 连接后发送握手帧时
|
||||||
|
- **THEN** 文档 SHALL 说明:MCU 在控制流连接成功后发送 TYPE_HANDSHAKE(Class=CLASS_SYSTEM,含 ProtocolVersion=0x0200、DeviceUUID=MAC 地址、AuthToken=全零);服务器 SHALL 回复握手响应以完成握手;握手完成后 MCU 才开始正常数据上报
|
||||||
|
|
||||||
|
### Requirement: 配置下发格式说明
|
||||||
|
文档 SHALL 描述服务器向 MCU 下发配置的帧格式和所有配置字段含义。
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 2D 触发模式
|
||||||
|
- **WHEN** 对接方发送 Config2D 配置帧时
|
||||||
|
- **THEN** 文档 SHALL 提供 Config2D_t 所有字段:Enabled(uint8_t)、TriggerMode(uint8_t, 0=内部温度/1=外部GPIO)、TriggerDebounceIntervalMs(uint16_t)、TriggerDelayMs(uint16_t)、TriggerBurstCount(uint8_t)、TriggerInternalIntervalMs(uint16_t)、TriggerTemperatureThreshold(int16_t, 0.1°C/LSB)、TriggerCondition(uint8_t, 0=Avg/1=Max)、TriggerRoiX/Y/W/H(uint16_t)、TargetWidth/Height(uint16_t)、NGioDelay(uint16_t, ms)
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 1D 采集模式
|
||||||
|
- **WHEN** 对接方发送 Config1D 配置帧时
|
||||||
|
- **THEN** 文档 SHALL 提供 Config1D_t 所有字段:Enabled(uint8_t)、RunMode(uint8_t, 0=STOP/1=RUN)、TriggerType(uint8_t, 1=内部/2=外部)、TriggerTempLimit(int16_t)、HighTimerLimit(uint16_t, ms)、NgCountLimit(uint16_t, 帧数)、BufferSize(uint16_t)、LSizeStart/RSizeStart(uint16_t, 切片参数)
|
||||||
|
|
||||||
|
### Requirement: 温度帧数据包格式说明
|
||||||
|
文档 SHALL 描述 MCU 上报的温度帧数据包的完整格式,使对接方能正确解析。
|
||||||
|
|
||||||
|
#### Scenario: 对接方解析收到的 2D 温度帧
|
||||||
|
- **WHEN** 数据流收到 TYPE_TEMP_FRAME 包时
|
||||||
|
- **THEN** 文档 SHALL 说明:Value 区域含 PreprocessResult 元数据(ValidWidth × ValidHeight 像素数组);像素为 uint16_t little-endian,单位 0.1°C/LSB;元数据含 MaxTemp/MinTemp/AvgTemp/RoiTemp(均 int16_t, 0.1°C/LSB)、ValidWidth/ValidHeight(实际 ROI 尺寸)、FrameNumber
|
||||||
|
|
||||||
|
#### Scenario: 对接方解析收到的 1D 温度帧
|
||||||
|
- **WHEN** 数据流收到 1D TYPE_TEMP_FRAME 包时
|
||||||
|
- **THEN** 文档 SHALL 说明:is2D=0,每个样本 4 字节:[time_off_lo, time_off_hi, temp_lo, temp_hi],time_offset 为相对采集开始的 ms 偏移(uint16_t),temp 为 uint16_t 0.1°C/LSB
|
||||||
|
|
||||||
|
### Requirement: 触发结果上报和 NG 响应机制说明
|
||||||
|
文档 SHALL 描述采集端如何响应服务器的 DetectionResult 决策和触发 NG 输出。
|
||||||
|
|
||||||
|
#### Scenario: 对接方下发检测结果
|
||||||
|
- **WHEN** 服务器完成图像分析后回传结果时
|
||||||
|
- **THEN** 文档 SHALL 说明:服务器发送 TYPE_DETECTION_RESULT(含 frameNumber、resultStatus,0=NG/1=OK);MCU 收到 resultStatus=0 时触发 PA8 高电平 NGioDelay ms(默认 200ms);对接方可通过 NGioDelay 配置 NG 输出脉宽
|
||||||
|
|
||||||
|
### Requirement: TEMP_REQ 按需截图说明
|
||||||
|
文档 SHALL 描述服务器如何向 MCU 请求即时截图。
|
||||||
|
|
||||||
|
#### Scenario: 对接方请求单帧截图
|
||||||
|
- **WHEN** 服务器需要调试或核查当前热场时
|
||||||
|
- **THEN** 文档 SHALL 说明:控制流发送 TYPE_CONFIG_COMMON 或专用 TEMP_REQ 命令(含 is2dRequest 字段);MCU 将在下一个业务循环发送当前最新帧,2D 返回 ROI 裁切后温度矩阵,1D 返回中心行 30 个等间距采样点;仅在对应模式已 Enabled 时响应
|
||||||
|
|
||||||
|
### Requirement: 错误码和异常处理说明
|
||||||
|
文档 SHALL 列出协议层所有错误码含义及对接方应采取的处理措施。
|
||||||
|
|
||||||
|
#### Scenario: 对接方收到 ERR 响应帧
|
||||||
|
- **WHEN** 服务器发送格式错误的配置帧时
|
||||||
|
- **THEN** 文档 SHALL 列出错误码含义:ERR_CRC(0x1001)=CRC 校验失败、ERR_VERSION(0x1002)=协议版本不匹配、ERR_LENGTH(0x1003)=帧长度异常、ERR_AUTH(0x2001)=认证失败、ERR_BUSY(0x2002)=设备忙、ERR_PARAM(0x3001)=参数非法;收到错误码后对接方 SHALL 检查发送的帧格式后重试
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: TLV 帧结构完整描述
|
||||||
|
文档 SHALL 描述 QDX 协议的完整帧结构,包含帧头各字段含义、TLV 格式和帧尾 CRC。
|
||||||
|
|
||||||
|
#### Scenario: 开发者手动构造一个协议帧
|
||||||
|
- **WHEN** 开发者需要调试发送内容时
|
||||||
|
- **THEN** 文档 SHALL 提供帧结构:[FrameHeader 16B] + [TLV_Header 3B] + [Value NB] + [CRC16 2B],其中 FrameHeader = {Magic(2)=0x55AA, Version(1)=0x20, Length(2), Sequence(2), Timestamp(4), Source(1), DevID(2), Class(1), Flags(1)}
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 Class 和 TLV Type 的对应关系
|
||||||
|
- **WHEN** 开发者解析收到的帧时
|
||||||
|
- **THEN** 文档 SHALL 列出完整的 Class/Type 映射表:CLASS_CONTROL(0x01)、CLASS_DATA(0x02)、CLASS_RESPONSE(0x03)、CLASS_SYSTEM(0x04);Type 包含 TYPE_HANDSHAKE(0x01)、TYPE_HEARTBEAT(0x02)、TYPE_TEMP_FRAME(0x10)、TYPE_CONFIG_COMMON(0x20)、TYPE_CONFIG_2D(0x22)、TYPE_CONFIG_1D(0x23)、TYPE_ACK_PAYLOAD(0x30)、TYPE_DETECTION_RESULT(0x40) 等
|
||||||
|
|
||||||
|
### Requirement: 零拷贝 TX 缓冲区架构描述
|
||||||
|
文档 SHALL 描述 TcpTxBuffer_t 的双缓冲零拷贝发送架构和 HeadOffset 的使用方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解零拷贝的实现方式
|
||||||
|
- **WHEN** 开发者需要在减少内存拷贝的前提下添加新的帧类型时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpTxBuffer_t` 包含 pBuffer(9216B)、HeadOffset(64B)、ValidPayloadLen;图像数据由预处理模块写入 `pBuffer + HeadOffset` 开始的位置;`TcpLogic_BuildAndSendTemperatureFrame` 在 HeadOffset 前的 64 字节内直接写入帧头(FrameHeader + TLV),**无需额外 memcpy**
|
||||||
|
|
||||||
|
#### Scenario: 开发者计算单次可发送的最大像素数
|
||||||
|
- **WHEN** 开发者调整 ROI 大小时
|
||||||
|
- **THEN** 文档 SHALL 说明:有效载荷容量 = TotalCapacity(9216) - HeadOffset(64) - CRC(2) - TLV_Header(3) - FrameHeader(16) = 9131 字节,每像素 2 字节,最多 4565 个像素(约 67×67 ROI)
|
||||||
|
|
||||||
|
### Requirement: 分片机制描述
|
||||||
|
文档 SHALL 描述协议层的分片上限和分片标志用法。
|
||||||
|
|
||||||
|
#### Scenario: 开发者发送超大载荷时
|
||||||
|
- **WHEN** 开发者发送超过 MAX_FRAGMENT_PAYLOAD(1400B) 的数据时
|
||||||
|
- **THEN** 文档 SHALL 说明分片最大载荷为 1400B(受 TCP MSS=1460 限制),大帧通过 FLAG_LAST_FRAGMENT(0x20) 标志标识末片;当前 TX 缓冲区为 9216B,单次发送通常不超过此限制
|
||||||
|
|
||||||
|
### Requirement: Flags 字段用法描述
|
||||||
|
文档 SHALL 描述 FrameHeader.Flags 各位的含义和当前固件的使用情况。
|
||||||
|
|
||||||
|
#### Scenario: 开发者解析收到的 Flags 字段
|
||||||
|
- **WHEN** 上位机解析帧时
|
||||||
|
- **THEN** 文档 SHALL 说明:FLAG_PRIORITY_MASK(0x03)=优先级(0-3)、FLAG_COMPRESSED(0x04)=压缩(当前未使用)、FLAG_ENCRYPTED(0x08)=加密(当前未使用)、FLAG_ACK_REQ(0x10)=需要 ACK、FLAG_LAST_FRAGMENT(0x20)=末片标志
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: FreeRTOS 任务拓扑描述
|
||||||
|
文档 SHALL 列出所有 RTOS 任务的名称、优先级、栈大小、职责及任务间通信方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者查阅任务优先级
|
||||||
|
- **WHEN** 开发者需要了解任务调度顺序时
|
||||||
|
- **THEN** 文档 SHALL 提供任务优先级表,包含:task_wchnet(优先级 6,512 字)、task_business(优先级 5,512 字)、task_heartbeat(优先级 3,256 字)、task_test_pattern(优先级 4,256 字,仅 TEST_PATTERN_MODE=1 时存在)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解任务间数据流
|
||||||
|
- **WHEN** 开发者需要理解 DVP → 业务任务的数据传递时
|
||||||
|
- **THEN** 文档 SHALL 说明通过 `Frame_Ready_Flag`(volatile 标志)和 `FrameBuffer`(共享内存)传递帧数据,IRQ 置位,业务任务轮询消费
|
||||||
|
|
||||||
|
### Requirement: 系统启动序列描述
|
||||||
|
文档 SHALL 描述 `main()` 的初始化顺序,涵盖所有外设和模块的初始化步骤及其依赖关系。
|
||||||
|
|
||||||
|
#### Scenario: 开发者定位初始化顺序问题
|
||||||
|
- **WHEN** 开发者遇到启动相关 bug 时
|
||||||
|
- **THEN** 文档 SHALL 提供启动序列:SystemClock → USART3/debug → Flash/SRAM 配置(128K+192K)→ DVP_Init → TIM2_Init → ExtTrigger_GPIO → NG_GPIO → ETH_LibInit → qdx_port_init → Preprocess_Init → TcpLogic_Init → 注册回调 → TcpLogic_Start → 创建 RTOS 任务 → vTaskStartScheduler
|
||||||
|
|
||||||
|
### Requirement: 2D 状态机完整描述
|
||||||
|
文档 SHALL 描述 2D 触发状态机的所有状态、转换条件和行为,包含外部触发和内部触发两种模式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 2D 外部触发流程
|
||||||
|
- **WHEN** 开发者排查外部触发抖动问题时
|
||||||
|
- **THEN** 文档 SHALL 描述:IDLE → [PA15 上升沿] → DEBOUNCE(等待 DebounceIntervalMs) → DELAY(等待 TriggerDelayMs) → BURST(发 BurstCount 帧,间隔 TriggerInternalIntervalMs) → IDLE
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 2D 内部触发流程
|
||||||
|
- **WHEN** 开发者配置温度阈值触发时
|
||||||
|
- **THEN** 文档 SHALL 描述:每帧调用 `Preprocess_CheckInternalTrigger2D`,在 TriggerRoi 区域内 Max/Avg 超过 TriggerTemperatureThreshold 时触发,跳过 DEBOUNCE 直接进入 DELAY → BURST
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解双缓冲 TX 逻辑
|
||||||
|
- **WHEN** 开发者排查发送顺序问题时
|
||||||
|
- **THEN** 文档 SHALL 说明 `use_buffer_A` 标志在每次发送时取反,g_TxNetBuffer_A / g_TxNetBuffer_B 交替使用,避免前一帧发送未完成时被覆盖
|
||||||
|
|
||||||
|
### Requirement: 1D 状态机完整描述
|
||||||
|
文档 SHALL 描述 1D 采集状态机的所有状态、转换条件、采集逻辑和发送条件。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 外部触发流程
|
||||||
|
- **WHEN** 开发者配置 TriggerType=2 时
|
||||||
|
- **THEN** 文档 SHALL 描述:S1D_IDLE → [PA15 上升沿] → S1D_DEBOUNCE(等待 HighTimerLimit ms) → S1D_COLLECTING(采集 temp+time 样本直到停止条件)→ 发送 → S1D_IDLE
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 内部触发流程
|
||||||
|
- **WHEN** 开发者配置 TriggerType=1 时
|
||||||
|
- **THEN** 文档 SHALL 描述:S1D_IDLE 中维护 3 帧滑动窗口,连续 3 帧帧最大温度 ≥ TriggerTempLimit 时进入 S1D_COLLECTING;停止条件:样本数达 BufferSize 或触发后连续 NgCountLimit 帧低温
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 数据格式
|
||||||
|
- **WHEN** 开发者解析 1D 数据时
|
||||||
|
- **THEN** 文档 SHALL 说明每个样本 4 字节:[time_offset_lo, time_offset_hi, temp_lo, temp_hi],time_offset 为相对采集开始的 ms 偏移,temp 为 0.1°C/LSB 的 uint16_t
|
||||||
|
|
||||||
|
### Requirement: TEMP_REQ 辅助通道描述
|
||||||
|
文档 SHALL 描述服务器按需截图(TEMP_REQ)的触发流程和限制条件。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 TEMP_REQ 工作方式
|
||||||
|
- **WHEN** 服务器发送 TEMP_REQ 命令时
|
||||||
|
- **THEN** 文档 SHALL 说明:`g_temp_req_pending` 置位,业务任务在下次循环读取当前 FrameBuffer 发送,is2D=1 走 Preprocess_Execute + 2D 封包,is2D=0 走 send_1d_snapshot(30 个等间距采样点);仅在对应模式 Enabled 时响应
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 双流连接架构描述
|
||||||
|
文档 SHALL 描述控制流(5511)和数据流(5512)的职责分工、连接方式和状态管理。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解双流设计意图
|
||||||
|
- **WHEN** 开发者排查连接问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:控制流 srcport=5511(MCU 主动连接 Server),用于握手、配置下发、心跳、TEMP_REQ、DetectionResult 上报;数据流 desport=5512(MCU 主动连接 Server),用于温度帧数据上报;两流独立管理,数据流断开不影响控制流
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 TCP 连接建立时序
|
||||||
|
- **WHEN** 开发者排查首次握手失败时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_Start` 派生后台连接状态机,先尝试控制流连接 → 连接成功后发送 TYPE_HANDSHAKE(含 DeviceUUID=MAC地址、AuthToken=NULL)→ 收到服务器握手响应后标记连接就绪 → 同步尝试数据流连接
|
||||||
|
|
||||||
|
### Requirement: 心跳机制描述
|
||||||
|
文档 SHALL 描述心跳帧的发送周期、TCP Keepalive 配置和连接保活策略。
|
||||||
|
|
||||||
|
#### Scenario: 开发者调整心跳参数
|
||||||
|
- **WHEN** 网络中间件(NAT/防火墙)超时断连时
|
||||||
|
- **THEN** 文档 SHALL 说明:应用层心跳 TYPE_HEARTBEAT 由 TcpLogic 内部定时发送;TCP Keepalive 配置:空闲 20 秒(`idle=20000ms`)后开始探测,每次间隔 15 秒(`interval=15000ms`),最多 9 次(`count=9`)探测无响应则断链重连
|
||||||
|
|
||||||
|
### Requirement: 配置下发与缓存机制描述
|
||||||
|
文档 SHALL 描述服务器配置的接收、缓存和通知流程。
|
||||||
|
|
||||||
|
#### Scenario: 开发者追踪配置更新流程
|
||||||
|
- **WHEN** 服务器下发配置后行为未按预期改变时
|
||||||
|
- **THEN** 文档 SHALL 描述:服务器发送 TYPE_CONFIG_COMMON + TYPE_CONFIG_2D + TYPE_CONFIG_1D → TcpLogic 解析并缓存至内部 shadow 寄存器 → 触发 ConfigUpdateCallback(`OnConfigUpdate`)→ 回调内调用 `Preprocess_Settings_Change` 更新预处理参数;`TcpLogic_GetLatestConfig` 可随时读取最新配置副本
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解无配置时的默认行为
|
||||||
|
- **WHEN** 设备启动后服务器尚未下发配置时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_GetLatestConfig` 返回 -1,业务任务跳过 2D/1D 处理,仅响应 TEMP_REQ(若有);默认 burst 参数 DEFAULT_BURST_COUNT=3、DEFAULT_BURST_INTERVAL_MS=200ms 作为 fallback
|
||||||
|
|
||||||
|
### Requirement: 数据发送队列和背压处理描述
|
||||||
|
文档 SHALL 描述温度帧发送的队列机制和发送失败时的处理策略。
|
||||||
|
|
||||||
|
#### Scenario: 开发者排查数据帧丢失问题
|
||||||
|
- **WHEN** 网络繁忙时出现帧丢失时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_BuildAndSendTemperatureFrame` 返回 0 表示成功入队,返回 <0 表示发送失败(队列满或连接断开);当前固件对发送失败仅打印 DBG_ERR,不重试;双缓冲 TX 保证当前帧被入队时,下一帧可立即写入另一个缓冲
|
||||||
|
|
||||||
|
### Requirement: WCHNET 网络栈驱动任务描述
|
||||||
|
文档 SHALL 描述 task_wchnet 的职责和与 WCHNET 库的交互方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解网络栈如何被驱动
|
||||||
|
- **WHEN** 开发者排查网络响应迟缓问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:`task_wchnet_entry` 每 5ms 轮询一次,调用 `WCHNET_MainTask`(协议栈心跳)和 `WCHNET_HandleGlobalInt`(socket 事件分发);socket 接收/连接/断开事件通过 `qdx_port_sock_*_notify` 转发给 QDX 网络层;`qdx_port_net_lock/unlock` 用互斥量保护 WCHNET 调用,防止业务任务和网络任务并发访问
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
## 1. 系统总览文档
|
||||||
|
|
||||||
|
- [x] 1.1 创建 `Doc/设计文档/系统总览.md`,包含项目背景和硬件平台(MCU、传感器、网络)概述
|
||||||
|
- [x] 1.2 编写 FreeRTOS 任务拓扑表:四个任务的名称、优先级、栈大小、职责
|
||||||
|
- [x] 1.3 编写系统启动序列说明,覆盖外设初始化到 vTaskStartScheduler 的完整步骤
|
||||||
|
- [x] 1.4 编写 2D 状态机流程:IDLE → DEBOUNCE → DELAY → BURST,含触发条件和参数
|
||||||
|
- [x] 1.5 编写 1D 状态机流程:S1D_IDLE → S1D_DEBOUNCE → S1D_COLLECTING,含停止条件
|
||||||
|
- [x] 1.6 编写 TEMP_REQ 实时温度请求处理流程(按需单帧下发逻辑)
|
||||||
|
- [x] 1.7 补充任务间同步机制说明(Frame_Ready_Flag、信号量、帧缓冲双 Buffer 策略)
|
||||||
|
|
||||||
|
## 2. DVP 模块设计文档
|
||||||
|
|
||||||
|
- [x] 2.1 创建 `Doc/设计文档/DVP模块设计.md`,包含 DVP 硬件连接引脚说明
|
||||||
|
- [x] 2.2 描述 DVP 时序配置:VSYNC 极性、PCLK 极性、CMOS-8bit 模式
|
||||||
|
- [x] 2.3 描述 DMA ping-pong 机制:两个 DMA 通道交替接收行数据(512 B/行)
|
||||||
|
- [x] 2.4 描述 DVP IRQ 处理逻辑:STR_FRM 重置行计数、ROW_DONE 累积行、第 191 行置就绪标志
|
||||||
|
- [x] 2.5 描述帧缓冲格式:`uint8_t[192][512]` = 256 列 × 2 字节/像素,小端序 uint16_t,单位 0.1°C
|
||||||
|
- [x] 2.6 描述 TMP 模式像素换算公式(uint16_t × 0.1 = 摄氏度)
|
||||||
|
|
||||||
|
## 3. QDX 协议设计文档
|
||||||
|
|
||||||
|
- [x] 3.1 创建 `Doc/设计文档/QDX协议设计.md`,描述帧头 FrameHeader_t 各字段含义和长度
|
||||||
|
- [x] 3.2 描述 TLV 结构:Type(1 B) + Length(2 B, LE) + Value(N B) 及嵌套帧 = FrameHeader + TLV
|
||||||
|
- [x] 3.3 列出所有帧类型(Class × Type):HANDSHAKE、HEARTBEAT、TEMP_FRAME、CONFIG_COMMON 等
|
||||||
|
- [x] 3.4 描述零拷贝 TX 方案:TcpTxBuffer 容量 9216 B,HeadOffset=64,帧头在发送前向前写入
|
||||||
|
- [x] 3.5 描述 TX 分片规则:单 TLV Value 超过 9131 B 时触发分片,Flags.FRAGMENT 置位
|
||||||
|
- [x] 3.6 描述 Sequence 字段单调递增规则和接收端顺序验证要求
|
||||||
|
|
||||||
|
## 4. TCP 通信模块设计文档
|
||||||
|
|
||||||
|
- [x] 4.1 创建 `Doc/设计文档/TCP通信模块设计.md`,说明双流架构:控制流(5511)+ 数据流(5512)
|
||||||
|
- [x] 4.2 描述各套接字专属职责:控制流负责握手/配置/心跳,数据流负责温度帧推送
|
||||||
|
- [x] 4.3 描述 TCP Keepalive 参数:idle=20000 ms,interval=15000 ms,count=9
|
||||||
|
- [x] 4.4 描述服务端断线重连机制和连接状态的管理方式
|
||||||
|
- [x] 4.5 描述配置缓存机制:配置通过控制流接收后缓存,重连后自动恢复
|
||||||
|
- [x] 4.6 描述 WCHNET 驱动任务(task_wchnet_entry)的轮询周期和优先级要求
|
||||||
|
|
||||||
|
## 5. 对接集成指南
|
||||||
|
|
||||||
|
- [x] 5.1 创建 `Doc/设计文档/对接集成指南.md`,包含网络接入参数表(IP、端口、协议)
|
||||||
|
- [x] 5.2 编写握手流程章节:双流连接顺序、HANDSHAKE 帧格式、DevID 验证
|
||||||
|
- [x] 5.3 编写配置下发章节:Config2D_t 全字段说明和推荐初始值表
|
||||||
|
- [x] 5.4 编写 1D 配置下发章节:Config1D_t 全字段说明、TriggerType 枚举值
|
||||||
|
- [x] 5.5 编写温度帧接收章节:2D 矩阵帧解析步骤(像素排列、单位换算)
|
||||||
|
- [x] 5.6 编写 1D 时序帧接收章节:Sample 结构(4 B:time_lo/hi + temp_lo/hi)解析步骤
|
||||||
|
- [x] 5.7 编写 DetectionResult 和 NG 输出说明:DetectionResult TLV 字段、NG GPIO(PA8)电平含义
|
||||||
|
- [x] 5.8 编写 TEMP_REQ 请求方法:主动下发 TEMP_REQ 帧触发单帧返回
|
||||||
|
- [x] 5.9 编写常见错误码表和对接故障排查步骤
|
||||||
53
openspec/specs/dvp-module-design/spec.md
Normal file
53
openspec/specs/dvp-module-design/spec.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# dvp-module-design Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change software-design-doc. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: DVP 硬件初始化配置描述
|
||||||
|
文档 SHALL 描述 DVP 外设的完整初始化配置,包含 GPIO 引脚映射、工作模式和信号极性。
|
||||||
|
|
||||||
|
#### Scenario: 开发者核查 DVP GPIO 引脚分配
|
||||||
|
- **WHEN** 开发者进行硬件调试时
|
||||||
|
- **THEN** 文档 SHALL 提供引脚映射表:PA4/5/6/9/10(数据线 D0-D4)、PC8/9/11(D5-D7 + PCLK)、PB3/8/9(VSYNC/HSYNC/PIXCLK),所有引脚配置为浮空输入
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 DVP 信号极性配置
|
||||||
|
- **WHEN** 开发者对接不同传感器时
|
||||||
|
- **THEN** 文档 SHALL 说明:VSYNC 高有效(RB_DVP_V_POLAR=1,对应 DIGITAL_FIELD_VALID 高电平有效帧)、HSYNC 高有效(RB_DVP_H_POLAR=0)、PCLK 上升沿采样(RB_DVP_P_POLAR=0),与 Mini212G2 手册时序图一致
|
||||||
|
|
||||||
|
### Requirement: DMA ping-pong 行缓冲机制描述
|
||||||
|
文档 SHALL 描述 DVP DMA 双缓冲的工作原理,包含缓冲切换逻辑和 IRQ 中的正确读取方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 DMA 缓冲切换逻辑
|
||||||
|
- **WHEN** 开发者排查行数据乱序问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:DVP 硬件在 DMA_BUF0/BUF1 间自动切换,ROW_DONE 中断触发时硬件已切换到下一个缓冲;BUF_TOG=1 表示 DMA 正在写 BUF0,此时应读 BUF1(公式:`src = (CR1 & BUF_TOG) ? DMA_LineBuf0 : DMA_LineBuf1`)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解行缓冲大小约束
|
||||||
|
- **WHEN** 开发者修改传感器分辨率时
|
||||||
|
- **THEN** 文档 SHALL 说明:DMA_LineBuf0/1 各 512 字节(256 像素 × 2 字节),COL_NUM=512,ROW_NUM=1(每行触发一次 IRQ),修改分辨率需同时更新 `BYTES_PER_LINE`、`SENSOR_WIDTH`、`SENSOR_HEIGHT`
|
||||||
|
|
||||||
|
### Requirement: DVP IRQ 帧组装逻辑描述
|
||||||
|
文档 SHALL 描述 `DVP_IRQHandler` 的完整逻辑,包含帧同步机制和 FrameBuffer 组装方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解帧同步锚点
|
||||||
|
- **WHEN** 开发者排查帧错位问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:STR_FRM 中断(VSYNC 上升沿)将 `current_line_idx` 清零,此为帧同步唯一锚点;ROW_DONE 中断依次将行数据 memcpy 至 `FrameBuffer[current_line_idx]` 并递增
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解帧完成标志
|
||||||
|
- **WHEN** 开发者需要知道何时帧数据可用时
|
||||||
|
- **THEN** 文档 SHALL 说明:当 `idx == SENSOR_HEIGHT - 1`(即第 191 行完成)时,`Frame_Ready_Flag` 置 1,`Ready_Frame_Count` 更新为当前帧号;业务任务轮询此标志,读取后将其清零
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 IRQ 使用 fast interrupt 属性的原因
|
||||||
|
- **WHEN** 开发者修改 IRQ 属性时
|
||||||
|
- **THEN** 文档 SHALL 说明:`__attribute__((interrupt("WCH-Interrupt-fast")))` 使中断处理跳过寄存器入栈,减少中断延迟,确保在下一行像素时钟到来前完成 memcpy(每行约 42.67µs @ 25Hz)
|
||||||
|
|
||||||
|
### Requirement: FrameBuffer 数据格式描述
|
||||||
|
文档 SHALL 描述 FrameBuffer 的内存布局和像素值含义。
|
||||||
|
|
||||||
|
#### Scenario: 开发者访问特定像素的温度值
|
||||||
|
- **WHEN** 开发者需要读取第 row 行第 col 列的温度时
|
||||||
|
- **THEN** 文档 SHALL 说明:`FrameBuffer` 声明为 `uint8_t[192][512]`,通过 `(uint16_t*)FrameBuffer` 访问,像素值 = `((uint16_t*)FrameBuffer)[row * 256 + col]`,单位 0.1°C/LSB(TMP 模式,需传感器预配置为 TMP)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解字节序要求
|
||||||
|
- **WHEN** 开发者移植到大端序平台时
|
||||||
|
- **THEN** 文档 SHALL 说明:CH32V307 为小端序,`uint8_t[0]` = 低字节,`uint8_t[1]` = 高字节;传感器必须配置为 CMOS8(LSB)(低字节先发),否则温度值字节反序
|
||||||
|
|
||||||
62
openspec/specs/integration-guide/spec.md
Normal file
62
openspec/specs/integration-guide/spec.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# integration-guide Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change software-design-doc. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: 对接方网络接入参数说明
|
||||||
|
文档 SHALL 提供上位机(ConfigServer)与采集端建立 TCP 连接所需的全部参数。
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 TCP Server 监听端口
|
||||||
|
- **WHEN** 对接方开发服务端程序时
|
||||||
|
- **THEN** 文档 SHALL 提供:MCU 默认 IP=192.168.7.10,Subnet=255.255.255.0,Gateway=192.168.7.1;Server 需在 192.168.7.50 监听两个端口:5511(控制流,MCU 主动连入)和 5512(数据流,MCU 主动连入);以太网接口为 1000M RGMII
|
||||||
|
|
||||||
|
### Requirement: 握手流程说明
|
||||||
|
文档 SHALL 描述 MCU 上电后的握手过程,使对接方能正确识别采集端设备。
|
||||||
|
|
||||||
|
#### Scenario: 对接方接收第一个数据包并识别设备
|
||||||
|
- **WHEN** MCU 连接后发送握手帧时
|
||||||
|
- **THEN** 文档 SHALL 说明:MCU 在控制流连接成功后发送 TYPE_HANDSHAKE(Class=CLASS_SYSTEM,含 ProtocolVersion=0x0200、DeviceUUID=MAC 地址、AuthToken=全零);服务器 SHALL 回复握手响应以完成握手;握手完成后 MCU 才开始正常数据上报
|
||||||
|
|
||||||
|
### Requirement: 配置下发格式说明
|
||||||
|
文档 SHALL 描述服务器向 MCU 下发配置的帧格式和所有配置字段含义。
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 2D 触发模式
|
||||||
|
- **WHEN** 对接方发送 Config2D 配置帧时
|
||||||
|
- **THEN** 文档 SHALL 提供 Config2D_t 所有字段:Enabled(uint8_t)、TriggerMode(uint8_t, 0=内部温度/1=外部GPIO)、TriggerDebounceIntervalMs(uint16_t)、TriggerDelayMs(uint16_t)、TriggerBurstCount(uint8_t)、TriggerInternalIntervalMs(uint16_t)、TriggerTemperatureThreshold(int16_t, 0.1°C/LSB)、TriggerCondition(uint8_t, 0=Avg/1=Max)、TriggerRoiX/Y/W/H(uint16_t)、TargetWidth/Height(uint16_t)、NGioDelay(uint16_t, ms)
|
||||||
|
|
||||||
|
#### Scenario: 对接方配置 1D 采集模式
|
||||||
|
- **WHEN** 对接方发送 Config1D 配置帧时
|
||||||
|
- **THEN** 文档 SHALL 提供 Config1D_t 所有字段:Enabled(uint8_t)、RunMode(uint8_t, 0=STOP/1=RUN)、TriggerType(uint8_t, 1=内部/2=外部)、TriggerTempLimit(int16_t)、HighTimerLimit(uint16_t, ms)、NgCountLimit(uint16_t, 帧数)、BufferSize(uint16_t)、LSizeStart/RSizeStart(uint16_t, 切片参数)
|
||||||
|
|
||||||
|
### Requirement: 温度帧数据包格式说明
|
||||||
|
文档 SHALL 描述 MCU 上报的温度帧数据包的完整格式,使对接方能正确解析。
|
||||||
|
|
||||||
|
#### Scenario: 对接方解析收到的 2D 温度帧
|
||||||
|
- **WHEN** 数据流收到 TYPE_TEMP_FRAME 包时
|
||||||
|
- **THEN** 文档 SHALL 说明:Value 区域含 PreprocessResult 元数据(ValidWidth × ValidHeight 像素数组);像素为 uint16_t little-endian,单位 0.1°C/LSB;元数据含 MaxTemp/MinTemp/AvgTemp/RoiTemp(均 int16_t, 0.1°C/LSB)、ValidWidth/ValidHeight(实际 ROI 尺寸)、FrameNumber
|
||||||
|
|
||||||
|
#### Scenario: 对接方解析收到的 1D 温度帧
|
||||||
|
- **WHEN** 数据流收到 1D TYPE_TEMP_FRAME 包时
|
||||||
|
- **THEN** 文档 SHALL 说明:is2D=0,每个样本 4 字节:[time_off_lo, time_off_hi, temp_lo, temp_hi],time_offset 为相对采集开始的 ms 偏移(uint16_t),temp 为 uint16_t 0.1°C/LSB
|
||||||
|
|
||||||
|
### Requirement: 触发结果上报和 NG 响应机制说明
|
||||||
|
文档 SHALL 描述采集端如何响应服务器的 DetectionResult 决策和触发 NG 输出。
|
||||||
|
|
||||||
|
#### Scenario: 对接方下发检测结果
|
||||||
|
- **WHEN** 服务器完成图像分析后回传结果时
|
||||||
|
- **THEN** 文档 SHALL 说明:服务器发送 TYPE_DETECTION_RESULT(含 frameNumber、resultStatus,0=NG/1=OK);MCU 收到 resultStatus=0 时触发 PA8 高电平 NGioDelay ms(默认 200ms);对接方可通过 NGioDelay 配置 NG 输出脉宽
|
||||||
|
|
||||||
|
### Requirement: TEMP_REQ 按需截图说明
|
||||||
|
文档 SHALL 描述服务器如何向 MCU 请求即时截图。
|
||||||
|
|
||||||
|
#### Scenario: 对接方请求单帧截图
|
||||||
|
- **WHEN** 服务器需要调试或核查当前热场时
|
||||||
|
- **THEN** 文档 SHALL 说明:控制流发送 TYPE_CONFIG_COMMON 或专用 TEMP_REQ 命令(含 is2dRequest 字段);MCU 将在下一个业务循环发送当前最新帧,2D 返回 ROI 裁切后温度矩阵,1D 返回中心行 30 个等间距采样点;仅在对应模式已 Enabled 时响应
|
||||||
|
|
||||||
|
### Requirement: 错误码和异常处理说明
|
||||||
|
文档 SHALL 列出协议层所有错误码含义及对接方应采取的处理措施。
|
||||||
|
|
||||||
|
#### Scenario: 对接方收到 ERR 响应帧
|
||||||
|
- **WHEN** 服务器发送格式错误的配置帧时
|
||||||
|
- **THEN** 文档 SHALL 列出错误码含义:ERR_CRC(0x1001)=CRC 校验失败、ERR_VERSION(0x1002)=协议版本不匹配、ERR_LENGTH(0x1003)=帧长度异常、ERR_AUTH(0x2001)=认证失败、ERR_BUSY(0x2002)=设备忙、ERR_PARAM(0x3001)=参数非法;收到错误码后对接方 SHALL 检查发送的帧格式后重试
|
||||||
|
|
||||||
41
openspec/specs/qdx-protocol-design/spec.md
Normal file
41
openspec/specs/qdx-protocol-design/spec.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# qdx-protocol-design Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change software-design-doc. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: TLV 帧结构完整描述
|
||||||
|
文档 SHALL 描述 QDX 协议的完整帧结构,包含帧头各字段含义、TLV 格式和帧尾 CRC。
|
||||||
|
|
||||||
|
#### Scenario: 开发者手动构造一个协议帧
|
||||||
|
- **WHEN** 开发者需要调试发送内容时
|
||||||
|
- **THEN** 文档 SHALL 提供帧结构:[FrameHeader 16B] + [TLV_Header 3B] + [Value NB] + [CRC16 2B],其中 FrameHeader = {Magic(2)=0x55AA, Version(1)=0x20, Length(2), Sequence(2), Timestamp(4), Source(1), DevID(2), Class(1), Flags(1)}
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 Class 和 TLV Type 的对应关系
|
||||||
|
- **WHEN** 开发者解析收到的帧时
|
||||||
|
- **THEN** 文档 SHALL 列出完整的 Class/Type 映射表:CLASS_CONTROL(0x01)、CLASS_DATA(0x02)、CLASS_RESPONSE(0x03)、CLASS_SYSTEM(0x04);Type 包含 TYPE_HANDSHAKE(0x01)、TYPE_HEARTBEAT(0x02)、TYPE_TEMP_FRAME(0x10)、TYPE_CONFIG_COMMON(0x20)、TYPE_CONFIG_2D(0x22)、TYPE_CONFIG_1D(0x23)、TYPE_ACK_PAYLOAD(0x30)、TYPE_DETECTION_RESULT(0x40) 等
|
||||||
|
|
||||||
|
### Requirement: 零拷贝 TX 缓冲区架构描述
|
||||||
|
文档 SHALL 描述 TcpTxBuffer_t 的双缓冲零拷贝发送架构和 HeadOffset 的使用方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解零拷贝的实现方式
|
||||||
|
- **WHEN** 开发者需要在减少内存拷贝的前提下添加新的帧类型时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpTxBuffer_t` 包含 pBuffer(9216B)、HeadOffset(64B)、ValidPayloadLen;图像数据由预处理模块写入 `pBuffer + HeadOffset` 开始的位置;`TcpLogic_BuildAndSendTemperatureFrame` 在 HeadOffset 前的 64 字节内直接写入帧头(FrameHeader + TLV),**无需额外 memcpy**
|
||||||
|
|
||||||
|
#### Scenario: 开发者计算单次可发送的最大像素数
|
||||||
|
- **WHEN** 开发者调整 ROI 大小时
|
||||||
|
- **THEN** 文档 SHALL 说明:有效载荷容量 = TotalCapacity(9216) - HeadOffset(64) - CRC(2) - TLV_Header(3) - FrameHeader(16) = 9131 字节,每像素 2 字节,最多 4565 个像素(约 67×67 ROI)
|
||||||
|
|
||||||
|
### Requirement: 分片机制描述
|
||||||
|
文档 SHALL 描述协议层的分片上限和分片标志用法。
|
||||||
|
|
||||||
|
#### Scenario: 开发者发送超大载荷时
|
||||||
|
- **WHEN** 开发者发送超过 MAX_FRAGMENT_PAYLOAD(1400B) 的数据时
|
||||||
|
- **THEN** 文档 SHALL 说明分片最大载荷为 1400B(受 TCP MSS=1460 限制),大帧通过 FLAG_LAST_FRAGMENT(0x20) 标志标识末片;当前 TX 缓冲区为 9216B,单次发送通常不超过此限制
|
||||||
|
|
||||||
|
### Requirement: Flags 字段用法描述
|
||||||
|
文档 SHALL 描述 FrameHeader.Flags 各位的含义和当前固件的使用情况。
|
||||||
|
|
||||||
|
#### Scenario: 开发者解析收到的 Flags 字段
|
||||||
|
- **WHEN** 上位机解析帧时
|
||||||
|
- **THEN** 文档 SHALL 说明:FLAG_PRIORITY_MASK(0x03)=优先级(0-3)、FLAG_COMPRESSED(0x04)=压缩(当前未使用)、FLAG_ENCRYPTED(0x08)=加密(当前未使用)、FLAG_ACK_REQ(0x10)=需要 ACK、FLAG_LAST_FRAGMENT(0x20)=末片标志
|
||||||
|
|
||||||
60
openspec/specs/system-overview/spec.md
Normal file
60
openspec/specs/system-overview/spec.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# system-overview Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change software-design-doc. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: FreeRTOS 任务拓扑描述
|
||||||
|
文档 SHALL 列出所有 RTOS 任务的名称、优先级、栈大小、职责及任务间通信方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者查阅任务优先级
|
||||||
|
- **WHEN** 开发者需要了解任务调度顺序时
|
||||||
|
- **THEN** 文档 SHALL 提供任务优先级表,包含:task_wchnet(优先级 6,512 字)、task_business(优先级 5,512 字)、task_heartbeat(优先级 3,256 字)、task_test_pattern(优先级 4,256 字,仅 TEST_PATTERN_MODE=1 时存在)
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解任务间数据流
|
||||||
|
- **WHEN** 开发者需要理解 DVP → 业务任务的数据传递时
|
||||||
|
- **THEN** 文档 SHALL 说明通过 `Frame_Ready_Flag`(volatile 标志)和 `FrameBuffer`(共享内存)传递帧数据,IRQ 置位,业务任务轮询消费
|
||||||
|
|
||||||
|
### Requirement: 系统启动序列描述
|
||||||
|
文档 SHALL 描述 `main()` 的初始化顺序,涵盖所有外设和模块的初始化步骤及其依赖关系。
|
||||||
|
|
||||||
|
#### Scenario: 开发者定位初始化顺序问题
|
||||||
|
- **WHEN** 开发者遇到启动相关 bug 时
|
||||||
|
- **THEN** 文档 SHALL 提供启动序列:SystemClock → USART3/debug → Flash/SRAM 配置(128K+192K)→ DVP_Init → TIM2_Init → ExtTrigger_GPIO → NG_GPIO → ETH_LibInit → qdx_port_init → Preprocess_Init → TcpLogic_Init → 注册回调 → TcpLogic_Start → 创建 RTOS 任务 → vTaskStartScheduler
|
||||||
|
|
||||||
|
### Requirement: 2D 状态机完整描述
|
||||||
|
文档 SHALL 描述 2D 触发状态机的所有状态、转换条件和行为,包含外部触发和内部触发两种模式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 2D 外部触发流程
|
||||||
|
- **WHEN** 开发者排查外部触发抖动问题时
|
||||||
|
- **THEN** 文档 SHALL 描述:IDLE → [PA15 上升沿] → DEBOUNCE(等待 DebounceIntervalMs) → DELAY(等待 TriggerDelayMs) → BURST(发 BurstCount 帧,间隔 TriggerInternalIntervalMs) → IDLE
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 2D 内部触发流程
|
||||||
|
- **WHEN** 开发者配置温度阈值触发时
|
||||||
|
- **THEN** 文档 SHALL 描述:每帧调用 `Preprocess_CheckInternalTrigger2D`,在 TriggerRoi 区域内 Max/Avg 超过 TriggerTemperatureThreshold 时触发,跳过 DEBOUNCE 直接进入 DELAY → BURST
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解双缓冲 TX 逻辑
|
||||||
|
- **WHEN** 开发者排查发送顺序问题时
|
||||||
|
- **THEN** 文档 SHALL 说明 `use_buffer_A` 标志在每次发送时取反,g_TxNetBuffer_A / g_TxNetBuffer_B 交替使用,避免前一帧发送未完成时被覆盖
|
||||||
|
|
||||||
|
### Requirement: 1D 状态机完整描述
|
||||||
|
文档 SHALL 描述 1D 采集状态机的所有状态、转换条件、采集逻辑和发送条件。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 外部触发流程
|
||||||
|
- **WHEN** 开发者配置 TriggerType=2 时
|
||||||
|
- **THEN** 文档 SHALL 描述:S1D_IDLE → [PA15 上升沿] → S1D_DEBOUNCE(等待 HighTimerLimit ms) → S1D_COLLECTING(采集 temp+time 样本直到停止条件)→ 发送 → S1D_IDLE
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 内部触发流程
|
||||||
|
- **WHEN** 开发者配置 TriggerType=1 时
|
||||||
|
- **THEN** 文档 SHALL 描述:S1D_IDLE 中维护 3 帧滑动窗口,连续 3 帧帧最大温度 ≥ TriggerTempLimit 时进入 S1D_COLLECTING;停止条件:样本数达 BufferSize 或触发后连续 NgCountLimit 帧低温
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 1D 数据格式
|
||||||
|
- **WHEN** 开发者解析 1D 数据时
|
||||||
|
- **THEN** 文档 SHALL 说明每个样本 4 字节:[time_offset_lo, time_offset_hi, temp_lo, temp_hi],time_offset 为相对采集开始的 ms 偏移,temp 为 0.1°C/LSB 的 uint16_t
|
||||||
|
|
||||||
|
### Requirement: TEMP_REQ 辅助通道描述
|
||||||
|
文档 SHALL 描述服务器按需截图(TEMP_REQ)的触发流程和限制条件。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 TEMP_REQ 工作方式
|
||||||
|
- **WHEN** 服务器发送 TEMP_REQ 命令时
|
||||||
|
- **THEN** 文档 SHALL 说明:`g_temp_req_pending` 置位,业务任务在下次循环读取当前 FrameBuffer 发送,is2D=1 走 Preprocess_Execute + 2D 封包,is2D=0 走 send_1d_snapshot(30 个等间距采样点);仅在对应模式 Enabled 时响应
|
||||||
|
|
||||||
48
openspec/specs/tcp-module-design/spec.md
Normal file
48
openspec/specs/tcp-module-design/spec.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# tcp-module-design Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change software-design-doc. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: 双流连接架构描述
|
||||||
|
文档 SHALL 描述控制流(5511)和数据流(5512)的职责分工、连接方式和状态管理。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解双流设计意图
|
||||||
|
- **WHEN** 开发者排查连接问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:控制流 srcport=5511(MCU 主动连接 Server),用于握手、配置下发、心跳、TEMP_REQ、DetectionResult 上报;数据流 desport=5512(MCU 主动连接 Server),用于温度帧数据上报;两流独立管理,数据流断开不影响控制流
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解 TCP 连接建立时序
|
||||||
|
- **WHEN** 开发者排查首次握手失败时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_Start` 派生后台连接状态机,先尝试控制流连接 → 连接成功后发送 TYPE_HANDSHAKE(含 DeviceUUID=MAC地址、AuthToken=NULL)→ 收到服务器握手响应后标记连接就绪 → 同步尝试数据流连接
|
||||||
|
|
||||||
|
### Requirement: 心跳机制描述
|
||||||
|
文档 SHALL 描述心跳帧的发送周期、TCP Keepalive 配置和连接保活策略。
|
||||||
|
|
||||||
|
#### Scenario: 开发者调整心跳参数
|
||||||
|
- **WHEN** 网络中间件(NAT/防火墙)超时断连时
|
||||||
|
- **THEN** 文档 SHALL 说明:应用层心跳 TYPE_HEARTBEAT 由 TcpLogic 内部定时发送;TCP Keepalive 配置:空闲 20 秒(`idle=20000ms`)后开始探测,每次间隔 15 秒(`interval=15000ms`),最多 9 次(`count=9`)探测无响应则断链重连
|
||||||
|
|
||||||
|
### Requirement: 配置下发与缓存机制描述
|
||||||
|
文档 SHALL 描述服务器配置的接收、缓存和通知流程。
|
||||||
|
|
||||||
|
#### Scenario: 开发者追踪配置更新流程
|
||||||
|
- **WHEN** 服务器下发配置后行为未按预期改变时
|
||||||
|
- **THEN** 文档 SHALL 描述:服务器发送 TYPE_CONFIG_COMMON + TYPE_CONFIG_2D + TYPE_CONFIG_1D → TcpLogic 解析并缓存至内部 shadow 寄存器 → 触发 ConfigUpdateCallback(`OnConfigUpdate`)→ 回调内调用 `Preprocess_Settings_Change` 更新预处理参数;`TcpLogic_GetLatestConfig` 可随时读取最新配置副本
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解无配置时的默认行为
|
||||||
|
- **WHEN** 设备启动后服务器尚未下发配置时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_GetLatestConfig` 返回 -1,业务任务跳过 2D/1D 处理,仅响应 TEMP_REQ(若有);默认 burst 参数 DEFAULT_BURST_COUNT=3、DEFAULT_BURST_INTERVAL_MS=200ms 作为 fallback
|
||||||
|
|
||||||
|
### Requirement: 数据发送队列和背压处理描述
|
||||||
|
文档 SHALL 描述温度帧发送的队列机制和发送失败时的处理策略。
|
||||||
|
|
||||||
|
#### Scenario: 开发者排查数据帧丢失问题
|
||||||
|
- **WHEN** 网络繁忙时出现帧丢失时
|
||||||
|
- **THEN** 文档 SHALL 说明:`TcpLogic_BuildAndSendTemperatureFrame` 返回 0 表示成功入队,返回 <0 表示发送失败(队列满或连接断开);当前固件对发送失败仅打印 DBG_ERR,不重试;双缓冲 TX 保证当前帧被入队时,下一帧可立即写入另一个缓冲
|
||||||
|
|
||||||
|
### Requirement: WCHNET 网络栈驱动任务描述
|
||||||
|
文档 SHALL 描述 task_wchnet 的职责和与 WCHNET 库的交互方式。
|
||||||
|
|
||||||
|
#### Scenario: 开发者理解网络栈如何被驱动
|
||||||
|
- **WHEN** 开发者排查网络响应迟缓问题时
|
||||||
|
- **THEN** 文档 SHALL 说明:`task_wchnet_entry` 每 5ms 轮询一次,调用 `WCHNET_MainTask`(协议栈心跳)和 `WCHNET_HandleGlobalInt`(socket 事件分发);socket 接收/连接/断开事件通过 `qdx_port_sock_*_notify` 转发给 QDX 网络层;`qdx_port_net_lock/unlock` 用互斥量保护 WCHNET 调用,防止业务任务和网络任务并发访问
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user