From c81bd93205429cf87f8e07a2487075c36ebd570c Mon Sep 17 00:00:00 2001 From: xiao Date: Wed, 15 Apr 2026 19:23:48 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A1=AE=E5=AE=9A=E8=B7=AF?= =?UTF-8?q?=E5=BE=84A=E6=9E=B6=E6=9E=84(NO=5FSYS=3D0+netconn+=E5=A4=9ATCP?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1)=EF=BC=8C=E7=B2=BE=E7=A1=AE=E5=8C=96?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E8=AE=BE=E8=AE=A1=E4=B8=8E=E5=86=85=E5=AD=98?= =?UTF-8?q?=E9=A2=84=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FreeRTOSConfig.h: 堆8->10KB, 优先级56->7, 添加lwIP sys_arch宏和任务优先级/栈大小定义 - lwipopts.h: LWIP_SOCKET=0节省RAM, LWIP_TCPIP_CORE_LOCKING=1, MEM_SIZE 8KB, PBUF_POOL 10, MEMP_NUM_NETCONN 8, TCP_SND_BUF/WND 8xMSS, 关闭DHCP/UDP, TCPIP_THREAD_STACKSIZE/PRIO明确指定 - 项目技术实现: 9+1任务架构, netconn阻塞模式每连接独立任务, 零拷贝route_msg_t, 内存精确估算49KB(RCT6超1KB需优化或换RDT6), 模块重写/复用清单 - 项目需求说明: 明确netconn API路线, 添加RDT6备选, 更新任务列表9个任务 --- Core/Inc/FreeRTOSConfig.h | 41 ++- Drivers/LwIP/src/include/arch/lwipopts.h | 70 +++-- 项目技术实现.md | 379 +++++++++++++++++------ 项目需求说明.md | 30 +- 4 files changed, 378 insertions(+), 142 deletions(-) diff --git a/Core/Inc/FreeRTOSConfig.h b/Core/Inc/FreeRTOSConfig.h index ba6fd6e..4ab429a 100644 --- a/Core/Inc/FreeRTOSConfig.h +++ b/Core/Inc/FreeRTOSConfig.h @@ -59,13 +59,13 @@ #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 1 #define configSUPPORT_DYNAMIC_ALLOCATION 1 -#define configUSE_IDLE_HOOK 0 +#define configUSE_IDLE_HOOK 1 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( SystemCoreClock ) #define configTICK_RATE_HZ ((TickType_t)1000) -#define configMAX_PRIORITIES ( 56 ) +#define configMAX_PRIORITIES ( 7 ) #define configMINIMAL_STACK_SIZE ((uint16_t)128) -#define configTOTAL_HEAP_SIZE ((size_t)8192) /* Fit R8 RAM budget with dynamic tasks */ +#define configTOTAL_HEAP_SIZE ((size_t)10240) #define configMAX_TASK_NAME_LEN ( 16 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 @@ -151,6 +151,41 @@ standard names. */ /* USER CODE BEGIN Defines */ /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */ + +/* lwIP sys_arch compatibility macros */ +#define sys_arch_protect() vPortEnterCritical() +#define sys_arch_unprotect(x) vPortExitCritical() +#define sys_now() ((uint32_t)xTaskGetTickCount()) +#define SYS_ARCH_DECL_PROTECT(lev) uint32_t lev +#define SYS_ARCH_PROTECT(lev) (lev) = vPortEnterCritical() +#define SYS_ARCH_UNPROTECT(lev) vPortExitCritical() + +/* Application task priorities (higher number = higher priority) */ +#define TASK_PRIORITY_TCPIP 6 +#define TASK_PRIORITY_NET_POLL 5 +#define TASK_PRIORITY_TCP_SERVER 4 +#define TASK_PRIORITY_TCP_CLIENT 4 +#define TASK_PRIORITY_UART_RX 4 +#define TASK_PRIORITY_ROUTE 3 +#define TASK_PRIORITY_CONFIG 2 +#define TASK_PRIORITY_DEFAULT 1 + +/* Application task stack sizes (in words) */ +#define TASK_STACK_TCPIP 512 +#define TASK_STACK_NET_POLL 384 +#define TASK_STACK_TCP_SERVER 384 +#define TASK_STACK_TCP_CLIENT 256 +#define TASK_STACK_UART_RX 384 +#define TASK_STACK_ROUTE 512 +#define TASK_STACK_CONFIG 256 +#define TASK_STACK_DEFAULT 128 + +/* Route message pool for zero-copy inter-task communication */ +#define ROUTE_MSG_POOL_SIZE 8 +#define ROUTE_MSG_MAX_PAYLOAD 512 + +/* lwIP thread name for tcpip_thread */ +#define TCPIP_THREAD_NAME "tcpip" /* USER CODE END Defines */ #endif /* FREERTOS_CONFIG_H */ diff --git a/Drivers/LwIP/src/include/arch/lwipopts.h b/Drivers/LwIP/src/include/arch/lwipopts.h index 3a9eb0c..edb8dfa 100644 --- a/Drivers/LwIP/src/include/arch/lwipopts.h +++ b/Drivers/LwIP/src/include/arch/lwipopts.h @@ -1,12 +1,15 @@ /** * @file lwipopts.h - * @brief LwIP configuration for STM32F103 + FreeRTOS + CH390 Ethernet + * @brief LwIP configuration for STM32F103RCT6 + FreeRTOS + CH390 Ethernet * - * This configuration is optimized for: - * - STM32F103 with limited RAM (~20KB available) - * - FreeRTOS integration (NO_SYS=0) - * - TCP Server + Client dual link transparent transmission - * - CH390 Ethernet controller + * Path A: NO_SYS=0, netconn API, multi-task TCP architecture. + * Optimized for STM32F103RCT6 (48KB SRAM) with pin-to-pin backup STM32F103RDT6 (64KB SRAM). + * + * Key design decisions: + * - netconn API for thread-safe multi-connection TCP + * - tcpip_thread handles all lwIP core operations + * - LWIP_TCPIP_CORE_LOCKING=1 allows direct send from application tasks + * - Conservative memory footprint: target ~16KB for lwIP */ #ifndef LWIP_LWIPOPTS_H @@ -19,11 +22,15 @@ /* Use FreeRTOS - this enables the sequential API (netconn, sockets) */ #define NO_SYS 0 -/* Enable socket API */ -#define LWIP_SOCKET 1 +/* Enable netconn API (primary), disable socket API to save RAM */ +#define LWIP_SOCKET 0 #define LWIP_NETCONN 1 #define LWIP_NETIF_API 0 +/* Core locking: allows netconn_write/recv from any task without going through mbox */ +#define LWIP_TCPIP_CORE_LOCKING 1 +#define LWIP_TCPIP_CORE_LOCKING_INPUT 0 + /* Critical section protection */ #define SYS_LIGHTWEIGHT_PROT 1 @@ -35,17 +42,20 @@ #define LWIP_PROVIDE_ERRNO 1 /*----------------------------------------------------------------------------- - * Memory Configuration (optimized for STM32F103 with ~20KB RAM) + * Memory Configuration (optimized for STM32F103RCT6 with 48KB SRAM) *---------------------------------------------------------------------------*/ /* Memory alignment (ARM Cortex-M3 = 4 byte alignment) */ #define MEM_ALIGNMENT 4 -/* Heap size for dynamic memory allocation */ -#define MEM_SIZE (4 * 1024) /* 4KB for LwIP heap */ +/* Heap size for dynamic memory allocation. + * With netconn: larger heap needed for netbuf allocation and connection management. + * 8KB provides headroom for 4 concurrent TCP connections. */ +#define MEM_SIZE (8 * 1024) -/* Number of pbufs in pool */ -#define PBUF_POOL_SIZE 8 +/* Number of pbufs in pool. + * 10 pools for 4 concurrent connections with some headroom. */ +#define PBUF_POOL_SIZE 10 /* Size of each pbuf in pool (must hold one Ethernet frame) */ #define PBUF_POOL_BUFSIZE LWIP_MEM_ALIGN_SIZE(TCP_MSS + 40 + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN) @@ -65,19 +75,20 @@ /* Number of listening TCP connections */ #define MEMP_NUM_TCP_PCB_LISTEN 2 -/* Number of simultaneously queued TCP segments */ -#define MEMP_NUM_TCP_SEG 17 +/* Number of simultaneously queued TCP segments + * Increased for 4 concurrent connections */ +#define MEMP_NUM_TCP_SEG 24 /* Number of simultaneously active timeouts */ #define MEMP_NUM_SYS_TIMEOUT 8 -/* Number of netbufs (for sequential API) */ -#define MEMP_NUM_NETBUF 4 +/* Number of netbufs (for netconn API, one per pending recv) */ +#define MEMP_NUM_NETBUF 8 -/* Number of netconns */ -#define MEMP_NUM_NETCONN 6 +/* Number of netconns: 2 listeners + 2 accepted + 2 clients + 2 margin = 8 */ +#define MEMP_NUM_NETCONN 8 -/* TCPIP message queue size */ +/* TCPIP message queue size (must be >= max simultaneous API calls) */ #define MEMP_NUM_TCPIP_MSG_API 8 #define MEMP_NUM_TCPIP_MSG_INPKT 8 @@ -118,15 +129,14 @@ * DHCP Configuration *---------------------------------------------------------------------------*/ -#define LWIP_DHCP 1 -#define DHCP_DOES_ARP_CHECK 1 +#define LWIP_DHCP 0 /* Static IP only */ +#define DHCP_DOES_ARP_CHECK 0 /*----------------------------------------------------------------------------- * UDP Configuration *---------------------------------------------------------------------------*/ -#define LWIP_UDP 1 -#define UDP_TTL 255 +#define LWIP_UDP 0 /* UDP not used in this project */ /*----------------------------------------------------------------------------- * TCP Configuration (optimized for transparent transmission) @@ -138,14 +148,14 @@ /* TCP Maximum Segment Size */ #define TCP_MSS 536 /* Conservative value for compatibility */ -/* TCP sender buffer space (bytes) */ -#define TCP_SND_BUF (4 * TCP_MSS) +/* TCP sender buffer space - increased for bridge throughput */ +#define TCP_SND_BUF (8 * TCP_MSS) /* TCP sender buffer space (pbufs) */ #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) -/* TCP receive window */ -#define TCP_WND (4 * TCP_MSS) +/* TCP receive window - increased for bridge throughput */ +#define TCP_WND (8 * TCP_MSS) /* TCP writable space threshold */ #define TCP_SNDLOWAT LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) @@ -188,8 +198,8 @@ * Callback Configuration *---------------------------------------------------------------------------*/ -#define LWIP_NETIF_STATUS_CALLBACK 1 -#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_STATUS_CALLBACK 0 +#define LWIP_NETIF_LINK_CALLBACK 0 /*----------------------------------------------------------------------------- * Checksum Configuration diff --git a/项目技术实现.md b/项目技术实现.md index 3852868..884eecb 100644 --- a/项目技术实现.md +++ b/项目技术实现.md @@ -48,105 +48,183 @@ +--------------------------------------------------+ ``` -## 四、FreeRTOS 任务设计 +## 四、FreeRTOS 任务设计(路径 A:netconn + 多 TCP 任务) -### 4.1 任务列表 +### 4.1 架构路线 -| 任务名 | 优先级 | 栈大小 | 周期 | 职责 | -|--------|--------|--------|------|------| -| `NetworkTask` | osPriorityHigh (4) | 512 words | 事件驱动 | CH390 事件消费 + lwIP 超时处理 | -| `UartTask` | osPriorityHigh (4) | 512 words | 事件驱动 | UART DMA/IDLE 接收 + MUX 帧提取 | -| `ConfigTask` | osPriorityNormal (3) | 256 words | 事件驱动 | AT 命令解析与响应 | -| `RouteTask` | osPriorityNormal (3) | 512 words | 事件驱动 | SRCID/DSTMASK 数据路由分发 | -| `DefaultTask` | osPriorityLow (1) | 128 words | 1000ms 周期 | LED 心跳 + IWDG 喂狗 | +本项目采用 **路径 A**:`NO_SYS=0 + netconn API + 每个 TCP 连接独立任务`。 -### 4.2 任务间通信机制 +核心决策: +1. lwIP 以 `NO_SYS=0` 模式运行,`tcpip_thread` 由 lwIP 自动创建 +2. TCP 连接使用 `netconn` 阻塞 API(`netconn_accept` / `netconn_recv` / `netconn_write`) +3. 每个 TCP Server 和 Client 实例各占一个独立任务 +4. 任务间通过 Queue 传递指针 + 元数据描述符,实现零拷贝 -```text -UART ISR --[Semaphore]--> UartTask --[Queue]--> RouteTask --[Queue]--> NetworkTask - | -ConfigTask <--[Queue]-- RouteTask <--[Queue]-- NetworkTask <--------+ - | - +--> UART TX (Direct DMA send) +### 4.2 任务列表(共 9 个任务 + 1 个 lwIP 自建) + +| 任务名 | 优先级 | 栈(words) | 模式 | 职责 | +|--------|--------|-----------|------|------| +| `tcpip_thread` | 6 (最高) | 512 | 阻塞 | lwIP 内核线程(自动创建) | +| `NetPollTask` | 5 | 384 | 事件驱动 | `ethernetif_poll` + 链路检测 | +| `TcpSrvTask_S1` | 4 | 384 | 阻塞 | `netconn_accept` + S1 收发 | +| `TcpSrvTask_S2` | 4 | 384 | 阻塞 | `netconn_accept` + S2 收发 | +| `TcpCliTask_C1` | 4 | 256 | 阻塞 | `netconn_connect` + C1 收发 | +| `TcpCliTask_C2` | 4 | 256 | 阻塞 | `netconn_connect` + C2 收发 | +| `UartRxTask` | 4 | 384 | 事件驱动 | UART DMA/IDLE 接收 + MUX 帧提取 | +| `ConfigTask` | 2 | 256 | 阻塞 | AT 命令解析与响应 | +| `DefaultTask` | 1 | 128 | 周期 | LED 心跳 + IWDG 喂狗 | + +说明: +- `tcpip_thread` 是 lwIP 自建的内核线程,处理所有协议栈内部事件 +- 所有 `netconn_*` API 通过 `tcpip_thread` 消息机制实现线程安全 +- `LWIP_TCPIP_CORE_LOCKING=1` 允许应用任务直接调用 `netconn_write` 而不经邮箱中转 +- `tcpip_thread` 优先级最高(6),确保 TCP ACK 和超时处理不被延迟 + +### 4.3 零拷贝路由消息设计 + +```c +/* 路由消息描述符 - Queue 传递的是此结构的指针,不拷贝负载数据 */ +typedef struct { + uint8_t src_id; /* 源端点 ID */ + uint8_t dst_mask; /* 目标端点位图 */ + uint16_t len; /* 数据长度 */ + uint8_t conn_type; /* 连接标识:LINK_S1/S2/C1/C2 */ + uint8_t *data; /* 指向预分配静态缓冲区 */ +} route_msg_t; ``` -具体通信对象: +静态缓冲池(预分配,避免动态分配): + +```c +#define ROUTE_BUF_COUNT 4 +#define ROUTE_BUF_SIZE 512 + +static uint8_t g_route_buf_pool[ROUTE_BUF_COUNT][ROUTE_BUF_SIZE]; +static volatile uint8_t g_route_buf_used[ROUTE_BUF_COUNT]; +``` + +- 发送方:从池中获取空闲缓冲区,拷贝数据,填充 `route_msg_t`,Queue 发送指针 +- 接收方:从 Queue 取 `route_msg_t*`,处理数据后标记缓冲区为可用 +- 无动态分配,无堆碎片 + +### 4.4 任务间通信机制 + +```text +UART ISR ──[TaskNotify]──> UartRxTask ──[Queue*]──> TcpSrvTask / TcpCliTask + │ ▲ + ├──[Queue*]──────────────>─┘ + │ + DSTMASK=0 └──[Queue]──> ConfigTask + +TcpSrvTask / TcpCliTask ──[Queue*]──> UartRxTask (UART TX 方向) + +EXTI0 ISR ──[BinarySem]──> NetPollTask ──> ethernetif_poll ──> tcpip_thread +``` + +通信对象: | 对象 | 类型 | 生产者 | 消费者 | 用途 | |------|------|--------|--------|------| -| `xUartRxQueue` | Queue (64 items) | UartTask | RouteTask | UART 接收帧传递 | -| `xTcpRxQueue` | Queue (32 items) | NetworkTask | RouteTask | TCP 接收数据传递 | -| `xConfigQueue` | Queue (16 items) | RouteTask | ConfigTask | AT 命令文本传递 | -| `xCh390Semaphore` | Binary Semaphore | EXTI0 ISR | NetworkTask | CH390 中断通知 | -| `xSpiMutex` | Mutex | NetworkTask | 多任务 | SPI/CH390 访问保护 | -| `xUart2TxStream` | StreamBuffer (1024) | RouteTask | UartTask | UART2 发送数据 | -| `xUart3TxStream` | StreamBuffer (1024) | RouteTask | UartTask | UART3 发送数据 | +| `xNetSemaphore` | Binary Semaphore | EXTI0 ISR | NetPollTask | CH390 中断通知 | +| `xUartRxNotify` | TaskNotification | UART IDLE ISR | UartRxTask | UART 接收通知 | +| `xTcpRxQueue` | Queue (16, route_msg_t*) | TCP 任务 | UartRxTask | TCP→UART 数据 | +| `xUartTxQueue` | Queue (8, route_msg_t*) | UartRxTask | TCP 任务 | UART→TCP 数据 | +| `xConfigQueue` | Queue (8, char*) | UartRxTask | ConfigTask | AT 文本 | -### 4.3 NetworkTask 实现方向 +说明: +- TCP→UART 方向:TCP 任务调用 `netconn_recv` 获取数据,构造 `route_msg_t` 指针投递到 `xTcpRxQueue`,UartRxTask 取出后写入 UART DMA +- UART→TCP 方向:UartRxTask 从 UART DMA 读取数据,构造 `route_msg_t` 指针投递到 `xUartTxQueue`,对应 TCP 任务取出后调用 `netconn_write` 发送 +- 路由逻辑内联在 UartRxTask 和各 TCP 任务中,不设独立 RouteTask + +### 4.5 NetPollTask 实现 ```c -void NetworkTask(void *argument) +void NetPollTask(void *argument) { /* 初始化 CH390 + lwIP netif */ - /* 创建 TCP Server/Client 实例 */ + ethernetif_init(&ch390_netif); + /* 等待链路就绪后启动 TCP 任务 */ + /* ... */ + for (;;) { - /* 等待 CH390 中断信号量 */ - xSemaphoreTake(xCh390Semaphore, pdMS_TO_TICKS(10)); - /* 处理 CH390 事件 */ - ethernetif_poll(); - ethernetif_check_link(); - /* lwIP 超时处理由 tcpip_thread 自动完成 */ - /* TCP 数据收发 */ - tcp_link_process(); + xSemaphoreTake(xNetSemaphore, pdMS_TO_TICKS(2)); + ethernetif_poll(&ch390_netif); + ethernetif_check_link(&ch390_netif); + /* sys_check_timeouts() 由 tcpip_thread 自动执行,此处不需要调用 */ } } ``` -### 4.4 UartTask 实现方向 +### 4.6 TcpSrvTask 实现模板 ```c -void UartTask(void *argument) +void TcpSrvTask_S1(void *argument) { + struct netconn *conn = netconn_new(NETCONN_TCP); + netconn_bind(conn, IP_ADDR_ANY, cfg->links[0].lport); + netconn_listen(conn); + for (;;) { - /* 等待 UART IDLE 中断通知 */ - ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)); - /* 处理 UART2/UART3 DMA 接收数据 */ - /* MUX=0: 直接投递到路由队列 */ - /* MUX=1: 提取 MUX 帧,分流控制帧与数据帧 */ - /* 检查 StreamBuffer,发送 UART TX 数据 */ + struct netconn *newconn; + if (netconn_accept(conn, &newconn) == ERR_OK) { + /* 在本任务内处理唯一客户端 */ + tcp_server_worker(newconn, LINK_S1); + netconn_close(newconn); + netconn_delete(newconn); + } } } ``` -### 4.5 ConfigTask 实现方向 +### 4.7 TcpCliTask 实现模板 + +```c +void TcpCliTask_C1(void *argument) +{ + for (;;) { + struct netconn *conn = netconn_new(NETCONN_TCP); + ip_addr_t remote_ip; + IP_ADDR4(&remote_ip, cfg->links[2].rip[0], ...); + + if (netconn_connect(conn, &remote_ip, cfg->links[2].rport) == ERR_OK) { + tcp_client_worker(conn, LINK_C1); + } + netconn_close(conn); + netconn_delete(conn); + vTaskDelay(pdMS_TO_TICKS(cfg->links[2].reconnect_interval)); + } +} +``` + +### 4.8 UartRxTask 实现 + +```c +void UartRxTask(void *argument) +{ + for (;;) { + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)); + /* 处理 UART2/UART3 DMA 接收数据 */ + /* MUX=0: 构造 route_msg_t 指针投递到 xUartTxQueue */ + /* MUX=1: 提取 MUX 帧,DSTMASK=0 投 xConfigQueue,否则投 xUartTxQueue */ + /* 从 xTcpRxQueue 取数据,写入 UART DMA 发送 */ + } +} +``` + +### 4.9 ConfigTask 实现 ```c void ConfigTask(void *argument) { for (;;) { - /* 从配置队列接收 AT 命令文本 */ + char *cmd; xQueueReceive(xConfigQueue, &cmd, portMAX_DELAY); - /* 解析并执行 AT 命令 */ config_process_at_cmd(cmd); /* 通过 UART1 发送响应 */ } } ``` -### 4.6 RouteTask 实现方向 - -```c -void RouteTask(void *argument) -{ - for (;;) { - /* 从 UART 队列和 TCP 队列读取数据帧 */ - /* 根据 SRCID/DSTMASK 决定路由目标 */ - /* 控制帧 (DSTMASK=0x00) -> ConfigTask */ - /* 数据帧 -> TCP 实例或 UART TX */ - } -} -``` - ## 五、最终协议实现模型 ### 5.1 MUX 帧承载层 @@ -276,13 +354,16 @@ EN,LPORT,RIP,RPORT,UART 3. 在 `MUX=1` 时执行 MUX 帧收发 4. 将控制帧与业务数据帧分流 -### 7.3 TCP Server / Client 模块 +### 7.3 TCP Server / Client 模块(需重写) -最终职责: +原 `tcp_server.c` / `tcp_client.c` 基于 lwIP RAW API 回调模式,需重写为 netconn 阻塞模式: -1. 不再从外部协议角度区分不同字段模型 -2. 统一受 `LINK[idx]` 配置驱动 -3. 由调度层决定实例与 UART 的数据交换路径 +1. 删除所有 `tcp_pcb` / `tcp_recv` / `tcp_accept` 回调代码 +2. 改用 `netconn_new` / `netconn_bind` / `netconn_listen` / `netconn_accept`(Server) +3. 改用 `netconn_new` / `netconn_connect`(Client) +4. 收发改为 `netconn_recv` / `netconn_write` 阻塞调用 +5. 内部 ring buffer 可取消(netconn 内部已有 pbuf 缓冲) +6. 每个 TCP 任务内直接处理路由,通过 Queue 指针传递数据 ### 7.4 FreeRTOS 初始化 `freertos.c` @@ -309,36 +390,67 @@ CubeMX 生成的 FreeRTOS 初始化文件,职责: | `configUSE_MALLOC_FAILED_HOOK` | 1 | 内存分配失败钩子 | | `configSUPPORT_DYNAMIC_ALLOCATION` | 1 | 动态内存分配 | -## 八、lwIP 配置方向 +## 八、lwIP 配置(NO_SYS=0 + netconn) ### 8.1 lwIP 线程模型 由于采用 `NO_SYS=0`,lwIP 将运行以下线程: -1. `tcpip_thread`:lwIP 核心线程,处理所有协议栈内部事件 -2. 应用任务通过 `netconn` / `socket` API 与 lwIP 交互 +1. `tcpip_thread`(优先级 6,栈 512 words):lwIP 核心线程,处理所有协议栈内部事件 +2. 应用任务通过 `netconn` API 与 lwIP 交互,由 `tcpip_thread` 消息机制保证线程安全 +3. `LWIP_TCPIP_CORE_LOCKING=1`:允许应用任务在持有核心锁时直接调用 `netconn_write`,无需通过邮箱中转 -### 8.2 lwIP 内存配置建议 +### 8.2 lwIP 关键配置项(lwipopts.h) -| 配置项 | 建议值 | 说明 | -|--------|--------|------| +| 配置项 | 值 | 说明 | +|--------|-----|------| +| `NO_SYS` | 0 | 启用 OS 抽象 | +| `LWIP_NETCONN` | 1 | 启用 netconn API | +| `LWIP_SOCKET` | 0 | 不使用 socket API,节省 RAM | +| `LWIP_TCPIP_CORE_LOCKING` | 1 | 允许直接发送,减少延时 | | `MEM_SIZE` | 8192 | lwIP 堆大小 | -| `MEMP_NUM_NETCONN` | 6 | netconn 连接数 | -| `MEMP_NUM_TCP_PCB` | 6 | TCP 控制块数 | -| `PBUF_POOL_SIZE` | 8 | pbuf 池大小 | -| `PBUF_POOL_BUFSIZE` | 1524 | pbuf 缓冲大小 | -| `TCP_WND` | 2048 | TCP 窗口大小 | -| `TCP_MSS` | 1460 | TCP 最大段大小 | +| `PBUF_POOL_SIZE` | 10 | pbuf 池数量 | +| `MEMP_NUM_NETCONN` | 8 | 2监听 + 4连接 + 2余量 | +| `MEMP_NUM_NETBUF` | 8 | netconn 缓冲 | +| `MEMP_NUM_TCP_PCB` | 4 | TCP 控制块 | +| `MEMP_NUM_TCP_PCB_LISTEN` | 2 | TCP 监听 | +| `MEMP_NUM_TCP_SEG` | 24 | TCP 段 | +| `MEMP_NUM_TCPIP_MSG_API` | 8 | API 消息池 | +| `MEMP_NUM_TCPIP_MSG_INPKT` | 8 | 入包消息池 | +| `TCP_MSS` | 536 | 保守 MSS | +| `TCP_SND_BUF` | 8×MSS=4288 | 发送缓冲 | +| `TCP_WND` | 8×MSS=4288 | 接收窗口 | +| `TCPIP_THREAD_STACKSIZE` | 512 | tcpip_thread 栈 | +| `TCPIP_THREAD_PRIO` | 6 (最高) | tcpip_thread 优先级 | +| `LWIP_DHCP` | 0 | 不使用 DHCP | +| `LWIP_UDP` | 0 | 不使用 UDP | ### 8.3 sys_arch 移植层 -`Drivers/LwIP/port/sys_arch.c` 提供 lwIP 到 FreeRTOS 的适配: +`Drivers/LwIP/port/sys_arch.c` 需实现 lwIP 到 FreeRTOS 的适配: -1. `sys_thread_new`:创建 lwIP 线程 -2. `sys_mbox_*`:消息邮箱(基于 FreeRTOS Queue) +1. `sys_thread_new`:创建 lwIP 线程(即 `tcpip_thread`) +2. `sys_mbox_*`:消息邮箱(基于 FreeRTOS Queue,容量 `TCPIP_MBOX_SIZE=8`) 3. `sys_sem_*`:信号量(基于 FreeRTOS Semaphore) 4. `sys_mutex_*`:互斥锁(基于 FreeRTOS Mutex) -5. `sys_arch_protect / unprotect`:临界区保护 +5. `sys_arch_protect / unprotect`:临界区保护(基于 `vPortEnterCritical / vPortExitCritical`) + +### 8.4 lwIP 初始化流程 + +```c +/* 在 MX_FREERTOS_Init 或 NetPollTask 中调用 */ +void lwip_init_task(void) +{ + /* tcpip_thread 在 lwip_init() 或第一个 sys_thread_new 时自动启动 */ + tcpip_init(NULL, NULL); + + /* 等待 tcpip_thread 就绪 */ + /* 添加网络接口 */ + netif_add(&ch390_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); + netif_set_default(&ch390_netif); + netif_set_up(&ch390_netif); +} +``` ## 九、中断与 HAL 时间基准 @@ -365,34 +477,65 @@ FreeRTOS 下 `SysTick` 被 FreeRTOS 占用,HAL 时间基准改用 `TIM4`: FreeRTOS 可管理的中断优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIORITY`(本工程为 5)。 -## 十、内存预算 +## 十、内存预算(路径 A 精确估算) 以 `STM32F103RCT6` 为目标(48KB SRAM): ### 10.1 RAM 预算 -| 项目 | 建议值 | 说明 | -|------|--------|------| -| 启动栈 (MSP) | 2 KB | startup_stm32f103xe.s 中定义 | -| FreeRTOS 堆 | 10 KB | heap_4.c 管理 | -| 任务栈合计 | ~6 KB | 5 个任务 | -| lwIP 堆 | 8 KB | MEM_SIZE | -| UART 缓冲 | 4 KB | RX/TX DMA 缓冲 | -| Queue/StreamBuffer | 4 KB | 任务间通信 | -| 参数/状态 | 2 KB | 配置结构 | -| 空闲栈 | 1 KB | 系统预留 | -| **合计** | ~37 KB | 预留约 11 KB 余量 | +| 项目 | 大小 | 说明 | +|------|------|------| +| 启动栈 (MSP) | 2,048 B | `startup_stm32f103xe.s` 定义 0x800 | +| FreeRTOS 堆 (heap_4) | 10,240 B | `configTOTAL_HEAP_SIZE` | +| 任务栈 (9 任务) | 13,312 B | 3,328 words × 4 (见上表) | +| lwIP 堆 (MEM_SIZE) | 8,192 B | | +| PBUF 池 (10 个) | ~6,000 B | 10 × ~600B | +| MEMP 池 | ~4,000 B | netconn/netbuf/tcpip_msg/pcb/seg | +| UART DMA+Ring 缓冲 | 2,304 B | 2 通道 × (256+256+512+384)/2 | +| 路由缓冲池 (4×512B) | 2,048 B | 零拷贝指针传递 | +| 配置结构 | 1,024 B | | +| **合计** | **~49,168 B** | | +| **余量 (RCT6 48KB)** | **~-928 B** | ⚠️ 超出,需优化或换 RDT6 | +| **余量 (RDT6 64KB)** | **~15,264 B** | ✅ 充裕 | -### 10.2 Flash 预算 +### 10.2 优化空间(RCT6 下) + +若坚持使用 RCT6,可通过以下措施压缩到 48KB 以内: + +| 优化项 | 节省 | +|--------|------| +| 取消 TCP 任务的 ring buffer(netconn 内部有 pbuf 缓冲) | -2,048 B | +| `configTOTAL_HEAP_SIZE` 降至 8KB | -2,048 B | +| `MEM_SIZE` 降至 6KB | -2,048 B | +| TcpCliTask 栈降至 192 words × 2 | -512 B | +| **优化后合计** | **~42,504 B** | +| **RCT6 余量** | **~5,464 B** | + +### 10.3 备选 MCU + +当 RAM 最终不够用时,切换为 `STM32F103RDT6`(pin-to-pin 替代): + +| 项目 | RCT6 | RDT6 | +|------|------|------| +| Flash | 256 KB | 384 KB | +| SRAM | 48 KB | 64 KB | +| 引脚 | LQFP64 | LQFP64(完全兼容) | +| 启动文件 | `startup_stm32f103xe.s` | `startup_stm32f103xd.s` | +| 宏定义 | `STM32F103xE` | `STM32F103xD` | +| Flash 算法 | `STM32F10x_HD` | `STM32F10x_HD`(相同) | +| SRAM 大小 | `0xC000` | `0x10000` | +| Flash 大小 | `0x40000` | `0x60000` | + +### 10.4 Flash 预算 | 项目 | 估计值 | 说明 | |------|--------|------| | FreeRTOS 内核 | ~8 KB | 含 CMSIS-RTOS V2 | | HAL 驱动 | ~20 KB | GPIO/UART/SPI/DMA/IWDG/TIM | -| lwIP 协议栈 | ~40 KB | core + api + ipv4 + netif | +| lwIP 协议栈 | ~50 KB | core + api + ipv4 + netif + sys_arch | | CH390 驱动 | ~4 KB | | -| 应用代码 | ~20 KB | config/uart_trans/tcp/route | -| **合计** | ~92 KB | 预留约 164 KB 余量 | +| 应用代码 | ~20 KB | config/uart_trans/tcp_server/tcp_client | +| **合计** | ~102 KB | RCT6 预留 154 KB,RDT6 预留 282 KB | ## 十一、硬件资源 @@ -451,3 +594,45 @@ FreeRTOS 可管理的中断优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIOR 2. 控制帧只使用 `DSTMASK=0x00` 3. MUX 帧格式固定为 `SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL` 4. AT 手册、需求说明、技术实现三份文档不得再出现历史展开式字段 + +## 十四、路径 A 实现清单 + +### 14.1 必须重写的模块 + +| 模块 | 原实现 | 目标实现 | 说明 | +|------|--------|----------|------| +| `tcp_server.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | `tcp_new` → `netconn_new`,回调 → 阻塞循环 | +| `tcp_client.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | 同上 | +| `sys_arch.c/.h` | NO_SYS=1 空壳 | FreeRTOS 移植层 | `sys_mbox`、`sys_sem`、`sys_mutex`、`sys_thread` | +| `lwipopts.h` | NO_SYS=1 | NO_SYS=0 + netconn | 已更新 | +| `main.c` | while(1) 轮询 | FreeRTOS 任务创建 | `App_Poll()` → 各任务函数 | +| `stm32f1xx_it.c` | 裸机 ISR | FreeRTOS ISR | `xxFromISR` API | + +### 14.2 可复用(需适配)的模块 + +| 模块 | 适配内容 | +|------|----------| +| `uart_trans.c/.h` | 添加 `xTaskNotifyFromISR` 替代 poll 模式 | +| `config.c/.h` | 添加 `xQueueReceive` 替代 `config_poll()` | +| `flash_param.c/.h` | 无需修改 | +| `CH390` 驱动 | `ethernetif_poll` 改为 NetPollTask 调用,SPI 加 Mutex 保护 | +| `ethernetif.c` | 添加 `netif_add` 的 `tcpip_input` 回调 | + +### 14.3 无需修改的模块 + +| 模块 | 说明 | +|------|------| +| `FreeRTOSConfig.h` | 已更新(任务优先级宏、堆大小) | +| `startup_stm32f103xe.s` | 已适配 RCT6 | +| `TCP2UART.ioc` | 已适配 RCT6 + FreeRTOS + TIM4 | +| `MDK-ARM/TCP2UART.uvprojx` | 已适配 RCT6 + xE 宏 | +| MUX 帧编解码 | 协议逻辑与 RTOS 无关 | + +### 14.4 新增模块 + +| 模块 | 职责 | +|------|------| +| `route_msg.c/.h` | 零拷贝路由消息池管理 | +| `task_tcp_server.c/.h` | netconn Server 任务模板 | +| `task_tcp_client.c/.h` | netconn Client 任务模板 | +| `task_net_poll.c/.h` | CH390 poll + link check 任务 | diff --git a/项目需求说明.md b/项目需求说明.md index 12551b0..a094c8e 100644 --- a/项目需求说明.md +++ b/项目需求说明.md @@ -22,6 +22,7 @@ ### 2.1 硬件边界 - 主控:`STM32F103RCT6`(256KB Flash / 48KB SRAM) +- 备选主控:`STM32F103RDT6`(384KB Flash / 64KB SRAM),pin-to-pin 兼容,当 RAM 不够时直接替换 - 以太网芯片:`CH390D` - 网卡数量:`1` - 配置口:`UART1` @@ -30,10 +31,11 @@ ### 2.2 软件边界 - 执行模型:`FreeRTOS` -- 网络协议栈:`lwIP + NO_SYS=0`(支持 socket/netconn 线程安全 API) +- 网络协议栈:`lwIP NO_SYS=0 + netconn API`(线程安全,每连接独立任务) - 调试输出:`SEGGER RTT` - 采用 `FreeRTOS` 任务调度 -- 采用 `lwIP socket/netconn` 或 `RAW API` 实现多路 TCP 并发 +- TCP 连接使用 `netconn` 阻塞 API(`netconn_accept` / `netconn_recv` / `netconn_write`) +- 每条 TCP 连路(S1/S2/C1/C2)独立一个任务 - 不包含 DHCP 协议支持 ## 三、最终协议需求 @@ -152,22 +154,26 @@ EN,LPORT,RIP,RPORT,UART | 任务 | 优先级 | 职责 | |------|--------|------| -| NetworkTask | 高 | CH390 事件轮询 + lwIP tcpip 处理 | -| UartTask | 高 | UART DMA/IDLE 接收 + MUX 帧处理 | -| ConfigTask | 中 | AT 命令解析与响应 | -| RouteTask | 中 | SRCID/DSTMASK 数据路由 | -| DefaultTask | 低 | LED 心跳 + 看门狗 | +| tcpip_thread | 6 (最高) | lwIP 内核线程(自动创建) | +| NetPollTask | 5 | CH390 事件轮询 + 链路检测 | +| TcpSrvTask_S1 | 4 | S1 netconn_accept + 收发 | +| TcpSrvTask_S2 | 4 | S2 netconn_accept + 收发 | +| TcpCliTask_C1 | 4 | C1 netconn_connect + 收发 | +| TcpCliTask_C2 | 4 | C2 netconn_connect + 收发 | +| UartRxTask | 4 | UART DMA/IDLE 接收 + MUX 帧提取 + 路由 | +| ConfigTask | 2 | AT 命令解析与响应 | +| DefaultTask | 1 | LED 心跳 + 看门狗 | ### 6.2 任务间通信 -- 使用 `Queue` 传递 UART 接收数据帧 -- 使用 `Semaphore` 同步 CH390 中断事件 -- 使用 `Mutex` 保护 SPI/CH390 共享访问 -- 使用 `StreamBuffer` 传递 TCP 数据到 UART 方向 +- 使用 `Queue` 传递指针 + 元数据描述符(零拷贝路由消息) +- 使用 `Binary Semaphore` 同步 CH390 中断事件 +- 使用 `TaskNotification` 通知 UART IDLE 事件 +- 预分配静态缓冲池,避免动态分配 ## 七、非功能需求 -1. 满足 `STM32F103RCT6` 的 `256KB Flash / 48KB SRAM` 约束 +1. 满足 `STM32F103RCT6` 的 `256KB Flash / 48KB SRAM` 约束,若 RAM 不足可切换 `STM32F103RDT6`(pin-to-pin,64KB SRAM) 2. 工程可在 `MDK-ARM` 下构建 3. 调试输出统一使用 `SEGGER RTT` 4. 不引入 DHCP、DNS、UDP 等当前非目标协议