2
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-14
|
||||
@@ -0,0 +1,93 @@
|
||||
## Context
|
||||
|
||||
当前 TCPClient 工程运行在裸机模式下,主循环直接轮询 WCHNET 和 DVP 任务。QDX 网络栈的 `qdx_tcp_logic` 被设计为多线程架构(3 个后台线程:1 个连接管理 + 2 个接收线程),依赖 `qdx_port.h` 定义的 11 个 HAL 函数(线程、互斥锁、延时、TCP socket),但这些函数在 `qdx_port_template.c` 中全部为空 stub。
|
||||
|
||||
关键约束:
|
||||
- CH32V307 拥有 64KB SRAM,FreeRTOS 堆配置为 12KB
|
||||
- WCHNET 是 WCH 私有 TCP/IP 协议栈,**非 BSD socket 模型**——采用中断回调通知 + 同步轮询接收的混合模式
|
||||
- WCHNET 必须周期性调用 `WCHNET_MainTask()` 和全局中断处理才能驱动协议栈运转
|
||||
- 当前 `net_config.h` 中 `WCHNET_NUM_TCP = 1`,但双流架构(5511 控制流 + 5512 数据流)需要 2 个 TCP socket
|
||||
- FreeRTOS 的 RISC-V 移植已存在于 `prj/FreeRTOS_Core/FreeRTOS/portable/GCC/RISC-V/`
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 实现 `qdx_port.c` 全部 11 个 HAL 函数,使 `qdx_tcp_logic` 三线程架构实际运行
|
||||
- 将裸机主循环迁移为 FreeRTOS 多任务模型
|
||||
- 桥接 WCHNET 回调模型与 `qdx_port_tcp_recv` 的阻塞/半阻塞语义
|
||||
- 补全 `OnConfigUpdate` 和 `OnDetectionResult` 回调逻辑
|
||||
|
||||
**Non-Goals:**
|
||||
- 不修改 `qdx_tcp_logic.c`、`qdx_protocol.c`、`qdx_preprocess.c` 的内部逻辑
|
||||
- 不实现外部硬件 DI 触发模式和连拍功能(属于后续 change)
|
||||
- 不移植到 LwIP 或其他 TCP/IP 协议栈
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1:FreeRTOS 任务划分
|
||||
|
||||
将系统拆分为 4+3 个 FreeRTOS 任务:
|
||||
|
||||
| 任务 | 优先级 | 栈大小 | 职责 |
|
||||
|------|--------|--------|------|
|
||||
| `task_wchnet` | 6 (高) | 1024 words | 周期调用 `WCHNET_MainTask()` + 全局中断处理 |
|
||||
| `task_business` | 5 | 1024 words | DVP 采集轮询 + 触发判定 + 预处理 + 封包发送 |
|
||||
| `tcp_mgr` | 3 | 512 words | 由 `TcpLogic_Start()` 创建,连接管理与心跳 |
|
||||
| `tcp_rx_c` | 4 | 512 words | 由 `TcpLogic_Start()` 创建,控制流接收 |
|
||||
| `tcp_rx_d` | 4 | 512 words | 由 `TcpLogic_Start()` 创建,数据流接收 |
|
||||
|
||||
**理由**:`task_wchnet` 优先级最高,因为 WCHNET 协议栈需要及时处理底层以太网帧和 TCP 状态机;`task_business` 次之,确保 DVP 帧不丢失;TCP 后台线程优先级最低,属于非实时任务。
|
||||
|
||||
**备选方案**:将 WCHNET 轮询放在定时器回调中而非独立任务——但 `WCHNET_MainTask()` 执行时间不确定,不适合放在中断上下文。
|
||||
|
||||
### Decision 2:WCHNET 接收桥接方案——信号量 + 环形缓冲
|
||||
|
||||
WCHNET 通过 `SINT_STAT_RECV` 中断通知数据到达,而 `qdx_port_tcp_recv` 被调用方期望为可阻塞/超时返回。桥接方案:
|
||||
|
||||
1. 为每个 socket 维护一个**接收环形缓冲区**(`RxRingBuf`,2920 字节)和一个**二值信号量**(`xSemaphoreRx`)
|
||||
2. 在 `WCHNET_HandleSockInt` 的 `SINT_STAT_RECV` 分支中:调用 `WCHNET_SocketRecv()` 将数据读入 `RxRingBuf`,然后 `xSemaphoreGiveFromISR(xSemaphoreRx)`
|
||||
3. `qdx_port_tcp_recv()` 实现:先检查 `RxRingBuf` 是否有数据,有则直接拷贝返回;无则 `xSemaphoreTake(xSemaphoreRx, pdMS_TO_TICKS(100))` 阻塞等待最多 100ms,超时返回 0
|
||||
|
||||
**理由**:这种方式让 recv 线程在无数据时让出 CPU(通过信号量阻塞),同时避免了忙等 10ms 轮询的 CPU 浪费。环形缓冲解耦了中断上下文读取和应用层消费的速率差异。
|
||||
|
||||
**备选方案**:FreeRTOS Stream Buffer——语义更匹配但引入额外依赖,且 WCHNET 的 `SocketRecv` 已提供了长度信息,环形缓冲更简单可控。
|
||||
|
||||
### Decision 3:Socket 映射管理
|
||||
|
||||
WCHNET 使用 `uint8_t socketid`(0~30)标识 socket,而 `qdx_port.h` 使用 `void* qdx_socket_t` 不透明句柄。映射方案:
|
||||
|
||||
- 维护一个静态数组 `SocketCtx_t g_sock_ctx[MAX_SOCKETS]`(MAX_SOCKETS = 2),每个元素包含:
|
||||
- `uint8_t wchnet_sock_id` — WCHNET socket ID
|
||||
- `uint8_t connected` — 是否已连接
|
||||
- `RxRingBuf_t rx_ring` — 接收环形缓冲
|
||||
- `SemaphoreHandle_t rx_sem` — 接收通知信号量
|
||||
- `qdx_port_tcp_connect()` 分配空闲的 `SocketCtx_t`,调用 `WCHNET_SocketCreat` + `WCHNET_SocketConnect`,返回 `&g_sock_ctx[i]` 作为句柄
|
||||
- `qdx_port_tcp_send/recv/close()` 从句柄中提取 `wchnet_sock_id` 操作 WCHNET API
|
||||
|
||||
**Config 变更**:`WCHNET_NUM_TCP` 从 1 改为 2,`WCHNET_MAX_SOCKET_NUM` 相应变为 2。
|
||||
|
||||
### Decision 4:TIM2 共享——FreeRTOS Tick + WCHNET Timer
|
||||
|
||||
当前 TIM2 以 10ms 周期驱动 `WCHNET_TimeIsr()`。FreeRTOS 需要 2ms tick(`configTICK_RATE_HZ = 500`)。方案:
|
||||
|
||||
- 将 TIM2 周期改为 **2ms**(匹配 FreeRTOS tick)
|
||||
- TIM2 ISR 中每次调用 `xPortSysTickHandler()`(FreeRTOS tick)
|
||||
- 设软件计数器,每累计 5 次(= 10ms)调用一次 `WCHNET_TimeIsr(WCHNETTIMERPERIOD)`
|
||||
|
||||
**理由**:共用一个硬件定时器节省外设资源,软件分频几乎无开销。
|
||||
|
||||
### Decision 5:`qdx_port_tcp_connect` 中的连接等待
|
||||
|
||||
WCHNET 的 `WCHNET_SocketConnect()` 是异步的——它发起三次握手后立即返回,连接完成通过 `SINT_STAT_CONNECT` 中断通知。但 `qdx_port_tcp_connect` 语义是阻塞直到连接建立。
|
||||
|
||||
方案:在 `SocketCtx_t` 中增加 `SemaphoreHandle_t connect_sem`。调用 `WCHNET_SocketConnect` 后 `xSemaphoreTake(connect_sem, pdMS_TO_TICKS(5000))` 阻塞。在 `SINT_STAT_CONNECT` 回调中 `xSemaphoreGive(connect_sem)`。超时返回 NULL。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[RAM 占用偏紧]** → FreeRTOS 堆 12KB + 5 个任务栈约 9KB + WCHNET 内部约 10KB + 2×2920 socket 缓冲 + 2×2920 环形缓冲 + 2×10KB 发送缓冲区 ≈ 52KB / 64KB。**缓解**:严格控制任务栈大小,使用 `uxTaskGetStackHighWaterMark` 运行时监测;发送缓冲保持 10KB 不变(已经预分配)。如仍紧张可将 FreeRTOS 堆缩减至 8KB。
|
||||
|
||||
**[WCHNET 非线程安全]** → WCHNET API 未声明线程安全性,多个 FreeRTOS 任务可能并发调用 send/recv。**缓解**:所有 WCHNET API 调用(send、recv、socket 操作)统一通过 `task_wchnet` 任务的消息队列委托执行,或者使用全局互斥锁保护。初期采用互斥锁方案,复杂度更低。
|
||||
|
||||
**[中断上下文限制]** → `WCHNET_HandleSockInt` 在中断上下文被调用(通过 `WCHNET_HandleGlobalInt` → TIM2/ETH ISR 链),其中调用 `WCHNET_SocketRecv` 和 `xSemaphoreGiveFromISR` 必须确保安全。**缓解**:将 `WCHNET_HandleGlobalInt` 移出 ISR,改为在 `task_wchnet` 中轮询调用 `WCHNET_QueryGlobalInt`,这样 recv 回调运行在任务上下文,可安全使用 FreeRTOS API。
|
||||
|
||||
**[双 Socket 内存增长]** → `WCHNET_NUM_TCP` 从 1 变为 2,`WCHNET_MEM_HEAP_SIZE` 和 `WCHNET_NUM_POOL_BUF` 相应增加约 3KB。**缓解**:CH32V307 有 64KB SRAM,增量可接受。
|
||||
@@ -0,0 +1,31 @@
|
||||
## Why
|
||||
|
||||
当前 QDX 网络栈的硬件抽象层 (`qdx_port_template.c`) 全部 11 个函数均为空 stub,导致 `qdx_tcp_logic` 的双流管理、心跳重连以及 `qdx_preprocess` 的并发互斥等核心功能无法真正运行。该 port 层需要对接 CH32V307 平台的 WCHNET 协议栈和 FreeRTOS 操作系统原语,使整条"采集→预处理→TCP 封包发送"的业务流水线实际贯通。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 将 FreeRTOS 内核合入 TCPClient 工程(源码已存在于 `prj/FreeRTOS_Core/`),完成编译链接集成。
|
||||
- 新增 `qdx_port.c`(基于 `qdx_port_template.c`),使用 FreeRTOS + WCHNET API 实现全部 port 函数:
|
||||
- 时间与延时:`qdx_port_get_tick_ms` / `qdx_port_delay_ms` → `xTaskGetTickCount` / `vTaskDelay`。
|
||||
- 互斥锁:`qdx_port_mutex_*` → `xSemaphoreCreateMutex` / `xSemaphoreTake` / `xSemaphoreGive`。
|
||||
- 线程:`qdx_port_thread_create` → `xTaskCreate`。
|
||||
- TCP Socket:`qdx_port_tcp_connect` / `send` / `recv` / `close` → 封装 WCHNET 的 socket 创建、`WCHNET_SocketSend`、接收回调缓冲与 `WCHNET_SocketClose`。
|
||||
- **BREAKING**:`main.c` 主循环重构为 FreeRTOS 任务调度模型,`main()` 末尾调用 `vTaskStartScheduler()` 替代裸机 `while(1)` 轮询。
|
||||
- 补全 `OnConfigUpdate` 回调,联级调用 `Preprocess_Settings_Change` 使上位机参数热更新生效。
|
||||
- 补全 `OnDetectionResult` 回调,实现 NG 剔除 GPIO DO 输出及定时器延时复位。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `freertos-wchnet-port`: 基于 FreeRTOS 和 WCHNET 实现 `qdx_port.h` 定义的全部硬件/OS 抽象接口(时间、延时、互斥锁、线程、TCP socket),使 QDX 网络栈在 CH32V307 上实际运行。
|
||||
|
||||
### Modified Capabilities
|
||||
- `tcp-stream-logic`: 回调实现补全——`ConfigUpdateCallback_t` 内联级调用 `Preprocess_Settings_Change`;`DetectionResultCallback_t` 内驱动 DO GPIO 执行 NG 剔除动作及定时复位。
|
||||
|
||||
## Impact
|
||||
|
||||
- **构建系统**:TCPClient 工程需链接 FreeRTOS 源文件(tasks.c、queue.c、list.c、timers.c、port.c、heap_4.c)和对应的 include 路径。
|
||||
- **main.c**:从裸机 `while(1)` 重构为 RTOS 多任务——主业务任务(DVP 采集 + 预处理 + 发送)、WCHNET 轮询任务、以及 `qdx_tcp_logic` 创建的 3 个后台线程。
|
||||
- **内存**:FreeRTOS 内核 + 任务栈额外占用约 8-12 KB RAM,需确认 CH32V307 64KB SRAM 余量充足。
|
||||
- **中断**:TIM2 中断需同时服务 FreeRTOS tick 和 WCHNET 定时器。
|
||||
- **依赖**:`FreeRTOSConfig.h` 需要适配 CH32V307 时钟频率与中断优先级。
|
||||
@@ -0,0 +1,119 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统时间获取
|
||||
`qdx_port_get_tick_ms()` 必须返回自系统启动以来的毫秒级单调递增时间戳,精度不低于 FreeRTOS tick 周期(2ms)。
|
||||
|
||||
#### Scenario: 获取系统运行时间
|
||||
- **WHEN** 任意任务或模块调用 `qdx_port_get_tick_ms()`
|
||||
- **THEN** 返回值为基于 `xTaskGetTickCount()` 转换的毫秒数,且数值不会回绕至 0(在 uint32_t 溢回之前)
|
||||
|
||||
### Requirement: 任务级阻塞延时
|
||||
`qdx_port_delay_ms()` 必须让当前 FreeRTOS 任务挂起指定毫秒数,期间让出 CPU 给其他任务。
|
||||
|
||||
#### Scenario: 延时期间 CPU 让出
|
||||
- **WHEN** 后台线程调用 `qdx_port_delay_ms(100)`
|
||||
- **THEN** 该任务进入阻塞态约 100ms,其余就绪任务在此期间获得调度
|
||||
|
||||
### Requirement: 互斥锁创建与操作
|
||||
`qdx_port_mutex_create/lock/unlock/delete` 必须基于 FreeRTOS 互斥信号量实现,支持跨任务的临界区保护和优先级继承。
|
||||
|
||||
#### Scenario: 并发访问配置结构体
|
||||
- **WHEN** `tcp_rx_c` 线程正在更新 `ConfigCommon_t` 且 `task_business` 同时读取该结构体
|
||||
- **THEN** 互斥锁确保同一时刻仅一个任务可访问,防止数据撕裂
|
||||
|
||||
#### Scenario: 互斥锁删除
|
||||
- **WHEN** 调用 `qdx_port_mutex_delete` 并传入有效句柄
|
||||
- **THEN** FreeRTOS 信号量资源被释放,句柄失效
|
||||
|
||||
### Requirement: 线程(任务)创建
|
||||
`qdx_port_thread_create` 必须通过 `xTaskCreate` 创建 FreeRTOS 任务,并正确映射名称、入口函数、栈大小和优先级参数。
|
||||
|
||||
#### Scenario: TcpLogic_Start 创建三个后台任务
|
||||
- **WHEN** `TcpLogic_Start()` 依次调用 `qdx_port_thread_create` 创建 `tcp_mgr`、`tcp_rx_c`、`tcp_rx_d`
|
||||
- **THEN** 三个 FreeRTOS 任务被成功创建并开始调度执行
|
||||
|
||||
### Requirement: TCP Socket 连接建立
|
||||
`qdx_port_tcp_connect` 必须创建 WCHNET TCP socket、配置目标 IP/端口,发起连接并阻塞等待三次握手完成(或超时 5 秒返回 NULL)。
|
||||
|
||||
#### Scenario: 成功连接至上位机
|
||||
- **WHEN** 调用 `qdx_port_tcp_connect("192.168.1.50", 5511)` 且上位机正在监听
|
||||
- **THEN** WCHNET 完成 TCP 三次握手后,`SINT_STAT_CONNECT` 中断释放连接信号量,函数返回有效的 `qdx_socket_t` 句柄
|
||||
|
||||
#### Scenario: 连接超时
|
||||
- **WHEN** 调用 `qdx_port_tcp_connect` 但目标主机不可达
|
||||
- **THEN** 阻塞等待 5 秒后超时,释放已分配的 WCHNET socket 资源,返回 NULL
|
||||
|
||||
### Requirement: TCP 数据发送
|
||||
`qdx_port_tcp_send` 必须将指定缓冲区数据通过 `WCHNET_SocketSend` 写入 TCP 发送队列,并返回实际发送的字节数。
|
||||
|
||||
#### Scenario: 发送温度帧数据
|
||||
- **WHEN** `TcpLogic_BuildAndSendTemperatureFrame` 组装完成后调用 `qdx_port_tcp_send` 发送到数据流 socket
|
||||
- **THEN** 数据被提交至 WCHNET 发送缓冲,函数返回已发送的字节数
|
||||
|
||||
#### Scenario: 连接已断开时发送
|
||||
- **WHEN** 对一个已断开的 socket 调用 `qdx_port_tcp_send`
|
||||
- **THEN** 函数返回 < 0 表示错误
|
||||
|
||||
### Requirement: TCP 数据接收(信号量阻塞模式)
|
||||
`qdx_port_tcp_recv` 必须从 socket 关联的接收环形缓冲中读取数据。若缓冲为空,则阻塞等待信号量通知(最长 100ms 超时),超时返回 0。
|
||||
|
||||
#### Scenario: 接收上位机配置指令
|
||||
- **WHEN** 上位机通过 5511 端口发送配置报文,WCHNET 中断触发将数据写入环形缓冲并释放信号量
|
||||
- **THEN** `tcp_rx_c` 线程从阻塞中唤醒,`qdx_port_tcp_recv` 返回实际读取的字节数和数据
|
||||
|
||||
#### Scenario: 无数据超时
|
||||
- **WHEN** 100ms 内未收到任何数据
|
||||
- **THEN** 函数返回 0,调用方继续执行心跳或重连检查逻辑
|
||||
|
||||
#### Scenario: 对端关闭连接
|
||||
- **WHEN** WCHNET 检测到对端断开(`SINT_STAT_DISCONNECT`)
|
||||
- **THEN** 函数返回 < 0,通知调用方连接已失效
|
||||
|
||||
### Requirement: TCP Socket 关闭
|
||||
`qdx_port_tcp_close` 必须调用 `WCHNET_SocketClose` 释放 WCHNET socket 资源,清空关联的环形缓冲,并将 `SocketCtx_t` 标记为空闲。
|
||||
|
||||
#### Scenario: 正常关闭
|
||||
- **WHEN** 连接管理线程检测到超时并调用 `qdx_port_tcp_close`
|
||||
- **THEN** WCHNET socket 被关闭,环形缓冲清零,`SocketCtx_t.connected = 0`,该槽位可被后续连接复用
|
||||
|
||||
### Requirement: WCHNET 协议栈周期驱动
|
||||
系统必须在独立的高优先级 FreeRTOS 任务 `task_wchnet` 中周期调用 `WCHNET_MainTask()` 和 `WCHNET_HandleGlobalInt()`,确保以太网帧收发和 TCP 状态机正常运转。
|
||||
|
||||
#### Scenario: 协议栈持续驱动
|
||||
- **WHEN** FreeRTOS 调度器启动后
|
||||
- **THEN** `task_wchnet` 以约 5~10ms 周期持续驱动 WCHNET 协议栈,socket 中断事件被及时处理
|
||||
|
||||
### Requirement: Socket 映射与上下文管理
|
||||
系统必须维护静态 `SocketCtx_t` 数组(容量 = 2),管理 WCHNET socket ID 到 `qdx_socket_t` 不透明句柄的映射,以及每个 socket 关联的环形接收缓冲和信号量。
|
||||
|
||||
#### Scenario: 双流同时活跃
|
||||
- **WHEN** 控制流 (5511) 和数据流 (5512) 同时建立连接
|
||||
- **THEN** 两个 `SocketCtx_t` 槽位被分别占用,各自拥有独立的 WCHNET socket ID、接收缓冲和信号量
|
||||
|
||||
### Requirement: net_config.h 双 Socket 配置
|
||||
`WCHNET_NUM_TCP` 必须从 1 修改为 2,以支持控制流和数据流两个并发 TCP 连接,相关宏(`WCHNET_MAX_SOCKET_NUM`、`WCHNET_NUM_POOL_BUF`、`WCHNET_MEM_HEAP_SIZE` 等)随之联动更新。
|
||||
|
||||
#### Scenario: 两个 TCP socket 可同时创建
|
||||
- **WHEN** 系统初始化后依次创建控制流和数据流 socket
|
||||
- **THEN** 两个 `WCHNET_SocketCreat` 调用均成功返回不同的 socket ID
|
||||
|
||||
### Requirement: TIM2 共享 FreeRTOS Tick 与 WCHNET Timer
|
||||
TIM2 必须配置为 2ms 周期,ISR 中每次调用 FreeRTOS tick handler,并以软件计数器每 5 次(10ms)调用一次 `WCHNET_TimeIsr()`。
|
||||
|
||||
#### Scenario: 双定时源正常工作
|
||||
- **WHEN** 系统运行中
|
||||
- **THEN** FreeRTOS 以 500Hz 频率获得 tick 中断,WCHNET 以 100Hz 频率获得定时服务,两者互不干扰
|
||||
|
||||
### Requirement: main 函数重构为 RTOS 启动模型
|
||||
`main()` 在完成硬件初始化和 WCHNET 初始化后,必须创建 `task_wchnet` 和 `task_business` 两个初始任务,然后调用 `vTaskStartScheduler()` 启动调度器,不再使用裸机 `while(1)` 主循环。
|
||||
|
||||
#### Scenario: 调度器启动
|
||||
- **WHEN** `main()` 完成 ETH_LibInit、DVP_Init、Preprocess_Init、TcpLogic_Init 后
|
||||
- **THEN** 调用 `vTaskStartScheduler()` 进入 RTOS 调度,所有业务逻辑在任务上下文中执行
|
||||
|
||||
### Requirement: FreeRTOS 内核集成
|
||||
TCPClient 工程必须链接 FreeRTOS 内核源文件(tasks.c、queue.c、list.c、timers.c、port.c、heap_4.c)和 RISC-V 移植文件,并配置 `FreeRTOSConfig.h` 适配 CH32V307 时钟和中断。
|
||||
|
||||
#### Scenario: 工程编译通过
|
||||
- **WHEN** 将 FreeRTOS 源文件和 include 路径加入 TCPClient 构建配置
|
||||
- **THEN** 工程编译无错误,FreeRTOS API 可正常调用
|
||||
@@ -0,0 +1,12 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Remote Parameters updating via callbacks
|
||||
必须注册实现监听由上位机下发的配置动作以及废料剔除等行为(基于 `ConfigUpdateCallback_t` 和 `DetectionResultCallback_t` 句柄),让这批回调充当系统动态控制信号输入源。回调函数体必须包含完整的业务逻辑实现。
|
||||
|
||||
#### Scenario: Update Triggering config on the fly
|
||||
- **WHEN** 通过 5511 端口收到控制主机的有效组包设置参数报文时
|
||||
- **THEN** `OnConfigUpdate` 回调被触发,内部必须调用 `Preprocess_Settings_Change(cfg2d, cfg1d, common)` 将新参数同步至预处理模块,使后续帧立即使用更新后的阈值与裁剪尺寸
|
||||
|
||||
#### Scenario: NG defect removal trigger by Host
|
||||
- **WHEN** 上位机判别系统对某帧评估失败(NG)并发回 `DetectionResult_t` 报文(`resultStatus != 0`)时
|
||||
- **THEN** `OnDetectionResult` 回调被触发,必须拉高预设 DO GPIO 引脚(驱动剔除气缸/报警灯),启动软件定时器维持高电平 `NGioDelay` 毫秒后自动拉低复位
|
||||
@@ -0,0 +1,62 @@
|
||||
## 1. 构建系统与 FreeRTOS 集成
|
||||
|
||||
- [x] 1.1 将 FreeRTOS 内核源文件(tasks.c、queue.c、list.c、timers.c)和内存管理(MemMang/heap_4.c)复制或链接到 TCPClient 工程目录
|
||||
- [x] 1.2 将 RISC-V 移植文件(portable/GCC/RISC-V/port.c、portASM.S、portmacro.h)加入工程
|
||||
- [x] 1.3 将 `FreeRTOSConfig.h` 从 `prj/FreeRTOS_Core/User/` 复制到 `prj/TCPClient/User/` 并适配:确认 `configTICK_RATE_HZ=500`、`configTOTAL_HEAP_SIZE=12288`、`configCPU_CLOCK_HZ=SystemCoreClock`
|
||||
- [x] 1.4 更新 TCPClient 工程 makefile/构建配置,添加 FreeRTOS 源文件和 include 路径,确保编译通过
|
||||
|
||||
## 2. net_config.h 双 Socket 配置
|
||||
|
||||
- [x] 2.1 修改 `net_config.h` 中 `WCHNET_NUM_TCP` 从 1 改为 2
|
||||
- [x] 2.2 验证 `WCHNET_MAX_SOCKET_NUM`、`WCHNET_NUM_POOL_BUF`、`WCHNET_NUM_TCP_SEG`、`WCHNET_MEM_HEAP_SIZE` 等联动宏自动调整正确
|
||||
- [x] 2.3 扩展 `main.c` 中 `SocketRecvBuf` 数组尺寸为 `[2][RECE_BUF_LEN]`,`socket` 数组为 `[2]`
|
||||
|
||||
## 3. TIM2 重构——共享 FreeRTOS Tick 与 WCHNET Timer
|
||||
|
||||
- [x] 3.1 修改 `TIM2_Init` 将定时周期从 10ms 改为 2ms(`TIM_Period = 2000 - 1`)
|
||||
- [x] 3.2 修改 `TIM2_IRQHandler`:每次调用 `xPortSysTickHandler()` 驱动 FreeRTOS tick,并用静态计数器每 5 次调用一次 `WCHNET_TimeIsr(WCHNETTIMERPERIOD)`
|
||||
- [x] 3.3 维护 `sys_tick_ms` 每次 +2 替代原来 +10
|
||||
|
||||
## 4. qdx_port.c 核心实现——时间、延时、互斥、线程
|
||||
|
||||
- [x] 4.1 新建 `prj/TCPClient/Middle/QDXnetworkStack/qdx_port.c`(基于 template),include FreeRTOS headers
|
||||
- [x] 4.2 实现 `qdx_port_get_tick_ms()`:返回 `xTaskGetTickCount() * portTICK_PERIOD_MS`
|
||||
- [x] 4.3 实现 `qdx_port_delay_ms()`:调用 `vTaskDelay(pdMS_TO_TICKS(ms))`
|
||||
- [x] 4.4 实现 `qdx_port_mutex_create/lock/unlock/delete`:封装 `xSemaphoreCreateMutex`、`xSemaphoreTake(portMAX_DELAY)`、`xSemaphoreGive`、`vSemaphoreDelete`
|
||||
- [x] 4.5 实现 `qdx_port_thread_create`:调用 `xTaskCreate` 映射 name、entry、arg、stack_size、priority
|
||||
|
||||
## 5. qdx_port.c Socket 层实现——映射与桥接
|
||||
|
||||
- [x] 5.1 定义 `SocketCtx_t` 结构体(wchnet_sock_id、connected、rx_ring、rx_sem、connect_sem)和静态数组 `g_sock_ctx[2]`
|
||||
- [x] 5.2 实现环形缓冲 `RxRingBuf_t` 的 init/write/read/available 操作(容量 2920 字节)
|
||||
- [x] 5.3 实现 `qdx_port_tcp_connect`:分配空闲 SocketCtx → 填充 SOCK_INF(PROTO_TYPE_TCP、目标 IP/Port)→ `WCHNET_SocketCreat` → `WCHNET_SocketConnect` → `xSemaphoreTake(connect_sem, 5000ms)` 阻塞等待连接完成
|
||||
- [x] 5.4 实现 `qdx_port_tcp_send`:校验句柄有效性 → 互斥锁保护 → `WCHNET_SocketSend(ctx->wchnet_sock_id, data, &len)`
|
||||
- [x] 5.5 实现 `qdx_port_tcp_recv`:检查 rx_ring 有数据直接读取返回;否则 `xSemaphoreTake(rx_sem, 100ms)` 阻塞 → 唤醒后再次检查 rx_ring → 返回数据或 0(超时)或 <0(连接断开)
|
||||
- [x] 5.6 实现 `qdx_port_tcp_close`:`WCHNET_SocketClose(ctx->wchnet_sock_id, 0)` → 清空 rx_ring → 标记 ctx 为空闲
|
||||
|
||||
## 6. WCHNET 中断回调改造
|
||||
|
||||
- [x] 6.1 修改 `WCHNET_HandleSockInt` 的 `SINT_STAT_RECV` 分支:通过 wchnet_sock_id 查找对应 SocketCtx → `WCHNET_SocketRecv` 读数据写入 rx_ring → `xSemaphoreGive(rx_sem)` 通知接收线程
|
||||
- [x] 6.2 修改 `SINT_STAT_CONNECT` 分支:查找对应 SocketCtx → 标记 `connected = 1` → `xSemaphoreGive(connect_sem)` 唤醒等待线程
|
||||
- [x] 6.3 修改 `SINT_STAT_DISCONNECT` 和 `SINT_STAT_TIM_OUT` 分支:标记 `connected = 0` → `xSemaphoreGive(rx_sem)` 确保阻塞中的 recv 能被唤醒退出
|
||||
|
||||
## 7. main.c 重构为 RTOS 多任务
|
||||
|
||||
- [x] 7.1 新增 `task_wchnet_entry` 函数:循环调用 `WCHNET_MainTask()` + `WCHNET_QueryGlobalInt` / `WCHNET_HandleGlobalInt`,周期约 5ms
|
||||
- [x] 7.2 新增 `task_business_entry` 函数:将原 `while(1)` 中的 DVP_Task + Frame 触发判定 + 预处理 + 封包发送逻辑迁入
|
||||
- [x] 7.3 修改 `main()` 函数:保留硬件初始化(SystemCoreClockUpdate、USART、DVP_Init、TIM2_Init、ETH_LibInit)→ Preprocess_Init → TcpLogic_Init → TcpLogic_RegisterCallbacks → `xTaskCreate(task_wchnet)` → `xTaskCreate(task_business)` → `vTaskStartScheduler()`
|
||||
- [x] 7.4 删除 `main()` 中的 `while(1)` 裸机主循环
|
||||
|
||||
## 8. 回调逻辑补全
|
||||
|
||||
- [x] 8.1 补全 `OnConfigUpdate`:内部调用 `Preprocess_Settings_Change(cfg2d, cfg1d, common)` 将新参数同步至预处理模块
|
||||
- [x] 8.2 补全 `OnDetectionResult`:当 `isOK == 0` 时拉高 NG DO GPIO 引脚,启动 FreeRTOS 软件定时器(`xTimerCreate`)延时 `NGioDelay` ms 后在回调中拉低复位
|
||||
- [x] 8.3 初始化 NG DO GPIO 引脚为推挽输出(在 main 硬件初始化阶段添加)
|
||||
|
||||
## 9. 验证与调试
|
||||
|
||||
- [ ] 9.1 编译整个 TCPClient 工程,修复链接错误
|
||||
- [ ] 9.2 使用 `uxTaskGetStackHighWaterMark` 验证各任务栈余量,防止溢出
|
||||
- [ ] 9.3 连接上位机测试控制流(5511)与数据流(5512)双连接建立、心跳和重连
|
||||
- [ ] 9.4 触发一帧图像,验证完整链路:DVP 采集 → 触发判定 → 预处理提取 → TCP 封包发送 → 上位机收到数据
|
||||
- [ ] 9.5 测试上位机下发参数更新,验证 Preprocess 参数热更新生效
|
||||
Reference in New Issue
Block a user