eb57a564ef
- 修改ioc文件中的芯片型号配置 - 调整HeapSize从10KB减至4KB以适应20KB RAM限制 - 更新项目技术实现文档中的MCU规格和内存配置 - 更新MDK-ARM工程文件的芯片定义(STM32F103xB) - 添加新的启动文件startup_stm32f103xb.s
414 lines
12 KiB
Markdown
414 lines
12 KiB
Markdown
# 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")
|
||
```
|