Files
TCP2UART/项目技术实现.md
T

10 KiB
Raw Blame History

TCP2UART 项目技术实现

一、系统架构

+-------------------+        +-------------------+
|   TCP Server      |        |   TCP Client      |
|   (监听端口)       |        |   (连接远程服务器)  |
+--------+----------+        +--------+----------+
         |                              |
         |          CH390D              |
         +------------------------------+
                     |
              +------v------+
              |   STM32     |
              |  F103R8T6   |
              +------+------+
                     |
       +-------------+-------------+
       |             |             |
   +---v---+    +---v---+    +---v---+
   | UART1 |    | UART2 |    | UART3 |
   | 配置口 |    | 透传口 |    | 透传口 |
   +-------+    +-------+    +-------+

二、外设分配

外设 功能 对应链路
SPI1 CH390D 通信接口 -
UART1 参数配置串口 配置通道
UART2 数据透传 Server 链路
UART3 数据透传 Client 链路
GPIO CH390D INT/RST -
TIM2 FreeRTOS Tick -

三、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 接口设计

/* 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. 复位 CH390DRST 引脚)
2. 配置 SPI 通信参数
3. 设置 MAC 地址
4. 设置 IP/Mask/Gateway
5. 配置 Socket0 为 TCP Server 模式
6. 配置 Socket1 为 TCP Client 模式
7. 使能中断

五、TCP 链路管理

5.1 Server 链路(Socket0

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

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 配置

/* 使用 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 透传数据流

/* 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 参数存储结构

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 存储

/* 使用 STM32F103 内部 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 配置

/* 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       10240  // 10KB
#define configUSE_MUTEXES           1
#define configUSE_COUNTING_SEMAPHORES 1

8.2 缓冲区配置

#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端)

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")