diff --git a/doc/设计文档/DVP模块设计.md b/doc/设计文档/DVP模块设计.md new file mode 100644 index 0000000..4f14d33 --- /dev/null +++ b/doc/设计文档/DVP模块设计.md @@ -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 模式配置命令 | diff --git a/doc/设计文档/QDX协议设计.md b/doc/设计文档/QDX协议设计.md new file mode 100644 index 0000000..47f7289 --- /dev/null +++ b/doc/设计文档/QDX协议设计.md @@ -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) | 外部接入方协议帧构造示例 | diff --git a/doc/设计文档/TCP通信模块设计.md b/doc/设计文档/TCP通信模块设计.md new file mode 100644 index 0000000..fd7b8d9 --- /dev/null +++ b/doc/设计文档/TCP通信模块设计.md @@ -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) | 上位机侧握手和配置下发操作 | diff --git a/doc/设计文档/对接集成指南.md b/doc/设计文档/对接集成指南.md new file mode 100644 index 0000000..ed27eeb --- /dev/null +++ b/doc/设计文档/对接集成指南.md @@ -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 = ' 外部报警装置应接在 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 模式配置 | diff --git a/doc/设计文档/系统总览.md b/doc/设计文档/系统总览.md new file mode 100644 index 0000000..84a13cd --- /dev/null +++ b/doc/设计文档/系统总览.md @@ -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 命令 | diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/.openspec.yaml b/openspec/changes/archive/2026-03-15-software-design-doc/.openspec.yaml new file mode 100644 index 0000000..3f8803d --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-15 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/design.md b/openspec/changes/archive/2026-03-15-software-design-doc/design.md new file mode 100644 index 0000000..27b1971 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/design.md @@ -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//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# 示例解析代码片段?后续可作为独立变更补充。 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/proposal.md b/openspec/changes/archive/2026-03-15-software-design-doc/proposal.md new file mode 100644 index 0000000..6a3a1a3 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/proposal.md @@ -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 开发团队) diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/specs/dvp-module-design/spec.md b/openspec/changes/archive/2026-03-15-software-design-doc/specs/dvp-module-design/spec.md new file mode 100644 index 0000000..a4580c0 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/specs/dvp-module-design/spec.md @@ -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)(低字节先发),否则温度值字节反序 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/specs/integration-guide/spec.md b/openspec/changes/archive/2026-03-15-software-design-doc/specs/integration-guide/spec.md new file mode 100644 index 0000000..574c695 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/specs/integration-guide/spec.md @@ -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 检查发送的帧格式后重试 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/specs/qdx-protocol-design/spec.md b/openspec/changes/archive/2026-03-15-software-design-doc/specs/qdx-protocol-design/spec.md new file mode 100644 index 0000000..93e3247 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/specs/qdx-protocol-design/spec.md @@ -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)=末片标志 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/specs/system-overview/spec.md b/openspec/changes/archive/2026-03-15-software-design-doc/specs/system-overview/spec.md new file mode 100644 index 0000000..61362f5 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/specs/system-overview/spec.md @@ -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 时响应 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/specs/tcp-module-design/spec.md b/openspec/changes/archive/2026-03-15-software-design-doc/specs/tcp-module-design/spec.md new file mode 100644 index 0000000..0e2fc60 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/specs/tcp-module-design/spec.md @@ -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 调用,防止业务任务和网络任务并发访问 diff --git a/openspec/changes/archive/2026-03-15-software-design-doc/tasks.md b/openspec/changes/archive/2026-03-15-software-design-doc/tasks.md new file mode 100644 index 0000000..b192aff --- /dev/null +++ b/openspec/changes/archive/2026-03-15-software-design-doc/tasks.md @@ -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 编写常见错误码表和对接故障排查步骤 diff --git a/openspec/specs/dvp-module-design/spec.md b/openspec/specs/dvp-module-design/spec.md new file mode 100644 index 0000000..d0e022e --- /dev/null +++ b/openspec/specs/dvp-module-design/spec.md @@ -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)(低字节先发),否则温度值字节反序 + diff --git a/openspec/specs/integration-guide/spec.md b/openspec/specs/integration-guide/spec.md new file mode 100644 index 0000000..3e1ca0e --- /dev/null +++ b/openspec/specs/integration-guide/spec.md @@ -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 检查发送的帧格式后重试 + diff --git a/openspec/specs/qdx-protocol-design/spec.md b/openspec/specs/qdx-protocol-design/spec.md new file mode 100644 index 0000000..a91c4d0 --- /dev/null +++ b/openspec/specs/qdx-protocol-design/spec.md @@ -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)=末片标志 + diff --git a/openspec/specs/system-overview/spec.md b/openspec/specs/system-overview/spec.md new file mode 100644 index 0000000..d3cb33b --- /dev/null +++ b/openspec/specs/system-overview/spec.md @@ -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 时响应 + diff --git a/openspec/specs/tcp-module-design/spec.md b/openspec/specs/tcp-module-design/spec.md new file mode 100644 index 0000000..7e5be66 --- /dev/null +++ b/openspec/specs/tcp-module-design/spec.md @@ -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 调用,防止业务任务和网络任务并发访问 +