d5803ca7dd
- 引入LwIP协议栈替代直接操作CH390D硬件Socket - CH390D驱动层直接移植官方EVT代码(CH390.c/h + CH390_Interface.c/h) - 重构FreeRTOS任务:LwIPTask统一处理协议栈,透传任务专注数据搬运 - 使用StreamBuffer替代Queue,更适合流式数据传输 - 更新中断优先级配置,符合FreeRTOS要求 - 添加内存使用估算(18KB/20KB) - 完善SPI配置和LwIP配置说明
28 KiB
28 KiB
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 型号
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 中断优先级分配
基于 FreeRTOS 的 NVIC 优先级分组配置(使用 4 位抢占优先级):
| 外设 | 抢占优先级 | 子优先级 | 说明 |
|---|---|---|---|
| EXTI0(CH390D 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设为 5(0x50),所有使用 FreeRTOS API 的 ISR 优先级数值必须 >= 5。
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 堆和任务栈。
三、软件架构分层
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/,需修改接口宏定义:
/* 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)
/* 引脚定义适配本项目 */
#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
/* 从 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 初始化流程
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)
/* 基础配置 */
#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 修改:
/* 发送以太网帧 */
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 中断处理
/* 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 链路(监听端口)
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 链路(主动连接)
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 + 空闲中断配置
/* 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 空闲中断回调(接收完成)
/* 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 透传任务
/* 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 主任务
/* 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 参数存储结构
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 存储
/* 使用 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)
/* 基础配置 */
#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) /* 8KB,LwIP 需要更多堆 */
/* 中断优先级配置 */
#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 缓冲区配置
/* 串口缓冲区 */
#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 测试方法
- TCP 发送端:连续发送递增序号数据包(10000包 × 100字节)
- 串口接收端:统计接收数据包数量及序号连续性
- 反向测试:串口发送 → 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")