# TCP2UART 项目技术实现 ## 一、系统架构 ``` +-------------------+ +-------------------+ | TCP Server | | TCP Client | | (监听端口) | | (连接远程服务器) | +--------+----------+ +--------+----------+ | | | CH390D | +------------------------------+ | +------v------+ | STM32 | | F103R8T6 | +------+------+ | +-------------+-------------+ | | | +---v---+ +---v---+ +---v---+ | UART1 | | UART2 | | UART3 | | 配置口 | | 透传口 | | 透传口 | +-------+ +-------+ +-------+ ``` ## 二、硬件配置 ### 2.1 MCU 型号 STM32F103R8T6(LQFP64,64KB Flash,20KB RAM) ### 2.2 引脚分配 | 引脚 | 功能 | 用途 | |------|------|------| | PA2 | USART2_TX | Server 透传串口 | | PA3 | USART2_RX | Server 透传串口 | | PA4 | SPI1_NSS | CH390D 片选(硬件自动管理) | | PA5 | SPI1_SCK | CH390D SPI 时钟 | | PA6 | SPI1_MISO | CH390D SPI 数据输入 | | PA7 | SPI1_MOSI | CH390D SPI 数据输出 | | PA9 | USART1_TX | 配置串口 | | PA10 | USART1_RX | 配置串口 | | PA13 | SWDIO | SWD 调试接口 | | PA14 | SWCLK | SWD 调试接口 | | PB0 | EXTI0 | CH390D 中断输入 | | PB1 | GPIO_Output | CH390D 复位 | | PB10 | USART3_TX | Client 透传串口 | | PB11 | USART3_RX | Client 透传串口 | | PC13 | GPIO_Output | 板载 LED(灌电流,系统状态指示) | | PD0/PD1 | HSE | 8MHz 外部晶振 | ### 2.3 DMA 通道分配 | DMA 通道 | 外设 | 方向 | |----------|------|------| | DMA1_Ch2 | USART3_TX | 内存→外设 | | DMA1_Ch3 | USART3_RX | 外设→内存 | | DMA1_Ch4 | USART1_TX | 内存→外设 | | DMA1_Ch5 | USART1_RX | 外设→内存 | | DMA1_Ch6 | USART2_RX | 外设→内存 | | DMA1_Ch7 | USART2_TX | 内存→外设 | ### 2.4 中断优先级分配(建议) | 外设 | 建议优先级 | 说明 | |------|------------|------| | EXTI0(CH390D INT) | 2 | 网络数据接收最紧急 | | SPI1 | 3 | 网络数据传输 | | USART2/3 DMA | 4 | 透传串口 | | USART1 DMA | 5 | 配置口,非实时 | | PendSV | 15 | FreeRTOS 任务切换 | | SysTick | 15 | FreeRTOS 系统节拍 | ### 2.5 时钟配置 | 参数 | 值 | |------|-----| | SYSCLK | 72MHz(PLL × 9) | | HCLK | 72MHz | | APB1 | 36MHz(分频 2) | | APB2 | 72MHz | | SPI1 | 18MHz(预分频 4) | ### 2.6 内存配置 | 参数 | 值 | |------|-----| | Heap | 0x1000(4KB) | | Stack | 0x800(2KB) | > **注意**:STM32F103R8T6 仅有 20KB RAM,需合理分配 FreeRTOS 堆和任务栈。 ## 三、FreeRTOS 任务设计 ### 3.1 任务划分 | 任务名 | 优先级 | 功能 | 栈大小 | |--------|--------|------|--------| | NetServerTask | High | TCP Server 管理 + 数据收发 | 512 words | | NetClientTask | High | TCP Client 管理 + 数据收发 | 512 words | | Uart2Task | Normal | UART2 数据收发(Server 透传) | 256 words | | Uart3Task | Normal | UART3 数据收发(Client 透传) | 256 words | | ConfigTask | Low | UART1 配置命令解析 | 256 words | ### 3.2 任务间通信 ``` NetServerTask <--Queue1--> Uart2Task NetClientTask <--Queue2--> Uart3Task ConfigTask <--独立运行--> (读写参数Flash) ``` - **Queue1**: Server 网络数据 <-> UART2 双向队列(128 bytes) - **Queue2**: Client 网络数据 <-> UART3 双向队列(128 bytes) ### 3.3 数据流向 ``` Server 链路: TCP Client → CH390D → NetServerTask → Queue1 → Uart2Task → UART2 UART2 → Uart2Task → Queue1 → NetServerTask → CH390D → TCP Client Client 链路: 远程服务器 → CH390D → NetClientTask → Queue2 → Uart3Task → UART3 UART3 → Uart3Task → Queue2 → NetClientTask → CH390D → 远程服务器 ``` ## 四、CH390D 驱动层 ### 4.1 接口设计 ```c /* ch390d.h */ typedef struct { SPI_HandleTypeDef *hspi; GPIO_TypeDef *int_port; uint16_t int_pin; GPIO_TypeDef *rst_port; uint16_t rst_pin; } CH390D_HandleTypeDef; HAL_StatusTypeDef CH390D_Init(CH390D_HandleTypeDef *hch); HAL_StatusTypeDef CH390D_SetMAC(CH390D_HandleTypeDef *hch, uint8_t *mac); HAL_StatusTypeDef CH390D_SetIP(CH390D_HandleTypeDef *hch, uint8_t *ip, uint8_t *mask, uint8_t *gw); HAL_StatusTypeDef CH390D_TCP_Listen(CH390D_HandleTypeDef *hch, uint8_t sock, uint16_t port); HAL_StatusTypeDef CH390D_TCP_Connect(CH390D_HandleTypeDef *hch, uint8_t sock, uint8_t *ip, uint16_t port); HAL_StatusTypeDef CH390D_TCP_Send(CH390D_HandleTypeDef *hch, uint8_t sock, uint8_t *data, uint16_t len); HAL_StatusTypeDef CH390D_TCP_Recv(CH390D_HandleTypeDef *hch, uint8_t sock, uint8_t *buf, uint16_t *len); HAL_StatusTypeDef CH390D_GetSocketStatus(CH390D_HandleTypeDef *hch, uint8_t sock, uint8_t *status); ``` ### 4.2 初始化流程 ``` 1. 复位 CH390D(RST 引脚) 2. 配置 SPI 通信参数 3. 设置 MAC 地址 4. 设置 IP/Mask/Gateway 5. 配置 Socket0 为 TCP Server 模式 6. 配置 Socket1 为 TCP Client 模式 7. 使能中断 ``` ## 五、TCP 链路管理 ### 5.1 Server 链路(Socket0) ```c void NetServerTask(void *param) { while(1) { switch(server_state) { case STATE_LISTEN: // 等待客户端连接 if(CH390D_CheckConnect(&hch, 0)) { server_state = STATE_CONNECTED; } break; case STATE_CONNECTED: // 接收数据 if(CH390D_TCP_Recv(&hch, 0, rx_buf, &len) == HAL_OK) { xQueueSend(uart2_queue, rx_buf, portMAX_DELAY); } // 发送数据 if(xQueueReceive(server_tx_queue, tx_buf, 0) == pdTRUE) { CH390D_TCP_Send(&hch, 0, tx_buf, len); } // 检测断开 if(CH390D_CheckDisconnect(&hch, 0)) { server_state = STATE_DISCONNECTED; } break; case STATE_DISCONNECTED: // 重新监听 CH390D_TCP_Listen(&hch, 0, SERVER_PORT); server_state = STATE_LISTEN; break; } vTaskDelay(pdMS_TO_TICKS(10)); } } ``` ### 5.2 Client 链路(Socket1) ```c void NetClientTask(void *param) { while(1) { switch(client_state) { case STATE_DISCONNECTED: // 尝试连接远程服务器 if(CH390D_TCP_Connect(&hch, 1, remote_ip, remote_port) == HAL_OK) { client_state = STATE_CONNECTING; } else { vTaskDelay(pdMS_TO_TICKS(3000)); // 3秒后重试 } break; case STATE_CONNECTING: if(CH390D_CheckConnect(&hch, 1)) { client_state = STATE_CONNECTED; } else if(CH390D_CheckTimeout(&hch, 1)) { client_state = STATE_DISCONNECTED; } break; case STATE_CONNECTED: // 数据收发同 Server 链路 // 断线后自动回到 STATE_DISCONNECTED break; } vTaskDelay(pdMS_TO_TICKS(10)); } } ``` ## 六、串口透传层 ### 6.1 UART DMA 配置 ```c /* 使用 DMA + 空闲中断接收,提高效率 */ void UART2_Init(void) { // 波特率可配置(9600/115200/230400/460800) huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; // 使能 DMA 接收 HAL_UART_Receive_DMA(&huart2, uart2_rx_buf, UART_BUF_SIZE); __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); } ``` ### 6.2 透传数据流 ```c /* UART2 空闲中断回调 */ void UART2_IdleCallback(void) { uint16_t len = UART_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart2.hdmarx); if(len > 0) { xQueueSendFromISR(server_tx_queue, uart2_rx_buf, len); HAL_UART_Receive_DMA(&huart2, uart2_rx_buf, UART_BUF_SIZE); } } /* UART2 发送任务 */ void Uart2Task(void *param) { uint8_t buf[128]; while(1) { if(xQueueReceive(uart2_queue, buf, portMAX_DELAY) == pdTRUE) { HAL_UART_Transmit(&huart2, buf, len, 100); } } } ``` ## 七、参数配置 ### 7.1 配置命令格式(UART1) ``` AT+IP=192.168.1.100\r\n // 设置设备IP AT+MASK=255.255.255.0\r\n // 设置子网掩码 AT+GW=192.168.1.1\r\n // 设置网关 AT+PORT=8080\r\n // 设置Server监听端口 AT+RIP=192.168.1.200\r\n // 设置Client连接的远程IP AT+RPORT=9000\r\n // 设置Client连接的远程端口 AT+BAUD1=115200\r\n // 设置UART2波特率 AT+BAUD2=115200\r\n // 设置UART3波特率 AT+SAVE\r\n // 保存参数到Flash AT+RESET\r\n // 重启设备 AT+?\r\n // 查询当前配置 ``` ### 7.2 参数存储结构 ```c typedef struct { uint32_t magic; // 0x54435055 "TCPU" uint8_t ip[4]; uint8_t mask[4]; uint8_t gw[4]; uint16_t server_port; // Server 监听端口 uint8_t remote_ip[4]; // Client 远程IP uint16_t remote_port; // Client 远程端口 uint32_t uart2_baud; // UART2 波特率 uint32_t uart3_baud; // UART3 波特率 uint32_t crc; // CRC32校验 } ConfigTypeDef; ``` ### 7.3 Flash 存储 ```c /* 使用 STM32F103R8 内部 Flash 最后一页存储配置 */ #define CONFIG_FLASH_ADDR 0x0800FC00 // 64KB Flash最后1KB void Config_Save(ConfigTypeDef *cfg) { cfg->crc = CRC32_Calculate((uint8_t*)cfg, sizeof(ConfigTypeDef)-4); FLASH_ErasePage(CONFIG_FLASH_ADDR); FLASH_Program(CONFIG_FLASH_ADDR, cfg, sizeof(ConfigTypeDef)); } void Config_Load(ConfigTypeDef *cfg) { memcpy(cfg, (void*)CONFIG_FLASH_ADDR, sizeof(ConfigTypeDef)); if(cfg->magic != 0x54435055 || CRC32_Check(cfg) != HAL_OK) { Config_Default(cfg); // 加载默认配置 } } ``` ## 八、关键配置 ### 8.1 FreeRTOS 配置 ```c /* FreeRTOSConfig.h 关键参数 */ #define configUSE_PREEMPTION 1 #define configCPU_CLOCK_HZ 72000000 #define configTICK_RATE_HZ 1000 #define configMAX_PRIORITIES 5 #define configMINIMAL_STACK_SIZE 128 #define configTOTAL_HEAP_SIZE 4096 // 4KB(适配R8T6的20KB RAM) #define configUSE_MUTEXES 1 #define configUSE_COUNTING_SEMAPHORES 1 ``` ### 8.2 缓冲区配置 ```c #define UART_BUF_SIZE 256 // 串口接收缓冲区 #define NET_BUF_SIZE 256 // 网络接收缓冲区 #define QUEUE_ITEM_SIZE 128 // 队列单次传输大小 #define QUEUE_LENGTH 4 // 队列深度 ``` ## 九、丢包测试方案 ### 9.1 测试方法 1. **TCP 发送端**:连续发送递增序号数据包(10000包 × 100字节) 2. **串口接收端**:统计接收数据包数量及序号连续性 3. **反向测试**:串口发送 → TCP 接收 ### 9.2 测试指标 | 指标 | 要求 | |------|------| | 丢包率 | < 0.01% | | 延迟 | < 10ms | | 吞吐量 | > 90% 理论值 | ### 9.3 测试脚本(PC端) ```python import socket import time def send_test(host, port, count=10000): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) for i in range(count): data = f"PKT{i:06d}".encode() + b'\x00' * 92 # 100字节 sock.send(data) time.sleep(0.001) # 1ms间隔 sock.close() print(f"Sent {count} packets") def recv_test(port, timeout=30): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('0.0.0.0', port)) sock.listen(1) conn, addr = sock.accept() recv_count = 0 start = time.time() while time.time() - start < timeout: data = conn.recv(1024) if data: recv_count += len(data) // 100 conn.close() print(f"Received {recv_count} packets") ```