d5803ca7dd
- 引入LwIP协议栈替代直接操作CH390D硬件Socket - CH390D驱动层直接移植官方EVT代码(CH390.c/h + CH390_Interface.c/h) - 重构FreeRTOS任务:LwIPTask统一处理协议栈,透传任务专注数据搬运 - 使用StreamBuffer替代Queue,更适合流式数据传输 - 更新中断优先级配置,符合FreeRTOS要求 - 添加内存使用估算(18KB/20KB) - 完善SPI配置和LwIP配置说明
938 lines
28 KiB
Markdown
938 lines
28 KiB
Markdown
# 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/`,需修改接口宏定义:
|
||
|
||
```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) /* 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 缓冲区配置
|
||
|
||
```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")
|
||
```
|