Files
TCP2UART/项目技术实现.md
T
gaoro-xiao d5803ca7dd refactor: 基于EVT示例重构技术实现,采用LwIP协议栈架构
- 引入LwIP协议栈替代直接操作CH390D硬件Socket
- CH390D驱动层直接移植官方EVT代码(CH390.c/h + CH390_Interface.c/h)
- 重构FreeRTOS任务:LwIPTask统一处理协议栈,透传任务专注数据搬运
- 使用StreamBuffer替代Queue,更适合流式数据传输
- 更新中断优先级配置,符合FreeRTOS要求
- 添加内存使用估算(18KB/20KB)
- 完善SPI配置和LwIP配置说明
2026-03-29 17:51:47 +08:00

938 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TCP2UART 项目技术实现
## 一、系统架构
```
+-------------------+ +-------------------+
| TCP Server | | TCP Client |
| (监听端口) | | (连接远程服务器) |
+--------+----------+ +--------+----------+
| |
| LwIP TCP/IP Stack |
+------------------------------+
|
+------v------+
| CH390D |
| (SPI接口) |
+------+------+
|
+------v------+
| STM32 |
| F103R8T6 |
+------+------+
|
+-------------+-------------+
| | |
+---v---+ +---v---+ +---v---+
| UART1 | | UART2 | | UART3 |
| 配置口 | | 透传口 | | 透传口 |
+-------+ +-------+ +-------+
```
> **架构说明**:基于官方 EVT 示例代码,采用 LwIP 轻量级 TCP/IP 协议栈,而非直接操作 CH390D 的硬件 Socket。CH390D 作为以太网 MAC+PHY 芯片,通过 SPI 接口与 STM32 通信。
## 二、硬件配置
### 2.1 MCU 型号
STM32F103R8T6LQFP6464KB Flash20KB 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 中断优先级分配
基于 FreeRTOS 的 NVIC 优先级分组配置(使用 4 位抢占优先级):
| 外设 | 抢占优先级 | 子优先级 | 说明 |
|------|------------|----------|------|
| EXTI0CH390D INT | 5 | 0 | 网络数据接收,需高于 FreeRTOS 临界区 |
| DMA1_Ch2/3 (USART3) | 6 | 0 | 透传串口 DMA |
| DMA1_Ch6/7 (USART2) | 6 | 1 | 透传串口 DMA |
| DMA1_Ch4/5 (USART1) | 7 | 0 | 配置口 DMA |
| SysTick | 15 | 0 | FreeRTOS 系统节拍(configKERNEL_INTERRUPT_PRIORITY |
> **注意**FreeRTOS 要求 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 设为 50x50),所有使用 FreeRTOS API 的 ISR 优先级数值必须 >= 5。
### 2.5 时钟配置
| 参数 | 值 |
|------|-----|
| SYSCLK | 72MHzPLL × 9 |
| HCLK | 72MHz |
| APB1 | 36MHz(分频 2 |
| APB2 | 72MHz |
| SPI1 | 18MHz(预分频 4 |
### 2.6 内存配置
| 参数 | 值 |
|------|-----|
| Heap | 0x10004KB |
| Stack | 0x8002KB |
> **注意**STM32F103R8T6 仅有 20KB RAM,需合理分配 FreeRTOS 堆和任务栈。
## 三、软件架构分层
### 3.1 软件分层结构
```
+----------------------------------------------------------+
| Application Layer |
| (ConfigTask, ServerTransTask, ClientTransTask) |
+----------------------------------------------------------+
| TCP/IP Stack (LwIP) |
| tcp_server / tcp_client / netif / pbuf / timeouts |
+----------------------------------------------------------+
| Network Interface Layer |
| ethernetif (low_level_input / low_level_output) |
+----------------------------------------------------------+
| CH390D Driver Layer |
| CH390.c (协议层) + CH390_Interface.c (硬件抽象层) |
+----------------------------------------------------------+
| HAL/LL Layer |
| SPI + GPIO + DMA + UART |
+----------------------------------------------------------+
```
### 3.2 目录结构
```
Core/
├── Inc/
│ ├── main.h
│ ├── FreeRTOSConfig.h
│ └── lwipopts.h # LwIP 配置
├── Src/
│ ├── main.c
│ ├── freertos.c # FreeRTOS 任务创建
│ └── stm32f1xx_it.c # 中断处理
Drivers/
├── CH390/ # CH390 驱动(从 EVT 移植)
│ ├── CH390.c # 协议层:收发包、PHY 配置等
│ ├── CH390.h
│ ├── CH390_Interface.c # 硬件抽象层:SPI 读写
│ └── CH390_Interface.h
├── LwIP/ # LwIP 协议栈(从 EVT 移植)
│ ├── src/
│ │ ├── core/ # TCP/IP 核心
│ │ ├── netif/ # 网络接口
│ │ │ ├── ethernetif.c # CH390 网卡驱动适配
│ │ │ └── ethernetif.h
│ │ └── include/
│ └── apps/
│ ├── tcp_server.c # TCP Server 透传
│ └── tcp_client.c # TCP Client 透传
App/
├── config.c # AT 命令解析
├── config.h
├── uart_trans.c # UART 透传管理
├── uart_trans.h
├── flash_param.c # Flash 参数存储
└── flash_param.h
```
## 四、FreeRTOS 任务设计
### 4.1 任务划分
| 任务名 | 优先级 | 功能 | 栈大小 |
|--------|--------|------|--------|
| LwIPTask | osPriorityHigh | LwIP 协议栈处理(定时器 + 网卡输入) | 512 words |
| ServerTransTask | osPriorityAboveNormal | TCP Server ↔ UART2 双向透传 | 384 words |
| ClientTransTask | osPriorityAboveNormal | TCP Client ↔ UART3 双向透传 | 384 words |
| ConfigTask | osPriorityNormal | UART1 AT 命令解析 | 256 words |
> **说明**:相比原设计减少任务数,将网络任务合并为 LwIPTask 统一处理,透传任务专注数据搬运。
### 4.2 任务间通信
```
+-------------+
| LwIPTask |
| (协议栈处理) |
+------+------+
|
+------------------+------------------+
| |
+-------v-------+ +-------v-------+
| tcp_server_pcb| | tcp_client_pcb|
| (LwIP PCB) | | (LwIP PCB) |
+-------+-------+ +-------+-------+
| |
+-------v-------+ +-------v-------+
|ServerTransTask| |ClientTransTask|
| StreamBuffer1 | | StreamBuffer2 |
+-------+-------+ +-------+-------+
| |
+---v---+ +---v---+
| UART2 | | UART3 |
+-------+ +-------+
```
### 4.3 同步机制
| 机制 | 用途 | 说明 |
|------|------|------|
| xStreamBuffer | UART DMA → TCP 发送 | 零拷贝流式缓冲,适合变长数据 |
| xSemaphore | TCP 接收 → UART 发送 | 通知信号量,TCP 收到数据后释放 |
| xMutex | CH390 SPI 访问保护 | LwIP 输出 + 中断输入共享 SPI |
### 4.4 数据流向
```
Server 链路(UART2 ↔ TCP Server:
[外部TCP Client] → CH390 → LwIPTask(tcp_recv) → StreamBuffer → ServerTransTask → UART2_TX(DMA)
UART2_RX(DMA+IDLE) → StreamBuffer → LwIPTask(tcp_write) → CH390 → [外部TCP Client]
Client 链路(UART3 ↔ TCP Client:
[远程服务器] → CH390 → LwIPTask(tcp_recv) → StreamBuffer → ClientTransTask → UART3_TX(DMA)
UART3_RX(DMA+IDLE) → StreamBuffer → LwIPTask(tcp_write) → CH390 → [远程服务器]
```
## 五、CH390D 驱动层(基于官方 EVT)
### 5.1 驱动文件结构
驱动代码直接移植自 `Reference/EVT/EXAM/PUB/`,需修改接口宏定义:
```c
/* CH390.h 中启用 SPI 接口 */
#define CH390_INTERFACE_SPI // CH390H/CH390D 使用 SPI
// #define CH390_INTERFACE_8_BIT // CH390L/CH390F 8-bit mode
// #define CH390_INTERFACE_16_BIT // CH390L 16-bit mode
```
### 5.2 SPI 接口层(CH390_Interface.c 适配 HAL
```c
/* 引脚定义适配本项目 */
#define CH390_SPI_HANDLE hspi1
#define CH390_CS_PORT GPIOA
#define CH390_CS_PIN GPIO_PIN_4
#define CH390_RST_PORT GPIOB
#define CH390_RST_PIN GPIO_PIN_1
#define CH390_INT_PORT GPIOB
#define CH390_INT_PIN GPIO_PIN_0
/* HAL SPI 适配 */
static uint8_t ch390_spi_exchange_byte(uint8_t byte)
{
uint8_t rx;
HAL_SPI_TransmitReceive(&CH390_SPI_HANDLE, &byte, &rx, 1, HAL_MAX_DELAY);
return rx;
}
/* FreeRTOS 延时适配 */
void ch390_delay_us(uint32_t time)
{
/* 微秒级延时,可用 DWT 或简单循环 */
uint32_t cycles = (SystemCoreClock / 1000000) * time;
while(cycles--) __NOP();
}
void ch390_delay_ms(uint32_t time)
{
/* FreeRTOS 环境下使用 vTaskDelay */
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
vTaskDelay(pdMS_TO_TICKS(time));
} else {
HAL_Delay(time);
}
}
```
### 5.3 协议层主要 API
```c
/* 从 CH390.h 提取的关键函数 */
/* 初始化与配置 */
void ch390_hardware_reset(void); // 硬件复位(拉低 RST 引脚)
void ch390_software_reset(void); // 软件复位(写 NCR 寄存器)
void ch390_default_config(void); // 默认配置:LED模式、校验和、使能RX/中断
void ch390_set_phy_mode(enum ch390_phy_mode mode); // CH390_AUTO / CH390_100MFD / CH390_10MFD
void ch390_set_mac_address(uint8_t *mac_addr); // 设置 MAC 地址
/* 数据收发 */
uint32_t ch390_receive_packet(uint8_t *buff, uint8_t *rx_status); // 接收以太网帧
void ch390_send_packet(uint8_t *buff, uint16_t length); // 发送以太网帧
void ch390_drop_packet(uint16_t len); // 丢弃当前包
/* 状态查询 */
int ch390_get_link_status(void); // 0: 断开, 1: 连接
int ch390_get_phy_speed(void); // 0: 100Mbps, 1: 10Mbps
uint8_t ch390_get_int_status(void); // 获取并清除中断状态
/* 中断配置 */
void ch390_interrupt_config(uint8_t mask); // IMR_PRI | IMR_PTI | IMR_LNKCHGI 等
uint16_t ch390_get_int_pin(void); // 读取 INT 引脚电平
```
### 5.4 初始化流程
```c
void CH390_Init(void)
{
/* 1. GPIO 初始化(由 CubeMX 完成) */
/* 2. SPI 初始化(由 CubeMX 完成)
* Mode: Master, Full-Duplex
* CPOL: High, CPHA: 2Edge (Mode 3)
* NSS: Software
* Prescaler: 4 (18MHz @ 72MHz PCLK)
*/
/* 3. 硬件复位 */
ch390_hardware_reset();
ch390_delay_ms(10); // 上电后等待 10ms
/* 4. 默认配置 */
ch390_default_config();
/* 5. 读取内置 MAC 或设置自定义 MAC */
uint8_t mac[6];
ch390_get_mac(mac); // CH390 内置 MAC
// ch390_set_mac_address(custom_mac); // 或使用自定义 MAC
/* 6. 等待 PHY 链路建立 */
while (!ch390_get_link_status()) {
ch390_delay_ms(100);
}
}
```
## 六、LwIP 协议栈集成
### 6.1 LwIP 配置(lwipopts.h
```c
/* 基础配置 */
#define NO_SYS 0 /* 使用 OS */
#define LWIP_NETCONN 0 /* 不使用 Netconn API */
#define LWIP_SOCKET 0 /* 不使用 Socket API */
/* 内存配置(适配 20KB RAM */
#define MEM_SIZE (4*1024) /* 堆大小 4KB */
#define MEMP_NUM_PBUF 8
#define MEMP_NUM_TCP_PCB 4 /* 同时 TCP 连接数 */
#define MEMP_NUM_TCP_PCB_LISTEN 2 /* Server + Client */
#define PBUF_POOL_SIZE 8
#define PBUF_POOL_BUFSIZE 512
/* TCP 配置 */
#define LWIP_TCP 1
#define TCP_MSS (1460)
#define TCP_SND_BUF (2*TCP_MSS)
#define TCP_WND (2*TCP_MSS)
#define TCP_SND_QUEUELEN (4*TCP_SND_BUF/TCP_MSS)
/* DHCP/静态 IP */
#define LWIP_DHCP 1
/* 校验和由 CH390 硬件计算 */
#define CHECKSUM_GEN_IP 0
#define CHECKSUM_GEN_TCP 0
#define CHECKSUM_GEN_UDP 0
#define CHECKSUM_CHECK_IP 0
#define CHECKSUM_CHECK_TCP 0
#define CHECKSUM_CHECK_UDP 0
```
### 6.2 网卡驱动适配(ethernetif.c
基于 `Reference/EVT/EXAM/LwIP_Example/lwip-2_2_0/src/netif/ethernetif.c` 修改:
```c
/* 发送以太网帧 */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
/* 获取 SPI 互斥锁(FreeRTOS 环境) */
xSemaphoreTake(ch390_spi_mutex, portMAX_DELAY);
for (q = p; q != NULL; q = q->next) {
ch390_write_mem(q->payload, q->len);
}
/* 等待上次发送完成 */
while(ch390_read_reg(CH390_TCR) & TCR_TXREQ);
/* 设置包长度并发起发送请求 */
ch390_write_reg(CH390_TXPLL, p->tot_len & 0xff);
ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xff);
ch390_send_request();
xSemaphoreGive(ch390_spi_mutex);
LINK_STATS_INC(link.xmit);
return ERR_OK;
}
/* 接收以太网帧 */
static struct pbuf *low_level_input(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state;
struct pbuf *p, *q;
uint8_t rx_ready;
uint8_t header[4];
uint16_t len;
/* 检查是否有数据包 */
ch390_read_reg(CH390_MRCMDX);
rx_ready = ch390_read_reg(CH390_MRCMDX);
if (rx_ready & CH390_PKT_ERR) {
/* 复位 RX FIFO */
ch390_write_reg(CH390_RCR, 0);
ch390_write_reg(CH390_MPTRCR, 0x01);
ch390_delay_ms(1);
ch390_write_reg(CH390_RCR, RCR_RXEN | RCR_DIS_CRC);
ethernetif->rx_len = 0;
return NULL;
}
if (!(rx_ready & CH390_PKT_RDY)) {
ethernetif->rx_len = 0;
return NULL;
}
/* 读取头部:状态 + 长度 */
ch390_read_mem(header, 4);
ethernetif->rx_status = header[1];
len = (header[2] | (header[3] << 8)) - 4; // 去掉 CRC
ethernetif->rx_len = len;
/* 分配 pbuf 并读取数据 */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
for (q = p; q != NULL; q = q->next) {
ch390_read_mem(q->payload, q->len);
}
ch390_drop_packet(4); // 跳过 CRC
LINK_STATS_INC(link.recv);
} else {
ch390_drop_packet(len + 4);
LINK_STATS_INC(link.memerr);
}
return p;
}
```
### 6.3 中断处理
```c
/* EXTI0 中断服务程序(PB0 连接 CH390D INT */
void EXTI0_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(CH390_INT_PIN) != RESET) {
__HAL_GPIO_EXTI_CLEAR_IT(CH390_INT_PIN);
/* 通知 LwIP 任务处理 */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(lwipTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/* LwIP 任务中的中断处理 */
void ch390_int_handler(void)
{
uint8_t int_status = ch390_get_int_status();
/* 链路状态变化 */
if (int_status & ISR_LNKCHG) {
vTaskDelay(pdMS_TO_TICKS(65)); // 等待 PHY 稳定
if (ch390_get_link_status()) {
netif_set_link_up(&ch390_netif);
} else {
netif_set_link_down(&ch390_netif);
}
}
/* 接收溢出 */
if (int_status & ISR_ROS) {
struct ethernetif *ethernetif = ch390_netif.state;
do {
ethernetif_input(&ch390_netif);
} while (ethernetif->rx_len != 0);
}
/* 收到数据包 */
if (int_status & ISR_PR) {
struct ethernetif *ethernetif = ch390_netif.state;
do {
ethernetif_input(&ch390_netif);
} while (ethernetif->rx_len != 0);
}
}
```
## 七、TCP 链路管理(基于 LwIP RAW API
### 7.1 Server 链路(监听端口)
```c
static struct tcp_pcb *tcp_server_pcb;
static struct tcp_pcb *tcp_server_conn; // 当前连接的客户端
/* 接收回调:TCP 数据 → StreamBuffer → UART2 */
static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
if (p != NULL) {
tcp_recved(tpcb, p->tot_len);
/* 写入 StreamBuffer,通知 ServerTransTask */
xStreamBufferSend(uart2_tx_stream, p->payload, p->tot_len, 0);
pbuf_free(p);
return ERR_OK;
} else {
/* 对端关闭连接 */
tcp_close(tpcb);
tcp_server_conn = NULL;
return ERR_OK;
}
}
/* 连接接受回调 */
static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
/* 只接受一个连接 */
if (tcp_server_conn != NULL) {
tcp_abort(newpcb);
return ERR_ABRT;
}
tcp_server_conn = newpcb;
tcp_recv(newpcb, tcp_server_recv);
tcp_err(newpcb, tcp_server_error);
return ERR_OK;
}
/* 初始化 TCP Server */
void tcp_server_init(uint16_t port)
{
tcp_server_pcb = tcp_new();
tcp_bind(tcp_server_pcb, IP_ADDR_ANY, port);
tcp_server_pcb = tcp_listen(tcp_server_pcb);
tcp_accept(tcp_server_pcb, tcp_server_accept);
}
/* UART2 数据发送到 TCP */
void tcp_server_send_data(uint8_t *data, uint16_t len)
{
if (tcp_server_conn != NULL && tcp_sndbuf(tcp_server_conn) >= len) {
tcp_write(tcp_server_conn, data, len, TCP_WRITE_FLAG_COPY);
tcp_output(tcp_server_conn);
}
}
```
### 7.2 Client 链路(主动连接)
```c
static struct tcp_pcb *tcp_client_pcb;
static uint8_t client_connected = 0;
/* 接收回调:TCP 数据 → StreamBuffer → UART3 */
static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
if (p != NULL) {
tcp_recved(tpcb, p->tot_len);
xStreamBufferSend(uart3_tx_stream, p->payload, p->tot_len, 0);
pbuf_free(p);
} else if (err == ERR_OK) {
/* 服务器断开,延时重连 */
tcp_close(tpcb);
client_connected = 0;
sys_timeout(3000, tcp_client_reconnect, NULL);
}
return ERR_OK;
}
/* 连接错误回调 */
static void tcp_client_error(void *arg, err_t err)
{
client_connected = 0;
sys_timeout(3000, tcp_client_reconnect, NULL);
}
/* 连接成功回调 */
static err_t tcp_client_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{
client_connected = 1;
tcp_recv(pcb, tcp_client_recv);
return ERR_OK;
}
/* 初始化 TCP Client */
void tcp_client_init(ip_addr_t *server_ip, uint16_t server_port)
{
tcp_client_pcb = tcp_new();
tcp_err(tcp_client_pcb, tcp_client_error);
tcp_connect(tcp_client_pcb, server_ip, server_port, tcp_client_connected);
}
/* 重连定时器回调 */
static void tcp_client_reconnect(void *arg)
{
if (!client_connected) {
tcp_client_init(&config.remote_ip, config.remote_port);
}
}
```
## 八、串口透传层
### 8.1 UART DMA + 空闲中断配置
```c
/* CubeMX 配置:
* USART2/3: 115200, 8N1, DMA TX/RX, 空闲中断
*/
#define UART_RX_BUF_SIZE 256
uint8_t uart2_rx_buf[UART_RX_BUF_SIZE];
uint8_t uart3_rx_buf[UART_RX_BUF_SIZE];
void UART_Trans_Init(void)
{
/* 启动 DMA 接收 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uart2_rx_buf, UART_RX_BUF_SIZE);
HAL_UARTEx_ReceiveToIdle_DMA(&huart3, uart3_rx_buf, UART_RX_BUF_SIZE);
/* 禁用半传输中断(减少中断次数) */
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
__HAL_DMA_DISABLE_IT(huart3.hdmarx, DMA_IT_HT);
}
```
### 8.2 空闲中断回调(接收完成)
```c
/* HAL 空闲中断回调 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (huart == &huart2) {
/* UART2 数据 → Server StreamBuffer */
xStreamBufferSendFromISR(server_rx_stream, uart2_rx_buf, Size, &xHigherPriorityTaskWoken);
/* 重启 DMA 接收 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uart2_rx_buf, UART_RX_BUF_SIZE);
__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
}
else if (huart == &huart3) {
/* UART3 数据 → Client StreamBuffer */
xStreamBufferSendFromISR(client_rx_stream, uart3_rx_buf, Size, &xHigherPriorityTaskWoken);
HAL_UARTEx_ReceiveToIdle_DMA(&huart3, uart3_rx_buf, UART_RX_BUF_SIZE);
__HAL_DMA_DISABLE_IT(huart3.hdmarx, DMA_IT_HT);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
```
### 8.3 透传任务
```c
/* Server 透传任务:UART2 ↔ TCP Server */
void ServerTransTask(void *param)
{
uint8_t buf[256];
size_t len;
for (;;) {
/* TCP → UART2:从 StreamBuffer 读取并 DMA 发送 */
len = xStreamBufferReceive(uart2_tx_stream, buf, sizeof(buf), pdMS_TO_TICKS(10));
if (len > 0) {
HAL_UART_Transmit_DMA(&huart2, buf, len);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待 DMA 完成
}
/* UART2 → TCP:从 StreamBuffer 读取并 TCP 发送 */
len = xStreamBufferReceive(server_rx_stream, buf, sizeof(buf), 0);
if (len > 0) {
tcp_server_send_data(buf, len);
}
}
}
/* DMA 发送完成回调 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (huart == &huart2) {
vTaskNotifyGiveFromISR(serverTransTaskHandle, &xHigherPriorityTaskWoken);
}
else if (huart == &huart3) {
vTaskNotifyGiveFromISR(clientTransTaskHandle, &xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
```
## 九、LwIP 主任务
```c
/* LwIP 任务:协议栈处理 + 定时器 */
void LwIPTask(void *param)
{
/* 初始化网卡 */
ip4_addr_t ipaddr, netmask, gateway;
IP4_ADDR(&ipaddr, config.ip[0], config.ip[1], config.ip[2], config.ip[3]);
IP4_ADDR(&netmask, config.mask[0], config.mask[1], config.mask[2], config.mask[3]);
IP4_ADDR(&gateway, config.gw[0], config.gw[1], config.gw[2], config.gw[3]);
init_lwip_netif(&ipaddr, &netmask, &gateway);
/* 等待 PHY 链路建立 */
while (!ch390_get_link_status()) {
vTaskDelay(pdMS_TO_TICKS(100));
}
netif_set_link_up(&ch390_netif);
/* 初始化 TCP Server/Client */
tcp_server_init(config.server_port);
ip_addr_t remote_ip;
IP4_ADDR(&remote_ip, config.remote_ip[0], config.remote_ip[1],
config.remote_ip[2], config.remote_ip[3]);
tcp_client_init(&remote_ip, config.remote_port);
/* 主循环 */
for (;;) {
/* 等待中断通知或超时 */
uint32_t notify = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10));
if (notify > 0 || ch390_get_int_pin()) {
xSemaphoreTake(ch390_spi_mutex, portMAX_DELAY);
ch390_int_handler();
xSemaphoreGive(ch390_spi_mutex);
}
/* LwIP 定时器处理 */
sys_check_timeouts();
}
}
```
## 十、参数配置
### 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); // 加载默认配置
}
}
```
## 十一、关键配置
### 11.1 FreeRTOS 配置(FreeRTOSConfig.h
```c
/* 基础配置 */
#define configUSE_PREEMPTION 1
#define configCPU_CLOCK_HZ 72000000
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 7
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE (8*1024) /* 8KBLwIP 需要更多堆 */
/* 中断优先级配置 */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << 4)
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << 4)
/* 功能启用 */
#define configUSE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
/* Stream Buffer 支持 */
#define configUSE_STREAM_BUFFERS 1
```
### 11.2 SPI 配置(CubeMX
| 参数 | 值 | 说明 |
|------|-----|------|
| Mode | Full-Duplex Master | |
| NSS | Software | 软件控制 CS |
| Data Size | 8 Bits | |
| First Bit | MSB First | |
| Prescaler | 4 | 72MHz/4 = 18MHz |
| CPOL | High | 空闲时 CLK 高电平 |
| CPHA | 2 Edge | 第二个边沿采样 |
| CRC | Disabled | |
### 11.3 缓冲区配置
```c
/* 串口缓冲区 */
#define UART_RX_BUF_SIZE 256 /* DMA 接收缓冲区 */
/* StreamBuffer 配置 */
#define STREAM_BUFFER_SIZE 512 /* 每个方向的流缓冲区大小 */
#define STREAM_TRIGGER_LEVEL 1 /* 触发等待任务的最小字节数 */
/* LwIP 缓冲区(见 lwipopts.h */
#define PBUF_POOL_SIZE 8
#define PBUF_POOL_BUFSIZE 512
```
### 11.4 内存使用估算(20KB RAM
| 组件 | 大小 | 说明 |
|------|------|------|
| FreeRTOS Heap | 8KB | 任务栈 + 队列 + 信号量等 |
| LwIP MEM_SIZE | 4KB | pbuf/TCP 缓冲区 |
| UART DMA 缓冲区 | 512B | 2 × 256B |
| StreamBuffer | 2KB | 4 × 512B |
| 全局变量 | ~1KB | 配置结构 + 状态变量 |
| 栈(main | 2KB | 启动前使用 |
| **总计** | ~18KB | 预留 2KB 安全余量 |
> **优化建议**
> - 如内存紧张,可减小 `PBUF_POOL_SIZE` 和 `StreamBuffer` 大小
> - 使用 `configSUPPORT_STATIC_ALLOCATION` 静态分配任务栈
## 十二、丢包测试方案
### 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")
```