18
This commit is contained in:
parent
e3d08f7f26
commit
b8dbaa619d
120
doc/下位机协议实现完备性分析.md
Normal file
120
doc/下位机协议实现完备性分析.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# 下位机协议实现完备性分析
|
||||||
|
|
||||||
|
> **分析日期**: 2026-03-14
|
||||||
|
> **对照文档**: 采集端通信协议规范 v2.1.3 (2026-02-07)
|
||||||
|
> **分析对象**: prj/TCPClient 工程全部源码
|
||||||
|
|
||||||
|
## 前提说明
|
||||||
|
|
||||||
|
协议规范描述了 ZMQ PUB-SUB + JSON 架构,但实际 MCU 与配置端服务器之间已约定采用**自定义二进制 TLV 帧格式**(`0x55AA` Magic + CRC16-MODBUS)通过双 TCP 通道(5511 控制 / 5512 数据)通信。以下审计基于 TLV 协议层面进行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、接收端(服务器 → 下位机,控制通道 5511)
|
||||||
|
|
||||||
|
| # | 功能 | 状态 | 说明 |
|
||||||
|
|---|------|:----:|------|
|
||||||
|
| 1 | ConfigCommon 全量配置 | ✅ 已实现 | `TYPE_CONFIG_COMMON(0x20)` 完整解析 23 字节 |
|
||||||
|
| 2 | Config2D 全量配置 | ✅ 已实现 | `TYPE_CONFIG_2D(0x22)` 完整解析 61 字节全部字段 |
|
||||||
|
| 3 | Config1D 全量配置 | ✅ 已实现 | `TYPE_CONFIG_1D(0x23)` 完整解析 26 字节 |
|
||||||
|
| 4 | 设备 ID 分配 | ✅ 已实现 | `TYPE_DEVID_ASSIGN(0x05)` 解析后断线重连 |
|
||||||
|
| 5 | 判定结果接收 | ✅ 已实现 | `TYPE_DETECTION_RESULT(0x40)` → `detect_cb` 回调 NG/OK |
|
||||||
|
| 6 | 帧请求(截图/采集) | ✅ 已实现 | `TYPE_TEMP_FRAME(0x10)` 请求 → `temp_req_cb` 回调 |
|
||||||
|
| 7 | ACK 响应接收 | ✅ 已实现 | `TYPE_ACK_PAYLOAD(0x30)` 在 TLV 解析循环内可识别 |
|
||||||
|
| 8 | 部分参数更新 | ❌ 未实现 | 规范 §4.2.2 定义 `UpdateParameters` 键值点更新,无对应 TLV 类型 |
|
||||||
|
| 9 | 控制命令组 | ❌ 未实现 | Start/Stop/Pause/Resume/GetStatus/GetConfiguration/SwitchPipelineMode/ResetStats 共 8 个控制命令均无解析 |
|
||||||
|
| 10 | 文件传输 | ❌ 未实现 | 规范 §3.3.1 `{deviceId}/file` 无对应功能 |
|
||||||
|
| 11 | 时间同步 | ⚠️ 仅定义 | `TYPE_SYNC_TIME(0x03)` 已定义常量但未在 `parse_and_dispatch_tlv` 中处理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、发送端(下位机 → 服务器,数据通道 5512)
|
||||||
|
|
||||||
|
| # | 功能 | 状态 | 说明 |
|
||||||
|
|---|------|:----:|------|
|
||||||
|
| 1 | 握手 | ✅ 已实现 | `TYPE_HANDSHAKE(0x01)` 连接后立即发送(UUID + 硬件/固件版本) |
|
||||||
|
| 2 | 心跳 | ✅ 已实现 | `TYPE_HEARTBEAT(0x02)` 每 2s 发送 UpTime + CpuLoad + MemUsage |
|
||||||
|
| 3 | ACK 应答 | ✅ 已实现 | `TYPE_ACK_PAYLOAD(0x30)` 响应带 `FLAG_ACK_REQ` 的帧 |
|
||||||
|
| 4 | 温度帧(2D 触发帧) | ✅ 已实现 | `TYPE_TEMP_FRAME(0x10)` 零拷贝发送 + 自动分片 |
|
||||||
|
| 5 | 原始帧上报 | ❌ 未实现 | `TYPE_RAW_FRAME(0x11)` 仅定义常量,无发送逻辑 |
|
||||||
|
| 6 | 设备注册 | ❌ 未实现 | 规范 §8.1 `DeviceRegisterEvent` 注册事件未发送 |
|
||||||
|
| 7 | 状态上报 | ❌ 未实现 | 规范 §5.3.5 定期 `AcquisitionStatusEvent` 未实现 |
|
||||||
|
| 8 | 命令处理回执 | ❌ 未实现 | 规范 §5.3.3 `ChangedEvent` 回执未实现 |
|
||||||
|
| 9 | Masked 帧 | ❌ 未实现 | 不区分 Triggered/Masked/Live 帧类型发送 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、核心处理逻辑
|
||||||
|
|
||||||
|
| # | 功能 | 状态 | 说明 |
|
||||||
|
|---|------|:----:|------|
|
||||||
|
| 1 | 2D 滑窗 ROI 提取 | ✅ 已实现 | 积分图 + 滑窗搜索最大平均温度区域,提取 TargetWidth × TargetHeight |
|
||||||
|
| 2 | 内部温度触发 | ✅ 已实现 | TriggerRoi 区域 Max/Avg vs TriggerTemperatureThreshold |
|
||||||
|
| 3 | Burst 连拍 | ✅ 已实现 | BurstCount + InternalIntervalMs 状态机 |
|
||||||
|
| 4 | NG GPIO 输出 | ✅ 已实现 | PA8 脉冲,宽度从 NGioDelay 配置读取 |
|
||||||
|
| 5 | 帧分片 | ✅ 已实现 | 超 1400B 自动分片,最后一片设置 FLAG_LAST_FRAGMENT |
|
||||||
|
| 6 | CRC16 校验 | ✅ 已实现 | 收发双向 CRC16-MODBUS 校验 |
|
||||||
|
| 7 | 自动重连 | ✅ 已实现 | 检测断线后 3s 重连双通道 |
|
||||||
|
| 8 | 阈值蒙版输出 | ⚠️ 部分 | 滑窗搜索时低温 → 90 替换,但规范要求**输出数据**中低于 MaskThreshold → 替换为 0 |
|
||||||
|
| 9 | 触发延迟 (DelayMs) | ❌ 未实现 | 字段已解析但触发后无延迟等待逻辑 |
|
||||||
|
| 10 | 外部 GPIO 触发 | ❌ 未实现 | 无 EXTI 中断配置、消抖(DebounceIntervalMs)、触发逻辑 |
|
||||||
|
| 11 | Alarm GPIO | ❌ 未实现 | AlarmGpioLine/AlarmHoldMs 字段已解析但无 GPIO 驱动 |
|
||||||
|
| 12 | 1D 数据采集处理 | ❌ 未实现 | Config1D 可接收但无 1D 环形缓冲区/切片/触发逻辑 |
|
||||||
|
| 13 | Training 模式 | ❌ 未实现 | 字段可解析但无训练采样功能 |
|
||||||
|
| 14 | 消抖 (DebounceIntervalMs) | ❌ 未实现 | 依赖外部触发,一并缺失 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、汇总统计
|
||||||
|
|
||||||
|
| 类别 | ✅ 已实现 | ⚠️ 部分 | ❌ 未实现 |
|
||||||
|
|------|:---------:|:-------:|:---------:|
|
||||||
|
| 接收(控制通道) | 7 | 1 | 3 |
|
||||||
|
| 发送(数据通道) | 4 | 0 | 5 |
|
||||||
|
| 处理逻辑 | 7 | 1 | 6 |
|
||||||
|
| **合计** | **18** | **2** | **14** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、优先级建议
|
||||||
|
|
||||||
|
### P0 — 必须补齐(影响基本功能验证)
|
||||||
|
|
||||||
|
1. **控制命令接收** — 至少实现 `StartAcquisition` / `StopAcquisition` / `GetStatus`,否则服务端无法控制采集启停
|
||||||
|
2. **状态上报** — 周期性上报运行状态给服务端,否则服务端无法监控设备健康
|
||||||
|
3. **触发延迟 (DelayMs)** — 简单改动,在触发检测到后 delay 再开始采集
|
||||||
|
4. **阈值蒙版输出** — 提取后的数据中 `val < MaskThreshold` → 替换为 0
|
||||||
|
|
||||||
|
### P1 — 高优先级(完善核心流程)
|
||||||
|
|
||||||
|
5. **外部 GPIO 触发** — 实际产线通常用外部触发信号
|
||||||
|
6. **Alarm GPIO** — 独立于 NG GPIO 的报警输出
|
||||||
|
7. **命令处理回执 (ChangedEvent)** — 服务端需要确认命令被执行
|
||||||
|
8. **设备注册事件** — 完整的上线握手流程
|
||||||
|
|
||||||
|
### P2 — 可延后
|
||||||
|
|
||||||
|
9. 1D 数据处理(如果当前只做 2D 热成像)
|
||||||
|
10. Training 模式
|
||||||
|
11. 文件传输
|
||||||
|
12. 原始帧/Masked 帧分类发送
|
||||||
|
13. 时间同步
|
||||||
|
14. 部分参数更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、涉及源文件清单
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `Middle/QDXnetworkStack/qdx_protocol.h` | 协议常量、TLV 类型定义、结构体 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_protocol.c` | CRC16、帧构建、序列化工具 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_tcp_logic.h` | TCP 逻辑层 API 声明 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_tcp_logic.c` | 双通道连接管理、TLV 解析/分发、心跳、帧发送 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_preprocess.h` | 预处理 API 声明 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_preprocess.c` | 滑窗 ROI、温度过滤、内部触发检测 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_port.h` | HAL 抽象层声明 |
|
||||||
|
| `Middle/QDXnetworkStack/qdx_port.c` | FreeRTOS + WCHNET HAL 实现 |
|
||||||
|
| `User/main.c` | 应用主逻辑:RTOS 任务、Burst 状态机、NG GPIO、测试模式 |
|
||||||
|
| `Debug/dvp.c` | DVP 图像采集驱动 |
|
||||||
|
| `Debug/mini212g2.c` | Mini212G2 传感器 UART 驱动 |
|
||||||
1664
doc/采集端通信协议规范.md
Normal file
1664
doc/采集端通信协议规范.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -61,6 +61,17 @@ static void NG_GPIO_Init(void)
|
|||||||
GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
|
GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Flag set by TempFrameRequest callback; consumed by business task */
|
||||||
|
static volatile uint8_t g_temp_req_pending = 0;
|
||||||
|
static volatile uint8_t g_temp_req_is2d = 1;
|
||||||
|
|
||||||
|
void OnTempFrameRequest(uint8_t is2dRequest)
|
||||||
|
{
|
||||||
|
g_temp_req_is2d = is2dRequest;
|
||||||
|
g_temp_req_pending = 1;
|
||||||
|
DBG_APP("TempFrameReq is2d=%d\r\n", (int)is2dRequest);
|
||||||
|
}
|
||||||
|
|
||||||
void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus)
|
void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus)
|
||||||
{
|
{
|
||||||
(void)frameNumber;
|
(void)frameNumber;
|
||||||
@ -341,6 +352,58 @@ static void task_test_pattern_entry(void *pvParameters)
|
|||||||
}
|
}
|
||||||
#endif /* TEST_PATTERN_MODE */
|
#endif /* TEST_PATTERN_MODE */
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* 1D mode: build and send a single 1D temperature frame from
|
||||||
|
* the current raw image. Scans the center row and packs
|
||||||
|
* TempPoint1D_t (2B time_offset + 2B temp, little-endian).
|
||||||
|
* ============================================================ */
|
||||||
|
static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *tx_buf)
|
||||||
|
{
|
||||||
|
uint16_t w = raw->Width;
|
||||||
|
uint16_t h = raw->Height;
|
||||||
|
uint16_t *src = raw->pData;
|
||||||
|
uint16_t row = h / 2; /* center row */
|
||||||
|
|
||||||
|
tx_buf->ValidPayloadLen = 0;
|
||||||
|
uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset;
|
||||||
|
uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset;
|
||||||
|
uint16_t points = w;
|
||||||
|
if ((uint32_t)points * 4 > capacity)
|
||||||
|
points = (uint16_t)(capacity / 4);
|
||||||
|
|
||||||
|
int16_t min_t = 32767, max_t = -32768;
|
||||||
|
int32_t sum_t = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < points; i++) {
|
||||||
|
uint16_t temp = src[row * w + i];
|
||||||
|
uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1));
|
||||||
|
int16_t t = (int16_t)temp;
|
||||||
|
if (t < min_t) min_t = t;
|
||||||
|
if (t > max_t) max_t = t;
|
||||||
|
sum_t += t;
|
||||||
|
dest[0] = (uint8_t)(time_offset & 0xFF);
|
||||||
|
dest[1] = (uint8_t)((time_offset >> 8) & 0xFF);
|
||||||
|
dest[2] = (uint8_t)(temp & 0xFF);
|
||||||
|
dest[3] = (uint8_t)((temp >> 8) & 0xFF);
|
||||||
|
dest += 4;
|
||||||
|
}
|
||||||
|
tx_buf->ValidPayloadLen = (uint32_t)points * 4;
|
||||||
|
|
||||||
|
PreprocessResult_t meta;
|
||||||
|
memset(&meta, 0, sizeof(meta));
|
||||||
|
meta.pValidData = tx_buf->pBuffer + tx_buf->HeadOffset;
|
||||||
|
meta.DataLength = tx_buf->ValidPayloadLen;
|
||||||
|
meta.ValidWidth = points;
|
||||||
|
meta.ValidHeight = 1;
|
||||||
|
meta.MinTemp = min_t;
|
||||||
|
meta.MaxTemp = max_t;
|
||||||
|
meta.AvgTemp = (int16_t)(sum_t / (points > 0 ? points : 1));
|
||||||
|
meta.RoiTemp = meta.AvgTemp;
|
||||||
|
meta.FrameNumber = raw->FrameNumber;
|
||||||
|
|
||||||
|
TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0 /* IS_1D */);
|
||||||
|
}
|
||||||
|
|
||||||
static void task_business_entry(void *pvParameters)
|
static void task_business_entry(void *pvParameters)
|
||||||
{
|
{
|
||||||
(void)pvParameters;
|
(void)pvParameters;
|
||||||
@ -358,6 +421,29 @@ static void task_business_entry(void *pvParameters)
|
|||||||
DVP_Task();
|
DVP_Task();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Handle on-demand frame request from server */
|
||||||
|
if (g_temp_req_pending)
|
||||||
|
{
|
||||||
|
g_temp_req_pending = 0;
|
||||||
|
RawImageBuffer_t raw_img;
|
||||||
|
raw_img.pData = (uint16_t *)FrameBuffer;
|
||||||
|
raw_img.Width = SENSOR_WIDTH;
|
||||||
|
raw_img.Height = SENSOR_HEIGHT;
|
||||||
|
raw_img.FrameNumber = Ready_Frame_Count;
|
||||||
|
|
||||||
|
TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
|
||||||
|
use_buffer_A = !use_buffer_A;
|
||||||
|
tx_buf->ValidPayloadLen = 0;
|
||||||
|
|
||||||
|
if (g_temp_req_is2d) {
|
||||||
|
PreprocessResult_t meta;
|
||||||
|
if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0)
|
||||||
|
TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x02 /* REQUEST */, 1);
|
||||||
|
} else {
|
||||||
|
send_1d_frame_from_raw(&raw_img, tx_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Frame_Ready_Flag)
|
if (Frame_Ready_Flag)
|
||||||
{
|
{
|
||||||
Frame_Ready_Flag = 0;
|
Frame_Ready_Flag = 0;
|
||||||
@ -475,9 +561,34 @@ int main(void)
|
|||||||
qdx_port_init();
|
qdx_port_init();
|
||||||
|
|
||||||
Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT);
|
Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT);
|
||||||
|
|
||||||
|
#if TEST_PATTERN_MODE
|
||||||
|
/* Set default preprocess config so trigger fires with test patterns.
|
||||||
|
* Without this, all Config2D_t fields are 0 and trigger never fires
|
||||||
|
* (roi_w==0 → immediate return 0). */
|
||||||
|
{
|
||||||
|
Config2D_t test_cfg2d;
|
||||||
|
memset(&test_cfg2d, 0, sizeof(test_cfg2d));
|
||||||
|
test_cfg2d.TargetWidth = 64; /* ROI extraction 64x64 */
|
||||||
|
test_cfg2d.TargetHeight = 64;
|
||||||
|
test_cfg2d.TriggerRoiX = 0; /* Full-frame trigger area */
|
||||||
|
test_cfg2d.TriggerRoiY = 0;
|
||||||
|
test_cfg2d.TriggerRoiW = SENSOR_WIDTH;
|
||||||
|
test_cfg2d.TriggerRoiH = SENSOR_HEIGHT;
|
||||||
|
test_cfg2d.TriggerCondition = 1; /* Max temperature */
|
||||||
|
test_cfg2d.TriggerTemperatureThreshold = 8000; /* 80.00°C */
|
||||||
|
test_cfg2d.TriggerBurstCount = 3;
|
||||||
|
test_cfg2d.TriggerInternalIntervalMs = 200;
|
||||||
|
test_cfg2d.NGioDelay = 200;
|
||||||
|
Preprocess_Settings_Change(&test_cfg2d, NULL, NULL);
|
||||||
|
printf("Test default config loaded: trigger thresh=8000 ROI=full burst=3\r\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TcpLogic_Init(MACAddr, NULL);
|
TcpLogic_Init(MACAddr, NULL);
|
||||||
TcpLogic_RegisterConfigCallback(OnConfigUpdate);
|
TcpLogic_RegisterConfigCallback(OnConfigUpdate);
|
||||||
TcpLogic_RegisterDetectionCallback(OnDetectionResult);
|
TcpLogic_RegisterDetectionCallback(OnDetectionResult);
|
||||||
|
TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest);
|
||||||
DBG_APP("TcpLogic_Start...\r\n");
|
DBG_APP("TcpLogic_Start...\r\n");
|
||||||
TcpLogic_Start();
|
TcpLogic_Start();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user